神庙逃亡(Temple Run)IL2CPP 逆向实战:从 APK 到 Frida 实现角色无敌

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,提取关键文件

bash 复制代码
apktool d game.apk -o game_apk

可以用 adb 查看手机 CPU 架构:

bash 复制代码
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

text 复制代码
game_apk/lib/arm64-v8a/libil2cpp.so

2️⃣ global-metadata.dat

text 复制代码
game_apk/assets/bin/Data/Managed/Metadata/global-metadata.dat

三、使用 Il2CppDumper工具还原 IL2CPP 结构

bash 复制代码
./Il2CppDumper libil2cpp.so global-metadata.dat il2cpp_dump

生成目录结构如下:

text 复制代码
il2cpp_dump/
├── dump.cs
├── script.json
├── stringliteral.json
├── DummyDll/
│   ├── Assembly-CSharp.dll
│   └── ...

四、从 dump.cs 找"真正有用的类"

dump.cs 中搜索查看关键信息 游戏核心角色类,例如:

csharp 复制代码
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 中可以找到方法对应关系:

json 复制代码
{
  "Address": 17287964,
  "Name": "CharacterPlayer$$Kill",
  "Signature": "void CharacterPlayer__Kill (CharacterPlayer_o* __this, int32_t deathType, const MethodInfo* method);"
}

注意:

  • Address十进制
  • 表示 RVA(相对于 libil2cpp.so 基址)

十进制 → 十六进制

text 复制代码
17287964 (decimal)
= 0x107CB1C (hex)

六、在 IDA 中精准跳转函数

1️⃣ 用 IDA 打开 libil2cpp.so

2️⃣ 按下键盘 G

3️⃣ 输入:

text 复制代码
0x107CB1C

4️⃣ 回车

👉 IDA 会直接跳转到 CharacterPlayer$$Kill 的真实函数实现


七、验证逻辑是否匹配 dump.cs

IDA 反编译中可以看到:

c 复制代码
*(_BYTE *)(a1 + 208) = 1;   // IsDead = true
*(_DWORD *)(a1 + 216) = a2;// DeathType

而 dump.cs 中:

csharp 复制代码
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中发现:

json 复制代码
{
  "Address": 17291184,
  "Name": "CharacterPlayer$$StartInvcibility",
  "Signature": "void CharacterPlayer__StartInvcibility (CharacterPlayer_o* __this, float duration, const MethodInfo* method);",
  "TypeSignature": "vifi"
}

或 dump.cs中发现:

csharp 复制代码
// RVA: 0x107D7B0 Offset: 0x107C7B0 VA: 0x107D7B0
public void StartInvcibility(float duration);

在 script.json 中查到的"Address": 17291184和dump.cs中查到的RVA: 0x107D7B0,两种都可以:

json 复制代码
"Address": 0x107D7B0

十、最终 Frida Hook 实现(实战)

在script.json中发现:

json 复制代码
{
  "Address": 17285176,
  "Name": "CharacterPlayer$$Update",
  "Signature": "void CharacterPlayer__Update (CharacterPlayer_o* __this, const MethodInfo* method);",
  "TypeSignature": "vii"
}

或 dump.cs中发现:

csharp 复制代码
// RVA: 0x107C038 Offset: 0x107B038 VA: 0x107C038 Slot: 4
public override void Update();

在 script.json 中查到的"Address": 17285176和dump.cs中查到的RVA: 0x107C038,两种都可以:

函数Update游戏开始后会一直调用,每次调用就调用函数StartInvcibility(player, 9999.0, ptr(0));设置无敌

神庙逃亡(Temple Run)的进程Frida Attach

bash 复制代码
frida -U -p 19480 -l hook.js

hook.js 脚本(核心代码)

js 复制代码
const base = Process.findModuleByName("libil2cpp.so").base;

// CharacterPlayer::StartInvcibility
const StartInvAddr = base.add(0x107D7B0);

// CharacterPlayer::Update
const 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 逆向不是"找函数",而是"算地址"。

相关推荐
努力长头发的程序猿30 分钟前
Unity使用ScriptableObject序列化资源
unity·游戏引擎
mxwin1 小时前
Unity Shader 手写基于 PBR 的 URP Lit Shader 核心光照计算
unity·游戏引擎·shader
小贺儿开发1 小时前
Unity3D 智能云端数字标牌系统
unity·阿里云·人机交互·视频·oss·广告·互动
魔士于安1 小时前
Unity windows 同步 异步 打开文件文件夹工具
游戏·unity·游戏引擎·贴图·模型
魔士于安2 小时前
unity lowpoly 风格 城市 建筑 道路 交通标志
游戏·unity·游戏引擎·贴图·模型
mxwin2 小时前
Unity GPU Shader 性能优化指南
unity·游戏引擎·shader
董董女友14 小时前
unity mcp 配置指南
unity·游戏引擎
垂葛酒肝汤19 小时前
Unity的可视化网格和文字标签
unity·游戏引擎
魔士于安20 小时前
Unity UI图片 复活节UI,卡通风格
游戏·ui·unity·游戏引擎·材质·贴图
weixin_4239950020 小时前
unity 团结开发小游戏,加载AssetBundles(第二种方法)
unity·游戏引擎