2.fastbin_dup

0x00:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
fprintf(stderr, "Allocating 3 buffers.\n");
char *a = malloc(9);
char *b = malloc(9);
char *c = malloc(9);
strcpy(a,"AAAAAAAA");
strcpy(b,"BBBBBBBB");
strcpy(c,"CCCCCCCC");
fprintf(stderr, "1st malloc(9) %p points to %s\n",a,a);
fprintf(stderr, "2nd malloc(9) %p points to %s\n",b,b);
fprintf(stderr, "3rd malloc(9) %p points to %s\n",c,c);
fprintf(stderr, "Freeing the first one %p.\n", a);
free(a);
fprintf(stderr, "Then freeing another one %p.\n", b);
free(b);
fprintf(stderr, "Freeing the first one %p again.\n",a);
free(a);

接上

1
2
3
4
5
6
7
8
9
10
11
	fprintf(stderr,	"Allocating	3 buffers.\n");
char *d = malloc(9);
char *e = malloc(9);
char *f = malloc(9);
strcpy(d,"DDDDDDDD");
fprintf(stderr, "4st malloc(9) %p points to %s the first time\n",d,d);
strcpy(e,"EEEEEEEE");
fprintf(stderr, "5nd malloc(9) %p points to %s\n",e,e);
strcpy(f,"FFFFFFFF");
fprintf(stderr, "6rd malloc(9) %p points to %s the second time\n",f,f);
}

源代码中可以看出, 在前面把a free了两遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
user@ubuntu:~/workspace/pwn/fastbin_dup$ vim fastbin_dup.c 
user@ubuntu:~/workspace/pwn/fastbin_dup$ gcc -g fastbin_dup.c -o fastbin_dup
user@ubuntu:~/workspace/pwn/fastbin_dup$ ./fastbin_dup
Allocating 3 buffers.
1st malloc(9) 0x6f4010 points to AAAAAAAA
2nd malloc(9) 0x6f4030 points to BBBBBBBB
3rd malloc(9) 0x6f4050 points to CCCCCCCC
Freeing the first one 0x6f4010.
Then freeing another one 0x6f4030.
Freeing the first one 0x6f4010 again.
Allocating 3 buffers.
4st malloc(9) 0x6f4010 points to DDDDDDDD the first time
5nd malloc(9) 0x6f4030 points to EEEEEEEE
6rd malloc(9) 0x6f4010 points to FFFFFFFF the second time
user@ubuntu:~/workspace/pwn/fastbin_dup$

==这个程序展示了利用fastbins的double-free攻击,可以泄漏出一块已经被分配的内存指针。fastbins可以看成一个LIFO的栈,使用单链表实现,通过 fastbin->fd来遍历fastbins。由于 free的过程会对free list做检查,我们不能连续两次free同一个chunk,所以这里在两次free之间,增加了一次对其他chunk的free 过程,从而绕过检查顺利执行。然后再malloc三次,就在同一个地址malloc了两次,也就有了两个指向同一块内存区域的指针。==

1
2
3
4
5
6
7
/*	Check that the top of the bin is not the record we are going to add
(i.e., double free). */
if(__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}

0x01:三次malloc后

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
pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg>

0x02:free(a)

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
pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4242424242424242,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4343434343434343,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bins
fastbins
0x20: 0x602000 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg>

被free后,fd 原本被填充的AAAAAAAA被清零,这个chunk也被加入到fastbins中。

但是这个chunk a被free后,并==没有使chunk b的pre_size和size的pre_inuse改变==,不像上一篇的first_fit那个free后chunk b的字段被改变,也没放到unsorted bins中,而是被放到了fastbins中

0x03:free(b)

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
pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x602000,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4343434343434343,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bins
fastbins
0x20: 0x602020 —▸ 0x602000 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg>

==chunk b被放入fastbins中,成为新的表头,chunk b的fd字段里存着上一个chunk的地址,即chunk a 的地址:0x602000==

0x04:free(a)

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
pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x602020,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x602000,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4343434343434343,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bins
fastbins
0x20: 0x602000 —▸ 0x602020 ◂— 0x602000
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg>

又再次free了 a,此时fastbins的表头变成了chunk a的地址,即0x602000,它的fd里的值是上一个表头里的值,即chunk b的地址,即0x602020。chunk b里的fd值依旧是 chunk a 的值。形成了一个闭环 a -> b -> a

0x05:malloc

1
2
3
char *d	= malloc(9);
char *e = malloc(9);
char *f = malloc(9);
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
pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x602020,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x602000,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4343434343434343,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bins
fastbins
0x20: 0x602020 —▸ 0x602000 ◂— 0x602020 /* ' `' */
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg>

0x06:

1
strcpy(d,"DDDDDDDD");
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
pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4444444444444444,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x602000,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4343434343434343,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bisn
Undefined command: "bisn". Try "help".
pwndbg> bins
fastbins
0x20: 0x602020 —▸ 0x602000 ◂— 'DDDDDDDD'
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
1
strcpy(e,"EEEEEEEE");
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
pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4444444444444444,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4545454545454545,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4343434343434343,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bins
fastbins
0x20: 0x602020 ◂— 'EEEEEEEE'
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg>
1
strcpy(f,"FFFFFFFF");
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
pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4646464646464646,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4545454545454545,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x4343434343434343,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bins
fastbins
0x20: 0x602020 ◂— 'EEEEEEEE'
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg>

这里把chunk a原来的”DDDDDDDD”覆盖为”FFFFFFFF”,即是说d指针与f指针指向同一个chunk mem处。

所以通过double-free可以泄露出一个chunk的指针。


总结:
double-free的触发是:==连续==free==同==一个chunk。
可以通过double-free泄漏出一个堆块的指针。指向同一个内存区域的指针。