堆溢出----Unlink

学习资料: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的指针

猜你喜欢

转载自blog.csdn.net/qq_42192672/article/details/83317669