FILE Structure Exploitation (FILE Structure Oriented Programing)



Introduction

当我们使用相关函数时,操作系统的处理过程大致如下图:

上述的处理过程称为标准I/O,经过I/O内容经过两层缓存:
一层是stdio buffer,我们一般成为C库缓存
另一层是Kernel实现的kernel buffer,一般被成为内核缓存
整个过程中用到了一个基本的数据结构,FILE Structure

file

在我们经常使用的stdin,stdout,stderr,fopen等等与FILE Sturcture相关的操作使用的都是
_IO_FILE_plus
在libioP.h定义其数据结构如下:

其中IO_FILE结构如下:

其中

  • _flags 标识File stream的相关属性,例如:ReadOnlyO_Append
  • _IO_buf_base 存放Read,Write,Reserve等Buffer的信息
  • _fileno 存放着打开该FILE结构的文件描述符

_IO_Jmup_t是一个存放着文件相关操作的vtable,file的任何操作相关函数的地址都在vtable中储存。

其中,在32bit系统中,_IO_jump_t的偏移是0x94,64bit系统中偏移是0xE8

linux下shell启动的进程都打开默认的三个文件描述符,0代表stdin,1代表stdout,2代表stderr,他们通过链表连接

我们用下面的例子来探究FILE_struct


_IO_list_all指针中的内容如下:

其中stderr的结构如下:

关注_chain指针中的内容正是stdin的_IO_FIlE_plus指针,所有的file结构都通过_chain连接,处理也是按照相应的顺序。

fopen


fopen的流程:

  1. 通过malloc申请FILE_structure的空间
  2. 初始化FILE_structure
  3. 将FILE_structure插入到elf默认的FILE链表中,如上图
  4. 通过sys_open系统调用打开文件

fread

如下读所示,可见标准I/O经过一层stdio buffer对输入数据进行了缓冲

fread的流程:

  1. 如果StreamBuffer为空,申请Buffer vtable->doallocate
  2. 将输入数据read进入到Stream buffer中
  3. 将data从buffer 拷贝到目的地址destination

fwrite

fwrite的流程:

  1. 如果StreamBuffer为空,申请Buffer vtable->doallocate
  2. 将用户输入的数据拷贝到stream Buffer中
  3. 如果stream 充足了或者flush stream 将sream Buffer的内容写入file

fclose

fclose的流程:

  1. Unlink file结构
  2. StreamBuffer flush掉
  3. 关闭文件
  4. 释放FILE structure

FSP

FSP 文件流指针溢出
主要思路是控制FILE结构,劫持_IO_JUMP_jumps vtable指针的内容,从而在调用相关fp类函数时完成Exploit。
我们下面分析官方的POC:

程序中分配了一个大小为0xD8的chunk,之后free掉该chunk
fopen时会再次调用malloc将fp的FILE_structure的空间分配到freed chunk中,它的内容如下图:

IOfile_jumps的偏移在File结构中为 $FILE+0xE8
*(unsigned long*)(str + sizeof(FILE)) = (unsigned long)funcs;
将_IO_file_jumps指针的内容篡改为自定义的funs指针数组,那么现在FILE的jump vtable指针表的内容变为:

当程序再次调用fclose函数,进入_IO_new_file_close_it函数,调试进入到

fclose通过FILE结构找到IO_jump_t结构,进而找到close函数的地址,并调用_IO_file_finish函数。
由于我们已将该函数的地址更改为&Pwn,所以调用时调用的是Pwn函数,这样劫持了程序的控制流,完成了POC。

FSOP

File-Stream Oriented Programing
类似ROP,SROP,利用FILE_sturcture 组成漏洞调用链,完成攻击流程。
FSOP中使用的最重要的函数是_IO_flush_all_lockp

_IO_flush_all_lockp

它的主要作用是fflush所有的的file stream,在进程结束前,需要fflush所有的标准I/O流,从而保证进程顺利结束。
如何保证fflush所有的FILE呢?就是用_chain,从链表头开始进行fflush
它的简化版源代码位于genops.c文件如下:

当满足条件

result=NULL时会执行fp = fp->_chain,fp通过_chain寻找下一个满足条件的file指针,可通过它进行oriented programing

在调用_IO_OVERFLOW 函数时可通过Hijack Vtable来劫持控制流。

构造如下图的结构进行FSOP:

abort

调用_IO_flush_all_lockp的主要情况有以下三种:

• Glib abort routine
• exit function
• Main return

下面,我们看abort,如下图:

在malloc中,malloc的报错函数malloc_printerr在结束时就会调用abort掉进程

于是可以通过以下的调用链实现FSOP

其中使用FSOP的一个具体的实例就是:Horse of Orange
当然在最新版本的ibc中已经加入了FILE的Patch,利用起来的难度更高。

参考资料

溢出利用FILE结构体

发表评论