从一道题来体会用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 * |