fastbin double free
是fastbin attack
的一种,在glibc heap
相关的漏洞中是较为常见也比较基础的。
原理(Ref1,Ref2,Ref3)
0 基础知识
malloc chunk结构:
c1
2
3
4
5
6struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /*前一个chunk的大小*/
INTERNAL_SIZE_T size; /*当前chunk的大小*/
struct malloc_chunk * fd; /*指向前一个释放的chunk*/
struct malloc_chunk * bk; /*指向后一个释放的chunk*/
}An allocated chunk looks like this:
Code1
2
3
4
5
6
7
8
9
10
11
12
13
14chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Free chunks are stored in circular doubly-linked lists, and look like this:
Code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' | Size of chunk, in bytes |A|0|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Forward pointer to next chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Back pointer to previous chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unused space (may be 0 bytes long) .
. .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' | Size of chunk, in bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
fast chunk
表示正在使用的长度在0x20-0x80
的堆块fastbin
表示长度在0x20-0x80
范围内的已经释放的堆块fast chunk
没有fd,只有prev_size、size、data。fastbin
只有 fd 指针,fd指向下一个fastbin
,且标志位P=1,且(P=0表示前一个chunk空闲)fastbin
由单链表构成,采用 LIFO, chunk 从链表头部插入,从头部取出。
1 利用原理
什么是 double free
释放一个 chunk 两次。
double free 能干什么
能造成malloc()
到同一个 chunk,造成混乱。如果利用得当,就可以达到任意地址写的目的。
怎么利用 double free
在malloc()
时,当用户所要求内存大小在 fastbin 的大小范围时,malloc()
优先从 fastbin 中拿出 chunk。malloc()
会查找与所要求大小相匹配的 fastbin 链表,然后从头部取出 chunk,并将取出 chunk 的 fd 赋给 fastbin 链表头部。这样就从 fastbin 链表的头部中取出了 chunk。
1 | if ((unsigned long)(nb) <= (unsigned long)(get_max_fast())) |
由于free()
时候检查了 fastbin 头部指向的 chunk 和被free()
的 chunk 是否相等,即检查是否两次free()
了同一个 chunk。所以不能通过直接free()
同一个 chunk 来进行 double free。
1 | unsigned int idx = fastbin_index(size); |
free()
还检查了 fastbin 里的 chunk 的 size 大小是否符合该 fastbin 的大小。
1 | /* Check that size of fastbin chunk at the top is the same as |
结合上面的代码,我们可以通过如下方法绕过检查:分配两个 chunk,分别命名为 chunk0 和 chunk1,然后free(chunk0);free(chunk1);free(chunk0)
就可以绕过上面的检查来进行 double free。此时的 fastbin 链表应该如下图所示。
1 | +------------+ |
在前面的free()
后我们进行第一次malloc()
,就可以分配到 chunk0。我们伪造一个 fake chunk,往 chunk0 的数据段中写入 fake chunk 的地址,就可以将 chunk0 的 fd 指向 fake chunk,即将 fake chunk 添加进了 fastbin 链表中。
1 | // 第一次 malloc() 分配到 chunk0,修改其 fd 指针 |
然后进行两次malloc()
,依次分配到 chunk1 和 chunk0,最后再进行malloc()
时就可以分配到 fake chunk。
1 | // 第二次 malloc() |
由于之前将 chunk0 的 fd 指针指向了 fake chunk,并且 chunk0 还呆在 fastbin 里,所以第三次 malloc()
时,malloc()
会将 chunk0 的 fd 赋给 fastbinY[i]
,此时只要再进行一次malloc()
就可以分配到 fake chunk 了。
1 | // 第三次 malloc() |
注意事项
malloc
到fake chunk时,会检查chunk的size是否合法,所以在伪造chunk时,需要考虑size的问题。
1 | fake +---------------------+ |
例如:malloc(0x40)
时,将要分配一个0x50
大小的fast chunk
:
1 | 0x4005c7 <main+97> mov edi, 0x40 |
此时fastbin
中存在0x50
的chunk:
1 | pwndbg> bins |
查看这个chunk
的size,为0x51
(P=1):
1 | pwndbg> x/10xg 0x602000 |
此时可以malloc
成功,经测试,只要size在0x50-0x60
的范围内都可以。
小结&例题
通过 fastbin double free 可以使用多个指针控制同一个堆块,这可以用于篡改一些堆块中的关键数据域或者是实现类似于类型混淆的效果。(栗子:MetasequoiaCTF Summoner)
如果更进一步修改 fd 指针,则能够实现任意地址分配堆块的效果 (首先要通过验证),这就相当于任意地址写任意值的效果。(栗子:Hgame2020 Roc826、MetasequoiaCTF Samsara)