ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CTF 堆利用 House of Orange:无 free 条件下伪造 top chunk 进入 Unsorted Bin 的完整原理与实战

CTF 堆利用 House of Orange:无 free 条件下伪造 top chunk 进入 Unsorted Bin 的完整原理与实战 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载导读House of Orange 是源自 HITCON CTF 2016 同名题目的经典堆利用手法其核心价值在于当目标程序完全没有free函数或其他释放堆块的函数时依然可以借助 ptmalloc 的 top chunk 扩展机制伪造出一个释放效果把一个已分配状态的堆块送入 unsorted bin从而为后续的 libc 泄漏、unsorted bin attack 乃至 IO_FILE 劫持铺路。本文将结合 ctf-wiki 仓库中 house-of-orange.md 文档从 glibc 源码路径出发逐步拆解sysmalloc的扩展流程、top chunk 合法性检查assert、伪造 size 的四条硬性约束并用两个一成一败的示例程序还原完整的调试过程最后说明拿到 unsorted bin 之后的进阶利用方向。House of Orange 的来历与核心思想名称起源House of Orange 与 Unlink、Fastbin Attack、House of Force 等House 系列利用方法不同其名称直接来自HITCON CTF 2016 中一道同名题目。由于这种利用手法在此前的 CTF 题目中从未出现过因此后续一系列基于同一思路的衍生题目其利用方法被统称为 House of Orange。与传统堆利用的本质差异常规堆漏洞利用的基本前提是目标程序存在堆上的漏洞如溢出、UAF、double free并且我们能够对堆块执行malloc 与 free 两种操作——malloc负责申请和布局堆块free负责把堆块放入空闲链表fastbin / unsorted bin 等供后续劫持。但 House of Orange 面对的场景是题目中存在堆漏洞却不存在free函数或任何释放堆块的途径。没有 free就无法主动构造空闲块常见攻击面全部失效。因此 House of Orange 的核心目标非常明确在完全没有free的情况下通过漏洞利用获得一次 free 的效果即得到一块位于 unsorted bin 中的空闲 chunk。这一思想可以推广到其他章节ctf-wiki 堆利用系列见 introduction.md中所有House 系列技巧本质上都是回答同一个问题——在某种受限条件下如何伪造出 ptmalloc 认为合法、可被继续操作的堆状态。原理如何在没有 free 的情况下获得 Unsorted Bin触发路径从 malloc 到 sysmallocHouse of Orange 的原理一句话概括当当前堆的 top chunk 尺寸不足以满足一次 malloc 申请的大小时ptmalloc 会先把原有的 top chunk 释放并置入 unsorted bin然后扩展堆。通过这一点我们就能在没有 free 函数的情况下获取到 unsorted bin。我们把这个过程逐步展开。假设程序当前的 top chunk 已经无法满足一次 malloc 的分配需求整个调用链如下程序中的malloc调用进入 libc.so 的_int_malloc函数_int_malloc依次检查 fastbin、small bins、unsorted bin、large bins 是否满足分配要求——因为尺寸问题这些 bin 中都没有可用块_int_malloc转而试图使用 top chunk 切割但 top chunk 也不满足需求于是执行如下分支把问题交给系统调用层处理/* Otherwise, relay to handle system-dependent cases */ else { void *p sysmalloc(nb, av); if (p ! NULL __builtin_expect (perturb_byte, 0)) alloc_perturb (p, bytes); return p; }此时 ptmalloc 已经无法在现有堆上满足用户申请必须调用sysmalloc向操作系统申请更多内存。brk 与 mmap必须让堆以 brk 形式扩展对于堆的扩展Linux 提供两种系统调用路径详见 heap-overview.md 中内存分配背后的系统调用一节(s)brk通过移动 program breakbrk沿低地址向高地址方向线性扩展堆段扩展后的内存与原有堆物理连续mmap / munmap创建独立的匿名映射段与原有堆不连续。House of Orange 的关键前提是必须让堆以 brk 形式拓展。因为只有 brk 扩展时sysmalloc内部的扩展逻辑才会把旧 top chunk作为_int_free的释放对象送入 unsorted bin而如果走 mmap 路径ptmalloc 会直接向系统申请一块全新的独立内存原有 top chunk 不会被回收也就无法获得 unsorted bin。第一道门槛申请尺寸不能超过 mmap 阈值要让分配走 brk 而不是 mmap首先必须绕过sysmalloc中对 mmap 阈值的判断if ((unsigned long)(nb) (unsigned long)(mp_.mmap_threshold) (mp_.n_mmaps mp_.n_mmaps_max))mp_.mmap_threshold是 mmap 分配阈值默认为 128K131072 字节当所需分配的 chunk 大小nb即 request 经对齐转换后的实际分配尺寸大于等于该阈值且当前进程通过mmap()分配的块数小于n_mmaps_max时ptmalloc 会直接用mmap()向操作系统申请内存此时不会触发 brk 扩展与 top chunk 释放因此我们后续触发的malloc申请尺寸必须小于 128K才能确保sysmalloc走 brk 扩展分支。第二道门槛sysmalloc 对 top chunk 的合法性检查在sysmalloc函数内部存在针对旧 top chunk 的 assert 检查assert((old_top initial_top(av) old_size 0) || ((unsigned long) (old_size) MINSIZE prev_inuse(old_top) ((unsigned long)old_end pagemask) 0));逐项解读这个 assert这与 ptmalloc-check.md 中整理的 top chunk 检查体系一脉相承首次调用豁免如果这是本函数第一次被调用heap 尚未初始化old_top指向 initial_top 且old_size 0则该分支成立old_size MINSIZEtop chunk 已经初始化后其 size 必须大于等于MINSIZE。这是因为 top chunk 中存在 fencepost守护字段其大小必须满足最小 chunk 约束。关于 MINSIZE 的定义MALLOC_ALIGNMENT对齐后的最小 chunk 大小64 位下通常为 0x20可参考 heap-structure.mdprev_inuse(old_top)top chunk 的 size 字段中PREV_INUSE位必须为 1。从源码层面看prev_inuse(p)宏就是提取 size 的最低比特位见 heap-structure.md 中的标记位宏定义top chunk 的 prev_inuse 位始终为 1否则其前面的 chunk 会被合并进 top chunkold_end 页对齐top chunk 的结束地址old_end必须满足(old_end pagemask) 0即页对齐现代操作系统内存页一般为 4KB即0x1000。此外还有一个隐含条件top chunk 除去 fencepost 之后的大小必须小于本次所需 chunk 的大小。否则在_int_malloc中就会直接使用 top chunk 分割出所需块根本不会走到sysmalloc分支。伪造 top chunk size 的四条硬性要求综合上述检查我们总结出伪造 top chunk size 必须同时满足的四条要求伪造的 size 必须对齐到内存页——确保old_end页对齐检查通过这是最关键、最容易踩坑的一条size 要大于 MINSIZE64 位下通常为 0x20size 要小于之后申请的 chunk size MINSIZE——否则_int_malloc会直接切割 top chunk 而不触发扩展size 的 prev inuse 位必须为 1。当这四条全部满足时sysmalloc会走 brk 扩展分支对旧 top chunk 执行_int_free使其顺利进入 unsorted bin——至此无 free 获得 unsorted bin的目标达成。失败的示例为什么 0x41 不能利用成功文档给出了一个模拟溢出覆盖 top chunk size 域的示例程序目标是把 size 改小从而触发 brk 扩展#include stdlib.h #define fake_size 0x41 int main(void) { void *ptr; ptrmalloc(0x10); ptr(void *)((long long)ptr24); *((long long*)ptr)fake_size; // overwrite top chunk size malloc(0x60); malloc(0x60); }程序逻辑先malloc(0x10)得到一个堆块把指针偏移 24 字节0x10 的 chunk 头 8 字节用户数据后落在 top chunk 的 size 字段上将 top chunk 的 size 覆盖为0x41随后申请0x60大于0x41的堆块期望触发 brk 扩展再申请一个0x60期望从 unsorted bin 中切割。但实际执行会失败assert 未满足并抛出异常。用 gdb 观察调用栈可以看到[#0] 0x7ffff7a42428 → Name: __GI_raise(sig0x6) [#1] 0x7ffff7a4402a → Name: __GI_abort() [#2] 0x7ffff7a8a2e8 → Name: __malloc_assert(assertion0x7ffff7b9e150 (old_top initial_top (av) old_size 0) || ((unsigned long) (old_size) MINSIZE prev_inuse (old_top) ((unsigned long) old_end (pagesize - 1)) 0), file0x7ffff7b9ab85 malloc.c, line0x95a, function0x7ffff7b9e998 __func__.11509 sysmalloc) [#3] 0x7ffff7a8e426 → Name: sysmalloc(nb0x70, av0x7ffff7dd1b20 main_arena)注意sysmalloc(nb0x70, ...)0x60的请求经request2size对齐后实际分配尺寸为0x70而伪造的 top chunk size 是0x41明显小于所需大小因此_int_malloc不会切割 top chunksysmalloc被真正触发。但0x41不满足 assert 中的页对齐条件程序在__malloc_assert处中止。对照四条要求逐条检查0x41大于 MINSIZE第 2 条满足、小于0x70第 3 条满足、低 bit 为 1 即 PREV_INUSE 置位第 4 条满足唯独第 1 条页对齐不满足——这就是失败的根本原因。正确的示例0x1fe1 与页对齐的艺术什么是对齐到内存页现代操作系统以内存页为单位管理内存一般内存页大小为 4KB0x1000。所谓伪造的 size 对齐到内存页是指旧 top chunk 的结束地址old_end old_top_addr old_size必须是 0x1000 的整数倍。在覆盖之前top chunk 的 size 为0x20fe1堆的布局如下用 gdb 观察堆内存0x602000: 0x0000000000000000 0x0000000000000021 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000020fe1 top chunk 0x602030: 0x0000000000000000 0x0000000000000000top chunk 的起始地址为0x602020原始 size 为0x20fe1因此0x602020 0x20fe0 0x6230000x623000恰好是0x1000的整数倍这就是页对齐的含义。关键在于修改后的 size 依然必须让old_end对齐到页。因此合法的 fake_size 可以是0x0fe1、0x1fe1、0x2fe1、0x3fe1等这些以0xfe1结尾、恰好补足到整页的值低三位0xfe1中低 bit 为 1 同时满足了 PREV_INUSE。而之前的0x41不满足对齐因此无法利用。正确示例程序#include stdlib.h #define fake_size 0x1fe1 int main(void) { void *ptr; ptrmalloc(0x10); ptr(void *)((long long)ptr24); *((long long*)ptr)fake_size; malloc(0x2000); malloc(0x60); }与失败示例相比有两处关键变化fake_size 改为0x1fe1满足页对齐要求触发分配的尺寸改为0x2000远大于伪造后的 top chunk size0x1fe1确保_int_malloc一定会把控制权移交给sysmalloc同时0x2000远小于 128K 的 mmap 阈值确保走 brk 扩展路径。观察结果一堆段确实发生了 brk 扩展执行后查看进程的内存映射可以发现堆段从原来的一页扩展到两页//原有的堆 0x0000000000602000 0x0000000000623000 0x0000000000000000 rw- [heap] //经过扩展的堆 0x0000000000602000 0x0000000000646000 0x0000000000000000 rw- [heap]堆的起始地址0x602000不变结束地址从0x623000扩展到了0x646000——这正是 brk 扩展的特征扩展后的堆与原堆在虚拟地址空间连续。这与 heap-overview.md 中描述的主线程堆经 brk 增长的行为完全一致。观察结果二旧 top chunk 进入了 unsorted binmalloc(0x2000)的返回值被分配到新扩展区域0x623010的位置同时原来的 top chunk 被释放并置入 unsorted bin[] unsorted_bins[0]: fw0x602020, bk0x602020 → Chunk(addr0x602030, size0x1fc0, flagsPREV_INUSE)这里有两个细节值得注意链表自指fw0x602020, bk0x602020指向 unsorted bin 的头节点自身说明这是链表中唯一的块——正如 unsorted-bin-attack.md 中强调的CTF 中堆往往是刚初始化的unsorted bin 一般都很干净只有一个块时其 fd 和 bk 都指向 main_arena 内部这正是后续泄漏 libc 的温床size 变为 0x1fc0旧 top chunk 在释放前被扣除了 fencepost 等开销0x20fe1 → 0x1fe1 → 0x1fc0flags 为 PREV_INUSE。观察结果三下一次分配从 unsorted bin 切割因为 unsorted bin 中已经存在块下一次malloc(0x60)会直接切割这个块malloc(0x60); 0x602030 [] unsorted_bins[0]: fw0x602090, bk0x602090 → Chunk(addr0x6020a0, size0x1f50, flagsPREV_INUSE)分配返回的地址是0x602030位于旧 top chunk 区域内而不是新扩展的 brk 区域——这证明分配确实来自 unsorted bin 的切割而非直接从新扩展的 top chunk 上切出。此时堆内存的布局如下0x602030: 0x00007ffff7dd2208 0x00007ffff7dd2208 未被清零的unsorted bin链表 0x602040: 0x0000000000602020 0x0000000000602020 0x602050: 0x0000000000000000 0x0000000000000000 0x602060: 0x0000000000000000 0x0000000000000000 0x602070: 0x0000000000000000 0x0000000000000000 0x602080: 0x0000000000000000 0x0000000000000000 0x602090: 0x0000000000000000 0x0000000000001f51 切割剩下的新unsorted bin 0x6020a0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 0x6020b0: 0x0000000000000000 0x0000000000000000这份布局信息量很大被分配出去的 chunk起始0x602030中残留着0x7ffff7dd2208——这是unsorted bin 链表的指针未被清零。在真实利用中若能通过程序的输出功能show/print读到这片残留数据就能直接泄漏出 libc 中的地址进而绕过 ASLR详见 unsorted-bin-attack.md 的 Unsorted Bin Leak 小节切割后剩余的部分0x6020a0size0x1f51作为新的 unsorted bin 块继续挂在链表中其 fd/bk 指向0x7ffff7dd1b78即 main_arena 内部的 bin 头。拿到 Unsorted Bin 之后House of Orange 的进阶利用House of Orange 的要点到此为止——核心就是伪造 top chunk 获得 unsorted bin。但获得 unsorted bin 只是第一步后续的利用链条在真实 CTF 题目中通常有两条主线泄漏 libc 基址unsorted bin 中只有单块时其 fd/bk 指向 main_arena 内部地址而 main_arena 是 libc 数据段中的全局变量见 heap-structure.md 中 malloc_state 一节通过泄漏 fd/bk 即可计算出 libc 基地址unsorted bin attack / IO_FILE 劫持控制 unsorted bin chunk 的 bk 指针可以在任意地址写入一个较大的值unsorted bin 头地址经典用法是改写global_max_fast让更大的 chunk 被当作 fastbin或结合_IO_FILE结构完成任意写。正如原文档所提示的之后的利用因为涉及到_IO_FILE的知识放到 IO_FILE 独立章节分享。这也解释了为什么 House of Orange 常与无 free 无输出型题目绑定它既解决了无法 free 的问题又顺带提供了 unsorted bin 指针泄漏的窗口一举两得。小结与调试要点House of Orange 的完整利用链条可归纳为四步溢出覆盖 top chunk 的 size 域伪造一个满足四条约束的新 size触发一次足够大的 malloc大于伪造后的 top chunk size且小于 mmap 阈值 128K迫使sysmalloc走 brk 扩展分支旧 top chunk 被_int_free进入 unsorted bin实现无 free 获得空闲块基于 unsorted bin 继续利用泄漏 libc、unsorted bin attack、IO_FILE 劫持等。实战调试时的三个关键校验点页对齐伪造 size 后务必计算top_addr size是否为0x1000的整数倍这是最常见的翻车点mmap 阈值触发申请的尺寸必须小于 128K否则走 mmap 路径直接前功尽弃sysmalloc assert程序 abort 时用 gdb 查看__malloc_assert的调用栈如本文sysmalloc(nb0x70, ...)的案例即可快速定位是哪一条 assert 条件未满足。相关文档导航本主题原始文档docs/zh/docs/pwn/linux/user-mode/heap/ptmalloc2/house-of-orange.md繁体镜像见 docs/zh-tw/docs/pwn/linux/user-mode/heap/ptmalloc2/house-of-orange.md堆宏观机制与 brk/mmap 系统调用heap-overview.mdmalloc_chunk、MINSIZE、bin、top chunk 等数据结构heap-structure.md堆中各类检查条件汇总ptmalloc-check.md拿到 unsorted bin 后的 Leak 与攻击手法unsorted-bin-attack.md赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐ctf-wiki 堆利用系列House Of Force 原理、chunk 尺寸计算与实战ctf wiki 堆利用系列House Of Force 原理、chunk 尺寸计算与实战 House Of Force下称 HOF是 glibc ptm文档网络安全教程3步解锁qBittorrent隐藏搜索功能一站式获取全网种子资源3步解锁qBittorrent隐藏搜索功能一站式获取全网种子资源 还在为寻找优质种子资源而烦恼吗search plugins项目为你带来了革命性的解决方案。文档网络安全教程ctf-wiki 堆利用系列House of Lore 攻击原理与 glibc Small Bin 实战剖析ctf wiki 堆利用系列House of Lore 攻击原理与 glibc Small Bin 实战剖析 House of Lore 是 ctf wiki文档网络安全教程上一篇SwiftNIO _NIOFileSystem 的 FileHandleProtocol 详解异步文件句柄的信息、权限、扩展属性与描述符生命周期管理下一篇网盘直链下载指南8 大网盘网页里解析出真实链接跳过客户端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表