House Of Orange
House Of Orange
House Of Orange 简介
该利用方式是最早的一种 IO 利用方式之一,适用条件为glibc 2.23,开启了堆与 IO 组合利用的先河。
当程序中没有free的情况下,利用溢出覆盖Topchunk,然后申请大于topchunk的堆块,这个时候就会把原来的topchunk放入unsorted bin,后续通过 **Unsorted Bin Attack + FSOP **进行攻击。
使用条件
堆溢出(读写)
glibc2.23
【重要版本提示】
glibc 2.26 后 malloc_printerr 不再调用 _IO_flush_all_lockp(commit 91e7cf98)
glibc 2.24 引入 _IO_FILE 虚表白名单机制,无法通过修改虚表为堆地址来进行攻击
原理分析
如简介中所说,House Of ORange 的第一步在于,程序中没有提供free的情况下,我们要得到一个进入unsorted bin 的chunk进行后续的泄露。我们来详细分析一个这个过程。对于主arena 和 非主arnea 的处理方式略有不同,我们这里仅讨论主 arena 的情况。
我们知道malloc函数底层会执行_int_malloc函数,在_int_malloc函数中,当我们申请内存的时候,他会依次扫描tcache(如果有的话)、fast bin 、unsorted bin、small bin、large bin 是否有符合要求的堆块,如果都没有,_int_malloc就会试图从**top chunk **中尝试分配,但如果 top chunk 的大小仍无法满足要求,就会执行如下分支
1 | //av是指向mstate(memory state)的指针,nb是请求的大小 |
核心处理放在了sysmalloc函数,我们进一步分析
在sysmalloc中,当我们申请的内存超过mmap阈值(默认是128K)且mmap申请的内存块数量未超过最大数量(默认为65536)的时候会使用mmap来分配内存(这部分不是重点,就不过多介绍了)
1 | if (contiguous (av) && old_size && brk < old_end) { |
sysmalloc函数中当申请的内存未达到mmap的阈值,会尝试通过brk来扩展内存,如果新分配的内存和原来的 top chunk 连续,就会扩展 top chunk ,相关处理如下
1 | else { /* av == main_arena */ |
倘若内存不连续,无法扩展,就会把原来的top chunk 通过调用_int_free函数放入 unsorted bin 中
1 | { |
我们的 top chunk 的大小是我们通过溢出等方法修改的,所以执行到sysmalloc的时候brk分配的内存必然和当前的top chunk 是不连续的,因此 top chunk 必然会被放入 unsorted bin 以便后续的泄露
获取libc之后,就可以开始进行 unsorted bin attack 了,我们通过 unsort bin attack 去劫持IO来进行 FSOP
FSOP的核心是劫持IO_FILE结构体。使之落在我们可控的内存上。这就意味着我们是可以控制vtable的,我们将vtable中的_IO_overflow函数地址改成system地址即可,而这个函数的第一个参数就是IO_FILE结构体的地址。如果我们让IO_FILE结构体中的flags成员为/bin/sh字符串,那么当执行exit函数或者libc执行abort流程时或者程序从main函数返回时触发了_IO_flush_all_lockp 即可 get shell
正常的IO链表结构是这样的

我们布局之后的结构是这样的

下面我们来看一下,我们成功布局需要绕过的一些检测
1 | if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) |
嗯……看着很晕……
其实就是分了两种情况,
情况1:普通文本模式下的刷新判断(ASCII流)
fp->_mode <= 0:表示 处于字节流模式,即不是宽字符流。fp->_IO_write_ptr > fp->_IO_write_base:- 表示写缓冲区中有 未刷新的数据。
write_ptr指向当前写入位置,write_base是缓冲区起始地址。
表示这个 FILE 结构体的 写缓冲区有内容需要 flush。
**情况2:**宽字符模式(wchar_t)下的刷新判断
这个条件只在:
- 内部使用 glibc(定义了
_LIBC)或 - 支持
wchar_t流(定义了_GLIBCPP_USE_WCHAR_T)时编译
才启用。
条件含义:
_IO_vtable_offset(fp) == 0:确保该FILE对象是标准的 libio 类型(不是用户扩展或替换的)。fp->_mode > 0:表示是 宽字符流模式。fp->_wide_data->_IO_write_ptr > _IO_write_base:- 宽字符缓冲区中也有 尚未写出的内容。
一般来说,我们只需要镇定情况1就可以了,绕过方法非常简单。
值得注意的是
House Of Orange 的攻击链不一定会成功,原因很简单,
aboet在调用_IO_flush_all_lockp的时候,会遍历 IO_FILE_plus 结构体链表,对每个 IO 流进行依次刷新,但是我们布局之后,破坏了 stderr 会导致刷新 stderr 的时候崩溃,所以只有当布局之后的假 stderr 不进行刷新的时候才能成功攻击。第一个结构体的mode字段是main_arena+88+0xc0处的数据决定的 ,这个值会因为libc地址随机而变动。(mode字段是四字节)
实例分析
样例:how2heap中的 House of orange
源码
1 |
|
编译运行:gcc ./house_of_orange.c -O0 -g -no-pie -o a
我们来gdb动调分析一下这个程序
我们在 43 行下断点(也就是模拟修改 top chunk的部分)
这是修改前的 top chunk

修改之后

由 0x20C01 改为 0xC01,示例代码中也提到了,这个低三位的 C01 很关键,是为了绕过 top chunk 的对齐检查。(取地址的末三位 0x400 , 0x400 + 0xC00 = 0x1000满足对其要求)
继续运行到 malloc 的地方, p2 = malloc(0x1000);,当运行完之后,查看堆信息可以看到原先的 top chunk 已经成功被放入 unsorted bin 中了

至此就完成了 libc 的泄露,下面我们来关注 Unsorted Bin Attack 这个过程
来到第 62 行代码, top[3] = io_list_all - 0x10; // bk 指针指向 _IO_list_all-0x10

最后一步就是伪造 IO_FILE 的布局了。
_IO_list_all最终会指向 main_arena + 88,对应的 chian 就在 main_arena + 88 + 0x68 ,前面也提到过,这个位置对应的就是 small bin 中0x61 大小的那一条链 top[1] = 0x61;
且在调用虚表中的函数的时候,传入的参数就时对应的 IO 结构的指针,因此我们把前8字节填入 /bin/sh memcpy((char *)top, "/bin/sh\x00", 8);
后面就是调用overflow函数检测的绕过了,这个很简单。 FILE *fp = (FILE *)top; fp->_mode = 0; // _mode <= 0 满足条件 fp->_IO_write_base = (char *)2; // 写指针范围检查 fp->_IO_write_ptr = (char *)3;
最后执行malloc ,由于 unsorted bin 被破坏了,就会触发abort,从而刷新所有缓冲区,成功执行攻击。