house_of_orange
house of orange
首先house of orange是一套组合全(利用了unsorted bin attack 以及 FSOP)
什么是house of orange?
house of orange该攻击手法是在我们没有free函数的情况下,来获得一个在unsorted bin中的堆块。house of orange到这里就结束了,但之后还会利用其他的手法来拿到shell。
原理:
如果我们申请的堆块大小大于了top chunk size的话,那么就会将原来的top chunk放入unsorted bin__中,然后再映射或者扩展一个新的top chunk出来。
利用过程:
1、先利用溢出等方式进行篡改top chunk的size
2、然后申请一个大于top chunk的size
关于对old_topd的检查函数如下:
1 |
|
第一次调用该函数top chunk还未被初始化,所以old_size为0,并且我们需要利用的时候这个条件也不可能满足,所以可以忽略。
第一个条件无法成立的话,那么就需要从第二个条件入手,也就是要保证我们的old_size必须不小于MINSIZE,以及需要保证old_size的prev_size位必须为1,并且原本old top chunk的地址加上其size之后的地址要与页对齐,也就是address&0xfff=0x000。最后old chunk的size必须要小于我们申请的堆块大小加上MINSIZE。
如果我们申请的堆块大于了0x20000,那么将会是mmap映射出来的内存,并非是扩展top chunk了。
总结下,我们需要绕过检查所需要构造的值:
old_top_size(我们通过溢出修改) nb(我们申请的堆块大小)
MINSIZE<old_top_size<nb+MINSIZE
old_top_size的prev_size位是1
(old_top_size+old_top-1)&0xfff=0x000//红色块为猜测
nb<0x20000
首先第一步就是要通过溢出的手法来把top_chunk的size位改成符合以上条件的大小,然后再申请一个大于top chunk的size一个chunk(也就是我们的nb),这样top chunk就进入到了unsorted bin(具体查看另一篇文章)
FSOP
首先通过house of orange把top chunk申请到了unsorted bin中,如果我们能够覆盖这个位于unsorted bin中堆块的bk指针,然后通过unsorted bin attack就可以向一个指定的地址写入一个很大的值(main_arena+88(96))。而我们需要的就是通过unsorted bin attack向_IO_list_all
写入这个地址main_arena+88(96),然后去打一个FSOP。
- FSOP就是要篡改
_IO_list_all
和_chain
,以达到劫持IO_FILE
的目的(也就是把这个结构体放到了可以控制的内存上)。 - 然后我们通过其中的
_IO_flush_all_lockp
来刷新_IO_list_all
链表上的所有文件(就是对所有的文件流执行了一个fflush) - 又因为fflush会调用vtable中的
_IO_overflow
并且IO_FILE
又被我们所劫持,所以我们可以修改vtable中的_IO_overflow
函数地址(如:将其修改为system的地址) - 触发该机制时,执行的函数会以
_IO_FILE
结构体的地址作为函数的第一个参数,因此我们让IO_FILE
结构的flags成员为/bin/sh字符串,那么当执行exit函数或者libc执行abort流程时或者程序从main函数返回时触发了_IO_flush_all_lockp
就会拿到shell。
正常的链表结构为:
通过FSOP的布局:
- 首先篡改
_IO_list_all
为main_arena+88这个地址(因为这一片的内存不可控),而chain字段是首地址加上0x68的偏移得到的,所以下一个_IO_FILE
结构体的地址为main_arena+88(96)+0x68
。 - 巧合的是这个地址(main_arena+88+0x68)的恰好是smallbin中size为0x60的chunk链,如果能将一个chunk放到这个small bin中size为0x60的链上,那么篡改
_IO_list_all
为main_arena+88 这个地址后,small bin中的chunk就是IO_FILE结构体了。 - 把这个chunk申请出来,我们就成功的控制了一块
_IO_FILE
结构体了,接下来就可以伪造vtable字段最终拿到shell
接下来就是具体的布局需要绕过的if检查以达到成功执行_IO_OVERFLOW
函数:
1 |
|
通过上面的代码可以知道我们如果需要_IO_OVERFLOW(fp,EOF)
成功执行就需要使连接其的&&的前半部分的结果为1,也就是使(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))
(称为二号条件)**的其中一个条件成立,前一个条件看起来更加的短,我们只需要mode=0,_IO_write_ptr
=1,_IO_wirte_base
=0(改成满足条件的三个值就可以),这样就可以触发_IO_OVERFLOW
函数。
注:为什么house of orange之后打FSOP的成功概率是1/2?
由于触发了_IO_flush_all_lockp
函数,会根据_IO_list_all和chain字段来去依次遍历链表上的每个_IO_FILE
结构体,在我们整体布局完成后,第一个结构体就是从main_arena+88开始。而第一个结构体的mode字段是main_arena+88+0xc0处的数据决定的(如下图)。mode字段是四字节
因为这个是一个libc的地址,而libc的地址是随机的,所以这个值有可能是真的也有可能是负的,这四个字节可能是0到0xffffffff之间的任何一个值,而这个值只要大于0x7fffffff就为负,反之则为正。所以刚好_mode字段的正负可能性都为1/2
至于为什么mode字段要求为正,是因为在上面提到的检查机制,我们的布局是以一号条件为目的的而其中就要求mode<=0
house of orange中的函数调用流程为:__libc_malloc->malloc_printerr->libc_message->abort->_IO_flush_all_lockp
IO_FILE结构体:
1 |
|
vtable中的函数指针:
1 |
|