-
-
[原创]神庙逃亡(Temple Run)IL2CPP 逆向实战:从 APK 到 Frida 实现角色无敌
-
发表于: 10小时前 56
-
Unity IL2CPP 逆向实战:从 APK 到 Frida 无敌 Hook 的完整流程
- Unity IL2CPP 游戏
- 无符号 / 无字符串 / 全是 sub_xxx 的 SO
- 目标:定位死亡逻辑 / 无敌 / 关键状态变量
本文以一个跑酷类神庙逃亡(Temple Run) Unity 游戏为例。
游戏截图

一、准备工具 & 我使用的设备 & 工具版本
- apktool
- Il2CppDumper
- IDA Pro(ARM64)
- Ubuntu / Linux 环境(本文以 Linux localhost 4.19.95-perf-g79255ac #1 SMP PREEMPT Thu Sep 2 03:29:24 CST 2021 aarch64 为例)
- Android 10 真机(arm64)
- Frida版本:17.5.2
- Frida Server版本:
frida-server-17.5.1-android-arm64 - Python版本:Python 3.12.3
二、解包 APK,提取关键文件
1 | apktool d game.apk -o game_apk |
可以用 adb 查看手机 CPU 架构:
1 | adb shell getprop ro.product.cpu.abi |
常见输出:
- arm64-v8a ✅(64 位 ARM)
- armeabi-v7a ❌(32 位)
- x86 / x86_64(模拟器或少数设备)
如果同时存在,可以使用上面的命令查看自己对应的库目录:
- arm64-v8a
- armeabi-v7a
优先选择 arm64-v8a
重点关注两个位置libil2cpp.so和global-metadata.dat这两个文件缺一不可。:
1️⃣ libil2cpp.so
1 | game_apk/lib/arm64-v8a/libil2cpp.so |
2️⃣ global-metadata.dat
1 | game_apk/assets/bin/Data/Managed/Metadata/global-metadata.dat |
三、使用 Il2CppDumper工具还原 IL2CPP 结构
1 | ./Il2CppDumper libil2cpp.so global-metadata.dat il2cpp_dump |
生成目录结构如下:
1 2 3 4 5 6 7 | il2cpp_dump/├── dump.cs├── script.json├── stringliteral.json├── DummyDll/│ ├── Assembly-CSharp.dll│ └── ... |
四、从 dump.cs 找“真正有用的类”
在 dump.cs 中搜索查看关键信息 游戏核心角色类,例如:
1 2 3 4 5 6 7 8 9 10 | public class CharacterPlayer : CommonPlayer{ public bool IsDead; // 0xD0 public float TimeSinceDeath; // 0xD4 public DeathTypes DeathType; // 0xD8 public void Kill(DeathTypes type); public void Update(); public void StartInvcibility(float duration);} |
这一步非常重要:
字段偏移 = 后续 Hook / 内存验证的锚点
五、利用 script.json 精准定位 SO 中的函数
在 script.json 中可以找到方法对应关系:
{
"Address": 17287964,
"Name": "CharacterPlayer$$Kill",
"Signature": "void CharacterPlayer__Kill (CharacterPlayer_o* __this, int32_t deathType, const MethodInfo* method);"
}
注意:
Address是 十进制- 表示 RVA(相对于 libil2cpp.so 基址)
十进制 → 十六进制
1 2 | 17287964 (decimal)= 0x107CB1C (hex) |
六、在 IDA 中精准跳转函数
1️⃣ 用 IDA 打开 libil2cpp.so
2️⃣ 按下键盘 G
3️⃣ 输入:
1 | 0x107CB1C |
4️⃣ 回车
IDA 会直接跳转到 CharacterPlayer$$Kill 的真实函数实现
七、验证逻辑是否匹配 dump.cs
IDA 反编译中可以看到:
1 2 | *(_BYTE *)(a1 + 208) = 1; // IsDead = true*(_DWORD *)(a1 + 216) = a2;// DeathType |
而 dump.cs 中:
1 2 | public bool IsDead; // 0xD0 (208)public DeathTypes DeathType; // 0xD8 (216) |
✅ 字段偏移完全一致
说明:
- metadata
- dump.cs
- IDA 反编译
三者已经完全对齐。
八、为什么“字符串搜索 / 函数名搜索”基本没用?
在 IL2CPP 游戏中通常存在:
- SO 全 strip
- 函数名被抽掉
- 字符串清空
- Update / LateUpdate 刷屏
靠猜函数 ≈ 大海捞针
正确思路是:
metadata 给你“地图”,SO 只是实现
九、用 Frida Hook 而不是硬改 SO
目标:让角色始终处于无敌状态。
在script.json中发现:
{
"Address": 17291184,
"Name": "CharacterPlayer$$StartInvcibility",
"Signature": "void CharacterPlayer__StartInvcibility (CharacterPlayer_o* __this, float duration, const MethodInfo* method);",
"TypeSignature": "vifi"
}
或 dump.cs中发现:
1 2 | // RVA: 0x107D7B0 Offset: 0x107C7B0 VA: 0x107D7B0public void StartInvcibility(float duration); |
在 script.json 中查到的"Address": 17291184和dump.cs中查到的RVA: 0x107D7B0,两种都可以:
"Address": 0x107D7B0
十、最终 Frida Hook 实现(实战)
在script.json中发现:
{
"Address": 17285176,
"Name": "CharacterPlayer$$Update",
"Signature": "void CharacterPlayer__Update (CharacterPlayer_o* __this, const MethodInfo* method);",
"TypeSignature": "vii"
}
或 dump.cs中发现:
1 2 | // RVA: 0x107C038 Offset: 0x107B038 VA: 0x107C038 Slot: 4public override void Update(); |
在 script.json 中查到的"Address": 17285176和dump.cs中查到的RVA: 0x107C038,两种都可以:
函数Update游戏开始后会一直调用,每次调用就调用函数StartInvcibility(player, 9999.0, ptr(0));设置无敌
神庙逃亡(Temple Run)的进程Frida Attach
1 | frida -U -p 19480 -l hook.js |
hook.js 脚本(核心代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | const base = Process.findModuleByName("libil2cpp.so").base;// CharacterPlayer::StartInvcibilityconst StartInvAddr = base.add(0x107D7B0);// CharacterPlayer::Updateconst UpdateAddr = base.add(0x107C038);const StartInvcibility = new NativeFunction( StartInvAddr, 'void', ['pointer', 'float', 'pointer']);Interceptor.attach(UpdateAddr, { onEnter(args) { const player = args[0]; // 每一帧刷新无敌时间 StartInvcibility(player, 9999.0, ptr(0)); }});console.log("[+] Invincibility hook installed"); |
效果说明:
- 普通碰撞、陷阱:不会死亡
- 游戏本身设计的“强制死亡”(掉深渊、被追上):仍然会死
说明这是调用游戏原生无敌逻辑,不是暴力 patch
十一、核心思维总结(非常重要)
IL2CPP 逆向不是“找函数”,而是“算地址”。