学习资料:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unlink/
http://www.anquan.us/static/drops/tips-16610.html
原理直接拷贝CTFWIKI的原话
这个直接看图挺容易懂得,有点像数据结构的链表操作
这个就是unlink的基本操作,函数如下
#define unlink(P, BK, FD) {
FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;
...
}
P就是当中的那块,BK就是最左边的那块,FD就是最右边的那块
一波血泪史,最早的时候unlink很简单,如下
一顿操作:
- FD=P->fd = target addr -12
- BK=P->bk = expect value
- FD->bk = BK,即 *(target addr-12+12)=BK=expect value
- BK->fd = FD,即 *(expect value +8) = FD = target addr-12
这会有什么效果呢,加入我们的target addr放着某个got表项,那么当程序执行对应的函数时,就会直接执行我们需要的expect value
这里给个例子
/*
Heap overflow vulnerable program.
*/
#include <stdlib.h>
#include <string.h>
int main( int argc, char * argv[] )
{
char * first, * second;
/*[1]*/ first = malloc( 666 );
/*[2]*/ second = malloc( 12 );
if(argc!=1)
/*[3]*/ strcpy( first, argv[1] );
/*[4]*/ free( first );
/*[5]*/ free( second );
/*[6]*/ return( 0 );
}
通过strcpy可以覆盖second chunk的chunk header
假设被覆盖后的chunk header相关数据如下:
1) prev_size = 一个偶数,这样其PREV_INUSE 位就是0 了,即表示前一个chunk为free。
2) size = -4
3) fd = free 函数的got表地址address – 12
#实际的address的数据存放在数据段中(chunk非空闲时,fd\bk存放具体数据,所以指向的地址其实是free_addr-12)
4) bk = shellcode的地址
那么当程序在[4]处调用free(first)后会发生什么呢?
Unlink是把free掉的chunk从所属的bins链中,卸下来的操作(当然还包括一系列的检测机制),它是在free掉一块chunk(除fastbin大小的chunk外)之后,glibc检查这块chunk相邻的上下两块chunk的free状态之后,做出的向后合并或者向前合并引起的。
这里显然不可能是后向合并,first是第一块,能否执行前向合并,就要看下下块的PREV_INUSE字段是否是0了,此时nextsize被我们设置为了-4,这样glibc malloc就会将next chunk的prev_size字段看做是next-next chunk的size字段,而我们已经将next chunk的prev_size字段设置为了一个偶数,因此此时下下个chunk的PREV_INUSE字段为0,即下个chunk为free!开始执行向前合并
而unlink后就会把free函数的实际数据改变,等执行free(second)时,就会执行我们的shellcode了
一切看上去都是这么的美好
但是现在多了个检查机制,怎么检查的呢
// fd bk if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr (check_action, "corrupted double-linked list", P, AV);
emmmm,肯定不满足呀,需要修改
接下来开始绕过:
首先我们通过覆盖,将 nextchunk 的 FD 指针指向了 fakeFD,将 nextchunk 的 BK 指针指向了 fakeBK 。那么为了通过验证,我们需要
fakeFD -> bk == P
<=>*(fakeFD + 12) == P
fakeBK -> fd == P
<=>*(fakeBK + 8) == P
然后就可以执行unlink了
fakeFD -> bk = fakeBK
<=>*(fakeFD + 12) = fakeBK
fakeBK -> fd = fakeFD
<=>*(fakeBK + 8) = fakeFD
那就意味着p->bk=nextchunk addr-8;p->fd=nextchunk addr-12,显然如果没有这个检测的话,我们可以构建任意的内容,但有了这个检测以后约束很大,但勉强可以改变chunk的指针