第十七章:逆向 Unity 游戏:Unity 游戏的结构(Mono 与 IL2CPP)、使用 dnSpy 反编译 C# 代码、在 Unity 游戏中查找关键函数

Unity 游戏,说白了就是 C# 脚本的集合体。但 Unity 有两种编译方式——Mono 和 IL2CPP。这两种方式决定了你逆向的难度和工具选择。我个人习惯先判断游戏用的是哪种,再决定下一步怎么走。

Unity 游戏的两大阵营:Mono 与 IL2CPP

Unity 脚本默认用 C# 编写,但最终怎么跑在玩家电脑上?这里就有两条路。

特性 Mono IL2CPP
编译产物 DLL 文件(Assembly-CSharp.dll 等) 原生 so/dll + 全局元数据文件
逆向难度 低(直接反编译为 C#) 高(需要分析汇编或全局元数据)
性能 一般(JIT 或解释执行) 较高(AOT 编译为原生代码)
常见平台 PC 编辑器、部分旧游戏 iOS、Android、新 PC 游戏

怎么判断?打开游戏目录,看看有没有 Managed 文件夹,里面有没有 Assembly-CSharp.dll。有的话,就是 Mono。没有?那大概率是 IL2CPP。我在项目中遇到过一款手游,目录里只有 libil2cpp.so 和一个 global-metadata.dat——嗯,典型的 IL2CPP 结构。

使用 dnSpy 反编译 C# 代码(Mono 篇)

如果是 Mono 编译的游戏,逆向简直不要太爽。dnSpy 就是你的瑞士军刀。

操作步骤很简单:

  1. 打开 dnSpy,拖入 Assembly-CSharp.dll
  2. 左侧树形结构展开,看到命名空间和类。
  3. 双击任意方法,右侧直接显示反编译后的 C# 代码。

举个例子,我想找游戏里的金币数量。搜索 CoinGold,dnSpy 会列出所有包含这些关键字的类和方法。我曾经逆向一款卡牌游戏,直接搜 AddGold 就找到了充值回调函数——连断点都不用打。

核心技巧:在 dnSpy 里按 Ctrl+Shift+K 可以搜索整个程序集。别只搜变量名,搜字符串也很有用——比如 "金币不足" 这种提示文本,往往能直接定位到逻辑函数。

我的习惯:先搜 UI 文本字符串,再反向追踪调用链。因为 UI 文本是玩家直接看到的,最容易定位。

IL2CPP 怎么办?—— 从 global-metadata 入手

IL2CPP 把 C# 代码编译成了原生代码,dnSpy 直接打开 so 文件只能看到汇编。但别慌,global-metadata.dat 这个文件里保存了类名、方法名、字段偏移等元数据。

你需要一个工具叫 Il2CppDumper。用法:

Il2CppDumper.exe libil2cpp.so global-metadata.dat output_dir

它会生成一堆文件,其中 DummyDll 文件夹里是假的 DLL——这些 DLL 不能运行,但包含了完整的类结构和方法签名。你可以把这些 DLL 拖进 dnSpy,虽然看不到方法体,但能看到方法名、参数和返回值。

为什么这样有用?你想想看,知道了方法名,你就能在 Cheat Engine 里搜函数地址。比如你看到 PlayerController.TakeDamage,那在 CE 里搜这个字符串,或者搜它的地址偏移,就能定位到原生代码里的对应函数。

注意:Il2CppDumper 生成的 DummyDll 里方法体是空的。别指望能看到 C# 源码。你只能看到「有什么函数」,看不到「函数里写了什么」。想看逻辑?老老实实分析汇编吧。

在 Unity 游戏中查找关键函数

不管 Mono 还是 IL2CPP,最终目标都是找到关键函数——比如扣血、加金币、无敌判定。

我的查找流程:

  1. 字符串搜索法:搜 UI 文本、Debug.Log 里的字符串。
  2. 数值搜索法:CE 里搜当前血量,修改后看哪个函数被调用。
  3. 调用栈回溯法:CE 下断点,看调用栈,找到上层函数。

举个例子,我在逆向一款 Unity 射击游戏时,想找「子弹伤害」的计算函数。我先在 CE 里搜当前血量,挨一枪后搜减少的值,找到地址后下「访问断点」。游戏一卡,CE 停在了某条汇编指令上。查看调用栈,发现来自 Bullet.OnHit——嗯,这就是关键函数。

对于 Mono 游戏,你甚至可以直接在 dnSpy 里给 Bullet.OnHit 下断点,然后修改返回值。对于 IL2CPP 游戏,你需要在 CE 里找到这个函数的地址,然后 hook 它。

避坑指南:我曾经在 IL2CPP 游戏里直接搜函数名,结果发现搜不到——因为 IL2CPP 会把函数名混淆或加密。这时候要用 Il2CppDumper 导出的 script.json,里面记录了每个函数的偏移地址。直接拿偏移去 CE 里搜,比搜名字靠谱得多。

知识体系总览

下面这张图概括了本章的核心逻辑:

Unity 游戏逆向核心流程 判断编译方式 Mono → Assembly-CSharp.dll IL2CPP → libil2cpp.so dnSpy 直接反编译 Il2CppDumper 提取元数据 搜索字符串/数值/调用栈 从 script.json 找偏移 定位关键函数 → 修改逻辑

这张图把 Mono 和 IL2CPP 两条路线画清楚了。左边是「简单模式」,右边是「困难模式」。但不管哪条路,最终都要落到「找到关键函数」这个点上。

实战:从字符串到函数

最后分享一个实战技巧。假设游戏里有个「购买成功」的提示,你想找到购买逻辑。

  1. 在 dnSpy(Mono)或 DummyDll(IL2CPP)里搜 "购买成功"。
  2. 找到引用这个字符串的类和方法。
  3. 查看调用这个方法的上层函数——往往就是购买的核心逻辑。

我曾经用这个方法,五分钟就找到了某款 Unity 游戏的「内购验证」函数。嗯,那款游戏后来更新了 IL2CPP,但字符串没加密——所以这招依然管用。

小提醒:Unity 的字符串通常存在 TextMeshProUnityEngine.UI.Texttext 属性里。搜的时候可以带上类名过滤,比如 Text.text 加上 "购买成功"。

逆向 Unity 游戏,说白了就是跟 C# 代码打交道。Mono 让你直接看源码,IL2CPP 让你多绕几步。但核心思路不变:找到关键字符串,追踪调用链,定位目标函数。多练几次,你就能形成自己的套路。

公众号:蓝海资料掘金营,微信deep3321