你可以把内存想象成一片巨大的未开发的土地而House Of Force就是一种通过“篡改土地面积”让你能够瞬间把房子盖到操作系统最核心区域的黑客魔法。第一步认识“Top Chunk”荒野在 C 语言的malloc机制中当你向系统申请内存时系统会先给你划出一大块连续的内存区域这就是堆 Heap。每次你调用malloc系统就像切蛋糕一样从这块区域里切下一小块给你。切剩下来的、最后面那块最大最完整的内存被称为Top Chunk也叫 Wilderness荒野。Top Chunk 有一个非常重要的属性它记录了自己还剩多大Size。当你要申请的内存大于 Top Chunk 的剩余大小时系统就会发现“哎呀内存不够了”然后去向操作系统申请扩容。第二步核心漏洞——“指鹿为马”House Of Force 的起点是你要有一个漏洞比如堆溢出能够让你修改 Top Chunk 的 Size。试想一下如果 Top Chunk 原本只剩0x1000大小但你通过溢出漏洞把它的 Size 强行改成了-1。在计算机的无符号整数unsigned世界里-1就是最大的数字在 64 位系统下是0xFFFFFFFFFFFFFFFF。这时候系统就被你骗了系统一看 Top Chunk 的 Size“哇原来我还有无限大的内存空间”从此以后无论你向系统malloc申请多大的内存它都不会去向操作系统要新的空间而是直接从现在的 Top Chunk 往下切给你。第三步空间跳跃魔法既然系统认为 Top Chunk 无限大我们就可以利用它进行“空间跳跃”。假设当前的 Top Chunk 在地址0x10000而你想去改写系统的核心函数指针假设在目标地址0x80000。你要怎么过去呢系统切分配内存的逻辑非常简单粗暴新的 Top Chunk 地址 现在的 Top Chunk 地址 你申请的内存大小所以你只需要做一道简单的减法题偏移量 目标地址 - 当前 Top Chunk 地址 - 头部大小0x10你直接调用malloc(偏移量)。系统一看你要这么大的内存没问题我的 Top Chunk 是“无限大”的于是它大笔一挥把 Top Chunk 的指针直接向前推移了这么多距离。此时Top Chunk 的位置已经被你精准地移动到了目标地址0x80000的前面(注如果目标地址在 Top Chunk 的“上面”即低地址区这个减法算出来会是一个负数。但没关系计算机里的负数相加会产生“整数溢出”照样能精准绕回到低地址)第四步完成绝杀现在Top Chunk 就在你梦寐以求的目标地址门口了。你只需要再执行一次普通的malloc比如malloc(0x20)。系统就会把目标地址比如0x80000分配给你。此时这个目标地址可能存放着非常重要的东西比如__malloc_hook或者 GOT 表中的puts函数指针。你往这个新申请的内存里写入你的恶意代码地址比如system(/bin/sh)。下次程序再调用相应的函数时就会直接执行你的恶意代码拿下服务器的控制权。总结House Of Force 的攻击三部曲破门 (Overwrite Top Chunk Size):利用溢出漏洞把 Top Chunk 的 size 改成-1最大值。铺路 (Bridge the Gap):计算目标地址和 Top Chunk 的距离申请一个巨大或极小的内存块把 Top Chunk 的指针推到目标地址附近。夺权 (Arbitrary Write):再次申请内存拿到目标地址的控制权写入恶意数据。发动这个魔法必须具备的三个条件能够覆盖 Top Chunk 的 size通常需要一个 Heap Overflow 漏洞。能够自由控制malloc申请的尺寸需要传入我们计算出的偏移量。知道地址需要有信息泄露漏洞知道当前 Top Chunk 在哪以及你想打的目标地址在哪。目前的现状这个方法非常经典且暴力但在较新的 glibc 版本2.29 及以后中加入了对 Top Chunk size 的合法性检查直接把 size 改成-1会导致程序崩溃。但这依然是理解堆利用Heap Exploitation和内存布局最棒的入门级手法。有没有哪个步骤的逻辑你觉得跳跃了我们可以针对那一部分再细聊。例子我们用一个具体的数字推演结合几行简单的代码来看看这场“空间跳跃”是如何在内存中真实发生的。假设我们有一个存在漏洞的 C 语言程序并且我们已经通过某种方式信息泄露漏洞知道了内存中的两个关键位置当前 Top Chunk 的地址0x602020我们的攻击目标地址0x804010比如这里存放着一个只有管理员才能调用的函数指针我们想把它改成我们的恶意代码地址第一步破门篡改 Top Chunk Size假设程序允许我们申请一段内存并且存在越界写入Heap Overflow的漏洞。// 1. 申请一段 0x10 大小的内存 char *p malloc(0x10); // 此时 p 的地址是 0x602000 // 因为这块内存本身需要 0x10 的头部存放元数据加上你申请的 0x10总共占了 0x20。 // 所以剩下的 Top Chunk 就在它紧挨着的地方0x602020。 // 2. 利用溢出漏洞把 Top Chunk 的 Size 强行覆盖为 -1 (在 64 位下是 0xFFFFFFFFFFFFFFFF) // 正常情况下你只能写 p[0] 到 p[15]但漏洞让你能写得更远 write_overflow(p, data_with_0xFFFFFFFFFFFFFFFF);经过这一步系统再去检查地址0x602020Top Chunk时会认为“我这里还有无限大的内存随便你要多少”第二步铺路计算偏移并跨越空间现在我们要算一笔账从现在的 Top Chunk (0x602020)跳到目标地址 (0x804010)中间差了多远因为我们下一次malloc申请拿到的内存其实是从目标地址往后算的。为了让我们最终能够往0x804010里面写东西我们要让新的 Top Chunk刚好落在目标地址的“头顶上”也就是目标地址减去 0x10 的块头部大小。核心计算公式跳跃距离 目标地址 - 0x10 (预留头部) - 当前 Top Chunk 地址代入我们的数字跳跃距离 0x804010 - 0x10 - 0x602020 0x201FE0接下来我们在攻击脚本比如 Python里向程序发送请求要求申请这么大的内存# 3. 申请巨大内存完成跳跃 malloc(0x201FE0)系统一看你要0x201FE0这么大没问题Top Chunk 的 Size 是无限大够切于是系统把 Top Chunk 的指针加上了0x201FE0 0x10。此时Top Chunk 的新地址已经变成了0x804000。第三步夺权拿到目标并写入这就到了激动人心的最后一步。Top Chunk 已经在家门口了。# 4. 再次申请一个小块内存 malloc(0x10)系统会从当前的 Top Chunk0x804000切下一块。系统会保留 0x10 作为块头部然后把供你使用的用户区域指针返回给你。0x804000 0x10 0x804010你拿到的返回指针精准地落在了0x804010也就是我们最初的目标地址。# 5. 向刚刚拿到的内存里写入恶意数据的地址比如 system(/bin/sh) write_to_new_malloc( 恶意代码地址 )就这样目标地址原本合法的函数指针被你替换成了打开服务器 shell 的钥匙。下次程序尝试调用那个合法函数时就会直接执行你的命令。如果目标地址比 Top Chunk 还低怎么办在真实的漏洞挖掘中目标地址比如 glibc 的__malloc_hook往往比堆的地址要低。这时候目标地址 - 当前 Top Chunk算出来会是一个负数。但由于底层是无符号运算这个负数会变成一个极其巨大的正数。当你malloc这个巨大正数时内存地址会一直向上加加到突破内存上限Integer Overflow最终“绕了一圈”回到底部精准落在低地址的目标上。逻辑和上面的加法完全一致
