IO_FILE基础与示例
字数 2993 2025-08-23 18:31:09

IO_FILE 结构体与文件操作机制深入解析

一、IO_FILE 结构体基础

1.1 核心结构体

IO_FILE 是 glibc 中用于文件操作的核心结构体,定义在 libio.h 头文件中。主要结构体包括:

  1. _IO_FILE:基础文件流结构
  2. _IO_FILE_plus:包含虚函数表的扩展结构
  3. _IO_jump_t:虚函数表结构
  4. _IO_wide_data:宽字符处理结构
struct _IO_FILE {
    int _flags;                 // 标志位,高位是_IO_MAGIC
    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;
    _IO_off_t _old_offset;      // 文件偏移
    unsigned short _cur_column;  // 当前列
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t* _lock;
    // ... 其他字段
};

1.2 文件流链表

  • 通过 _chain 域连接 stderrstdoutstdin 的链表
  • 链表的表头是 _IO_list_all
  • 这三个是程序启动时自动打开的标准文件流

1.3 _IO_FILE_plus 结构

struct _IO_FILE_plus {
    _IO_FILE file;
    const struct _IO_jump_t* vtable;  // 虚函数表
};

vtable 是一个函数指针数组,存储了文件操作的各种虚函数地址。

二、关键操作流程分析

2.1 fopen 操作流程

  1. 调用链

    fopen → _IO_new_fopen → __fopen_internal
    
  2. 关键步骤

    • 分配 locked_FILE 结构体(0x230 大小)
    • _IO_no_init:初始化结构体成员为 NULL
    • _IO_file_init:将结构体链接到 _IO_list_all
    • _IO_file_fopen:实际打开文件并设置 _fileno
  3. 内存布局

    struct locked_FILE {
        struct _IO_FILE_plus fp;
        _IO_lock_t lock;         // 多线程安全锁
        struct _IO_wide_data wd;  // 宽字符数据
    };
    

2.2 fread 操作流程

  1. 调用链

    fread → _IO_fread → _IO_sgetn → _IO_XSGETN → _IO_file_xsgetn
    
  2. 关键步骤

    • 检查缓冲区是否为空,调用 _IO_doallocbuf 分配缓冲区
    • _IO_file_doallocate:获取文件信息并分配缓冲区
    • _IO_SYSSTAT:获取文件状态(stat64 结构)
    • _IO_setb:设置缓冲区指针
    • __underflow:实际读取数据
  3. 缓冲区初始化

    • 调用 malloc 分配缓冲区(大小通常为 st_blksize)
    • 设置 _IO_buf_base_IO_buf_end

2.3 fwrite 操作流程

  1. 调用链

    fwrite → _IO_fwrite → _IO_sputn → _IO_new_file_xsputn
    
  2. 关键步骤

    • 计算输出缓冲区剩余空间
    • 将数据复制到输出缓冲区
    • 缓冲区满时调用 _IO_OVERFLOW(即 _IO_new_file_overflow
    • _IO_doallocbuf:必要时分配缓冲区
    • _IO_new_do_write:实际执行写入操作
    • _IO_SYSWRITE:最终调用 write 系统调用
  3. 缓冲区刷新

    • 设置 _IO_write_ptr 等指针
    • 调用 _IO_default_xsputn 处理剩余数据

三、虚函数表(vtable)机制

3.1 _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);
    // ... 其他函数指针
};

3.2 关键虚函数

函数指针 对应实现 作用
__finish _IO_new_file_finish 文件结束处理
__overflow _IO_new_file_overflow 缓冲区溢出处理
__underflow _IO_new_file_underflow 缓冲区不足处理
__xsputn _IO_new_file_xsputn 写入操作
__xsgetn _IO_file_xsgetn 读取操作
__doallocate _IO_file_doallocate 缓冲区分配
__read _IO_file_read 底层读取
__write _IO_new_file_write 底层写入

3.3 vtable 检查机制

在 glibc 2.23 及以上版本中,引入了 vtable 检查机制:

  • 检查 vtable 地址是否合法
  • 防止直接劫持 vtable 指针

四、利用技术要点

4.1 读取函数利用

攻击 fread/scanf/gets 等函数的关键是控制 _IO_new_file_underflow,需要注意:

  1. 标志位检查

    if (fp->_flags & _IO_NO_READS) {
        fp->_flags |= _IO_ERR_SEEN;
        __set_errno(EBADF);
        return EOF;
    }
    
  2. 利用路径

    • 控制 _IO_buf_base_IO_buf_end
    • 劫持 vtable 中的 __underflow 或相关函数

4.2 写入函数利用

攻击 fwrite/printf 等函数的关键是控制 _IO_new_file_overflow,需要注意:

  1. 标志位检查

    if (f->_flags & _IO_NO_WRITES) {
        f->_flags |= _IO_ERR_SEEN;
        __set_errno(EBADF);
        return EOF;
    }
    
  2. 利用路径

    • 控制输出缓冲区指针
    • 劫持 vtable 中的 __overflow__write

4.3 vtable 劫持技术

  1. 经典劫持

    • 修改 _IO_FILE_plus.vtable 指向可控区域
    • 构造虚假的 _IO_jump_t 结构
    • 控制程序执行流
  2. 绕过检查

    • 使用合法范围内的 vtable 地址
    • 部分覆盖 vtable 指针
    • 利用现有 vtable 中的 gadget

五、调试与分析技巧

5.1 GDB 实用命令

  1. 查看 _IO_list_all

    p *_IO_list_all
    
  2. 查看 vtable:

    p *_IO_list_all->vtable
    
  3. 查看文件结构体:

    p *((struct _IO_FILE_plus*)0x<address>)
    

5.2 关键内存布局

  1. fopen 后的堆布局

    Allocated chunk | PREV_INUSE 
    Addr: 0x55555555b000 
    Size: 0x230
    
  2. fread 后的缓冲区

    Allocated chunk | PREV_INUSE 
    Addr: 0x405230 
    Size: 0x1010
    

六、防御与对抗

6.1 常见防护机制

  1. vtable 验证

    • glibc 2.24+ 的 vtable 检查
    • 只允许使用合法 vtable
  2. 指针保护

    • _IO_list_all 指针加密
    • 关键结构体只读保护

6.2 绕过思路

  1. FSOP(File Stream Oriented Programming)

    • 通过伪造文件流结构触发漏洞
    • 利用 _IO_list_all 链表的操作
  2. 部分覆盖

    • 修改 vtable 中的特定函数指针
    • 保持其他指针合法
  3. 堆布局控制

    • 精确控制堆分配布局
    • 利用已有结构体进行攻击

七、总结

IO_FILE 机制是 glibc 文件操作的核心,理解其内部结构和工作原理对于二进制安全和漏洞利用至关重要。关键点包括:

  1. 掌握 _IO_FILE_IO_FILE_plus 结构布局
  2. 熟悉文件操作的主要流程和虚函数调用链
  3. 了解 vtable 机制及其安全限制
  4. 掌握相关调试和分析技术
  5. 理解现代防护机制及可能的绕过方法

通过深入分析 IO_FILE 机制,可以更好地理解用户态文件操作的底层实现,为高级漏洞利用技术打下坚实基础。

IO_ FILE 结构体与文件操作机制深入解析 一、IO_ FILE 结构体基础 1.1 核心结构体 IO_ FILE 是 glibc 中用于文件操作的核心结构体,定义在 libio.h 头文件中。主要结构体包括: _ IO_ FILE :基础文件流结构 _ IO_ FILE_ plus :包含虚函数表的扩展结构 _ IO_ jump_ t :虚函数表结构 _ IO_ wide_ data :宽字符处理结构 1.2 文件流链表 通过 _chain 域连接 stderr 、 stdout 、 stdin 的链表 链表的表头是 _IO_list_all 这三个是程序启动时自动打开的标准文件流 1.3 _ IO_ FILE_ plus 结构 vtable 是一个函数指针数组,存储了文件操作的各种虚函数地址。 二、关键操作流程分析 2.1 fopen 操作流程 调用链 : 关键步骤 : 分配 locked_FILE 结构体(0x230 大小) _IO_no_init :初始化结构体成员为 NULL _IO_file_init :将结构体链接到 _IO_list_all _IO_file_fopen :实际打开文件并设置 _fileno 内存布局 : 2.2 fread 操作流程 调用链 : 关键步骤 : 检查缓冲区是否为空,调用 _IO_doallocbuf 分配缓冲区 _IO_file_doallocate :获取文件信息并分配缓冲区 _IO_SYSSTAT :获取文件状态(stat64 结构) _IO_setb :设置缓冲区指针 __underflow :实际读取数据 缓冲区初始化 : 调用 malloc 分配缓冲区(大小通常为 st_ blksize) 设置 _IO_buf_base 和 _IO_buf_end 2.3 fwrite 操作流程 调用链 : 关键步骤 : 计算输出缓冲区剩余空间 将数据复制到输出缓冲区 缓冲区满时调用 _IO_OVERFLOW (即 _IO_new_file_overflow ) _IO_doallocbuf :必要时分配缓冲区 _IO_new_do_write :实际执行写入操作 _IO_SYSWRITE :最终调用 write 系统调用 缓冲区刷新 : 设置 _IO_write_ptr 等指针 调用 _IO_default_xsputn 处理剩余数据 三、虚函数表(vtable)机制 3.1 _ IO_ jump_ t 结构 3.2 关键虚函数 | 函数指针 | 对应实现 | 作用 | |------------------|--------------------------|-----------------------------| | __ finish | _ IO_ new_ file_ finish | 文件结束处理 | | __ overflow | _ IO_ new_ file_ overflow | 缓冲区溢出处理 | | __ underflow | _ IO_ new_ file_ underflow | 缓冲区不足处理 | | __ xsputn | _ IO_ new_ file_ xsputn | 写入操作 | | __ xsgetn | _ IO_ file_ xsgetn | 读取操作 | | __ doallocate | _ IO_ file_ doallocate | 缓冲区分配 | | __ read | _ IO_ file_ read | 底层读取 | | __ write | _ IO_ new_ file_ write | 底层写入 | 3.3 vtable 检查机制 在 glibc 2.23 及以上版本中,引入了 vtable 检查机制: 检查 vtable 地址是否合法 防止直接劫持 vtable 指针 四、利用技术要点 4.1 读取函数利用 攻击 fread / scanf / gets 等函数的关键是控制 _IO_new_file_underflow ,需要注意: 标志位检查 : 利用路径 : 控制 _IO_buf_base 和 _IO_buf_end 劫持 vtable 中的 __underflow 或相关函数 4.2 写入函数利用 攻击 fwrite / printf 等函数的关键是控制 _IO_new_file_overflow ,需要注意: 标志位检查 : 利用路径 : 控制输出缓冲区指针 劫持 vtable 中的 __overflow 或 __write 4.3 vtable 劫持技术 经典劫持 : 修改 _IO_FILE_plus.vtable 指向可控区域 构造虚假的 _IO_jump_t 结构 控制程序执行流 绕过检查 : 使用合法范围内的 vtable 地址 部分覆盖 vtable 指针 利用现有 vtable 中的 gadget 五、调试与分析技巧 5.1 GDB 实用命令 查看 _IO_list_all : 查看 vtable: 查看文件结构体: 5.2 关键内存布局 fopen 后的堆布局 : fread 后的缓冲区 : 六、防御与对抗 6.1 常见防护机制 vtable 验证 : glibc 2.24+ 的 vtable 检查 只允许使用合法 vtable 指针保护 : _IO_list_all 指针加密 关键结构体只读保护 6.2 绕过思路 FSOP(File Stream Oriented Programming) : 通过伪造文件流结构触发漏洞 利用 _IO_list_all 链表的操作 部分覆盖 : 修改 vtable 中的特定函数指针 保持其他指针合法 堆布局控制 : 精确控制堆分配布局 利用已有结构体进行攻击 七、总结 IO_ FILE 机制是 glibc 文件操作的核心,理解其内部结构和工作原理对于二进制安全和漏洞利用至关重要。关键点包括: 掌握 _IO_FILE 和 _IO_FILE_plus 结构布局 熟悉文件操作的主要流程和虚函数调用链 了解 vtable 机制及其安全限制 掌握相关调试和分析技术 理解现代防护机制及可能的绕过方法 通过深入分析 IO_ FILE 机制,可以更好地理解用户态文件操作的底层实现,为高级漏洞利用技术打下坚实基础。