堆溢出利用的精髓就是:用精心构造的数据去溢出下一个 chunk 的 header,改写 chunk header 中的前向指针 (flink) 和后向指针 (blink) ,然后在分配、释放、合并等操作发生时伺机获得一次向内存任意地址写入任意数据的机会。
unlink 利用方式就是在 free 的时候得到任意地址写入任意数据的机会。
利用条件:
- 连续的两个堆内存空间,第一个堆可溢出,覆写第二个堆 header
- free 第二个堆
本文对 unlink 检查机制及绕过做简单分析,然后实战分析 how2heap 里 unsafe_unlink。
unlink 检查机制及绕过
unlink 是一个宏。在最初 unlink 实现的时候,其实是没有对双向链表检查的。代码大致如下:
1 | BK=second->bk |
而以最新的 glibc 2.27 为例,则早已添加了检查机制:
1 | /* Take a chunk off a bin list */ |
可以看到这里会修正指针,FD->bk = P,BK->fd = P
这样要满足条件的话,就需要预先构造合适的条件来 bypass 这个限制:
- P->fd->bk = P
- P->bk->fd = P
这样在 free unlink 后:
- FD->bk = BK 等价于 P = P->bk
- BK->fd = FD 等价于 P = P->fd
unsafe unlink 构造分析
这里以 how2heap 里最新版的 unsafe_unlink.c : https://github.com/shellphish/how2heap/blob/master/glibc_2.26/unsafe_unlink.c 为例,分析 bypass 及 fake 过程。
1 |
|
分析环境:
- OS:wsl debian 4.4.0 64bits
- gdb + gef
- libc 2.24
连续申请两个堆后,第一个堆的mem ptr = 0x8403220,第二个堆的 mem ptr = 0x84032b0
第一个堆地址存储在 chunk0_ptr 中,&chunk0_ptr = 0x8202050
查看 0x8202038~0x8202050 存储的内容:
Addr | Offset | Value |
---|---|---|
0x8202038 | 0x0 | 0x0 |
0x8202040 | 0x8 | 0x8202040 |
0x8202048 | 0x10 | 0x0 |
0x8202050 | 0x18 | 0x8403220 |
查看第一个堆的状态:1
2
3
4
5
6gef➤ hexdump qword 0x8403210 L5
0x8403210+0000 │ 0x0000000000000000
0x8403218+0008 │ 0x0000000000000091
0x8403220+0010 │ 0x0000000000000000
0x8403228+0018 │ 0x0000000000000000
0x8403230+0020 │ 0x0000000000000000
fake chunk 后第一个堆的状态:1
2
3
4
5
6
7
8
9gef➤ hexdump qword 0x8403210 L8
0x8403210+0000 │ 0x0000000000000000
0x8403218+0008 │ 0x0000000000000091
0x8403220+0010 │ 0x0000000000000000
0x8403228+0018 │ 0x0000000000000000
0x8403230+0020 │ 0x0000000008202038
0x8403238+0028 │ 0x0000000008202040
0x8403240+0030 │ 0x0000000000000000
0x8403248+0038 │ 0x0000000000000000
free 第二个 chunk 后 chunk0_ptr 存储的内容变了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16gef➤ hexdump qword 0x8403210 L8
0x8403210+0000 │ 0x0000000000000000
0x8403218+0008 │ 0x0000000000000091
0x8403220+0010 │ 0x0000000000000000
0x8403228+0018 │ 0x0000000000020de1
0x8403230+0020 │ 0x0000000008202038
0x8403238+0028 │ 0x0000000008202040
0x8403240+0030 │ 0x0000000000000000
0x8403248+0038 │ 0x0000000000000000
gef➤ hexdump qword 0x8202038 L6
0x8202038+0000 <data_start+0000> │ 0x0000000000000000
0x8202040+0008 <__dso_handle+0000> │ 0x0000000008202040
0x8202048+0010 <completed+0000> │ 0x0000000000000000
0x8202050+0018 <chunk0_ptr+0000> │ 0x0000000008202038
0x8202058+0020 │ 0x0000000000000000
0x8202060+0028 │ 0x0000000000000000
即满足条件绕过 unlink 链表 check 。
unsafe unlink 实现任意地址写数据分析
基于以上的分析,此时 chunk0_ptr 存储的是 0x8202038 这个地址,也就是说此时第一个堆的 mem ptr 不再是 0x8403220,而是 0x8202038
现在向第一个堆写入数据,其实就是往 0x8202038 这个地址写入数据,但是如果可以换掉这个地址,那么就可以往任意地址写任意数据了。效果如下:
1 | gef> n |