『Heap Exploitation』largebin_attack
前置知识
首先每个Large Bin中存在63个bin链表,在binmap中的index是64-126。每个bin中存的是一定范围内大小的chunk,而不是像tcachebin那样的一个bin一个大小。举个栗子,index64的bin中存的是0x400到0x430的chunk。每个bin能存取的范围一般是0x30。
Large Bin既不是FIFO也不是LIFO,它的排序是根据chunk大小来进行的,并且结构更加复杂。large chunk被释放的时候不仅会被写入fd和bk,还有fd_nextsize和bk_nextsize两个指针来维护bin的结构。fd和bk用来链接bin中相同大小的chunk,而nextsize则用来链接bin中不同大小的chunk。更具体地说,fd
指向比自己晚释放的相同大小的chunk,bk
则指向比自己晚释放的相同大小的chunk,fd_nextsize
用来指向比自己大的chunk,bk_nextsize
则指向比自己小的chunk。在相同大小的chunkbin中只有首堆块会有nextsize的指针。bin中首尾chunk的nextsize会指向另一端,首堆块的fd会指向对应index的bin头地址,尾堆块的bk会指向对应index的bin头地址。
这里引用Sr0cky师傅的一张图,可以更直观地看清楚largebin的结构:
源码
我们只关注释放chunk时候的代码,因为主要检查在这个地方,主要利用的地方也在这里。
1 | ... |
如果chunk在smallbin范围,则插入到smallbin中,如果不是,则进行下一步,进行插入largebin的处理。此时bck
是对应index的bin头。victim
指的是当前正在被释放的chunk。
第17行的if与第60行的else匹配,检查如果该bin为空,则直接将victim的nextsize都指向自身,fd和bk指向bin头。如果不为空则进入下一个检查。
第42行检查victim的size是否小于当前bin中最小的那个chunk,则直接将victim插入到bin的头部。在这里有个很重要的语句(第31行),也是我们需要利用到的语句。我们来逐行解释一下这个语句块。
1 | fwd = bck; |
为避免歧义,首先说明,在这些语句执行完之前我们不认为victim已经进入了bin。首先将fwd
赋值为bin头,然后bck
赋值为当前bin中最小那个chunk。紧接着将victim的fd_nextsize
赋值为bin中的尾堆块,然后将bk_nextsize
赋值为当前bin中最小的chunk。然后重点来了!当前bin中的尾堆块的bk_nextsize
指向victim,当前最小chunk的fd_nextsize
也指向victim。现在我们才视为victim完全进入了largebin当中。
可以注意到在这个过程,有两个指针被赋值为victim的地址。如果我们在释放victim之前有机会修改当前bin中最小chunk的bk_nextsize
为target
,那也就意味着我们可以往target+0x20的位置
写入victim的地址。这个结果就是我们所说的largebin attack的结果。
2.30前后的区别在于第50行和第57行的两个检查,多了两个检查所以就不能使用传统的largebin attack方法了。
攻击手法
其实可以看how2heap的PoC学习,这里我直接结合2024litctf的2.35那题来讲。
根据源码分析,我们如果想要执行到那条重要语句,我们需要先后释放掉一大一小两个largechunk。在每个largechunk下面要多一个chunk用来防止largechunk被释放后被向下合并,大小任意。
1 | add(0, 0x510) |
接着我们释放chunk2
。这时候它先进入到unsortedbin
中,我们申请一个比chunk2
还要大的chunk,因为chunk2
不够被分配,所以它会被整理到largebin当中。这时候他就同时拥有了libc地址和heap地址。
因为有UAF,所以可以直接从chunk2
泄露出libc地址和heap。因为show函数使用的是printf语句,所以要注意\x00截断的问题:保护chunk如果是0x20,那么chunk2
的地址最后一个字节刚好是\x00,那就没法泄露了;fd和bk是指向main_arena+0x490处的libc地址,同样有\x00字节,所以在泄露堆地址的时候要先随便填点什么00之外的东西在fd和bk位置。
1 | delete(2) |
然后我们释放chunk0
进入到unsrotedbin
中,然后修改chunk2
的bk_nextsize
为_IO_list_all-0x20
(因为后续会打house of apple2)。接着我们申请一个大于chunk2的chunk,把chunk0放进largebin中。注意改chunk2的时候除了bk_nextsize
其他东西尽量保持原状,因为会检查其他三项的合法性。
1 | delete(0) |
根据源码分析,我们可以知道_IO_list_all
就会被写入chunk0的地址。至此,largebin attack就算完成了。
我们断点检查一下:
可以看到_IO_list_all已经被写入chunk0的地址了。后面就是house of apple2了,这里不展开赘述。