美文网首页程序员
unlink原理及利用

unlink原理及利用

作者: 星辰照耀你我 | 来源:发表于2019-02-27 20:12 被阅读1次

    unlink是利用glibc malloc 的内存回收机制造成攻击的,核心就在于当两个free的堆块在物理上相邻时,会将他们合并,并将原来free的堆块在原来的链表中解链,加入新的链表中,但这样的合并是有条件的,向前或向后合并。但这里的前和后都是指在物理内存中的位置,而不是fd和bk链表所指向的堆块。
    以当前的chunk为基准,将preivous free chunk合并到当前chunk称为向后合并,将后面的free chunk合并到当前chunk就称为向前合并。

    向后合并

    /* consolidate backward */
        if (!prev_inuse(p)) {
          prevsize = p->prev_size;
          size += prevsize;
          p = chunk_at_offset(p, -((long) prevsize));
          unlink(p, bck, fwd);
        }
    #define chunk_at_offset(p, s)  ((mchunkptr)(((char*)(p)) + (s)))
    

    unlink的定义如下:

    #define unlink(P, BK, FD) {                                            \
      FD = P->fd;                                                          \
      BK = P->bk;                                                          \
      if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                \
        malloc_printerr (check_action, "corrupted double-linked list", P); \
      else {                                                               \
        FD->bk = BK;                                                       \
        BK->fd = FD;                                                       \
        if (!in_smallbin_range (P->size)                       \
        && __builtin_expect (P->fd_nextsize != NULL, 0)) {         \
          assert (P->fd_nextsize->bk_nextsize == P);               \
          assert (P->bk_nextsize->fd_nextsize == P);               \
          if (FD->fd_nextsize == NULL) {                       \
        if (P->fd_nextsize == P)                       \
          FD->fd_nextsize = FD->bk_nextsize = FD;              \
        else {                                 \
          FD->fd_nextsize = P->fd_nextsize;                \
          FD->bk_nextsize = P->bk_nextsize;                \
          P->fd_nextsize->bk_nextsize = FD;                \
          P->bk_nextsize->fd_nextsize = FD;                \
        }                                  \
          } else {                                 \
        P->fd_nextsize->bk_nextsize = P->bk_nextsize;              \
        P->bk_nextsize->fd_nextsize = P->fd_nextsize;              \
          }                                    \
        }                                      \
      }                                                                    \
    }
    

    其中有很多检测是之前没有的,首先把单纯的unlink弄清楚。

    #define unlink(P,BK,FD){
    FD=P->fd;
    BK=P->bk;
    FD->bk=BK;
    BK->fd=FD;
    ...
    }
    

    unlink可以简单看作上面的代码部分,将链表中的P脱链,把之前P的下一个chunk与P的上一个chunk连接,使P离开链表。
    首先检测前一个chunk是否为free状态,通过检测当前free chunk的PREV_INUSE(P)标志位,如果为0表示free状态,但内存中第一个申请的chunk的前一个chunk一般都被认为在使用中,所以不会发生向后合并。
    如果不是内存中的第一个chunk且它的前一个chunk标记为free状态时,发生向后合并:
    首先修改chunk的size位大小为两个chunk size之和
    再将指针移动到前一个chunk处
    最后调用unlink将前一个chunk从它所在的链表中移除。

    向前合并

    if (nextchunk != av->top) {
          /* get and clear inuse bit */
          nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
    
          /* consolidate forward */
          if (!nextinuse) {
        unlink(nextchunk, bck, fwd);
        size += nextsize;
          } else
        clear_inuse_bit_at_offset(nextchunk, 0);
    
    #define clear_inuse_bit_at_offset(p, s)\
     (((mchunkptr)(((char*)(p)) + (s)))->size &= ~(PREV_INUSE))
    

    向前合并不修正P的指针,只增加size大小。
    在合并后glbc malloc会将合并后新的chunk加入unsorted bin中,加入第一个可用的chunk之前,更改自己的size字段将前一个chunk标记为已用,再将后一个chunk的previous size改为当前chunk的大小。

    利用unlink改写got表执行shellcode的第一种姿势(理解来自 阿里@走位)

    如果两个相邻的chunk在第一个chunk写入数据时发生了溢出,覆盖了后一个chunk的数据为特殊含义的数据,那么就有可能使程序执行特定的代码,从而控制程序流程达到相应的目的。
    当我们通过溢出改写数据如下时会满足特别的条件:

    previous size => 一个偶数
    size =>-4
    fd => free@got addr-12
    bk =>shellcode addr

    当覆盖数据后,因为改写的是下一个chunk的数据,当free当前第一个chunk时,先考虑会不会向后合并,这时因为第一个chunk 的前一个总是占用的,即使他根本不存在,所以当第向后chunk被free后不会发生向后合并,再判断向前合并,
    首先去检测下一个chunk是否处于free状态
    需要通过next->next chunk的size标志位检测,当我们设置next chunk的size为-4时,next chunk的previous size字段会被看作next->next chunk的size字段,此时又因为next chunk的previous size 字段为偶数,即next->next chunk->size为偶数,P标志位为0,表示next chunk为free状态,满足向后合并的条件,触发unlink。
    当满足unlink的条件时,内存的变化
    利用两个临时变量FD、BK将后一个chunk从原来的free链表中解链


    首先FD=P->fd;BK=P->bk;这时FD的值为free@got-12,BK为shellcode地址
    再次FD->bk=BK;BK->fd=FD;因为这时FD,BK是强制被看作两个chunk的,所以它的bk与fd相对的地址与一个正常chunk是一样的,FD->bk与FD的地址相差12,而FD为free@got-12,那么FD->bk的位置就是free@got的位置,被赋值了BK=shellcode,这时当执行free函数时转而执行shellcode,达成了通过unlink修改free@got表的目的。


    利用unlink的第二种姿势

    将一个大的chunk伪造成两个较小的堆块,在填充数据时通过第二个伪造堆块的size标志位P使前一个伪chunk处于free状态,这时因为这个大的堆块本来就处于inuse状态,所以第二个伪堆块的下一个堆块size位P=1,表示前一个chunk处于使用状态,当free后面的伪堆块时会将前一个伪堆unlink,

    伪堆构造

    图中红色边框为一个大的申请的堆块,黄色为第一个伪堆块,蓝色为第二个伪堆块,通过数据填充构造上图的内存布局,因为ptr为申请内存时返回的指针,所以ptr一开始就指向fake_prev,要绕过unsafe_unlink,将第一个伪堆块的fd=ptr-0x18,bk=ptr-0x10,这样当检查FD->bk=p的时候成功绕过了检测,满足了unlink可以实现后续的利用。

    相关文章

      网友评论

        本文标题:unlink原理及利用

        本文链接:https://www.haomeiwen.com/subject/ewrnnftx.html