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机制,主要包含两个层次的检查:
- 地址范围检查:验证vtable指针是否位于glibc的vtable段中
- 合法性检查:如果不是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);
}
利用条件:
- 将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地址
- vtable指向
四、实践案例
案例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
- vtable指向
案例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结构体以满足检查条件和触发代码执行路径。