从一道题来体会用UAF打unlink
之前对于 unlink 的理解停留在表面,一直以为得有个堆溢出才能利用。今天做了一道 0ctf2015_freenote ,发现利用 UAF ,依然可以打 unlink … 本来记录一下用 UAF 打 unlink 的思路,关于堆溢出打 unlink 以及该手法的更多细节请见 本文
UAF 导致的 unlink
unlink 的关键在于两点
- 去伪造一个
fake_chunk,并且要已知&fake_chunk的地址 - 能够控制
fake_chunk下面的(高地址方向)合法堆块的prev_size和size
上面提到的合法堆块,也被称为引线堆块。当释放掉引线堆块时,因为引线堆块的 prev_inuse 为 0 (需要想办法控制),就会让 ptmalloc 以为引线堆块上面还有一个堆块是处于释放状态,然后要触发合并,通过精心构造的引线堆块 prev_size 找到上一个(低地址方向) fake_chunk ,最终触发 unlink
如果是堆溢出的话,那么很自然 fake_chunk 的构造以及通过溢出来控制引线堆块的 prev_size 和 size 位都轻而易举。下面来看一下只有 UAF 漏洞,如何来做到同样的效果。
前提:存在 UAF 漏洞,以 libc 2.23 为例
- 申请
size为0x100堆块A和堆块B(如下图)
- 现在释放掉
A和B,二者会合并成一个0x220的堆块,处于释放状态
将这个
0x220处于释放状态的堆块申请出来,命名为堆块C(如下),此时将堆块申请出来后,是可以往里面写入数据的,此时来在原本堆块A的位置伪造fake_chunk,然后让写入的数据来覆盖掉堆块B的prev_size和size
因为存在
UAF的原因,所以堆块B是可以再次被释放的,而它也就被当做了引线堆块。
说到底,其实 UAF 能导致 unlink 的原因实际上是 double free (是位于 unsorted bin 中的堆块两次释放) 至此前戏完成,后面的伪造 fake_chunk 以及触发 unlink 的操作正常进行即可。下面来结合一道题目具体分析一下
0ctf2015_freenote
保护策略
代码审计
经典菜单堆,增,删,编辑,打印功能都有。delete 函数中存在 UAF 漏洞
在 add 功能中,对申请堆块的大小做了要求,必须要为 0x80 字节对齐(下图红框中体现了这一点),这就意味着申请的堆块都无法进入到 fastbin 中
edit 功能中首先限制了堆块自定义的标志位是否为 1 ,如果不为1的话,说明该堆块已经被释放了(虽然存在 UAF ,但是自定义标志位确实置空了),如果编辑的 size 不等于原本的值,那么会调用 realloc 扩展或缩小堆块

show 函数可以一次直接打印所有堆块里面的数据
利用思路
总结一下前面的已知信息
- 申请堆块最小为
0x90,也就是堆块无法进入fastbin - 有
UAF,但是会将自定义标志位置零 show函数和edit函数会检查自定义标志位,但是delete函数不会- 可以篡改
got表,并且没开PIE - 堆块的地址是记录在了初始大堆块中
所以本题的思路是用 show 函数先泄露出 libc 地址和堆地址(因为检查了自定义标志位,所以要将堆块申请出来,利用里面的残留值进行打印),按照本文最开始说的来布局,利用 uaf 做出 unlink
图解过程如下:



释放引线堆块,触发 unlink ,在记录堆块地址的位置写入了一个 &fake_chunk 的地址
然后再记录堆块地址的区域写一个 atoi 函数的 got 表地址,最后用 edit 功能篡改 atoi 的 got 表为 system 地址,执行到 atoi("/bin/sh") 获取 shell
EXP
from tools import * |
