FSOP
FSOP
介绍
FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用 _chain 域相互连接形成一个链表,这个链表的头部由 _IO_list_all 维护。
FSOP 的核心思想就是劫持 _IO_list_all 的值来伪造链表和其中的 _IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用 _IO_flush_all_lockp,这个函数会刷新 _IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的 _IO_overflow。
1 | int |
而_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:
当 libc 执行 abort 流程时
当执行 exit 函数时
当执行流从 main 函数返回时
示例
梳理一下 FSOP 利用的条件,首先需要攻击者获知 libc.so 基址,因为 _IO_list_all 是作为全局变量储存在 libc.so 中的,不泄漏 libc 基址就不能改写 _IO_list_all。
之后需要用任意地址写把 _IO_list_all 的内容改为指向我们可控内存的指针,
之后的问题是在可控内存中布置什么数据,毫无疑问的是需要布置一个我们理想函数的 vtable 指针。但是为了能够让我们构造的 fake_FILE 能够正常工作,还需要布置一些其他数据。 这里的依据是我们前面给出的
1 | if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)) |
也就是
- fp->_mode <= 0
- fp->_IO_write_ptr > fp->_IO_write_base
在这里通过一个示例来验证这一点,首先我们分配一块内存用于存放伪造的 vtable 和_IO_FILE_plus。 为了绕过验证,我们提前获得了_IO_write_ptr、_IO_write_base、_mode 等数据域的偏移,这样可以在伪造的 vtable 中构造相应的数据
1 |
|
我们使用分配内存的前 0x100 个字节作为_IO_FILE,后 0x100 个字节作为 vtable,在 vtable 中使用 0x41414141 这个地址作为伪造的 _IO_overflow 指针。
之后,覆盖位于 libc 中的全局变量 _IO_list_all,把它指向我们伪造的_IO_FILE_plus。
全局变量 _IO_list_all 存储着结构体 _IO_FILE_plus 的地址,这个地址也是 _IO_FILE 所在地址,后面自然是 vtable
通过调用 exit 函数,程序会执行 _IO_flush_all_lockp,经过 fflush[^1] 获取_IO_list_all 的值并取出作为 _IO_FILE_plus 调用其中的 _IO_overflow
1 | ---> call _IO_overflow |
[^1]: 用 fwrite 等这种流 I/O 函数写入写出,数据会先放在缓冲区,并没有真正输入或者输出,需要用 fflush 冲洗流中信息才完成写入写出。避免用 fflush 冲洗就用 setbuf 函数关闭缓冲(pwn 题初始化必备)