IO利用之FSOP

First Post:

Last Update:

Page View: loading...

IO利用之FSOP

建议了解的前置知识:

1
2
3
vtable劫持
FILE结构
fopen函数

提要

通过前面对IO的学习,我们知道_IO_FILE结构会使用_chain域相互连接成一个链表:!

该链表的头部是由_IO_list_all进行管理的,调试的时候发现有点像头插法

我们使用fopen函数打开一个文件时会返回一个FILE类型的指针,这时候我们的一个FILE结构体已经悄悄挂进了我们上面所说的链表了,其中挂进链表的操作是由_IO_link_in函数执行的

1
2
3
4
...
//关键代码
fp->file._chain = (_IO_FILE *) _IO_list_all; //将当前file_plus_chain设置为IO_list_all指向的FILE结构体
_IO_list_all = fp; //将fp其挂进链表

此时链表结构变为:

原理:

Wiki原话:

FSOP(File Stream Oriented Programming)就是通过劫持 _IO_list_all 的值来伪造链表和其中的 _IO_FILE 项,其中关键的函数为 _IO_flush_all_lockp,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用 _IO_flush_all_lockp,这个函数会刷新 _IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用 _IO_FILE_plus.vtable 中的 _IO_overflow。

简单来说就是我们得伪造一个FILE结构然后修改_IO_list_all 指向我们伪造的FILE结构,然后去触发 _IO_flush_all_lockp函数

那为什么要这样干呢

看源码

_IO_flush_all_lockp函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
_IO_flush_all_lockp (int do_lock)
{
...
fp = (_IO_FILE *) _IO_list_all;
while (fp != NULL)
{
...
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
{
result = EOF;
}
...
}
}

其中_IO_OVERFLOW看起来很熟悉,它是vtable中的函数,那么我们就可以通过触发 _IO_flush_all_lockp可以去劫持它为我们的gadget,但是有条件:当我们FILE结构输出缓冲区还有数据的话才会去调用 _IO_OVERFLOW函数

我们得绕过一些检查:

1
2
3
4
5
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
{
result = EOF;
}
  1. fp->_mode <= 0

  2. fp->_IO_write_ptr > fp->_IO_write_base 也就是 (fp->_IO_write_ptr)- (fp->_IO_write_base) >0

假设我们已经伪造了一个FILE结构 我们就可以将我们伪造的FILE结构中fp->_mode 设置为 0 ,fp->_IO_write_ptr设置为1,fp->_IO_write_base设置为0这样就可以绕过我们的检查

然后我们将_IO_OVERFLOW的vtable指针改为system,伪造fp的头部改为/bin/sh

回到_IO_flush_all_lockp函数的问题

那我们怎么触发 _IO_flush_all_lockp函数?

_IO_flush_all_lockp不需要我们手动调用,当程序退出的时候会自动调用此函数

触发_IO_flush_all_lockp时机:

  • libc执行abort函数时。
  • 程序执行exit函数时。
  • 程序从main函数返回时。

当我们布置好这一切后,当程序退出时,执行abort函数中的_IO_flush_all_lockp然后调用 _IO_OVERFLOW的时候执行system(‘/bin/sh’)

这样就完成了我们的FSOP