Fsop Exploit File Structure
百度安全Backer Talk 第二期议题 #fsop
大纲
- 概述
- 结合题目 seethefile熟悉文件指针结构,分析文件流函数执行过程
- exploit
- 另一种文件流劫持方法
- 参考资料
概述
通常情况下,文件指针的利用有这几种方式:
- 覆盖vtable
- 覆盖fp
- _IO_acquire_lock等溢出
- FSOP
本议题将以一道比较简单的ctf题为例, _IO_acquire_lock中溢出的方式来缕清执行流程、熟悉结构体,进而分析FSOP的利用思路。
seethefile
题目来源:pwnable.tw
- 对照运行结果查看相应代码
- 文件打开读取关闭功能,打开的文件名不能带
flag
-
发现写入全局变量
name
是可溢出覆盖到相邻的fp
指针 - 触发崩溃
fclose+23
处提示有段错误,此时esi
= aaaa,即我们覆盖的值
!!所以,我们覆盖的fd
应为一个地址而不是一个字符串
- 分析原因
下载
glibc 2.23
源码搜索_IO_new_fclose
可在iofclose.c
中看到定义: 同时发现覆盖的aaaa
为结构体IO_FILE
,查看定义: 注意图中的chain
- 当
fp
指针为正常指针时 - 来看看它的结构:
就是下边这么个链表:
每一个节点的开头为
_flags
,正常的_flags
前两个字节为0xfbad
exploit
- 直接结合程序来一步步绕过对结构体的检测,把溢出到
fp
的内容换成地址0x0804B260
即全局变量name
的地址 * 崩溃信息如下 可以看到箭头指向的前面用DWORD PTR [esi+0x48]
给edx
为0
,导致箭头处访问0x8
处的内存导致出错。 - 不妨把
edx
设置为一个地址: - 仍存在段错误:
溢出的地方为
edx
寄存器,分析发现对应结构体中:_lock
成员的值 - 查看崩溃之前执行的指令
- 修正payload:
- 查看崩溃前指令
OK,已经很接近了,即:
eax
= ebx + eax * 1 + 0x94
= bss_name + 0 + 0x94
那么我们可以通过设置bss_name + 0x94
处的值从而设置eax
,进而控制call
的参数
- 如下步骤可以控制eip:
- 找出要调用的函数地址记为
func_addr
,写入某个可控区域记为func_ptr
- 将
func_ptr - 0x44
写入bss_name + 0x94
- 找出要调用的函数地址记为
- 修正payload,下面的payload将eip设置为
0xcafebabe
另一种方法
上面介绍结构体的时候有个
_chain
,这个是做什么的呢? 还有_IO_list_all
这个东东,看起来里边的东西都可以伪造,能不能利用呢?
在glibc源码中搜索_IO_list_all
可以在genops.c
中找到主要的几个使用了该结构体的函数:
void _IO_link_in (struct _IO_FILE_plus *fp)
void _IO_un_link (struct _IO_FILE_plus *fp)
int _IO_flush_all_lockp (int do_lock)
重点来看第三个函数。
- 精简后的代码如下:
如果巧妙地控制判断的条件,伪造
_IO_list_all
结构体,则可以设置fp
为任意地址。
全局搜索可以看到_IO_flush_all_lockp
在abort.c
中是fflush
预处理后真实的样子,被函数abort()
调用。
-
往上继续跟踪,最后被
assert()
调用。 -
调用链为:
assert() -> __assert_fail() -> __assert_fail_base() -> abort() -> _IO_flush_all_lockp()
此时我们发现_IO_flush_all_lockp
出现的场景非常多,包括
- glibc abort
- exit()
- main return
- … 所有包含断言的地方。
由此,我们又多了个利用思路:
- 伪造
_IO_list_all
- 通过某种方式触发
assert()
从而调用_IO_flush_all_lockp
使得fp
为我们预期的地址 - 利用
_IO_flush_all_lockp
对条件的判断时的一个预处理函数_IO_OVERFLOW
达到利用效果 ps:有点像windows 下伪造异常处理句柄并通过触发异常来实现利用的过程。
文件结构体创建时涉及堆的操作,由此可以通过触发堆块检测异常来达到利用效果。
(from:angelboy’s topic on HITB SG2018)
//相关定义
//libioP.h
#define JUMP_FIELD(TYPE, NAME) TYPE NAME
//JUMP_FIELD(_IO_overflow_t, __overflow)
//_IO_overflow_t __overflow()
(*(struct _IO_jump_t **)
((void *) &_IO_JUMPS_FILE_plus (fp) + (fp)->_vtable_offset))
//genops.c
int __overflow (_IO_FILE *f, int ch)
{
/* This is a single-byte stream. */
if (f->_mode == 0)
_IO_fwide (f, -1);
return _IO_OVERFLOW (f, ch);
}
libc_hidden_def (__overflow)
- 当fsop由堆结构校验出错触发时,该利用方法又叫
house of orange
其他
- 需要注意的是,glibc 2.24开始,添加了对
_IO_file->vtable
的检查,此时需要如果用该方法需要绕过该检查。 - 从glibc2.26开始,
malloc_printerr()
移除了_IO_flush_all_lockp()
函数,house of orange
方法将失效,但是fsop仍可通过其他包含断言的函数错误触发。