IO FILE 之劫持vtable及FSOP
字数 2756 2025-08-05 08:20:09
IO FILE 利用技术详解:vtable劫持与FSOP
一、IO FILE基础回顾
IO FILE结构体是glibc中用于文件操作的核心数据结构,其中最重要的组件之一是vtable(虚函数表)。在之前的文章中已经详细分析了fopen、fread、fwrite和fclose等函数的源码实现。
关键结构体定义:
struct _IO_FILE_plus {
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
struct _IO_jump_t {
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
// ...共19个函数指针
};
二、vtable劫持技术
1. 基本原理
vtable劫持的核心思想是通过控制FILE结构体中的vtable指针,使其指向可控内存,从而在调用IO函数时劫持程序执行流。
适用条件:
- 仅适用于libc 2.23及之前版本
- libc 2.24之后引入了vtable check机制,无法直接伪造vtable
2. 劫持方式
有两种主要方式实现vtable劫持:
-
修改已有FILE结构体的vtable字段:
- 直接修改内存中已存在的FILE结构体的vtable指针
- 示例代码:
FILE *fp = fopen("123.txt", "rw"); long long *vtable_addr = (long long *)((long long)fp + 0xd8); // vtable偏移 vtable_addr[0] = (long long)fake_vtable; // 指向伪造的vtable
-
伪造整个FILE结构体:
- 完全构造一个伪造的FILE结构体
- 修改指针指向这个伪造结构体
3. 关键IO函数调用的vtable函数
| 函数 | 调用的vtable函数 |
|---|---|
| fopen | 不直接调用vtable函数 |
| fread | _IO_file_xsgetn, _IO_file_doallocate, __GI__IO_file_stat, __GI__IO_file_read |
| fwrite | _IO_new_file_xsputn, _IO_new_file_overflow, _IO_file_doallocate, _IO_new_file_write |
| fclose | __close, __finish |
三、FSOP(File Stream Oriented Programming)
1. 基本原理
FSOP利用的是glibc中管理所有打开文件的单链表_IO_list_all。通过伪造链表节点,控制程序执行流。
关键数据结构:
_IO_list_all:指向FILE结构体链表的头部_chain字段:链接下一个FILE结构体
2. 触发时机
_IO_flush_all_lockp函数会在以下情况下被调用:
- 程序调用
abort()时 - 程序调用
exit()时 - 程序从main函数正常返回时
3. 利用条件
要成功触发FSOP,需要满足以下条件:
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0 && fp->_mode > 0
&& (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))
#endif
) && _IO_OVERFLOW (fp, EOF) == EOF)
即:
- 对于普通文件(
_mode <= 0):_IO_write_ptr > _IO_write_base - 对于宽字符文件(
_mode > 0):_wide_data->_IO_write_ptr > _wide_data->_IO_write_base
4. 利用步骤
- 伪造一个FILE结构体
- 利用漏洞将
_IO_list_all指向伪造的结构体- 或修改链表中的
_chain字段指向伪造数据
- 或修改链表中的
- 触发
_IO_flush_all_lockp调用 - 绕过检查条件,使程序调用
_IO_OVERFLOW时劫持执行流
四、House of Orange攻击实例分析
以"东华杯2016-pwn450"为例,演示FSOP的实际利用。
1. 题目分析
- 菜单题:创建、编辑、删除堆块
- 限制:同一时间只能操作一个堆块
- 漏洞:编辑函数存在堆溢出
2. 利用步骤
阶段一:信息泄露
- 通过创建函数泄露堆地址
- 申请大块内存(如0x200000)使mmap区域紧贴libc,计算libc基址
阶段二:构造unsorted bin
- 利用堆溢出伪造top chunk的size
- size > 0x20
- prev_inuse位为1
- top chunk address + size必须页对齐(0x1000)
- 申请超过伪造size的内存,触发sysmalloc将旧top chunk释放到unsorted bin
阶段三:unsorted bin attack
- 利用unsorted bin attack修改
_IO_list_all- 使其指向main_arena中的unsorted_bins数组
- 伪造一个0x60大小的small bin
- 释放后其bk指针会被当作
_chain字段
- 释放后其bk指针会被当作
阶段四:FSOP利用
- 当
_IO_flush_all_lockp遍历链表时:- 第一个节点(main_arena)不可控
- 第二个节点(通过
_chain字段)完全可控
- 伪造FILE结构体:
- 设置
_mode = 0 - 设置
_IO_write_ptr > _IO_write_base - 将vtable全部指针设置为
system地址
- 设置
- 最终执行
system("bin/sh")获取shell
3. 伪造结构体示例
# 使用pwn_debug工具检查伪造结构体
from pwn_debug import IO_FILE_plus
IO_FILE_plus.orange_check(fake_file) # 检查是否满足house of orange条件
IO_FILE_plus.show(fake_file) # 显示伪造的FILE结构体
五、防御机制与绕过
1. libc 2.24的vtable check
libc 2.24引入了vtable检查机制:
- 只允许vtable指向特定的合法区域
- 防止直接伪造vtable
2. 可能的绕过方法
- 重用合法vtable中的函数指针
- 利用部分写修改合法vtable中的指针
- 结合其他漏洞绕过检查
六、总结
-
vtable劫持:
- 通过修改vtable指针控制程序流
- 适用于libc 2.23及之前版本
-
FSOP:
- 利用
_IO_list_all链表和_IO_flush_all_lockp机制 - 需要精心构造FILE结构体满足检查条件
- House of Orange是经典利用方式
- 利用
-
防御:
- libc 2.24+的vtable check有效阻止了直接伪造
- 需要寻找新的利用方式
七、扩展阅读
- [IO FILE之fopen详解]
- [IO FILE之fread详解]
- [IO FILE之fwrite详解]
- [IO_FILE之fclose详解]
- [unsorted bin attack分析]
注:本文所述技术仅用于安全研究和CTF比赛,请勿用于非法用途。