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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
old_top = av->top;//原本old top chunk的地址
old_size = chunksize (old_top);//原本old top chunk的size
old_end = (char *) (chunk_at_offset (old_top, old_size));//old top chunk的地址加上其size

brk = snd_brk = (char *) (MORECORE_FAILURE);

/*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/

assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));

assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

第一次调用该函数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。

正常的链表结构为:

image-20240408193408781

通过FSOP的布局:

  1. 首先篡改_IO_list_all为main_arena+88这个地址(因为这一片的内存不可控),而chain字段是首地址加上0x68的偏移得到的,所以下一个_IO_FILE结构体的地址为main_arena+88(96)+0x68
  2. 巧合的是这个地址(main_arena+88+0x68)的恰好是smallbin中size为0x60的chunk链,如果能将一个chunk放到这个small bin中size为0x60的链上,那么篡改_IO_list_all为main_arena+88 这个地址后,small bin中的chunk就是IO_FILE结构体了。
  3. 把这个chunk申请出来,我们就成功的控制了一块_IO_FILE结构体了,接下来就可以伪造vtable字段最终拿到shell

image-20240408194015739

接下来就是具体的布局需要绕过的if检查以达到成功执行_IO_OVERFLOW函数:

1
2
3
4
5
6
7
8
     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
)
result = EOF;

通过上面的代码可以知道我们如果需要_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字段是四字节

image-20240408201423828

因为这个是一个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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
0x0   _flags
0x8 _IO_read_ptr
0x10 _IO_read_end
0x18 _IO_read_base
0x20 _IO_write_base
0x28 _IO_write_ptr
0x30 _IO_write_end
0x38 _IO_buf_base
0x40 _IO_buf_end
0x48 _IO_save_base
0x50 _IO_backup_base
0x58 _IO_save_end
0x60 _markers
0x68 _chain
0x70 _fileno
0x74 _flags2
0x78 _old_offset
0x80 _cur_column
0x82 _vtable_offset
0x83 _shortbuf
0x88 _lock
0x90 _offset
0x98 _codecvt
0xa0 _wide_data
0xa8 _freeres_list
0xb0 _freeres_buf
0xb8 __pad5
0xc0 _mode
0xc4 _unused2
0xd8 vtable

vtable中的函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const struct _IO_jump_t _IO_wstrn_jumps attribute_hidden =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstrn_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
JUMP_INIT(xsputn, _IO_wdefault_xsputn),
JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
JUMP_INIT(seekoff, _IO_wstr_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_wdefault_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};

house_of_orange
http://ak0er.github.io/2024/05/04/house-of-orange/
作者
Ak0er
发布于
2024年5月4日
许可协议