House of Orange
0x00 前言:
堆学习之路仍在继续,这次依旧是PolarD&NCTF上的一道堆题,名字是easy_exit,其实House of Orange挺模板的,这里只是简单过一遍,让自己大概理解一下这个模板是怎么运作的。House of Orange本身并不难理解,难理解的是后面的FSOP…
0x01 分析:
Checksec 一下:
Arch: amd64-64-little |
Ida 启动程序还是很简单的,可以申请两次自定义大小的堆块,然后可以多次申请
0x1000
大小的堆。主要是没有 free 功能,没法正常泄露 libc。
这就要用到 house 系列中的 house of orange
了,简单来说就是当前堆的 top chunk 尺寸不够用户申请的大小时,原来的 top chunk 会被放到 unsorted bin 中。
而且程序中的编辑堆功能有堆溢出,所以我们需要伪造合适的 top chunk size,用以触发 house of orange
malloc(0x18) |
House of Orange
要触发 house of orange 的话 topchunk 的 size 需要符合一定的要求,我们可以看看 ctfwiki 的描述:
伪造的 top chunk size 的要求:
- 伪造的 size 必须要对齐到内存页
- size 要大于 MINSIZE(0x10)
- size 要小于之后申请的 chunk size + MINSIZE(0x10)
- size 的 prev inuse 位必须为 1
- 之后原有的 top chunk 就会执行
_int_free
从而顺利进入 unsorted bin 中。
省流:fake_size 可以是 0x0fe1、0x1fe1、0x2fe1、0x3fe1 等对 4kb 对齐的 size。因为程序中 malloc 的时候有对 size 进行限制,必须小于
0x1000
,所以选择 0x0fe1
实际上
house of orange
的部分到此就结束了,要理解这个对我来说不是很困难,后面的 FSOP
才是最难以理解的东西,为了进行 FSOP
攻击呢,我们还需要 libc 基地址以及堆的地址,所以我们再申请一个 0x18
大小的堆块,这个堆块会从 unsorted bin 中割出来
leak一下
然后我们先打印一次,利用这个指向 main_arena+xx
的地址来泄露 libc。
遍历unsorted bin的时候,会将其中的堆块分类放入small bin或者large bin中,这样程序中那个大堆块就会被分到large bin中,然后启用fd_nextsize和bk_nextsize指针(堆地址就会残留到这上面),从large bin申请出来的chunk上面残留了libc和堆地址,我们可以通过编辑堆的方式,将前面 0x10
字节填满,这样再次输出的话就能得到堆地址了。
FSOP
接下来则是重量级的部分,也就是 FSOP
,涉及到一些_IO_FILE
的知识点,我们可以先看一下 _IO_list_all
原本的结构我们要让
_IO_list_all
变成这样:
FSOP
选择的触发方法是调用_IO_flush_all_lockp
,这个函数会刷新_IO_list_all
链表中所有项的文件流,相当于对每个FILE
调用 fflush,也对应着会调用_IO_FILE_plus.vtable
中的_IO_overflow
。
其实主要思想就是劫持这个 _IO_overflow
, 将其改写为 system
函数,而 _IO_overflow
的参数主要是 _flags
位置上的内容。接下来就是详细的攻击解释
首先是通过 unsorted bin
劫持 _IO_list_all
,使其指向 main_arena + 88
的位置,然后然后在 smallbin[4](smallbin[0x60])
这里伪造 stdout
的内容。
为什么是这些位置?
因为原本的_IO_list_all
是先指向stderr
的,而stderr+0x68
则是stderr
的 chain,原本是指向stdout
的。
现在我们劫持了_IO_list_all
,使它指向main_arena+88
, 正好main_arena+88+0x68
指向的内容是smallbin[0x60]
, 那么只要在smallbin
中伪造一个 chunk 来代替原本的stdout
即可
执行 _IO_overflow
我们还需要绕过一些检测:
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)||(_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)))&& _IO_OVERFLOW (fp, EOF) == EOF) |
省流:mode=0,_IO_write_ptr=1,_IO_write_base=0
即可。现在来看看 exp 的构造:其实也算比较模板了,我们可以来分解一下:
其实上面这里都是属于模板,先是设置
_flags
段为 /bin/sh
,然后是 size 位为 0x61
,接着为了实行 unsorted bin attack
设置一下 fd 和 bk 指针,然后是为了绕过 _IO_overflow
的检测需要设置的 _IO_write_base
和 _IO_wrtie_ptr
,最后是 mode
。
值得关注的是 vtable_ptr
位置的设置,他在 _flags + 0xd8
的位置上:这里在官方 wp 中被设置为自身,而
+ 0x18
的地址上设置为 system
的地址,官方的说法是这里就是 _IO_overflow
的地址,但是我参考了一下其他师傅的 wp,觉得应该是这样一回事,在 《House Of Orange》 中提到,攻击的流程大概是这样:
将_IO_list_all设置为main_arena+88 |
这里提到了第一个 FILE 未能触发异常,文章的后半段也提到了:
__libc_malloc => malloc_printerr => __libc_message => abort => _IO_flush_all_lockp |
我的理解是
__libc_malloc |
属于 程序触发异常,而
进行_IO_flush_all_lockp |
则是对应剩下的部分:
malloc_printerr |
一共四个步骤,最后一个 _IO_flush_all_lockp
正好对应了 jump_table[3]
,在那篇博客中作者给出的示例代码也有类似的片段编辑完之后只需要再
malloc
一下就有概率能 getshell,这个概率是 1/2
,具体原因可看关于house of orange(unsorted bin attack &&FSOP)的学习总结 当中有提到。无论是这个原因还是 FSOP
对于现在的我来说都很难理解,大概明白打 FSOP
也算是一个模板就可以了,具体原理可以等之后学了 IO
再回来回顾。
0X02 EXP:
from pwn import * |