标签:#GameSecurity #ReverseEngineering #Unity #Il2Cpp #Assembly #IDA
🧱 前言:Mono vs Il2Cpp
- Mono: C# -> IL (中间语言)。DLL 包含元数据,极易反编译。
- Il2Cpp: C# -> IL -> C++ -> Native Code (机器码)。DLL 只有汇编,元数据(类名、方法名)被剥离并打包到了
global-metadata.dat中。
逆向难度对比 (Mermaid):
🛠️ 一、 准备工作:提取核心文件
你需要从游戏安装目录中找到两个文件:
- GameAssembly.dll(或
libil2cpp.so): 存放逻辑代码。 - global-metadata.dat: 存放字符串、类定义、方法签名。通常在
.../Il2Cpp_Data/Metadata/目录下。
工具准备:
- Il2CppDumper: 开源神器,用于提取地址映射。
- DnSpy: 用于查看还原后的 C# 结构。
- IDA Pro(或 Ghidra): 用于分析和修改汇编指令。
🔓 二、 还原符号:Il2CppDumper 实战
运行Il2CppDumper.exe,依次选择GameAssembly.dll和global-metadata.dat。
程序运行完毕后,会生成一个输出文件夹,其中最核心的是:
- DummyDll/: 这是一个文件夹,里面包含了一堆 DLL。
- 注意:这些 DLL 只有“壳”(类和方法定义),没有“肉”(具体代码逻辑是空的)。
- script.json&il2cpp.h: 用于在 IDA 中导入符号。
1. 使用 DnSpy 搜索逻辑
打开 DnSpy,加载DummyDll文件夹下的Assembly-CSharp.dll。
虽然方法体是空的,但你可以看到完整的类结构!
搜索关键词:Health,Damage,PlayerController,BattleManager。
假设我们要实现“无敌”,我们找到了这样一个类:
// 在 DnSpy 中看到的伪代码publicclassPlayerHealth:MonoBehaviour{// 关键点!Dumper 告诉了我们这个方法在内存中的偏移地址 (Offset)[Address(RVA="0x180ABC10",Offset="0xABC10")]publicvoidTakeDamage(intdamage){}}信息获取:
我们拿到了核心情报:TakeDamage函数的地址是0x180ABC10。
💉 三、 深入汇编:IDA 修改逻辑
有了地址,我们就可以去 IDA 里操作手术了。
1. 加载并恢复符号
用 IDA 打开原始的GameAssembly.dll。等待分析完成。
File -> Script file -> 选择 Dumper 生成的ida_with_struct_py3.py,并加载script.json。
奇迹发生了:原本的sub_180ABC10自动变成了PlayerHealth$$TakeDamage!
2. 分析汇编逻辑
双击跳转到该函数。你可能会看到类似这样的 x64 汇编(示意):
PlayerHealth$$TakeDamage: sub rsp, 20h mov eax, [rcx+30h] ; 读取当前血量 (rcx是this指针) sub eax, edx ; 血量 - 伤害 (edx是参数damage) mov [rcx+30h], eax ; 写回血量 ; ... 判断是否死亡 ... add rsp, 20h retn3. 实施“无敌”修改 (Hex Patch)
要实现无敌,最简单的方法是:让扣血函数直接返回,什么都不做。
我们只需要修改函数开头指令,让它直接RET。
- x64 指令:
C3(ret) - ARM64 指令:
C0 03 5F D6(ret)
修改步骤:
- 在函数开头按
KeyPatch(如果没有插件就用 Hex View)。 - 将开头的指令修改为
ret(或者nop掉减法指令)。 - Edit -> Patch program -> Apply patches to input file。
修改后的逻辑:
PlayerHealth$$TakeDamage: retn ; 直接返回!血量不会减少 ; ... 下面的代码永远不会执行 ...🛡️ 四、 进阶:Hook 注入 (Frida/C++)
直接修改 DLL 文件(静态修改)容易被游戏完整性校验(CRC)发现导致封号。
更高阶的做法是Hook (动态修改)。
我们可以编写一个 DLL 注入到游戏进程,利用MinHook库,在内存中拦截0x180ABC10。
// C++ Hook 伪代码typedefvoid(*TakeDamage_t)(void*_this,intdamage);TakeDamage_t Original_TakeDamage=nullptr;voidHooked_TakeDamage(void*_this,intdamage){// 只有当受击者是主角时,才免疫伤害if(IsPlayer(_this)){return;// 啥都不做,无敌!}// 敌人还是正常扣血Original_TakeDamage(_this,damage);}voidInit(){uint64_tbase=(uint64_t)GetModuleHandle("GameAssembly.dll");// Hook Dumper 找到的偏移地址MH_CreateHook((void*)(base+0xABC10),&Hooked_TakeDamage,(void**)&Original_TakeDamage);MH_EnableHook(MH_ALL_HOOKS);}🔒 五、 防御:开发者如何应对?
作为开发者,看到自己的代码被扒得底裤都不剩,该怎么办?
- Metadata 加密:
Dumper 的工作原理是解析global-metadata.dat。修改 Unity 源码,对该文件进行 XOR 或 AES 加密,并在加载时动态解密,可以防住 90% 的脚本小子。 - 代码混淆 (Obfuscation):
使用 Obfuscator Pro 等插件,把TakeDamage重命名为a,把类名混淆。即使 Dumper 还原了符号,黑客看到的也是一堆乱码。 - 完整性校验:
在游戏启动时计算GameAssembly.dll的 Hash 值,如果发现被修改(Hex Patch),直接闪退。
🎯 总结
Il2CppDumper 是连接 “C# 逻辑” 和 “C++ 汇编” 的桥梁。
- DnSpy用来看懂结构。
- IDA用来修改指令。
- Frida/Hook用来实现复杂的运行时修改。
Next Step:
找一个练手用的单机 Unity 游戏(APK 或 PC版),尝试使用Frida脚本动态 Hook 一个get_Gold()函数,打印出当前的返回值,并尝试将其返回值修改为 999999。