简介
对IO_FILE的一种利用方法是,不修改虚表,修改IO_FILE结构中的数据指针,可以实现任意地址读和任意地址写
IO_FILE 结构
pwndbg> dt FILE
FILE
+0x0000 _flags : int
+0x0008 _IO_read_ptr : char *
+0x0010 _IO_read_end : char *
+0x0018 _IO_read_base : char *
+0x0020 _IO_write_base : char *
+0x0028 _IO_write_ptr : char *
+0x0030 _IO_write_end : char *
+0x0038 _IO_buf_base : char *
+0x0040 _IO_buf_end : char *
+0x0048 _IO_save_base : char *
+0x0050 _IO_backup_base : char *
+0x0058 _IO_save_end : char *
+0x0060 _markers : struct _IO_marker *
+0x0068 _chain : struct _IO_FILE *
+0x0070 _fileno : int
+0x0074 _flags2 : int
+0x0078 _old_offset : __off_t
+0x0080 _cur_column : short unsigned int
+0x0082 _vtable_offset : signed char
+0x0083 _shortbuf : char [1]
+0x0088 _lock : _IO_lock_t *
+0x0090 _offset : __off64_t
+0x0098 _codecvt : struct _IO_codecvt *
+0x00a0 _wide_data : struct _IO_wide_data *
+0x00a8 _freeres_list : struct _IO_FILE *
+0x00b0 _freeres_buf : void *
+0x00b8 __pad5 : size_t
+0x00c0 _mode : int
+0x00c4 _unused2 : char [20]
原理简述 - 读
fread过程中IO_FILE字段的作用:
IO_FILE使用的是线性缓冲区,起始位置和末尾位置由buf指针标识,当不存在的时候会创建:
- buf_base:线性缓冲区基地址
- buf_end:线性缓冲区末尾地址
读缓冲区三个指针:
- read_ptr:读缓冲区当前指针
- read_base:读缓冲区基地址
- read_end:读缓冲区末尾地址
缓冲区使用原理用pwn.college的图了就(参考资料[1])
首先最初读的时候,会通过虚函数_IO_UNDERFLOW
,也就是_IO_new_file_underflow
函数,通过系统调用read去将buf缓冲区读满(如果buf不存在,则会这里会申请),然后把buf赋给read缓冲区,用指针标识读过的内容和未读的内容
初始情况下的缓冲区:
读入一些内容后:
读完时:
读完之后,如果还需要读,则会再次调用_IO_UNDERFLOW
进行刷新
关于任意地址写入
读的函数的利用效果是写,有两个利用法
-
修改read缓冲区为空的状态:将buf缓冲区中的内容写入到read缓冲区中
读操作的虚函数:_IO_file_xsgetn
... // 计算可用空间 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; ...
伪造
read_ptr = read_base = 目标地址
,read_end >= read_ptr + length
触发读的时候,就会将一个地址的内容,写入另一个地址
-
修改read缓冲区为满的状态:从fd文件中读取一整个buf缓冲区大小的内容到buf缓冲区中
读操作函数:_IO_file_xsgetn
... // 需要读取的字节数小于缓冲区 if (fp->_IO_buf_base && want < (size_t)(fp->_IO_buf_end - fp->_IO_buf_base)) { // 填充缓冲区,读取一个页的文件内容存放到了read缓冲区里 if (__underflow(fp) == EOF) break; // 继续循环 continue; } ...
伪造
read_ptr = read_end
,buf_base = 目标地址
,buf_end = buf_base + length >= 需要读取的字节数
会调用underflow虚函数进行刷新缓冲区的操作,将fd文件中的内容读取到buf缓冲区中
fread 是对目标文件fd触发该流程的调用
gets,scanf等函数会触发stdin的该函数调用流程
原理简述 - 写
fwrite过程中IO_FILE字段的作用:
写缓冲区三个指针:
- write_ptr:写缓冲区当前指针
- write_base:写缓冲区基地址
- write_end:写缓冲区末尾地址
一样是使用参考资料[1]的图
在调用写的时候,会通过xsputn
进行处理,默认没有buf缓冲区,在首次调用的时候会申请,这里会将要写入到fd文件的内容先保存到缓冲区里,当写满的时候,再调用虚函数_IO_OVERFLOW
进行刷新缓冲区,使用系统调用写入到fd文件
尚未写入的时候:
写入的时候:
写满的状态:
当需要刷新或者写满的时候,会进入_IO_OVERFLOW
进行刷新,将缓冲区的内容写入到文件
关于任意地址读取
写的函数的利用效果是读,也有两个利用法
-
修改write缓冲区为空的状态:将指定内存中的内容写入到write缓冲区中
读操作的函数:_IO_default_xsputn
... /* Space available. */ // 空间可用 if (f->_IO_write_ptr < f->_IO_write_end) { // 计算剩余大小 size_t count = f->_IO_write_end - f->_IO_write_ptr; // 足够了的话,就设置count为目标大小 if (count > more) count = more; // 大于20的话 if (count > 20) { // 调用库函数复制到写缓冲区 f->_IO_write_ptr = __mempcpy(f->_IO_write_ptr, s, count); s += count; } // 小于等于20 else if (count) { // 逐字节复制 char *p = f->_IO_write_ptr; ssize_t i; for (i = count; --i >= 0;) *p++ = *s++; f->_IO_write_ptr = p; } // 更新待写入字节数 more -= count; } ...
伪造
write_ptr = write_base = 目标地址
,write_end >= write_ptr + length
触发写的时候,就会将一个地址的内容,写入另一个地址
-
修改write缓冲区为满的状态:向fd文件中写入一整个write缓冲区大小的内容
读操作函数:_IO_OVERFLOW
... if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL) { ... } // ch是参数 if (ch == EOF) return _IO_do_write(f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base); ...
伪造
write_base = 要写入的内容地址
,write_ptr = write_base + length
,write_base = read_end
flags & 0x0800 = 1
:至关重要,不然会进入其他流程将缓冲区地址初始化
会调用overflow虚函数进行刷新缓冲区的操作,将write缓冲区的内容写入fd,如果设置fileno = 1,则会将缓冲区中的东西打印出来
fwrite 是对目标文件fd触发该流程的调用,
fclose 中有fflush调用,刷新文件缓冲区,也可以触发
puts,printf等函数会触发stdout的该函数调用流程