1. 常见的House Of系列漏洞技术
“House of” 系列是Linux内存管理(Glibc ptmalloc)中针对堆溢出及相关堆漏洞的经典利用技术代称。 攻击者通过堆溢出修改堆块(Chunk)的头部信息,诱导内存分配器将堆块分配到任意内存地址(如__free_hook或返回地址),从而控制程序执行流。
它们本身通常不是漏洞,而是当程序出现堆溢出(Heap Overflow)、Off-By-One 或 UAF 等基础漏洞时,用于将这些漏洞转化为“任意地址读写”或“控制流劫持”的高级手法。
根据不同的Glibc版本和利用场景,主要有以下几种经典技术:
- House Of Force:利用堆溢出覆盖Top Chunk的size字段为最大值(如-1或0xffffffffffffffff):
- 结果:欺骗内存分配器,使其认为堆空间无限大。后续可以通过申请一个超大内存块,将Top Chunk转移到任意内存地址(如目标全局变量附近),并在下一次分配时直接控制该地址;
- 现代版本:Glibc2.29及以上版本中加入了对Top Chunk的size属性合法性检查,此技术已失效;
- House Of Spirit:攻击者在已知地址(如栈上或全局变量区)伪造一个空闲的Chunk结构(伪造size和相邻的next_size),然后利用漏洞将该伪造地址传给free()函数:
- 结果:将目标地址骗入fastbin或tcache链表中,下次调用malloc()时即可分配到该伪造的地址,实现任意地址写;
- House Of Lore:通过堆溢出修改Small Bin或Large Bin中空闲堆块的bk(后向指针)或fd(前向指针);
- 结果:在分配内存时,强制让内存分配器将预先伪造在目标地址的堆块加入到分配链表中,最终实现写任意地址;
- House Of Orange:当程序中没有free函数时使用。通过堆溢出减小Top Chunk的size,然后申请一个大于该size的空间,迫使Glibc自动调用sysmalloc来释放原有的Top Chunk并将其放入Unsorted Bin;
- 结果:配合Unsorted Bin Attack和IO_FILE虚表劫持(如_IO_flush_all_lockp),在触发内存错误时执行恶意代码;
- House Of Rabbit:利用堆溢出修改Fastbin中堆块的size字段;
- 结果:触发malloc_consolidate将 Fastbin合并并移入Unsorted Bin。通过这种大小的改变,让小堆块在排序时被当成大堆块,从而实现重叠堆块(Chunk Overlap);
- House of Einherjar:通过堆溢出覆盖相邻高地址堆块的PREV_INUSE 标志位(将其清零),同时修改其prev_size字段;
- 结果:当高地址的堆块被free时,Glibc 误以为低地址的堆块也是空闲的,会根据prev_size强行进行向后合并(Backward Merge)。这允许攻击者将合并的首地址推到任意位置,实现堆块重叠;
- House of Botcake:利用tcache和Unsorted Bin之间的交互绕过tcache的Double Free检查,实现堆块重叠;
- House of Pig:利用largebin分配时的__int_malloc写入机制,结合Tcache Stashing Unlink攻击;
- 结果:在现代Glibc中实现任意地址写,通常用于劫持_IO_FILE结构;
- House of Roman:一种在开启了ASLR(地址空间布局随机化) 且没有任何信息泄露(Leakless) 的情况下,实现Get Shell的高级堆利用技术;
- 结果:在不需要泄露任何内存地址(如Libc基址或堆基址)的情况下,纯粹利用局部写(Partial Write)和爆破(Brute Force),实现任意代码执行;
- House of Kiwi:由于现代Glibc移除了__malloc_hook和__free_hook,该技术通过修改_IO_file_jumps或相关的内置IO虚表;
- 结果:在触发malloc报错(如malloc_printerr)时,借由报错流程直接触发Payload;
- House of Lys:利用现代Glibc中tcache_get的机制,配合对tcache->counts的不当处理;
- 结果:绕过Safe Linking(指针加密)保护,实现对特定地址的控制;
2. House Of Force(修改Top Chunk size)
House of Force是一种针对Glibc内存分配器(ptmalloc)的经典堆漏洞利用技术,其核心是通过改大Top Chunk的Size字段,实现任意地址分配。
由于现代Linux系统对安全机制的升级,该技术在Glibc 2.29及以上版本已被完全修复(即引入了Top Chunk边界检查)。
2.1 漏洞触发的过程
House of Force的利用过程主要分为以下三个阶段:
- 1. 通过堆溢出修改size字段
- 核心操作:利用程序中存在的堆溢出或任意写漏洞,覆盖物理相邻的Top Chunk的头部;将Top Chunk的size字段修改为一个极大的数值,在64位系统上通常设置为-1(即0xffffffffffffffff);
- 后果:欺骗Glibc,使其误认为当前堆区拥有无限大的可用空间,从而在后续申请极大内存时不会调用brk或mmap扩容;
- 2. 申请特定大小的内存(越界跨越)
- 首先计算偏移量:计算当前Top Chunk地址与目标写入地址(如__free_hook、全局变量、栈地址)之间的差值(Offset),计算公式为:Malloc Size = Target Address – Current Top Chunk Address – Chunk Header Size;
- 核心操作:调用malloc(Malloc Size)函数;
- 结果:Top Chunk的位置会被强行推到攻击者指定的目标内存地址;
- 3. 再次申请实现任意写
- 核心操作:再次调用malloc()函数申请一个普通大小的堆块;
- 后果:此时返回的内存指针将完美指向攻击者的目标地址,对其进行写入即可劫持控制流(例如覆盖函数指针);
成功实施House of Force必须满足以下硬性条件:
- 1. 漏洞支持:存在能够修改到Top Chunk Size的漏洞(如 Heap Overflow);
- 2. 泄露地址:必须能泄露堆基址(Heap Base)以计算精确差值;若要劫持Libc函数,还需要泄露Libc基址;
- 3. 分配无限制:程序允许用户申请自定义且足够大的内存块(malloc 的参数受控且未被限制上限);
2.2 漏洞成因(源码层)
在老版本的Glibc中,sysdeps/malloc/malloc.c处理Top Chunk分配时缺乏对 Size 的合法性校验:
// 老版本 Glibc 的简化逻辑
victim = av->top;
size = chunksize (victim);
// 如果请求的大小小于 Top Chunk 的大小,则直接切分分配
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder; // 直接更新 Top Chunk 指针,没有校验remainder是否越界
set_head (victim, nb | PREV_INUSE);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
由于size被改成了0xffffffffffffffff,第一步的if条件永远成立。接着通过chunk_at_offset(victim, nb) 计算新的Top Chunk地址时,指针直接发生了大范围越界偏移。
2.3 漏洞现状
Glibc 2.29版本的更新中,官方合入了安全补丁,在分配时对Top Chunk的大小进行了合法性检查:
// Glibc 2.29+ 防御代码
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): memory corruption");
如果发现Top Chunk的size大于当前系统分配给该分配区的总内存(av->system_mem),程序会直接抛出malloc(): memory corruption错误并崩溃,导致该技术彻底失效。
3. House Of Spirit(伪造Chunk释放)
House of Spirit是一种针对Glibc内存分配器(ptmalloc)的经典堆漏洞利用技术。
它的核心思想是:“偷梁换柱”——攻击者不在堆上动手脚,而是在已知地址(如栈内存、全局变量区、甚至Libc内部数据段)人工伪造一个假的堆块(Fake Chunk),然后利用漏洞调用free()将其释放。
一旦这个假的堆块被放进Glibc的空闲链表(如fastbin或tcache),下次程序调用malloc()时,就会把这个指定的目标地址当作正常堆块分配出来,从而实现任意地址写(Arbitrary Write)并劫持控制流。
3.1 漏洞触发过程
House of Spirit的构造主要依赖以下四个流程:
- 1. 寻找或控制目标内存
- 核心操作:首先锁定一个你想要修改的目标地址(例如:栈上的局部变量、保存了函数指针的全局变量区、或者是__malloc_hook附近);
- 2. 人工伪造Chunk结构
- 核心操作:在这个目标地址处,利用程序的写入或溢出能力,手工布置符合Glibc校验的Chunk头部信息;
- 关键布局:连续伪造两个Chunk的大小(当前Fake Chunk和物理相邻的Next Fake Chunk):
- Fake Chunk 的 Size:必须是一个合法的快表大小(例如0x30或0x40);
- Next Fake Chunk的Size:紧随其后的内存位置也必须写入一个合法的Size(通常设置在0x20到0x80之间,且不能太小,以通过基本的大小检查);
- 3. 欺骗并触发free()函数
- 核心操作:利用程序中存在的漏洞(如指针覆盖、格式化字符串等),将free()函数的参数指针,强行指向你伪造的Fake Chunk的数据区(User Data Address);
- 后果:Glibc误以为这是一个正常的空闲堆块,将其收入fastbin或tcache链表中;
- 4. 再次分配内存块
- 核心操作:调用malloc()申请与伪造大小(Size)完全一致的内存块;
- 后果:Glibc会直接把刚才释放的那个Fake Chunk分配出来。此时,返回的指针直接指向你的目标内存(如栈或全局变量)。对这个指针进行写入,即可直接篡改敏感数据或劫持返回地址。
限制条件与利用前提:
- 1. 原语能力:必须能够控制传给free()的指针参数(即拥有Arbitrary Free或特定指针覆盖能力);
- 2. 内存可控:在目标地址周围必须有足够的写能力,用来同时布置伪造的当前Size和下一个Size;
- 3.版本变化要求:
- Glibc 2.26以前 (Fastbin时代):需要严格遵循上面提到的nextchunk->size检查;
- Glibc 2.26+ (Tcache时代):由于引入了tcache机制,释放时若进入tcache,Glibc的检查会大大减少(甚至在某些早期小版本中不再严厉检查nextchunk->size),这使得House of Spirit在Tcache场景下极易成功。
3.2 漏洞成因(源码层)
老版本Glibc中,free()针对fastbin大小的堆块有如下安全检查:
// 检查 1:释放的指针必须对齐
if (__builtin_expect ((uintptr_t) p & (MALLOC_ALIGNMENT - 1), 0))
malloc_printerr ("free(): invalid pointer");
// 检查 2:当前 Fake Chunk 的 size 不能太小,也不能太大
if (__builtin_expect (chunksize (p) < min_chunk_size || chunksize (p) > max_fast, 0))
malloc_printerr ("free(): invalid size");
// 检查 3(致命):检查下一个物理相邻的 Chunk 的 size 是否合法!
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ || nextchunk->size >= av->system_mem, 0))
malloc_printerr ("free(): invalid size");
这就是解释了为什么攻击者不能只填一个size的原因。如果只伪造了当前Chunk的size = 0x30,Glibc会自动定位到当前指针+ 0x30的内存位置去检查nextchunk->size。如果那个位置全是垃圾数据(为 0),就会直接触发free(): invalid size报错崩溃。因此,攻击者必须提前在nextchunk的位置也写入一个有效值(如0x20)。
3.3 漏洞现状
虽然House of Spirit的漏洞思想至今在很多CTF和实际漏洞中依然适用,但现代Glibc引入了以下防御大大提高了其门槛:
- 1. Tcache 计数与安全校验(Glibc 2.29+ / 2.31+):
- 新版本对tcache的释放增加了双重释放检查(Double Free Check)和更严格的地址对齐检查
- 2. 指针混淆(Pointer Mangling, Glibc 2.32+):
- 进入单链表(tcache/fastbin)的指针会被随机的堆基址异或加密。即使成功利用House of Spirit将栈地址放进了链表,由于你无法轻易得知加密所需的堆随机密钥,在下一次malloc弹出该地址解密时,会导致其变成一个无意义的野指针进而崩溃。
4. House Of Lore(伪装Bin链表)
House of Lore是一种针对Glibc内存分配器(ptmalloc)中双向链表(Small Bin或Large Bin)的经典堆漏洞利用技术。
它的核心思想是:“染指双向链表”——通过堆溢出(Heap Overflow)或UAF漏洞,篡改Small Bin中空闲堆块的bk(后向指针),将其指向攻击者在任意位置(如栈、全局变量区)伪造的Fake Chunk。当程序连续申请相同大小的内存时,内存分配器就会将这个伪造的Chunk分配出来,从而实现任意地址写。
Glibc中,fastbin和tcache是单向链表(只用fd指针),只检查链表头,而Small Bin是双向循环链表(同时使用fd前向和bk后向指针),采用FIFO(先进先出) 机制进行分配。因此,当用户调用malloc申请Small Bin大小的内存时,Glibc会从链表的尾部(即靠近bin头部的一端)取出堆块。它寻找下一个堆块的依据,就是当前堆块的bk指针。
4.1 漏洞触发过程
House of Lore的四大攻击流程如下:
- 1. 泄露地址并准备Fake Chunk
- 核心操作:攻击者首先需要泄露堆地址。随后,在想要劫持的目标地址(如栈上0x7fffffff…)人工伪造两个Fake Chunk(Fake_Chunk_1和Fake_Chunk_2);
- 关键布局:
- Fake_Chunk_1的fd指针必须指向Small Bin的头部(main_arena里的Bin数组地址);
- Fake_Chunk_1的bk指针必须指向Fake_Chunk_2;
- Fake_Chunk_2的fd指针必须反向指回Fake_Chunk_1;
- 2. 修改正常堆块的bk
- 核心操作:将一个堆块(假设为Chunk A)释放到Small Bin中;
- 后果:利用堆溢出漏洞,将Chunk A的bk指针修改为Fake_Chunk_1的地址;
- 3. 绕过双向链表检查(Bck->fd校验)
- 核心操作:当Glibc从Small Bin弹出Chunk A时,会执行以下安全校验;Fake_Chunk_1的fd指针必须精准指回Chunk A(即这里的victim)。只有满足Fake_Chunk_1->fd == Chunk A,程序才不会崩溃;
- 后果:校验通过后,Fake_Chunk_1被成功链接到了Small Bin的链表尾部,等待下一次分配;
- 4. 再次分配内存块
- 核心操作:程序下一次再调用malloc()函数申请相同大小的内存;
- 后果:Glibc直接将Small Bin尾部的Fake_Chunk_1(即你的目标地址)当作正常堆块返回给程序,对其写入即可劫持返回地址或覆盖敏感变量;
漏洞限制条件与利用前提:
- 1. 原语能力:需要极强的溢出或读写能力,因为不仅要修改正常堆块的指针,还要在目标地址提前布置好伪造的双向链表闭环;
- 2. 地址泄露:由于需要精确伪造fd和bk指向堆块和Libc的main_arena,必须成功泄露堆基址和Libc基址;
- 3. 版本限制:经典House of Lore主要活跃在Glibc 2.26以前;
4.2 漏洞现状
随着Glibc的不断更新,House of Lore在现代系统上已经极难实施:
- 1. Tcache 机制的拦截(Glibc 2.26+):在较新版本中,释放的堆块通常会优先进入tcache。由于tcache是单链表且不进入Small Bin流程,导致无法轻易将堆块打入Small Bin。即使进入了Small Bin,在分配时如果tcache没满,Glibc还会触发Stashing机制,顺便把Small Bin里的其他堆块打包挪动到tcache中。这种挪动对链表指针有更复杂的校验,极易触发崩溃;
- 2. 安全双链表校验的强化:现代Glibc对双向链表的合并、取出(Unlink)操作加入了全方位的__builtin_expect破坏检测,伪造多级闭环链表的门槛呈指数级上升;
5. House Of Orange(无Free堆利用)
House of Orange是一种针对Glibc内存分配器(ptmalloc)的顶级堆漏洞利用技术。核心在于它打破了“利用堆漏洞必须调用free()”的传统思维定势。该技术由台湾著名安全安全研究员Angelboy 于2016年提出,核心是通过“改小Top Chunk”迫使系统替我们调用free(),并最终劫持Libc的_IO_FILE结构体夺取控制流。
5.1 三大核心阶段
House of Orange的整个利用流程环环相扣,主要分为以下三个核心阶段:
- 第一阶段:伪造sysmalloc扩容,强行触发“假 Free”:当程序没有free时,攻击者可以通过堆溢出(Heap Overflow)强行修改Top Chunk的Size字段,将其改小(例如改为0xc01):
- 触发分配:此时,攻击者调用malloc申请一个大于当前被改小的Top Chunk Size的内存(例如申请0x2000字节);
- 底层响应:Glibc发现当前的Top Chunk太小了,无法满足分配需求。于是会调用底层的sysmalloc函数向操作系统申请扩容(通过brk或mmap);
- 系统接盘(核心爆发点):在申请到新内存之前,sysmalloc认为原有的那个Top Chunk还有剩余空间,不能浪费。于是,系统会自动在内部调用_int_free(),将原有的、被改小的旧Top Chunk释放掉;
- 效果:旧的Top Chunk被系统亲手放入了Unsorted Bin中。攻击者在完全没有调用free()的情况下,成功在堆上获得了一个空闲堆块;
- 第二阶段:Unsorted Bin Attack(任意地址写):有了Unsorted Bin堆块后,攻击者可以再次利用堆溢出,修改这个处于空闲状态的旧Top Chunk的bk(后向指针):
- 操作:将bk指针修改为目标地址 – 0x10(在经典利用中,这个目标地址通常是Libc中的全局变量_IO_list_all);
- 触发:当再次调用malloc申请内存时,Glibc会对Unsorted Bin进行清空和重分类。在此过程中,会触发著名的Unsorted Bin Attack写入原语,将一个Libc区域的地址(main_arena附近地址)强行写入到_IO_list_all中;
- 效果:全局的文件流链表表头_IO_list_all被篡改,指向了攻击者控制的内存区域;
- 第三阶段:FSOP(文件流面向对象编程)一击必杀:由于_IO_list_all被修改为了一个混乱的地址,Glibc链表中的文件流结构已被完全破坏:
- 触发崩溃:在阶段2中,由于链表被改坏,接下来的malloc往往会因为无法通过校验而直接触发内存错误,或者攻击者主动触发错误,使程序抛出异常并调用abort();
- 刷新缓冲区:在程序崩溃退出前,Glibc为了保护数据,会自动遍历_IO_list_all链表,去刷新所有未关闭的文件流缓冲区(即调用虚表中的_IO_flush_all_lockp函数);
- 劫持虚表(vtable):该函数会顺着虚表指针执行。由于_IO_list_all已经被我们通过前期的堆布局精准控制,它最终会执行攻击者在堆上伪造的 Fake _IO_FILE结构体中的虚表函数指针(如_IO_overflow);
- 夺权:将该指针覆盖为system函数的地址,并将参数(文件流开头的结构体字段)布置为 “/bin/sh”,即可在崩溃的瞬间完美弹回一个Shell;
漏洞限制条件与利用前提:
- 原语能力:需要稳定的堆溢出(Heap Overflow)或任意写原语,用于改写Top Chunk的Size以及后续在空闲块中布置复杂的Fake_IO_FILE结构;
- 地址泄露:必须成功泄露Libc基址和堆基址(Heap Base),因为伪造_IO_FILE结构体时需要精密计算单向链表和跳转虚表的绝对地址;
- 版本黄金期:主要针对 Glibc 2.23 至 2.25。这是House of Orange的黄金时代;
5.2 漏洞成因(源码级)
在阶段1中,利用sysmalloc强行让系统替我们free并不是随心所欲的。Glibc在老版本中对Top Chunk的大小有如下严格的对齐和边界检查:
// Glibc 源码中的 sysmalloc 校验
if (old_size >= MINSIZE
&& next_chunk (old_top)->size == 0 // 检查下一个物理相邻块的 size(必须为0)
&& ((unsigned long) old_end & (pagesize - 1)) == 0) // 检查页对齐
伪造的Top Chunk Size必须满足页对齐(即old_top + old_size必须是对齐到系统内存页边界的,通常低12位要与原先一致,所以经典的修改值是0xc01、0xd01等,其中的1是为了保留PREV_INUSE标志位)。被修改后的Size不能小于MINSIZE(通常为0x20);
5.3 漏洞现状
随着Glibc的升级,经典House of Orange的每一条攻击路径在现代Linux系统中都遭到了毁灭性打击:
- 1. Tcache 机制的引入(Glibc 2.26+):引入tcache后,Unsorted Bin Attack的触发路径受到极大干扰,甚至在后续版本中,Unsorted Bin Attack这种“往任意地址写入一个Libc地址”的原语被官方合入补丁彻底修复;
- 2. vtable虚表校验(Glibc 2.24+ 引入,后不断强化):Glibc引入了IO_validate_vtable机制。现在,如果虚表指针(vtable)指向了合法的__libc_IO_vtables段之外的区域(比如你伪造在堆上的Fake虚表),系统会直接报错fatal error: invalid ttable pointer并瞬间无条件卡死,阻断了阶段3的执行;
- 3. 现代变种的生命力:虽然经典House of Orange走不通了,但它的精髓——“通过改小Top Chunk强行诱导sysmalloc触发Free”的思想,在现代高版本(如Glibc 2.35+)的CTF堆题中依然被广泛使用。现代研究员通常用它来凭空创造出一个Unsorted Bin,然后转去配合Largebin Attack或劫持受到支持的底层结构(如House of Kiwi / House of Emma路径),继续在现代防线上寻求突破;
6. House Of Rabbit(Fastbin倒带利用)
House of Rabbit是一种针对Glibc内存分配器(ptmalloc)的经典高级堆漏洞利用技术。
它的核心思想是:“Fastbin倒带与重叠”——通过堆溢出(Heap Overflow)等漏洞,强行修改处于已释放状态的Fastbin堆块的size字段,随后诱导Glibc触发malloc_consolidate(堆块合并与重分类)。通过这种大小的伪造,让一个小堆块在排序时被分配器误当成“超大堆块”,从而实现跨越式、大范围的堆块重叠(Chunk Overlap),最终夺取执行权。
该技术主要活跃于Glibc2.23至2.25时代,在纯Fastbin管理的场景下威力极大。
6.1 三大核心利用原理(如何让小堆块吞噬整个堆)
House of Rabbit的整个利用过程环环相扣,其绝妙之处在于将单向链表的Fastbin与双向管理的Unsorted Bin进行无缝联动:
- 1. 触发Fastbin链表内的伪造
- 核心操作:首先释放两个Fastbin大小的堆块(假设为Chunk A和Chunk B),它们会进入Fastbin 单向链表;
- 溢出篡改:利用堆溢出漏洞,将Chunk A内部的size字段改写为一个极大的数值(例如 0xffffffff或者是自定义的一个超大合法值,但低位需要保持标志位清零以示空闲);
- 2. 诱导malloc_consolidate(核心爆发点)
- 核心原理:Glibc为了防止堆内存碎片化,当用户申请一个非常大的内存(大到Fastbin无法满足,需要从小bins或Top Chunk分配)时,系统会自动触发一个叫做malloc_consolidate的紧缩机制;
- 结果:这个机制会遍历整个Fastbin链表,将里面的所有小堆块从单向链表中解扣出来,清理它们的空闲标志,并把它们通通转移到Unsorted Bin中;
- 致命误判:当系统遍历到被改了大Size的Chunk A时,由于Glibc在此处缺乏对Fastbin真实大小的二次校验,它会完全相信被篡改后的超大size;
- 3. 实现大范围堆块重叠(Chunk Overlap)
- 结果:Chunk A带着它被改大的虚拟体积(例如0x1000)被塞进了Unsorted Bin;在Glibc看来,Chunk A现在是一个占地极广的超大空闲腹地。此时如果攻击者再次调用malloc申请一个稍小一点的空间,分配器就会从Unsorted Bin里的Chunk A头上“切”下一块返还给用户;
- 一击必杀:由于这个超大体积是伪造的,Chunk A的名义尾部其实已经远远越界,覆盖了堆后面大量正在被其他正常变量使用的存活堆块。新返回的指针允许攻击者直接以合法写入的方式,肆意践踏和篡改后面所有存活堆块的指针或敏感数据;
漏洞限制条件与利用前提:
- 1. 原语能力:需要有稳定的堆溢出(Heap Overflow)或UAF任意写能力,用于修改处于释放状态的Fastbin的头部size;
- 2. 布局能力:需要能在堆的后方精准布置一个Fake Chunk充当“挡箭牌”,以通过malloc_consolidate的相邻块大小校验;
- 3. 版本黄金期:针对Glibc 2.23至2.25。在Tcache时代到来前,这是通过Fastbin实现Chunk Overlap最经典路径之一;
6.2 漏洞成因(源码级)
在阶段2中,虽然系统会把Fastbin的块移入Unsorted Bin,但在将其当作大堆块处理时,仍会触发经典的后向合并(Backward Merge) 和Top Chunk边界检查。
// Glibc源码中malloc_consolidate后的Unsorted Bin校验
nextchunk = chunk_at_offset (p, size);
nextsize = chunksize (nextchunk);
// 校验1:下一个相邻块的size不能太小
if (__builtin_expect (nextsize <= 2 * SIZE_SZ, 0))
malloc_printerr ("malloc_consolidate(): invalid next size");
// 校验2:不能与Top Chunk发生无法解释的冲突
为了不让程序直接崩溃(如触发invalid next size),攻击者改大的那个size不能盲目填0xffffffff。必须精准计算:
让当前指针 + 伪造的 Size恰好指向一个由攻击者提前布置好的Fake Chunk(其内部要写好一个合法的size),或者干脆让它指向原有的Top Chunk的头部。这样Glibc去检查“下一个相邻块”时,才能完美通过完整性校验。
6.3 漏洞现状
House of Rabbit在现代Linux系统中遭遇了重大的防御阻击,导致经典路径基本失效:
- Tcache机制的拦截(Glibc 2.26+):现代Glibc引入tcache后,释放的小堆块会优先进入tcache缓存。由于tcache不参与malloc_consolidate的合并流程,导致攻击者极难把受控堆块推入受灾的合并路径中;
- 指针混淆(Pointer Mangling, Glibc 2.32+):新版本将单链表指针进行了XOR加密,导致通过堆溢出直接操控链表和尺寸的难度呈几何级上升;
- 更严苛的Size合法性硬校验:在较新的Glibc小版本修订中,官方在从Fastbin弹出堆块或者进行consolidate合并时,增加了对当前分配区(Arena)系统总内存(system_mem)的范围比对。如果发现一个原属于Fastbin的堆块尺寸突然超越了Fastbin的法定上限(通常为0x80),会直接抛出内存损坏异常并强制退出程序;
7. House of Botcake(绕过Double Free检查)
House of Botcake是一种专门针对Glibc 2.29及以上版本(特别是Glibc 2.29至Glibc 2.34)的高级堆漏洞利用技术。
它的核心价值在于:完美绕过了Glibc 2.29引入的针对tcache的Double Free严厉安全检查,在不需要高超原语的情况下,仅通过简单的UAF或堆溢出,就能再次实现经典的“堆块重叠(Chunk Overlap)”和任意地址写。
7.1 核心利用原理:Tcache与Unsorted Bin的跨界联动
House of Botcake的核心思想是:“惹不起,就躲得起”。既然不能把同一个块连续释放进tcache,那我就把这个块分别释放进两个完全不同的Bin链表里(一个进tcache,一个进Unsorted Bin),从而完美绕过查重;它的标准构造流程需要申请并操纵一整排连续的堆块,主要分为以下五个步骤:
- 1. 铺垫内存(申请 7 + 2 个堆块)
- 核心操作:连续申请7个较大(如0x100字节)的堆块用于填满tcache,再额外申请2个相邻的堆块(假设为Chunk A和Chunk B),以及一个防止与Top Chunk合并的保护块;
- 2. 填满Tcache哨兵
- 核心操作:首先把那7个用于铺垫的堆块全部free掉;
- 后果:此时,该大小对应的tcache链表刚好被完全填满(数量达到上限7个)。接下来再释放同等大小的堆块,就只能被迫降级进入底层的Unsorted Bin;
- 3. 让Chunk B沦陷(进入Unsorted Bin)
- 核心操作:释放Chunk B,由于tcache已经饱了,Chunk B则只能进入了Unsorted Bin;
- 4. 让Chunk A被吞噬(后向合并产生重叠)
- 核心操作:紧接着,释放物理上与Chunk B紧挨着的前一个堆块——Chunk A;
- 后果:Glibc发现Chunk A释放了,且它后面紧挨着的Chunk B在Unsorted Bin中也是空闲的。于是,Glibc自动触发后向合并(Backward Merge),把Chunk A和Chunk B融合成了一个超大的空闲块(假设叫Chunk A+B),并挂在Unsorted Bin中;
- 5. 跨界释放,制造幽灵堆块(一击必杀)
- 核心操作:攻击者调用一次malloc(),把tcache链表里的块消耗掉一个(腾出一个空位)。然后,利用UAF漏洞,再次释放Chunk B;
- 后果:此时tcache有空位了,Chunk B成功进入了tcache链表;tcache的查重机制此时只检查tcache链表本身,它根本不知道Chunk B其实早就作为Chunk A+B的一部分,被融合在了Unsorted Bin里面;
- 最终形态:整个堆区发生了物理重叠;大块Chunk A+B在Unsorted Bin中嗷嗷待哺,而小块Chunk B同时挂在tcache头部;
- 任意地址写:攻击者从Unsorted Bin中切分申请出Chunk A,此时Chunk A的数据区会直接覆盖掉正在tcache中等待分配的Chunk B的头部指针(fd)。直接将Chunk B->fd修改为目标地址(如__free_hook或某个全局变量),下一次malloc即可直接对其写入;
漏洞限制条件与利用前提:
- 原语能力:需要有UAF(Use After Free) 漏洞,允许在堆块被合并或释放后,依然能再次对该指针调用free()函数;
- 大小控制:申请的Chunk大小必须大于fastbin的上限(通常需要>= 0x90字节),确保它们释放时能顺利进入Unsorted Bin并触发双向合并;
- 地址泄露:虽然它不需要爆破,但后续要劫持特定的钩子函数或Libc结构时,依然需要泄露Libc基址;
7.2 漏洞成因(源码级)
在Glibc2.26到2.28(早期的Tcache时代),利用堆漏洞非常简单:直接连续free两次同一个堆块(Double Free),它就会在tcache链表里形成一个环。下次malloc时就能直接修改它的指针,轻松实现Tcache Poisoning(任意地址写);
但是,从Glibc 2.29开始,官方在tcache_put中引入了计数和重复检查:
//Glibc 2.29+引入的防御
if (__glibc_unlikely (e->key == tcache)) // 检查 key 标志位
{
//如果发现已经释放过,遍历整个tcache链表查重
...
malloc_printerr ("free(): double free detected in tcache 2");
}
一旦检测到同一个块被释放进同一个tcache链表,程序会立刻崩溃。House of Botcake正是为了打破这一重锁而发明的。
7.3 漏洞现状
House of Botcake的终点通常是把伪造指针挂到__free_hook或__malloc_hook。但在Glibc 2.34中,官方把这些全局Hook钩子函数彻底删除了,导致原版Botcake失去了最直接的落脚点;
尽管Hook没了,但House of Botcake制造“堆块重叠(Chunk Overlap)”的宏观思路在Glibc 2.34+甚至Glibc 2.39+依然完全适用。现代安全研究员利用Botcake制造出重叠块后,不再去改写Hook,而是转向去修改其他仍在存活的堆块指针,去构造Largebin Attack,或者去劫持_IO_FILE结构体(配合House of Emma / House of Kiwi)来最终夺取系统的控制权。
8. House of Einherjar(向后合并/向低地址吞噬)
House of Einherjar是一种针对Glibc内存分配器(ptmalloc)的经典高级堆漏洞利用技术。
它的核心思想是:“向低地址强行吞噬”——通过堆溢出(Heap Overflow)或Off-by-One(单字节溢出)漏洞,篡改相邻高地址堆块的头部标志位,欺骗Glibc触发后向合并(Backward Merge)。这使得高地址堆块在释放时,会跨越中间区域,强行向低地址合并不属于它的内存,从而在任意指定位置(如栈、全局变量区、堆上游)凭空制造出“堆块重叠(Chunk Overlap)”,活跃于Glibc 2.23至2.28时代。
8.1 核心利用原理:如何触发“低地址吞噬”
在Glibc内存管理中,当一个堆块被free时,系统会检查它前后的物理相邻块是否也是空闲的。如果是,为了减少内存碎片,系统会自动将它们合并。House of Einherjar正是利用了向低地址合并(后向合并)的底层逻辑。
整个攻击流程通常需要操纵两个物理相邻的堆块(假设为低地址的Chunk A和高地址的Chunk B):
- 1. 溢出篡改物理相邻块的头部
- 核心操作:利用Chunk A的堆溢出(或Off-by-One)漏洞,精准覆盖相邻高地址Chunk B的头部信息(Chunk Header);
- 篡改内容:
- 清空PREV_INUSE标志位:将Chunk B的size字段的最低位清零(例如将0x101改为0x100)。这会欺骗分配器,使其确信“低地址的Chunk A目前是空闲的”;
- 伪造prev_size字段:将Chunk B头部的prev_size改写为一个精心计算的超大数值;
- 关键计算:prev_size = Chunk B 的起始地址 – 目标伪造地址(Fake Chunk Address);
- 2. 触发Free,强行触发Unlink合并(核心爆发点)
- 核心操作:调用free(Chunk B),释放Chunk B;
- 系统操作:
- Glibc检查Chunk B的头部,发现PREV_INUSE为 0,认为前一个块是空闲的;
- 系统通过公式Target_Chunk = Chunk B – prev_size寻找前一个块。由于prev_size被我们篡改了,系统计算出来的Target_Chunk将直接跨过Chunk A,精准定位到攻击者指定的任意目标地址(如栈内存、全局变量区或堆上方的Fake Chunk);
- Glibc试图将这个Target_Chunk从原有的空闲链表中剥离出来(执行unlink操作),然后与Chunk B合并;
- 3. 绕过Unlink校验,实现大范围重叠
- 检验:在触发合并时,Glibc会对目标Target_Chunk执行双向链表完整性校验(unlink 检查);
- 破解:攻击者必须提前在目标地址处人工布置一个符合规范的Fake Chunk,使其fd和bk指针相互闭环(绕过较老版本的检查),或者在Tcache时代利用更宽松的单链表机制;
- 后果:校验通过后,Glibc会在内部合并出一个极其庞大的空闲堆块,其首地址就是你的目标地址,而末尾延伸到了Chunk B;
- 4. 再次分配,一击必杀
- 核心操作:再次调用malloc()函数申请内存;
- 后果:系统会直接把那个从目标地址开始的、被强行合并的超大堆块分配给用户。此时,你获得的指针直接指向了你的目标区域(如栈或敏感全局变量),对其写入即可彻底劫持程序控制流;
漏洞限制条件与利用前提:
- 原语能力:需要至少Off-by-One(单字节溢出) 的能力。因为只需要把相邻块size的最低位(PREV_INUSE标志位)从1盖成0,并且修改前面的prev_size即可;
- 地址泄露:由于需要通过prev_size = Chunk B – Target这一减法公式来计算精确的跨度,必须成功泄露堆基址(Heap Base)以及目标区域(如栈或Libc)的绝对地址;
- 内存可写:目标地址周围必须允许攻击者提前写入伪造的Chunk头部结构,以通过合并前的最后一关校验;
8.2 漏洞成因(源码级)
在Glibc源码中,后向合并的核心拦截点在于对低地址Target_Chunk的unlink 宏校验:
// Glibc经典Unlink双向链表校验
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");
if (__builtin_expect (P->fd->bk != P || P->bk->fd != P, 0))
malloc_printerr ("corrupted double-linked list");
绕过方式:
- Size 校验绕过:攻击者在目标地址伪造Fake Chunk时,其内部的size字段必须与你填入Chunk B的prev_size保持绝对一致;
- 指针校验绕过:在Glibc 2.26以前,需要确保Fake Chunk的fd和bk形成闭环;而在Glibc 2.26+引入tcache后,如果能够合理控制内存布局,使Fake Chunk处于不需要严格Unlink的状态,利用门槛会大幅降低;
8.3 漏洞现状
随着Glibc的演进,经典House of Einherjar技术在现代Linux系统上受到了全方位的围剿:
- Size vs Prev_Size强校验(Glibc 2.29+):官方加强了对相邻堆块尺寸的交叉检查(corrupted size vs. prev_size)。现在,不仅在unlink时检查,在很多常规合并路径上都会强制比对物理相邻块的尺寸是否吻合。通过一个Off-by-One漏洞在没有精密闭环的情况下修改prev_size会直接导致崩溃;
- 指针混淆(Pointer Mangling, Glibc 2.32+):单链表指针(tcache/fastbin)被XOR加密,使得伪造链表节点变得极其困难;
- 现代变种的生命力:虽然直接在栈上或Libc上进行跨空间后向合并在现代Glibc中极难实现,但是在堆区内部(Heap Arena)实施House of Einherjar的思路依然长盛不衰。现代安全研究员经常利用Off-by-One触发堆内部的后向合并,从而在堆上制造出一个包裹了其他存活堆块的“重叠大块(Chunk Overlap)”,接着结合House of Botcake或Largebin Attack,最终绕过现代防线;
9. House of Pig(Largebin与Tcache组合拳)
House of Pig是一种针对较新版本Glibc(主要用于Glibc 2.31至2.33之间)的高级复合型堆漏洞利用技术。
该技术最早起源于2021年CTF比赛中的“Peppa Pig”题目。它的最大亮点在于完美解决了一个极端限制:当目标程序只允许调用calloc()而完全没有malloc()时如何实现任意代码执行。
9.1 三大核心利用技术
House of Pig的利用流程环环相扣,它将三种不同的高级堆利用技术强行黏合在一起:
- 1. Largebin Attack(初次突破)
- 核心目的:往目标地址写入一个可写的堆地址;
- 成因:后续的Tcache Stash Unlink攻击要求伪造的目标Fake Chunk的bk指针处必须是一个“可写的内存地址”。攻击者首先通过Largebin Attack,在__free_hook – 0x8的位置写入一个堆地址,以此绕过检查;
- 2. Tcache Stashing Unlink+ Attack(偷梁换柱)
- 核心目的:将包含__free_hook的伪造堆块硬塞进tcache链表的头部;
- 核心原理:虽然calloc不会从tcache中取堆块,但是当调用calloc且触发smallbin分配时,Glibc的Stashing机制为了优化性能,会自动把smallbin链表中剩余的空闲块挪到(Stash)对应的 tcache 链表中;
- 后果:利用此机制,攻击者修改smallbin块的指针,在挪动过程中,成功让__free_hook – 0x10的地址变成tcache链表的头节点;
- 3. FSOP + _IO_str_overflow(终结比赛)
- 难点:此时__free_hook已经被挂在tcache头部了,但因为程序只有calloc,我们依然无法直接把它申请出来;
- 核心操作:通过另一次Largebin Attack修改全局变量_IO_list_all,将其指向一个攻击者在堆上伪造的_IO_FILE结构体(文件流伪造,即 FSOP);
- 触发:当程序退出(调用exit)或触发abort崩溃时,虚表(vtable)中的_IO_str_overflow函数会被调用。该函数内部极为特殊——它内部使用的是原生malloc而非calloc;
- 执行:_IO_str_overflow中的malloc被触发,直接从tcache头部精准取出了包含__free_hook的目标堆块。紧接着,函数内的memcpy会把攻击者提前布置好的数据(如system函数地址)写入 __free_hook。最后,该函数执行free()时,参数恰好是开头的”/bin/sh“字符串,从而完美触发system(“/bin/sh”) 获取Shell;
9.2 漏洞限制条件与利用前提
要想成功实施House of Pig技术,通常需要程序满足以下条件:
- 原语能力:程序存在UAF(Use After Free) 漏洞,或者能够进行稳定的堆溢出写,用于修改Largebin和Smallbin堆块的指针;
- 地址泄露:需要成功泄露Libc基址和堆基址(Heap Base);
- 结束流程:程序能够正常执行到exit、主函数返回(return),或者能被主动触发abort崩溃以激活FSOP链;
9.3 漏洞成因
在Glibc引入 tcache(线程缓存)机制后,大多数堆漏洞利用都极度依赖 tcache 的便捷性。
然而,calloc() 在分配内存时会故意跳过tcache链表,直接去较底层的 fastbin、smallbin 或 unsorted bin 中寻找空闲堆块。这就导致很多如Tcache Poisoning等直接改写 tcache 指针的经典技术在纯 calloc 环境下直接抓瞎。House of Pig正是为了打破这一僵局而诞生的复杂组合拳。
9.4 漏洞防御与现状
该技术之所以主要活跃于 Glibc 2.31 到 2.33 之间,主要是因为:
- Glibc 2.34 的终结:在 Glibc 2.34 版本中,官方彻底移除了
__free_hook和__malloc_hook等全局钩子函数`。这导致 House of Pig 赖以落脚的终点(覆盖 free_hook)不复存在; - 现代变种:虽然原版 House of Pig 终结了,但其“利用
calloc触发 Stashing 绕过限制,再利用 FSOP 的内部malloc劫持控制流”的绝妙思想,在面对2.34+版本的现代 CTF 挑战时,常被安全研究员结合_IO_cookie_jumps等新结构体演变成新的变种攻击;
10. House of Roman(局部写和爆破)
House of Roman 是一种专门针对 Glibc 内存分配器(通常在 Glibc 2.23 至 2.26 版本)的经典堆漏洞利用技术。
它的最大特点和核心思想是:“盲打”——在不需要泄露(Leak)任何内存地址(如Libc基址或堆基址)的情况下,纯粹利用局部写(Partial Write)和爆破(Brute Force),实现任意代码执行;
由于现代 Glibc 引入了指针混淆等保护,该技术在较高版本中已较难直接使用,但其“不依赖 Leak”的攻击思维在堆利用中极具代表性。
10.1 三大核心利用原理:如何做到不泄露地址
该技术巧妙利用了内存地址在低位字节的相对固定性和局部重叠性,其利用流程主要分为以下三个步骤:
- 1. 局部写入(Partial Write)
- 核心原理:由于操作系统存在 ASLR(地址空间配置随机化),Libc 的加载基地址每次都在变。但是,地址的低12位(即最后 3 个十六进制位)是绝对固定的(因为内存按页对齐);
- 核心操作:假设一个堆块已经在Unsorted Bin中,它的fd指针会天然指向Libc中的 main_arena 区域;
- 后果:攻击者利用堆溢出(Heap Overflow)或UAF漏洞,只修改该指针的最后2个字节(16位)。通过改写这16位,我们可以将其强行指向同样位于Libc中的
__malloc_hook附近;
- 2. 1/16 的概率爆破(Brute Force)
- 矛盾点:虽然最后3个十六进制位(12位)是固定的,但我们修改了 2 个字节(4个十六进制位,共 16位)。这意味着有 1 个十六进制位(4位)是受到 ASLR 随机化影响的;
- 核心操作:2⁴ = 16。攻击者只需要在代码中硬编码一个猜测的值,然后不断重新运行程序进行爆破。平均只需要尝试 16 次,就能成功命中正确的地址,让指针完美指向
__malloc_hook - 0x23(利用 Fastbin 的 Fake Chunk 错位校验);
- 3. 同样的原理劫持控制流(结合 Fastbin 与 Unsorted Bin)
- 第一步:利用上述的 1/16 爆破,成功将一个 Fastbin 堆块s的 fd 指针修改并指向了
__malloc_hook附近; - 第二步:过正常的
malloc()申请,把包含__malloc_hook的这个 Fake Chunk 申请出来; - 第三步:此时我们需要往
__malloc_hook写入一键夺权的 one_gadget 地址。one_gadget 同样属于 Libc,其低位与原先堆上残存的 Libc 地址高度相似; - 第四步:再次使用局部写入,只修改
__malloc_hook处的低几位字节,将其覆盖为 one_gadget 的地址。由于 one_gadget 的低 12 位固定,这里根据具体偏移,可能需要再次进行一次 1/16 的爆破; - 成功率:1/16(第一次指向 malloc_hook)× 1/16(第二次写入 one_gadget) = 1/256。在 CTF 或实际自动化攻击中,运行 256 次程序即可拿到 Shell;
- 第一步:利用上述的 1/16 爆破,成功将一个 Fastbin 堆块s的 fd 指针修改并指向了
10.2 漏洞限制条件和利用前提
要想成功实施 House of Roman 技术,通常需要程序满足以下条件:
- 原语能力:程序必须有堆溢出(Heap Overflow)或UAF(Use After Free) 漏洞,且允许对已释放的堆块进行写操作;
- 特定的 Glibc 版本:主要针对 Glibc 2.23 和 2.24。此时 fastbin 和 tcache(2.26 引入)还没有过于严苛的单向链表校验;
- 环境支持:程序在运行崩溃后会自动重启(或者攻击者可以反复发起请求,如 Fork 型的网络服务),以支持 256 次的暴力破解;
10.3 漏洞成因
在标准的堆漏洞利用(如 Fastbin Attack)中,攻击者通常需要分为两步:
- 第一步(Leak):利用漏洞打印出某个空闲堆块的指针,计算出 Libc 的基地址;
- 第二步(Attack):根据算好的基地址,修改堆块指针指向
__malloc_hook或__free_hook,从而写入system地址;
如果程序非常严密,完全没有提供任何可以打印/读取堆内存的函数(没有 view 或 show 功能),那么House of Roman 正是为了解决这种“零信息泄露”的极端困境而诞生的。
10.4 漏洞防御与现状
House of Roman 现今已很难在现代 Linux 系统中实施,主要是因为 Glibc 引入了以下硬核防御:
- 1. 引入了Tcache 机制(Glibc 2.26+):引入 tcache 后,优先从 tcache 分配。而 tcache 在释放和分配时,会对堆块的计数和结构有更严格的追踪,打乱了原有的 Fastbin 布局;
- 2. 指针混淆 / 安全单链表(Safe Linking, Glibc 2.32+):这是对 House of Roman 的毁灭性打击。现代 Glibc 会将 fastbin 和 tcache 中的 fd 指针与一个随机的堆基址进行 XOR(异或)加密;
- 既然指针是被加密过的,攻击者通过堆溢出修改低 2 字节时,在解密后会变成一个完全随机的荒谬地址,局部写入彻底失效,爆破概率从 1/256 变成了几近于零;
11. House of Kiwi(现代劫持控制流)
House of Kiwi 是一种针对较新版本 Glibc(如 Glibc 2.32 至 2.34+ 场景)的高级堆漏洞利用与控制流劫持技术。
它的核心目的是解决常规IO劫持方案的死穴:当目标程序不仅彻底封杀了 __free_hook 和 __malloc_hook,还故意使用 _exit() 系统调用直接退出程序,导致依赖 exit() 刷新 _IO_list_all 缓冲区的传统 FSOP技术完全无法被触发时,攻击者该如何夺取执行权;
House of Kiwi 巧妙地开辟了一条通过主动触发内存断言错误(Assert)来强行开启 IO 链并劫持控制流的新路径。
11.1 三大核心利用原理:Kiwi 的绝妙调用链
在纯堆溢出/任意写的环境下,即使不通过正常的程序退出,也可以通过触碰 Glibc 的底层红线引发崩溃。House of Kiwi 正是锁定了 Glibc 在发生断言失败时的内部处理逻辑:
- 1. 触发__malloc_assert
- 核心操作:最常见的方法是通过堆溢出修改 Top Chunk 的 Size 字段,使其不满足“页对齐”要求(例如将其改成一个奇数或未对齐的值),然后调用
malloc()申请一个大内存; - 结果:Glibc 底层的
sysmalloc校验失败,触发断言函数__malloc_assert;
- 核心操作:最常见的方法是通过堆溢出修改 Top Chunk 的 Size 字段,使其不满足“页对齐”要求(例如将其改成一个奇数或未对齐的值),然后调用
- 2. 断言内部的特殊 IO 调用
- 在 Glibc 源码中,
__malloc_assert的执行逻辑如下:
- 在 Glibc 源码中,
static void __malloc_assert (const char *assertion, const char *file, unsigned int line, const char *function) {
// 1. 打印错误信息
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n", ...);
// 2. 刷新标准错误流(核心爆发点!)
fflush (stderr);
// 3. 结束程序
abort ();
}
秘密就在 fflush(stderr) 中:调用 fflush 时,系统底层会寻着虚表指针去调用 _IO_helper_jumps 结构体中的 .sync 函数指针;
- 3. 借助 RDX 寄存器完成 SROP 栈迁移
- 构成条件:攻击者利用任意地址写原语,提前修改 Libc 中的
_IO_helper_jumps结构体,将其中的.sync指针覆盖为控制流劫持的目标(例如常见的setcontext + 61或是特定劫持小工具); - 致命细节(RDX 控制):在 Glibc 2.29 之后,经典的
setcontext赖以改变寄存器的控制权从RDI移交给了RDX。安全研究员调试发现,当系统执行到_IO_helper_jumps->sync这一步时,RDX寄存器恰好指向_IO_helper_jumps结构体本身; - 一击必杀:攻击者只要将
_IO_helper_jumps + 0xA0的位置部署为 ROP 链的起始栈地址(RSP),+ 0xA8的位置部署为ret指令(RCX),一经触发即可完美完成栈迁移,直接执行由open / read / write组成的 ORW 链读取 Flag;
- 构成条件:攻击者利用任意地址写原语,提前修改 Libc 中的
11.2 漏洞限制条件与利用前提
要成功实施 House of Kiwi,环境通常需要具备以下条件:
- 原语能力:必须拥有能修改 Libc 数据段的可控任意写能力,因为你需要精准修改
_IO_helper_jumps结构体的内容; - 地址泄露:需要获取 Libc 的基地址以精确定位
_IO_helper_jumps; - 大小可控:能够通过溢出等手段破坏 Top Chunk 结构,并有能力触发
malloc()促使断言生成;
11.3 漏洞现状
House of Kiwi 的生命周期在更高版本的 Glibc 演进中受到了限制:
- Glibc 2.34+ 补丁的打击:在 Glibc 2.34 之后的某些修订版(如
glibc-2.34-0ubuntu3.2及以上)中,官方加强了对 vtable(虚表)的全局校验(IO_validate_vtable);由于_IO_helper_jumps存放在不可写入的只读段,或者对该段的非法篡改会在运行时直接触发 vtable check 异常,导致直接修改其内部虚表指针的经典路径在最新高版本中失效; - 后继者的进化:为了解决 Kiwi 在 2.34+ 虚表校验后的无力,后续的安全研究中诞生了 House of Emma 等更高级的技术。Emma 继承了 Kiwi “依靠
__malloc_assert -> fflush触发 IO”的宏观思路,但将攻击落脚点转移到了绕过 vtable 校验的结构(如劫持_IO_cookie_jumps的指针间接解密调用),实现了新防线下的成功突破;
12. House of Lys(现代任意地址分配)
House of Lys(常被称为“Lys 府邸”)是一种针对较新版本 Glibc(Glibc 2.23 至今皆适用,通常在 2.34+ 移除 Hook 的高版本中作为主流)的高级 IO_FILE 漏洞利用技术。
其核心思想是:劫持或伪造 _IO_FILE 结构体,将其虚表(vtable)指针指向 _IO_obstack_jumps。通过精心布置 obstack 内部的结构体指针,利用其特殊的内存分配函数间接调用,最终实现控制流劫持(RCE)。
由于它不需要修改任何废弃的全局 Hook 钩子(如 __free_hook),是在高版本 Linux 环境下绕过虚表边界校验(IO_validate_vtable)的经典方案。
12.1 三大核心利用原理:Lys 的调用链条
obstack(Object Stack)是 GNU 扩展的一种内存分配机制。当我们将一个 _IO_FILE 结构体的虚表强行替换为 _IO_obstack_jumps 时,如果程序对该流触发了相关的 IO 操作(如调用 _IO_overflow 或 _IO_xsputn),底层的执行路径会发生奇妙的跑偏:
- 1. 触发特殊虚表函数
- 核心操作:通过堆溢出(Heap Overflow)或任意写,修改目标
_IO_FILE结构体:- 将
vtable覆盖为_IO_obstack_jumps的绝对地址; - 将结构体内部特定的缓冲区指针(如
_IO_write_ptr、_IO_write_base等)进行微调,以在调用时强行触发_IO_overflow;
- 将
- 核心操作:通过堆溢出(Heap Overflow)或任意写,修改目标
- 2. 指针劫持到 obstack 内部
- 在
_IO_obstack_jumps中,_IO_obstack_overflow函数内部有如下的执行逻辑:
- 在
// Glibc 内部处理 obstack overflow 的简化逻辑
struct obstack *ob = ...; // 这里的 ob 指针是通过 _IO_FILE 结构体中的某些字段(如 _obstack)计算出来的
...
// 当 obstack 空间不够需要扩容时,它会调用用户自定义的分配函数!
struct _obstack_chunk *new_chunk = (ob->chunkfun) (ob, size);
秘密就在 ob->chunkfun 中:obstack 允许开发者自定义在分配内存时使用的函数。在正常的 _IO_FILE 里,这个位置原本是一块混乱的缓冲区数据,但由于我们劫持了结构体,我们可以将 ob->chunkfun 的位置精准篡改写为控制流劫持的目标(如 system 函数或 one_gadget);
- 3. 参数控制与一击必杀
- 参数设置:调用
(ob->chunkfun) (ob, size)时,第一个参数RDI天然就是ob结构体指针本身; - 终结攻击:攻击者只需要将 ob 结构体的开头前几个字节填入
"/bin/sh",当ob->chunkfun被当做system执行时,底层实际执行的指令就会变成system("/bin/sh"),从而绕过一切限制,成功获取 Shell;
- 参数设置:调用
12.2 漏洞限制条件与利用前提
要成功实施 House of Lys,环境通常需要具备以下条件:
- 原语能力:需要拥有篡改
_IO_FILE结构体(如stdout、stderr或某个文件指针)的能力。这通常通过堆溢出或 Largebin Attack 来改写全局变量实现; - 地址泄露:由于需要将虚表精准指向 Libc 中的
_IO_obstack_jumps,并且需要精确布置结构体内的多处绝对地址,必须成功泄露 Libc 基址; - 触发机制:程序随后必须有能够触发该流 IO 操作的代码路径(例如调用了
puts、printf,或者程序退出、崩溃时刷新全局文件流);
12.3 漏洞成因
随着 Glibc 的演进,拉起了两道防御线:
- 删除 Hook:Glibc 2.34 删除了
__free_hook和__malloc_hook,堆漏洞失去了最直接的落脚点; - vtable 强校验:Glibc 引入了虚表合法性检查。如果攻击者直接把
_IO_FILE的虚表指针改成堆地址,程序在校验时会直接抛出非法虚表错误并卡死;
House of Lys 的操作:_IO_obstack_jumps 是 Glibc 自带的、合法的、位于只读数据段(.rodata)中的标准虚表。既然它是官方自带的,修改之后就能完美绕过 IO_validate_vtable 校验。攻击者不需要伪造虚表本身,只需要伪造虚表函数要操作的数据结构(即 obstack 相关的函数指针)。
12.4 漏洞防御与现状
House of Lys 是一种极其顽强的利用技术。由于它利用的是只读段中完全合法的 _IO_obstack_jumps 虚表,且利用的是 Glibc 官方 obstack 机制中本身就支持自定义 chunkfun 函数指针的宏观特性,官方很难在不重构整个 obstack 库的前提下对其发布简单的校验补丁。因此该链条从 Glibc 2.23 一直延续到现在的最新版本皆有效。
在现代 CTF 和高版本 Pwn 挑战中,House of Lys(obstack 链)与 House of Apple(通过 _IO_wfile_jumps 和 _codecvt 触发 DL_CALL_FCT 宏)并列为高版本 Linux 劫持 _IO_FILE 的两大王牌手法。Lys的优势在于它的结构体依赖变量相对较少,参数控制(RDI 直接传 shell 字符串)非常直接。
13. House Of系列防范与修复建议
在现代堆安全对抗中,纯粹靠堆溢出直接覆盖函数指针的情况越来越少。“House of” 系列教我们如何通过溢出或利用其他漏洞破坏堆块元数据(元数据损坏),让 Glibc 堆管理器在执行自我管理(如分配、释放、合并、报错)时触发漏洞。防范与修复建议:
- 1. 严格边界检查:使用
snprintf、strncpy等安全函数替代strcpy、gets,从根本上防止堆溢出; - 2. 启用现代保护机制:
- ASLR:地址空间配置随机化,增加泄露堆基址和 Libc 基址的难度;
- NX/DEP:数据执行保护,防止直接在堆栈上执行 Shellcode;
- 3. 升级 Glibc 版本:新版本的 Glibc 引入了 Tcache 安全检查、Top Chunk 校验、指针混淆 (Pointer Mangling) 等机制,能够直接阻断大部分 House of 技术的实施;
1. 一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。