大家好,我是一名 Unity 开发者,正在为一款小型独立游戏工作。 我经常听同事说,Unity 在保护项目代码和资源方面做得不够好。 我知道使用 Mono 是不可能的 - 这相当于把项目交给陌生人 - 但我认为 IL2Cpp 脚本后端提供了足够的保护。

但有一天,我对如何反编译我的项目产生了兴趣。 我将 Il2CppDumper 指向我的构建的 global-metadata.dat,它吐出了一个 DummyDll 文件夹,我在 Rider 中打开了它。 没有任何麻烦,我就能查看命名空间、方法、类名和字段名。 我不喜欢这样,因为我有一个私有的 GitHub 存储库,如果我的构建是泄露的,那么这有什么意义呢?

我开始寻找隐藏代码的方法。 我开始检查 Asset Store 和 GitHub 以寻找如何混淆我的构建的选项。 说干就干,唯一的优秀资产是 GuardingPearSoftware 的 Obfuscator Free。 它做得一切完美,但免费版不支持 Unity 方法、序列化字段、属性或命名空间,并且完全忽略 MonoBehaviour、ScriptableObject 和 [Serializable] 类。 有一个付费版本可以做到这一切,但 80 美元对于独立团队来说是一个很大的金额,所以我决定尝试制作自己的替代方案,这个替代方案可以作为 Obfuscator Free 插件的扩展。

为什么混淆器忽略序列化类型?

每个脚本在构建中都收到一个 MonoScript 条目(level0sharedassets*.assetsresources.assetsglobalgamemanagers 文件)。 它包含三个行:m_ClassNamem_Namespacem_AssemblyName。 每个场景对象和每个预设都引用这个条目。 如果你重命名一个类在 DLL 中,Unity 就无法再次绑定到该类型 - 并且组件变成一个缺失的脚本。

技巧

当我意识到这一点时,我开始寻找一个方法来绕过它。 我发现了一个既聪明又荒谬的方法:我生成混淆后的名称与原始名称相同的字符数。 这样我就不用调整长度前缀、重新计算偏移量和开发一个工具来重写序列化文件。 感谢这个意外发现的技巧,我肯定可以节省一周的失眠夜。 但是,请注意,即使采用这种方法,你仍然必须存储一堆文件的混淆后的名称。 所有项目中的名称都在预先保留,因此你无法生成一个已经被占用的名称。

纯 C# 类型没有 MonoScript 条目,因此它们的命名空间被缩短到我喜欢使用的短、无意义的字符串(_Project.Code.AssetManagementpqmpqu)。 序列化类型,另一方面,被迫保持长度一致(_Project.Code.Sounddis4nAw5EW74Z6wLxDN)。

找到条目而不损坏文件

你不能简单地搜索文件中 “PlayerController” 的字符串并覆盖它。 那个同样的字符串可能是 GameObject 名称、字符串字面值、可访问的密钥 - 如果你击中了错误的,它就不会让你喜欢的调试。

所以我在三个长度前缀的连续条目上锚定:[len][m_ClassName][len][m_Namespace][len][m_AssemblyName],它们之间有 4 个字节的对齐。 三个匹配的字符串在序列中具有正确的对齐是足够强大的信号。 并且修补 m_Name 几个字节之前也会泄露你刚刚隐藏的内容。

堆栈跟踪问题

我还必须编写自己的堆栈跟踪反混淆器,因为任何异常都会使跟踪对人类来说完全不可读,例如 hdektk.uHN1wAaEGHkYq6ul.qoaktw()。 每个构建写入 JSON 映射文件(_Project.Code.AssetManagementpqmpqu),并且有一个编辑器窗口我可以粘贴从 Player.log 中粘贴的原始堆栈跟踪。 它在四个阶段中运行:GUPS 反混淆器 → 我的序列化类型名称 → 我的命名空间 → 剩余成员名称。

不重命名的内容

Unity 的消息方法:AwakeOnTriggerEnter2DOnBecameInvisible 和大约六十个其他方法 - 引擎通过名称调用它们。 虚拟、接口和覆盖成员,因为重命名实现但不重命名声明会打破 vtable 插槽。 并且任何在运行时通过名称序列化的内容 - 我的保存是 JSON,重命名一个属性会损坏每个现有的保存文件。

当前构建的结果

40 个命名空间重命名。 82 个 MonoBehaviour/ScriptableObject 类型重命名并在玩家数据中修补。 848 个成员。

是否值得?

混淆是障碍,而不是防御。 anyone 确定要弄清楚的仍然会通过调试器实现目标,并且我更喜欢坦白这一点而不是假装不这样做。 对于独立开发者来说,设置一次并且随后忘记的构建步骤在我看来是一个值得的努力投资。

我不会把我的扩展发布 - 它是我的构建管道的一部分,是独立的,并且有几个角落我不想在公共领域辩护。 但你不需要我的代码就可以开始 - 这就是这篇文章的真正意义。

做这件事。 运行 Il2CppDumper 或 AssetRipper 在自己的构建上 - 五分钟,看到自己的架构滑过会有很大的不同。 然后安装 Obfuscator Free,列出你的程序集,构建。 免费,一个晚上,会保护你的纯 C# 类型和方法。

这将使你比大多数今天运输的 Unity 构建都高一个档次。 以上内容都是第二步 - 你会知道什么时候需要它。

我为 Peak or Die 这款游戏做了所有这些 - 一款基于卡片的生存策略游戏。 如果你感兴趣,请将它添加到你的 Steam wishlist - 这将显著增加游戏的可见性:https://store.steampowered.com/app/4273370