概述
House of some是一条改进House of apple2的新链,也是一种攻击思路,效果十分显著,并且可以适用于未来的高版本,可以实现任意地址写,其中触发条件为
- 已知glibc基地址
- 可控的已知地址(可写入内容构造fake file)
- 需要一次libc内任意地址写可控地址
- 程序能正常退出或者通过exit()退出
House of some具有以下优点:
- 无视目前的
IO_validate_vtable
检查(wide_data的vtable加上检查也可以打) - 第一次任意地址写要求低
- 最后攻击提权是栈上ROP,可以不需要栈迁移
- 源码级攻击,不依赖编译结果
自动化脚本(将于2024年2月1日发布)https://github.com/CsomePro/Some-of-House
利用思路
构造任意地址写的fake file
首先回顾一下House of apple2 https://bbs.kanxue.com/thread-273832.htm
其中有一条链是如下进行的
1 | _IO_wfile_overflow |
如果fp->_wide_data->_wide_vtable
加上了检查,那么只能选择虚表内的函数进行执行,我们能够选什么呢?
那么就需要_IO_new_file_underflow
这个函数出场了
1 | int |
我们可以发现在_IO_new_file_underflow
函数内会调用_IO_SYSREAD (fp, fp->_IO_buf_base,fp->_IO_buf_end - fp->_IO_buf_base)
宏其对应的常规read函数如下
1 | ssize_t |
最后是调用syscall(read)读,我们可以看到read的三个参数都是可控的
fd
=>fp->_fileno
buf
=>fp->_IO_buf_base
size
=>fp->_IO_buf_end - fp->_IO_buf_base
那么就可以构造一个任意地址写,那么有了任意地址写之后有啥用呢?FSOP!
我们再回到_IO_flush_all
函数观察一下
1 | int |
其中的for循环我们可以看到对于_IO_list_all
上的单向链表,通过了_chain
串起来,并在_IO_flush_all
中,会遍历链表上每一个FILE,如果条件成立,就可以调用_IO_OVERFLOW(fp, EOF)
1 | for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain) |
那么接下来就开始构造一个实现任意地址写的fake file
由于_IO_new_file_underflow
内有一个_IO_switch_to_get_mode
函数其中有这个分支
1 | if (fp->_IO_write_ptr > fp->_IO_write_base) |
如果还是使用fp->_IO_write_ptr > fp->_IO_write_base
来使得触发OVERFLOW就会出现无限递归,所以不可行,我们需要采取另一个分支,即
1 | if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) // 不可行 |
那么实现任意地址读的fake file设置如下
_flags
设置为~(2 | 0x8 | 0x800)
,设置为0
即可(与apple2相同)vtable
设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap
地址,使得调用_IO_wfile_overflow
即可(注意此处与apple2不同的是,此处的vtable不能加偏移,否则会打乱_IO_SYSREAD
的调用)_wide_data->_IO_write_base
设置为0
,即满足*(_wide_data + 0x18) = 0
(与apple2相同)_wide_data->_IO_write_ptr
设置为大于_wide_data->_IO_write_base
,即满足*(_wide_data + 0x20) > *(_wide_data + 0x18)
(注意此处不同)_wide_data->_IO_buf_base
设置为0
,即满足*(_wide_data + 0x30) = 0
(与apple2相同)_wide_data->_wide_vtable
设置为任意一个包含_IO_new_file_underflow
,其中原生的vtable就有,设置成_IO_file_jumps-0x48
即可_vtable_offset
设置为0
_IO_buf_base
与_IO_buf_end
设置为你需要写入的地址范围_chain
设置为你下一个触发的fake file地址_IO_write_ptr <= _IO_write_base
即可_fileno
设置为0
,表示read(0, buf, size)
_mode
设置为2
,满足fp->_mode > 0
即可
一个任意地址写的fake file模板如下
1 | fake_file_read = flat({ |
构造任意地址读的fake file
这个就很简单了,以前也有这些研究,利用_IO_write_base
和_IO_write_ptr
实现任意地址读,这里给出构造模板,具体原理网上有很多教程
1 | fake_file_write = flat({ |
FSOP!
我们已经有了任意地址读、任意地址写的fake file构造,那么只需要将其用_chain
串起来就可以达成强大的攻击效果
那么我将House of some的攻击流程分成4步(RWRWR过程)(这也是一个广泛的思路,拥有任意地址写就不止一个方法了)
- 第一步 任意地址写
_chain
,这里可以写_IO_list_all
或者stdin、stdout、stderr的_chain
位置,在这一步需要在可控地址上布置一个任意地址写的Fake file,之后将Fake file地址写入上述位置 - 第二步 扩展fake file链条并泄露栈地址,在第一步的中,我们只有一个fake file,并不能完成更复杂的操作,所以这一步我们需要写入两个fake file,一个用于泄露
environ
内的值(即栈地址),另一个用于写入下一个fake file - 第三步 泄露栈内数据,并寻找ROP起始地址,这一步同样需要写入两个fake file,一个任意地址读,读取栈上内存,另一个任意地址写,向栈上写ROP
- 第三步 写入ROP,实现栈上ROP攻击!
下图是攻击的图示,黄色代表_IO_flush_all
还未遍历的FILE,黑色代表已经处理过的FILE
简单的分析
这个链条是基于House of apple2基础上衍生的,为什么需要apple2呢?因为,在意外调用vtable的过程中,需要给vtable项加上偏移,但是_IO_SYSREAD
等宏也是通过偏移索引,所以会导致偏移出错无法按照预定逻辑,那么就想到wide data内的vtable,修改此处的偏移可以不影响IO FILE的vtable。
这个利用链条从源码中分析得出,不依赖二进制编译结果,以及可以无视加上wide data内的vtable的检查,这就导致了非常强大的泛用性。
同时House of some带回了原生的FSOP流程(RWRWR过程),我们重新回到了起点——angelboy提出的FSOP原来的样子,利用chain把一个一个fake file串起来,通过多次的fake file调用_IO_OVERFLOW
,实现二次泄露甚至多次泄露,使得我们游走在任意地址中,修改任意的地址内容!
为何选择栈上ROP,因为这是最简单最有效最暴力的攻击方法,可以无需栈迁移,无视canary(任意读可以泄露,甚至我能控制写入起点,可以选择canary后面作为起点),最后栈溢出永不过时!
作者: Csome