IO FILE 之vtable check 以及绕过
字数 1767 2025-08-05 08:20:12

IO FILE vtable check机制及绕过方法详解

一、背景介绍

在libc 2.23及之前版本中,劫持vtable指针以及FSOP(File Stream Oriented Programming)是常见的利用技术。然而在libc 2.24及以后版本中,glibc引入了vtable check机制,使得传统的修改vtable指针指向可控内存的方法失效。

二、vtable check机制分析

1. 检查机制概述

glibc 2.24引入了vtable check机制,主要包含两个层次的检查:

  1. 地址范围检查:验证vtable指针是否位于glibc的vtable段中
  2. 合法性检查:如果不是glibc内部的vtable,则检查是否为外部合法vtable

2. 核心检查函数

IO_validate_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;
}

_IO_vtable_check函数

void attribute_hidden _IO_vtable_check (void) {
#ifdef SHARED
    void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
    PTR_DEMANGLE (flag);
#endif
    if (flag == &_IO_vtable_check)
        return;
    
    Dl_info di;
    struct link_map *l;
    if (_dl_open_hook != NULL || 
        (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0 && l->l_ns != LM_ID_BASE))
        return;
#endif
    libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}

3. vtable段布局

glibc中有一段完整的内存存放着各个vtable:

  • __start___libc_IO_vtables:指向第一个vtable地址_IO_helper_jumps
  • __stop___libc_IO_vtables:指向最后一个vtable_IO_str_chk_jumps结束的地址

三、绕过vtable check的方法

1. 使用内部vtable进行利用

glibc的vtable段中包含两个特殊的vtable:

  • _IO_str_jumps:处理字符串的vtable
  • _IO_wstr_jumps:处理宽字符的vtable

_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-8,使得_IO_str_finish成为伪造的_IO_OVERFLOW函数
  2. 构造fp->_IO_buf_base不为NULL
  3. 构造fp->_flags不包含_IO_USER_BUF(即最低位为0)
  4. 构造fp->_s._free_buffersystem或one gadget地址

定位内部vtable的方法

  1. 定位_IO_str_jumps

    • 通过vtable的最后地址减去0x168
  2. 定位_IO_wstr_jumps

    • 先定位_IO_wfile_jumps,然后减去0x240

2. 构造步骤

  1. 通过FSOP触发_IO_flush_all_lockp
  2. 确保满足_mode <= 0_IO_write_ptr > _IO_write_base
  3. 伪造IO FILE结构体:
    • vtable指向_IO_str_jumps-8
    • _IO_buf_base指向/bin/sh字符串
    • _flags设置为0
    • _s._free_buffer设置为system地址

四、实践案例

案例1:hctf 2017 babyprintf

利用步骤:

  1. 使用格式化字符串泄露地址
  2. 利用堆溢出覆盖top chunk的size
  3. 通过unsorted bin attack改写_IO_list_all
  4. 伪造IO结构体:
    • vtable指向_IO_str_jumps-8
    • _IO_buf_base指向/bin/sh
    • _s._free_buffer指向system

案例2:ASIS2018 fifty-dollars

特殊点:

  1. 只能申请0x60大小的堆块
  2. 需要通过两次_chain链接实现利用:
    • _IO_list_all指向unsorted bin
    • _IO_list_all->_chain指向unsorted bin+0x68
    • _IO_list_all->_chain->_chain指向unsorted bin+0xb8

五、总结

glibc 2.24引入的vtable check机制虽然增加了利用难度,但通过分析内部vtable的功能,仍然可以找到可利用的代码路径。_IO_str_jumps_IO_wstr_jumps中的函数提供了绕过检查的可能性,关键在于精心构造IO FILE结构体以满足检查条件和触发代码执行路径。

IO FILE vtable check机制及绕过方法详解 一、背景介绍 在libc 2.23及之前版本中,劫持vtable指针以及FSOP(File Stream Oriented Programming)是常见的利用技术。然而在libc 2.24及以后版本中,glibc引入了vtable check机制,使得传统的修改vtable指针指向可控内存的方法失效。 二、vtable check机制分析 1. 检查机制概述 glibc 2.24引入了vtable check机制,主要包含两个层次的检查: 地址范围检查 :验证vtable指针是否位于glibc的vtable段中 合法性检查 :如果不是glibc内部的vtable,则检查是否为外部合法vtable 2. 核心检查函数 IO_ validate_ vtable函数 _ IO_ vtable_ check函数 3. vtable段布局 glibc中有一段完整的内存存放着各个vtable: __start___libc_IO_vtables :指向第一个vtable地址 _IO_helper_jumps __stop___libc_IO_vtables :指向最后一个vtable _IO_str_chk_jumps 结束的地址 三、绕过vtable check的方法 1. 使用内部vtable进行利用 glibc的vtable段中包含两个特殊的vtable: _IO_str_jumps :处理字符串的vtable _IO_wstr_jumps :处理宽字符的vtable _ IO_ str_ jumps利用原理 _IO_str_jumps 中的 _IO_str_finish 函数存在可利用的代码路径: 利用条件: 将vtable地址覆盖为 _IO_str_jumps-8 ,使得 _IO_str_finish 成为伪造的 _IO_OVERFLOW 函数 构造 fp->_IO_buf_base 不为NULL 构造 fp->_flags 不包含 _IO_USER_BUF (即最低位为0) 构造 fp->_s._free_buffer 为 system 或one gadget地址 定位内部vtable的方法 定位 _IO_str_jumps : 通过vtable的最后地址减去0x168 定位 _IO_wstr_jumps : 先定位 _IO_wfile_jumps ,然后减去0x240 2. 构造步骤 通过FSOP触发 _IO_flush_all_lockp 确保满足 _mode <= 0 且 _IO_write_ptr > _IO_write_base 伪造IO FILE结构体: vtable指向 _IO_str_jumps-8 _IO_buf_base 指向 /bin/sh 字符串 _flags 设置为0 _s._free_buffer 设置为 system 地址 四、实践案例 案例1:hctf 2017 babyprintf 利用步骤: 使用格式化字符串泄露地址 利用堆溢出覆盖top chunk的size 通过unsorted bin attack改写 _IO_list_all 伪造IO结构体: vtable指向 _IO_str_jumps-8 _IO_buf_base 指向 /bin/sh _s._free_buffer 指向 system 案例2:ASIS2018 fifty-dollars 特殊点: 只能申请0x60大小的堆块 需要通过两次 _chain 链接实现利用: _IO_list_all 指向unsorted bin _IO_list_all->_chain 指向unsorted bin+0x68 _IO_list_all->_chain->_chain 指向unsorted bin+0xb8 五、总结 glibc 2.24引入的vtable check机制虽然增加了利用难度,但通过分析内部vtable的功能,仍然可以找到可利用的代码路径。 _IO_str_jumps 和 _IO_wstr_jumps 中的函数提供了绕过检查的可能性,关键在于精心构造IO FILE结构体以满足检查条件和触发代码执行路径。