【Unity】C#热更dll混淆加密 代码加固 dll合并 .NET Reactor

 使用.NET Reactor三方工具,把混淆加固热更dll的功能融入自动化打包工作流。

很多人用HybridCLR进行全代码热更,即使加密AB包也毫无安全可言。而HybridCLR社区版对于非全热更,AOT程序集往往还得作为AOT泛型元数据补充的aot dll直接打进包里,也就意味着AOT程序集也暴露出去了,同样毫无安全可言。

对于HybridCLR社区版,除了加密热更dll AB包,如何进一步提升安全性呢?代码混淆是一种比较常用的方式。

.Net Reactor是一款非常好用稳定的dll加固软件,并且支持dll合并。除了客户端GUI界面外,还提供了命令行工具,dotNET_Reactor.Console.exe -h 即可查看参数用法。

工具设计如图:

 工具设计:

1. 需支持自定义忽略混淆方法名列表,配置到列表内的方法名将不进行混淆,如MonoBehavior声明周期方法,Awake、Start、Update等等,这些方法Unity以反射方式获取,一旦方法名改变将不会被执行。

2.自动扫描dll中的外部接口、抽象方法,并跳过这些方法名的混淆。

3. 支持配置混淆策略,如是否混淆可序列化类、字符串、枚举等。

4. 在打包工具界面,可选择加密方式。只需实现接口自己即可扩展自定义加密方式;

然而代码混淆有利有弊,如果处理不当风险很大,比如某些类和方法通过反射调用,类名和方法名一旦混淆处理就无法通过名字反射调用,序列化类也会无法反序列化。因此过滤混淆就需要小心翼翼。

通过标签忽略某个类名混淆:

可参考官方文档:Declarative Protection

1. 跳过该类的任何混淆

[System.Reflection.Obfuscation(Feature = "renaming")]

2.  仅跳过该类的类名混淆,内部类、方法名、变量不跳过

[System.Reflection.Obfuscation(Feature = "renaming", ApplyToMembers = false)]

 通过调用dotNET_Reactor.Console.exe命令行加密dll:

如果不清楚命令行参数,.Net Reactor客户端还支持在UI上手动点选混淆配置,然后通过Command line菜单一键生成命令行,只需复制即可。

 自动化混淆:

using GameFramework;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;

namespace UGF.EditorTools
{
    /// <summary>
    /// 默认热更代码加密:使用.Net Reactor对hotfix.dll进行混淆加密
    /// </summary>
    public class DefaultProtectHotfixDllsHandler : IProtectHotfixDllsHandler
    {
        const bool invalid_metadata = false;
        const bool obfuscate_public_types = false;//public类型加密
        const bool stringencryption = true;//字符串加密
        const bool enumencryption = false;//枚举加密
        const bool fieldsencryption = false;
        const bool serializableTypeEncryption = false;
        const string DotNetReactorPath = "Tools/NETReactor/dotNET_Reactor.Console.exe";
        const string IgnoreMethodsListFile = "Tools/NETReactor/IgnoreMethodsList.txt";
        string mainHotfixDllName = Path.GetFileNameWithoutExtension(ConstEditor.HotfixAssembly);
        public bool OnProtectDll(string inputDllFile, string outputDllFile)
        {
            if (Path.GetFileNameWithoutExtension(inputDllFile).CompareTo(mainHotfixDllName) == 0)
            {
                //只加密Hotfix.dll
                return DotNetReactor(inputDllFile, outputDllFile);
            }
            return false;
        }

        private static bool DotNetReactor(string inputDllFile, string outputDllFile)
        {
            string toolsFile = UtilityBuiltin.AssetsPath.GetCombinePath(Directory.GetParent(Application.dataPath).FullName, DotNetReactorPath);
            if (!File.Exists(toolsFile))
            {
                return false;
            }

            StringBuilder strBuilder = new StringBuilder();
            strBuilder.AppendFormat(@"{0} -quiet -file ""{1}"" -targetfile ""{2}""", toolsFile, inputDllFile, outputDllFile);
            strBuilder.AppendFormat(@" -invalid_metadata {0} -stringencryption {1} -exclude_enums {2} -exclude_fields {3} -exclude_serializable_types {4} -obfuscate_public_types {5} -rules "".*::methods:", invalid_metadata ? 0 : 1, stringencryption ? 0 : 1, enumencryption ? 0 : 1,
                fieldsencryption ? 0 : 1, serializableTypeEncryption ? 0 : 1, obfuscate_public_types ? 1 : 0);
            var ignoreMethods = MethodsFilter(Path.GetFileNameWithoutExtension(inputDllFile));
            strBuilder.Append(ignoreMethods);
            strBuilder.Append(@"::fields:$.::properties:$.::events:$.::onamespaces:""");//::otypes:
            string commandLineStr = strBuilder.ToString();
            GFBuiltin.LogInfo(commandLineStr);
            var proceInfo = new System.Diagnostics.ProcessStartInfo(toolsFile, commandLineStr);
            proceInfo.CreateNoWindow = true;
            proceInfo.UseShellExecute = false;
            proceInfo.Verb = "runas";
            proceInfo.WorkingDirectory = Directory.GetParent(Application.dataPath).FullName;
            bool success;
            using (var proce = System.Diagnostics.Process.Start(proceInfo))
            {
                proce.WaitForExit();
                success = proce.ExitCode == 0;
                if (success)
                {
                    GFBuiltin.LogInfo($"加密dll成功:{inputDllFile}");
                }
                return success;
            }
        }
        
        private static string MethodsFilter(string assemblyName)
        {
            var assemblies = Utility.Assembly.GetAssemblies();
            System.Reflection.Assembly hotfixAssembly = null;
            foreach (var item in assemblies)
            {
                if (item.GetName().Name == assemblyName)
                {
                    hotfixAssembly = item;
                    break;
                }
            }

            if (hotfixAssembly == null) return null;

            HashSet<string> ignoreMethodsNames = new HashSet<string>();
            string ignoreFile = UtilityBuiltin.AssetsPath.GetCombinePath(Directory.GetParent(Application.dataPath).FullName, IgnoreMethodsListFile);
            if (File.Exists(ignoreFile))
            {
                var methodNames = File.ReadAllLines(ignoreFile);
                foreach (var method in methodNames)
                {
                    if (string.IsNullOrWhiteSpace(method) || method.StartsWith("//")) continue;

                    ignoreMethodsNames.Add(method);
                }
            }
            var typesInAssembly = hotfixAssembly.GetTypes();
            foreach (var tp in typesInAssembly)
            {
                System.Type currentType = tp;

                while (currentType != null)
                {
                    if (currentType.BaseType != null && currentType.BaseType.Assembly != hotfixAssembly)
                    {
                        var methods = currentType.GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                        if (currentType.IsInterface)
                        {
                            foreach (var method in methods)
                            {
                                ignoreMethodsNames.Add(method.Name);
                            }
                        }
                        else
                        {
                            foreach (var method in methods)
                            {
                                if (method.IsVirtual || method.IsAbstract)
                                {
                                    ignoreMethodsNames.Add(method.Name);
                                }
                            }
                        }

                    }
                    currentType = currentType.BaseType;
                }
            }

            StringBuilder result = new StringBuilder();
            foreach (var methodName in ignoreMethodsNames)
            {
                //Debug.Log(methodName);
                result.AppendFormat("^{0}$,", methodName);
            }

            return result.ToString(0, result.Length - 1);
        }
    }

}

混淆前:

 混淆后:

猜你喜欢

转载自blog.csdn.net/final5788/article/details/141130716