啊,花了一天多的时间来弄懂unlink的原理和一个简单的应用。只能说,pwn真好玩,就是头上有点凉,就是我太菜了。写个学习笔记记录一下,以免忘了。。。
环境:Ubuntu16.04
0x00 Unlink
在弄清楚unlink之前,需要先了解一下glibc的内存管理。
unlink发生在当释放一个堆块P时,glibc会检查与这个堆块物理相邻的堆块(假设是)S是否是空闲的,如果是的话,就会unlink(S),然后合并两个堆块P、S。
malloc_chunk的定义是这样子的:
1 | /* |
unlink的操作可以借助wiki上的图片:
可以看到,就是双向链表中删除节点P的操作。
1 | p->fd->bk = p->bk; |
然后P节点就被从链表中删除了。
0x01 利用
简单的栗子
假设这样一个堆块
1 | +--------------+ <- chunk0 ptr |
现在chunk1已经是空闲状态了,当通过对chunk0的写能够溢出到chunk1的时候,我们覆盖user data部分的fd和bk指针,使fd = target addr - 12,bk = except value,然后我们free(chunk0)。这样的话glibc检查发现chunk1也是空闲的,就会发起合并操作,这样就会触发unlink(chunk1),从而:
1 | chunk1->fd->bk = chunk1->bk; //效果就是target addr - 12 + 12 = except value |
这样的话我们就能在target addr处是实现一个改写(如果有写权限的话)。
实际上
然鹅,实际上unlink并没有这么顺利,unlink宏现在已经加了一个检查:
1 | #define unlink(P, BK, FD) { \ |
可以看到第4行的检查,所以需要通过这个判断才行。简单的利用是不能通过的。
为了过判断,需要一个全局指针chunk_ptr,然后构造fake chunk,举个栗子:
1 | +--------------+ <- chunk_ptr //使全局变量指向堆块 |
在chunk0中伪造一个fake chunk,使得fake chunk.fd = chunk_ptr-12,fake chunk.bk = chunk_ptr-8,然后通过溢出,修改chunk1的prev_size为fake chunk的大小,然后要把chunk1的size的最低位改成0,表示前一个chunk(fake chunk)是空闲的。
然后free(chunk1),然后就会触发unlink(fake chunk),在进行检验的时候:
1 | FD = chunk_ptr->fd = &chunk_ptr-12 |
所以,chunk_ptr就是指向fake chunk,也就是P,所以满足判断(FD->bk == P && BK->fd == P),这样就能继续完成unlink,完成后结果就是
1 | FD->bk = chunk_ptr->bk; //使得*chunk_ptr = &chunk_ptr-8 |
这样的的话chunk_ptr就会指向&chunk_ptr-12的位置,也就是说,这样以后,就能把chunk_ptr改写成任意目标地址,然后再次往chunk_ptr中写,这样就能实现一次任意地址覆盖,从而控制程序流程。
0x02 栗子
这里借用how2heap的例子,源地址。
1 |
|
当堆分配完的时候,内存分布可以表示为这样:
然后通过溢出,构造fake_chunk,并将&chunk0_ptr-3*8和&chunk_ptr-2*8作为其fd和bk,并修改chunk1的Pre size和Size的值。
在chunk0中布置好以后,需要把chunk1的pre size改掉,以及chunk1的size的最低为修改为0,表示前一个chunk是“空闲”的。然后free掉chunk1。触发unlink。最终使得chunk0_ptr指向&chunk_ptr-3*8的位置。
然后就通过写入,把chunk0_ptr改成目标地址,然后再写入,修改目标地址的值。
可以看到成功了。
0x03 Demo
在ida中可以看得出来,set函数是有问题的,可以发生溢出。
1 | ssize_t func_set_chunk() |
是个萌新友好向的demo,可以试着修改一下free的got表的值。
利用脚本
1 | #!/usr/bin/python |
可以看到free的got表地址已经被修改了。
0x04 总结
总共花了两天多的时间,除去记录以外,学习unlink的时间竟然大部分卡在了。。。这个fake chunk和要free的那一块这么合并啊???啊,菜得真实。。。其实完全不需要管后面到底怎么合并的嘛!
其实这个demo的利用还没结束,最好还能任意代码执行,下次有时间再填坑好了(挖坑!接着挖坑!。