House of Orange 原理
参考资料:https://blog.csdn.net/yjh_fnu_ltn/article/details/141143144
什么是 House Of Orange:
House of Orange 与其他的 House of XX 利用方法不同,这种利用方法来自于 Hitcon CTF 2016 中的一道同名题目。由于这种利用方法在此前的 CTF 题目中没有出现过,因此之后出现的一系列衍生题目的利用方法我们称之为 House of Orange。在此之前,我们复习一下内存的分配与回收:
sbrk&mmap
复习一下 sbrk 和 mmap:
内存的分配与回收
内存分配的具体步骤



内存回收概述



House Of Orange 的概述:
House Of Orange 的利用比较特殊,我们在之前的学习中了解到,如果我们想要去利用堆漏洞,就需要对堆块进行 malloc 和 free 去达到篡改指针的目的。不过 House Of Orange 类题目并不存在 free 函数或者其他能够释放堆块的函数。因此 House Of Orange 的核心漏洞是利用当前已有漏洞去获取 free 的效果。
House Of Orange 的原理:
我们前面说到:House Of Orange 的核心在于在没有 free 函数的情况下得到一个释放的堆块(UnSortedbin)。这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配大小的时候,原来的 topchunk 会被释放到 UnSorted bin 当中。通过这一点,我们可以再没有 free 的情况下获取到 UnSortedbins。
我们通过 libc 源码看一下整个过程的详细情况:假设我们目前的 top chunk 已经不满足 malloc 的分配需求。首先我们在程序中的 malloc 调用会执行到 libc.so 的 _int_malloc 函数当中,在 _int_malloc 函数当中会依次检验 fastbins、smallbins、UnSortedbins、largebins 是否可以满足分配需求,不过由于尺寸问题,这些 bins 都不符合我们的需求。接下来 _int_malloc 函数会试图使用 top_chunk,在这里 top_chunk 也不能满足分配的需求,因此会执行以下分支 :
1 | /* |
此时 ptmalloc 已经不能满足用户的申请堆内存的操作,需要执行 sysmalloc 来向系统申请更多的空间。但是对于堆来说有 mmap 和 brk 两种分配方式,我们需要让堆以 brk 的形式扩展,之后原有的 top_chunk 会被置于 UnSortedbin 当中。
综上:我们要实现 brk 拓展 top_chunk,但是要实现这个目的需要绕过一些 libc 中的 check。首先 malloc 的储存不能大于 mmap_.mmap_threshold
1 | if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max)) |
如果所需分配的 chunk 大小大于 mmap 分配阈值,默认为 128K = 0x2000,并且当前进程使用 mmap()分配的内存块小于设定的最大值,将使用 mmap()系统调用直接向操作系统申请内存。
在 sysmalloc 函数中对 top_chunk_size 的 check 如下:
1 | assert((old_top == initial_top(av) && old_size == 0) || |
这里检查了 top_chunk 的合法性,如果第一次调用本函数,由于 top_chunk 可能没有初始化,所以 old_size 可能为 0。如果 top_chunk 已经初始化了,那么 top_chunk 的大小必须大于等于 MINSIZE。因为 top_chunk 中包含了 fencepost,所以 top_chunk 的大小一定要大于 MINSIZE。其次 top_chunk 必须标识前一个 chunk 处于 inuse 状态, 并且 top_chunk 的结束地址必定是页对齐的。此外 top_chunk 出去 fencepost 的大小必定要小于所需的 chunk 的大小,否则在 _int_malloc()函数当中会使用 top_chunk 分割出 chunk。
总结一下伪造 top_chunk_size 的要求:
- 伪造的 size 必须要对齐到内存页
- size 要大于 MINSIZE(0x10)
- size 要小于之后申请的 chunk size + MINSIZE*(0x10)
- size 的 prev_inuse 位必须为 1
之后原有的 top_chunk 就会执行 _int_free 从而顺利进入到 UnSorted bin 当中。
进一步了解 House Of Orange 原理:
先来看一个部分正确的示例:
1 |
|
我们都知道,top_chunk 的地址是在新创建的第一个 chunk 的高地址并且物理相邻,像这样:
那么我们就可以覆盖 top_chunk 的 size 为 0x41.之后申请大于这个尺寸的堆块,即 malloc(0x60); 。但是当我们执行这个示例的时候却发现这个程序并不能被利用成功。原因在于 assert 并没有被满足从而抛出了异常。
报错原因是不符合我们上面总结的四条:(任意一个不符合都会造成报错)
1 | assertion failed: |
看一下报错之前的堆情况:分析是哪里出错了
1 | pwndbg> heap |
- &topchunk + size 得到的是 top chunk 结束地址,也就是堆在这个 chunk 之后的下一个地址。ptmalloc 会用这个地址来判断堆的边界,并进行后续的页面对齐检查。0x555555559320 + 0x20ce0
- old_size >= MINSIZE
- top chunk 的 prev_inuse 位(size 最低位)=1
- 0x555555559320 + 0x20ce0 & 0xFFF = 0
这里是页对齐出错而导致的报错,我们并不能进行 malloc(0x60);
那下面怎么做呢?来看正确的案例:
要让伪造的 top_chunk 对齐页面(按照 0x1000 对齐):现代操作系统都是以内存页为单位进行内存管理的,一般内存页的大小为 4kb。那么我们伪造的 size 就必须要对齐到这个尺寸。在覆盖之前 top_chunk 的 size 大小是 0x20ce1,那么计算得知 0x…559320+0x20ce0 = 0x…57A000 就是对于 0x1000(4kb)对齐的。
所以我们伪造的 top_size 大小都应该是 0x20ce1、0x1fe1 等等可以保证与 top_chunk 地址相加后可以页对齐。
1 |
|
这里 0x5555555559000 是最初始分配的堆:
malloc(0x2000);
在 glibc 的 malloc 分配器中,堆的结构核心分为两类 chunk:
- Top chunk:堆的顶部分块,是堆中未被分配的最大空闲块,malloc 优先从 Top chunk 切割内存;
- Unsorted bin:空闲 chunk 的临时中转站,当堆需要扩展 / 合并 chunk 时,空闲的大 chunk 会先进入 unsorted bin,等待后续分配 / 合并。
malloc 分配内存的核心逻辑:
- 若申请的内存大小 ≤ Top chunk 的可用大小 → 直接从 Top chunk 切割;
- 若申请的内存大小 > Top chunk 的可用大小 → 触发「堆扩展 / Top chunk 替换」:
- 原 Top chunk 会被标记为「空闲」,并放入 unsorted bin;操作系统向进程申请新的内存页,创建新的更大的 Top chunk;之后会从新 Top chunk 中切割内存满足本次申请。
更新: 2026-03-08 15:49:49
原文: https://www.yuque.com/idcm/wnemg9/is888mlnx7h6g18m