介绍

Obfuz 是一款开源、强大、易用及稳定可靠的充分满足商业化游戏项目需求的Unity代码混淆和加固解决方案。

这个插件比较新,2025 年 5 月 17 日首次提交。项目是由 HybridCLR 的作者编写的。

文档

环境

  • Unity 2022.3.62f1
  • Obfuz 20250728 38ad0de746979c2df8adad0f1f442d9e4478c522

需求

  1. 支持游戏中使用的 Assembly Definition
  2. 必须直接使用源代码,而不是使用包管理器安装,以支持离线无网环境打包。
  3. 支持混淆部分代码,需要改动尽量小。

快速上手

按照文档搭建一个测试环境,首先将游戏代码放到单独的 Game.asmdef 中,然后使用源码直接放到 Assets/Plugins 目录中。

经过测试可以直接满足上面的 1 和 2 两点。

混淆功能

  • 符号混淆:支持丰富的配置规则和增量混淆,灵活高效地保护代码。

将所有的符号改名,符号包括方法名、类名、成员名等等。

Unity 中存在大量依赖符号名字的功能,例如 MonoBehaviour 事件方法、Animation 动画事件方法。

游戏中也可能存在使用反射获取符号或类型的方法,例如序列化与反序列化。

如果需要游戏正常运行,必须将所有需要的符号保留不进行混淆。

符号混淆 Debug 模式

默认情况下,生成的混淆名类似于$a之类,如果不去查看symbol mapping文件,很难从代码中得知原始名是哪个。这给追踪符号混淆引发的bug时,带来较多不便, 因此Obfuz特地支持了Debug模式。在Debug模式下,会将Name映射为$Name,可以直接看出原始名是什么,方便调试追踪。

Debug模式下的混淆规则是固定的,即Name映射为$Name。如果遇到名称冲突,会尝试$Name1$Name2直到找到不冲突的名字。因此Debug模式下会 忽略symbol mapping文件,既不会加载它,也不会混淆完成后更新它。

Mono 版本混淆

使用 Windows 打包测试 Mono 版本是否正常

EncrytionService 初始化

1
2
3
4
5
6
7
8
9
NullReferenceException: Object reference not set to an instance of an object
  at $S.$qaA`1[T].$moc (System.Int32 1, System.Int32 1, System.Int32 1) [0x00000] in <42c7806c7b7a4c0c9fb38ee434bb04f9>:0 
  at $DaA..cctor () [0x00000] in <e05911ff7562453d8335f2166805a307>:0 
Rethrow as TypeInitializationException: The type initializer for '$DaA' threw an exception.
  at (wrapper managed-to-native) System.Object.__icall_wrapper_mono_generic_class_init(intptr)
  at $pZ..cctor () [0x00000] in <e05911ff7562453d8335f2166805a307>:0 
Rethrow as TypeInitializationException: The type initializer for '$pZ' threw an exception.
  at (wrapper managed-to-native) System.Object.__icall_wrapper_mono_generic_class_init(intptr)
  at XXXXX.XXX..ctor () [0x00011] in <e05911ff7562453d8335f2166805a307>:0 

还未初始化EncrytionService<T>::Encryptor就运行了混淆代码。解决办法为先初始化EncryptionService<T>::Encryptor,详细文档见初始化Encryptor

不需要混淆的符号

1
2
3
Can not find JSON helper type 'XXXX.LitJsonHelper'.
Can not find procedure type 'XXXX.ProcedureXXXX'.
Can not get data row type with class name 'XXXX.DRXxxxx'.

直接将这些类型忽略。

GameFramework 混淆方法

有人已经将 GameFramework 使用 Obfuz 混淆了,使用属性 [Obfuz.ObfuzIgnore] 来控制哪些内容不混淆。

混淆用时过长

加入混淆后打包时间增加了 25 分钟,迭代效率太低,因此必须控制混淆的代码数量,只混淆需要的部分。

拆分需要混淆的代码

首先将混淆的代码放入单独的 DLL 中,其他的代码也放入单独的 DLL 引用这个混淆 DLL,或者其他代码放到 Assembly-CSharp 中默认自动引用所有的 DLL。

使用依赖注入拆分需要混淆的游戏代码。将所有混淆代码使用到的方法定义到一个单独的接口中,在外部代码中实现这个接口,并在运行时将实例注入到混淆代码中。

反射兼容性问题

1
[ReflectionCompatibilityDetector] Reflection compatibility issue in XXXX.XXXXXXX SROptions::get_XXXXXXXX(): Enum.TryParse<T> field of T:XXXX.XXXXXXX is renamed.

将所有 Obfuz 提示的反射兼容性问题都使用属性 [Obfuz.ObfuzIgnore] 禁止混淆。

ScriptableObject 问题

ScriptableObjectOnEnable 被混淆了,Obfuz 应该是忽略了这个类与 MonoBehaviour 相同也需要忽略,因此手工忽略一下。

IL2CPP 版本混淆

使用 Android 打包测试 IL2CPP 版本是否正常

1
2
3
D:\Test\GradleProject\unityLibrary\src\main\Il2CppOutputProject\Source\il2cppOutput\XXXX__4.cpp(13015,9): error: incompatible pointer types assigning to 'XXXXXXXXXX_t5D9EDB5EEAE1D1345A2A27B96A7D49607C628DF0 *' from 'XXXXXXXXXX_tC5E9CAA1B1F46A43EB6532002E6188D666AB2F55 *'
                V_3 = G_B15_0;
                      ^~~~~~~

Mono 版本中没问题,但是 IL2CPP 版本中异常,手动将这个类型忽略混淆。

使用 iOS 打包测试 IL2CPP 版本是否正常

无问题。

验证混淆是否生效

Mono 使用 ILSpy

IL2CPP 使用 Il2CppDumper