VTable Hijacking
Inline Hook 确实简单粗暴:找到地址,改写前 5 个字节为 jmp,完事。
但在现代反作弊(BE, EAC)越来越严苛的环境下,无脑改写 .text(代码段)无异于在安检门前大喊自己带了违禁品。内存完整性校验(Memory Integrity Check)分分钟教你做人。
这时候,我们需要一种更优雅、更底层的手段——VTable Hijacking(虚函数表劫持)。它不改一行机器码,只在堆内存里做数据指针的偷天换日。
理论篇:什么是 VTable Hijacking?
要理解虚表劫持,必须先彻底搞懂 C++ 编译器是如何实现多态的。
虚函数表(VTable)的本质
当一个 C++ 类中存在 virtual 关键字修饰的函数时,编译器为了在运行时决定到底该调用哪个具体实现(父类还是子类的),会在这个类的内存布局的最开头,强行塞入一个隐藏的指针(vptr)。
这个 vptr 指向哪里?指向一个属于该类的函数指针数组,这个数组就是大名鼎鼎的 VTable(虚函数表)。
假设有一个简单的类:
1 | class CBuilding { |
在 64 位游戏的内存里,一个 CBuilding 实例的样子是这样的:
[0x00 - 0x07]:vptr(指向.rdata段里的虚表)[0x08 - 0x09]:durability(耐久度)
当我们调用 pBuilding->ApplyDamage(50); 时,CPU 底层执行的汇编其实是:
1 | mov rax, [rcx] ; rcx 是 this 指针,拿到 vptr (虚表地址) |
劫持的艺术:偷天换日
Inline Hook 是去 ApplyDamage 的代码实体里改指令。
而 VTable Hijacking 的思路是:我不动原函数,我给你换一本“花名册”。
- 游戏原版的 VTable 存在
.rdata(只读数据段),直接改会触发保护或崩溃。 - 我们在堆内存(Heap)里,自己
malloc或new一个同样大小的指针数组。 - 把原版 VTable 里的所有函数指针,原封不动地
memcpy拷贝到我们的新数组里。 - 【核心】:把新数组里
ApplyDamage对应的那个位置,替换成我们自己写的外挂函数地址。 - 最后,把内存里那个建筑对象的
vptr,强行指向我们的新数组。
就这么简单。业务代码依然执行 call qword ptr [rax+10h],但此时 rax 已经是我们的假表了,程序神不知鬼不觉地飞进了我们的外挂逻辑里。完全规避了 .text 段的扫描。
实战篇
纸上谈兵没用,直接上强度。前两天在拆 San14 的内存时,通过底层的流序列化函数(Serialization Archive),我们精准还原了地图建筑/陷阱对象(CBuilding)的底层结构。
第一步:情报收集与结构体重建
通过 IDA 静态分析和 CE 动态比对,确定 CBuilding 大小为 32 字节(0x20),核心偏移如下:
1 |
|
第二步:定位目标虚函数 (VTable Index)
我们要劫持扣除耐久的函数。如何在几百个虚函数里找到它?
- 打开 Cheat Engine,找到你自己建的一个投石台,锁定它的耐久度地址(比如
0x1418C9014,注意这正好是对象首地址+0x14)。 - 右键该地址 -> **找出是什么改写了这个地址 (Find out what writes to this address)**。
- 在游戏里让敌人打一下这个投石台。
- CE 会捕获到一条类似
sub eax, edx然后mov [rcx+14h], ax的指令。 - 顺着 CE 给出的堆栈往上一层找,看是谁
call了这个逻辑。你会看到极其经典的虚表调用特征:call qword ptr [rax+28h]
0x28 除以 8(64位指针大小)等于 5。
破案了!扣除耐久的函数,在 CBuilding 虚表里的 Index 是 5。 函数原型推测为 void ApplyDamage(CBuilding* this, int damage)。
第三步:C++ 劫持代码实现
我们在自己的 DLL 里写下这套 VTable Hijacking 的标准模板:
1 |
|
第四步:注入与批量应用
上述代码只修改了 pTargetBuilding 这一个建筑实例的虚表指针。
在实际写代码时,我们通常会挂一个底层的遍历 Hook(比如拦截引擎生成对象的工厂函数 CreateBuilding),在每个属于玩家的建筑被 new 出来的时候,顺手调用一下 SetupBuildingGodMode(pNewBuilding)。
只要对象的 vptr 被换成了我们的指针,从今往后,无论是 AI 投石、火攻还是战法,只要试图削减它的耐久,CPU 都会乖乖跳进我们的 DetourApplyDamage 里,把伤害化为乌有。
结语
VTable Hijacking 是每一个 C++ 逆向工程师必修的内功。它利用了 C++ 面向对象底层的实现机制,实现了真正意义上的“逻辑层劫持”。
当然,这种技术也不是完美无缺的。如果引擎在核心循环里对对象的 vptr 做了硬编码校验(检测 vptr 是否落在 .rdata 段内),这种方法就会暴露。但在大多数单机游戏和中低强度的网游中,这一手“偷梁换柱”,足以让你所向披靡。