unlink原理及利用

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可以实现后续的利用。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星辰照耀你我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值