0.unlink
在堆块进行合并时,会把要合并的堆块从当前的链表中取出,此时会涉及到一系列的双向链表的操作,这些操作称之为unlink。
有一张流传盛广的图片:
这里简单解释一下,图中的BK、P、FD都指的是堆块。
并且,P->fd指向FD,P->bk指向BK。BK->fd和FD->bk都指向P。
当P要从当前双向链表中unlink出时,会检查两个条件:
1)P的size和next_chunk的prev_size是否一致。即对P的大小的检查。
2)BK的fd指针以及FD的bk指针进行检查,看其是否都指向P。即检查P是否位于这个双向链表内。
当以上两个条件均满足时,unlink才会发生。
检查的代码如下:
1 | // 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查) |
而从上图可以看出,unlink最终的结果是,FD->bk指向了BK,BK->fd指向了FD,P从链表中unlink。
从整个过程可以看出,unlink的结果是修改了BK的fd指针和FD的bk指针,而BK和FD的位置是由P的fd和bk这两个指针决定的。这也就意味着,如果我们能够控制P的fd和bk指针,那么我们就可以达到内存地址写。
所以,针对unlink的攻击,关键是如何绕过那两个检查。
这里我们分两种情况。
0-1.结合UAF的unlink
这种情况不用考虑第一个size的检查。
条件:
1)存在UAF漏洞;2)存在堆指针指向P的user_data区。
利用:
chunk_0和chunk_1为相邻前后的堆布局,ptr为指向chunk_0的user_data区的指针。UAF修改chunk_0->fd=ptr-0x18,chunk_0->bk=ptr-0x10,再释放chunk_1,就会触发合并,触发unlink,之后原本指向chunk_0->user_data的ptr就会指向ptr-0x18的位置,这样我们访问chunk_0实际上就是访问ptr-0x18,我们也可以修改ptr为任意地址,间接达到任意内存写。
解释:
为什么这样操作后ptr就会指向ptr-0x18呢?
我们回到最开始那幅图,FD->bk和BK->fd均指向P,也就是FD+0x18和BK+0x10指向P。而P的fd,bk分别指向FD和BK。如果分别修改为ptr-0x18,ptr-0x10,那么这两个地方就会被解析为FD和BK,也就是说,FD+0x18和BK+0x10的位置都是ptr,ptr是肯定指向P的,因此就绕过了fd和bk指针的检查。
又由上图可知,unlink先执行FD->bk=BK,这就等价于ptr=ptr-0x10;后执行BK->fd=FD,等价于ptr=ptr-0x18。因此最后的效果就是ptr指向了ptr-0x18。
0-2.结合offbyone的unlink
条件:
1)存在offbyone漏洞,可以修改P的next_chunk的prev_inuse位;2)存在堆指针指向P的user_data区。
利用:
同刚刚一样的chunk_0和chunk_1的布局。我们先在chunk_0内部伪造一个fake_chunk,使得它的size等于chunk_0-0x10,也就是将chunk_0的user_data区当成一个chunk;并伪造fd和bk分别为ptr-0x18和ptr-0x10来绕过双向链表的检查。之后先修改next_chunk的prev_size为fake_chunk的size来绕过size的检查;再利用offbyone漏洞修改next_chunk的prev_inuse位为0,使得系统误以为fake_chunk已被释放。再释放chunk_1就会触发合并操作,触发unlink。最终会使得ptr指向ptr-0x18。
解释:
和上一个原理差不多。
1.axb_2019_heap
BUU上的一道题,说起来,我最近才知道原来BUU是有给libc的……
保护全开:
程序不存在show_note函数:
但是程序存在格式化字符串漏洞:
可以通过这个泄露出程序基地址和libc基地址。
从add_note函数可以看出,只有当全局变量key=43时,才允许创建fastbin大小的堆块。并且可以知道(&(qword)note+index*2)处存放的是堆块的写入地址,(&(dword)note+index*4+2)处存放的是堆块的申请大小。也就是结构体note中有两个成员,note->addr保存user_data区地址,note->size保存申请大小,各占8字节。
get_input函数中存在offbyone漏洞:
由于无法正常申请一个fastbin大小的堆块,因此fastbin attack便不好使用。这里就符合结合offbyone漏洞利用unlink的情况,我们可以在chunk_0里伪造堆块,将chunk_0的ptr指向改为ptr-0x18,从而可以改写ptr指向__free_hook。(由于保护全开所以无法改got表,改__malloc_hook理论应该也行,但会比较麻烦。)
完整的exp如下:
1 | from pwn import * |
成功获取flag: