From 8c48e6bf61f05ecdedff51d7fe58f255910028ba Mon Sep 17 00:00:00 2001 From: walon Date: Sat, 10 May 2025 09:41:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84CallObfus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BasicBlockObfuscationPassBase.cs | 4 +- Editor/ObfusPasses/CallObfus/CallObfusPass.cs | 46 +- .../CallObfus/CallObfusPolicyBase.cs | 11 - .../CallObfus/CallProxyAllocator.cs | 2 +- .../CallObfus/ConfigurableCallObfusPolicy.cs | 47 -- .../ConfigurableObfuscationPolicy.cs | 524 ++++++++++++++++++ ...cator.cs => DefaultCallProxyObfuscator.cs} | 4 +- .../ObfusPasses/CallObfus/ICallObfusPolicy.cs | 16 - .../CallObfus/IObfuscationPolicy.cs | 25 + .../{ICallObfuscator.cs => IObfuscator.cs} | 2 +- .../CallObfus/ObfuscationPolicyBase.cs | 13 + ...allObfuscatorBase.cs => ObfuscatorBase.cs} | 2 +- .../ConstEncrypt/ConfigurableEncryptPolicy.cs | 126 ++--- .../ConstEncrypt/ConstEncryptPass.cs | 10 +- Editor/Settings/CallObfusSettings.cs | 3 + Editor/Settings/ConstEncryptSettings.cs | 4 +- Editor/Utils/ConfigUtil.cs | 60 ++ 17 files changed, 712 insertions(+), 187 deletions(-) delete mode 100644 Editor/ObfusPasses/CallObfus/CallObfusPolicyBase.cs delete mode 100644 Editor/ObfusPasses/CallObfus/ConfigurableCallObfusPolicy.cs create mode 100644 Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs rename Editor/ObfusPasses/CallObfus/{DefaultProxyCallObfuscator.cs => DefaultCallProxyObfuscator.cs} (92%) delete mode 100644 Editor/ObfusPasses/CallObfus/ICallObfusPolicy.cs create mode 100644 Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs rename Editor/ObfusPasses/CallObfus/{ICallObfuscator.cs => IObfuscator.cs} (90%) create mode 100644 Editor/ObfusPasses/CallObfus/ObfuscationPolicyBase.cs rename Editor/ObfusPasses/CallObfus/{CallObfuscatorBase.cs => ObfuscatorBase.cs} (83%) create mode 100644 Editor/Utils/ConfigUtil.cs diff --git a/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs b/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs index fe02a35..3fa03c6 100644 --- a/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs +++ b/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs @@ -37,7 +37,7 @@ namespace Obfuz.ObfusPasses protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, BasicBlock block, int instructionIndex, - List outputInstructions, List totalFinalInstructions); + IList globalInstructions, List outputInstructions, List totalFinalInstructions); private void ObfuscateData(MethodDef method) { @@ -52,7 +52,7 @@ namespace Obfuz.ObfusPasses Instruction inst = instructions[i]; BasicBlock block = bbc.GetBasicBlockByInstruction(inst); outputInstructions.Clear(); - if (TryObfuscateInstruction(method, inst, block, i, outputInstructions, totalFinalInstructions)) + if (TryObfuscateInstruction(method, inst, block, i, instructions, outputInstructions, totalFinalInstructions)) { // current instruction may be the target of control flow instruction, so we can't remove it directly. // we replace it with nop now, then remove it in CleanUpInstructionPass diff --git a/Editor/ObfusPasses/CallObfus/CallObfusPass.cs b/Editor/ObfusPasses/CallObfus/CallObfusPass.cs index 54387c4..3fa1df3 100644 --- a/Editor/ObfusPasses/CallObfus/CallObfusPass.cs +++ b/Editor/ObfusPasses/CallObfus/CallObfusPass.cs @@ -13,19 +13,20 @@ using Obfuz.Settings; namespace Obfuz.ObfusPasses.CallObfus { - public class CallObfusPass : InstructionObfuscationPassBase + public class CallObfusPass : BasicBlockObfuscationPassBase { + private readonly List _configFiles; private readonly IRandom _random; private readonly IEncryptor _encryptor; - private readonly ICallObfusPolicy _dynamicProxyPolicy; - private readonly ICallObfuscator _dynamicProxyObfuscator; + private readonly IObfuscator _dynamicProxyObfuscator; + private IObfuscationPolicy _dynamicProxyPolicy; public CallObfusPass(CallObfusSettings settings) { + _configFiles = settings.configFiles.ToList(); _random = new RandomWithKey(new byte[] { 0x1, 0x2, 0x3, 0x4 }, 0x5); _encryptor = new DefaultEncryptor(new byte[] { 0x1A, 0x2B, 0x3C, 0x4D }); - _dynamicProxyPolicy = new ConfigurableCallObfusPolicy(); - _dynamicProxyObfuscator = new DefaultProxyCallObfuscator(_random, _encryptor); + _dynamicProxyObfuscator = new DefaultCallProxyObfuscator(_random, _encryptor); } public override void Stop(ObfuscationPassContext ctx) @@ -35,7 +36,7 @@ namespace Obfuz.ObfusPasses.CallObfus public override void Start(ObfuscationPassContext ctx) { - + _dynamicProxyPolicy = new ConfigurableObfuscationPolicy(ctx.toObfuscatedAssemblyNames, _configFiles); } protected override bool NeedObfuscateMethod(MethodDef method) @@ -43,8 +44,8 @@ namespace Obfuz.ObfusPasses.CallObfus return _dynamicProxyPolicy.NeedDynamicProxyCallInMethod(method); } - protected override bool TryObfuscateInstruction(MethodDef method, Instruction inst, IList instructions, int instructionIndex, - List outputInstructions, List totalFinalInstructions) + protected override bool TryObfuscateInstruction(MethodDef callerMethod, Instruction inst, BasicBlock block, + int instructionIndex, IList globalInstructions, List outputInstructions, List totalFinalInstructions) { IMethod calledMethod = inst.Operand as IMethod; if (calledMethod == null || !calledMethod.IsMethod) @@ -56,32 +57,35 @@ namespace Obfuz.ObfusPasses.CallObfus return false; } + bool callVir; switch (inst.OpCode.Code) { case Code.Call: { - if (!_dynamicProxyPolicy.NeedDynamicProxyCalledMethod(calledMethod, false)) - { - return false; - } - _dynamicProxyObfuscator.Obfuscate(method, calledMethod, false, outputInstructions); - return true; + callVir = false; + break; } case Code.Callvirt: { - if (instructionIndex > 0 && instructions[instructionIndex - 1].OpCode.Code == Code.Constrained) + if (instructionIndex > 0 && globalInstructions[instructionIndex - 1].OpCode.Code == Code.Constrained) { return false; } - if (!_dynamicProxyPolicy.NeedDynamicProxyCalledMethod(calledMethod, true)) - { - return false; - } - _dynamicProxyObfuscator.Obfuscate(method, calledMethod, true, outputInstructions); - return true; + callVir = true; + break; } default: return false; } + + ObfuscationCachePolicy cachePolicy = _dynamicProxyPolicy.GetMethodObfuscationCachePolicy(callerMethod); + bool cachedCallIndex = block.inLoop ? cachePolicy.cacheInLoop : cachePolicy.cacheNotInLoop; + + if (!_dynamicProxyPolicy.NeedDynamicProxyCalledMethod(callerMethod, calledMethod, callVir, cachedCallIndex)) + { + return false; + } + _dynamicProxyObfuscator.Obfuscate(callerMethod, calledMethod, callVir, outputInstructions); + return true; } } } diff --git a/Editor/ObfusPasses/CallObfus/CallObfusPolicyBase.cs b/Editor/ObfusPasses/CallObfus/CallObfusPolicyBase.cs deleted file mode 100644 index fd547e0..0000000 --- a/Editor/ObfusPasses/CallObfus/CallObfusPolicyBase.cs +++ /dev/null @@ -1,11 +0,0 @@ -using dnlib.DotNet; - -namespace Obfuz.ObfusPasses.CallObfus -{ - public abstract class CallObfusPolicyBase : ICallObfusPolicy - { - public abstract bool NeedDynamicProxyCallInMethod(MethodDef method); - - public abstract bool NeedDynamicProxyCalledMethod(IMethod method, bool callVir); - } -} diff --git a/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs b/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs index 6b27a60..f1c550b 100644 --- a/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs +++ b/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs @@ -138,7 +138,7 @@ namespace Obfuz.ObfusPasses.CallObfus } case ThisArgType.ValueType: { - methodSig.Params.Insert(0, _module.CorLibTypes.UIntPtr); + methodSig.Params.Insert(0, _module.CorLibTypes.IntPtr); break; } } diff --git a/Editor/ObfusPasses/CallObfus/ConfigurableCallObfusPolicy.cs b/Editor/ObfusPasses/CallObfus/ConfigurableCallObfusPolicy.cs deleted file mode 100644 index b861d9a..0000000 --- a/Editor/ObfusPasses/CallObfus/ConfigurableCallObfusPolicy.cs +++ /dev/null @@ -1,47 +0,0 @@ -using dnlib.DotNet; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Obfuz.ObfusPasses.CallObfus -{ - public class ConfigurableCallObfusPolicy : CallObfusPolicyBase - { - public override bool NeedDynamicProxyCallInMethod(MethodDef method) - { - return true; - } - - public override bool NeedDynamicProxyCalledMethod(IMethod method, bool callVir) - { - - ITypeDefOrRef declaringType = method.DeclaringType; - TypeDef typeDef = declaringType.ResolveTypeDef(); - // doesn't proxy call if the method is a delegate - if (typeDef != null) - { - // need configurable - if (typeDef.Module.IsCoreLibraryModule == true) - { - return false; - } - if (typeDef.IsDelegate) - return false; - } - // doesn't proxy call if the method is a constructor - if (method.Name == ".ctor") - { - return false; - } - // special handle - // don't proxy call for List.Enumerator GetEnumerator() - if (method.Name == "GetEnumerator") - { - return false; - } - return true; - } - } -} diff --git a/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs b/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs new file mode 100644 index 0000000..86933f3 --- /dev/null +++ b/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs @@ -0,0 +1,524 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using UnityEngine; + +namespace Obfuz.ObfusPasses.CallObfus +{ + public class ConfigurableObfuscationPolicy : ObfuscationPolicyBase + { + private readonly List _toObfuscatedAssemblyNames; + + + class WhiteListAssembly + { + public string name; + public NameMatcher nameMatcher; + public bool? obfuscateNone; + public List types = new List(); + } + + class WhiteListType + { + public string name; + public NameMatcher nameMatcher; + public bool? obfuscateNone; + public List methods = new List(); + } + + class WhiteListMethod + { + public string name; + public NameMatcher nameMatcher; + } + + class ObfuscationRule + { + public bool? disableObfuscation; + public bool? obfuscateCallInLoop; + public bool? cacheCallIndexInLoop; + public bool? cacheCallIndexNotLoop; + + public void InheritParent(ObfuscationRule parentRule) + { + if (disableObfuscation == null) + { + disableObfuscation = parentRule.disableObfuscation; + } + if (obfuscateCallInLoop == null) + { + obfuscateCallInLoop = parentRule.obfuscateCallInLoop; + } + if (cacheCallIndexInLoop == null) + { + cacheCallIndexInLoop = parentRule.cacheCallIndexInLoop; + } + if (cacheCallIndexNotLoop == null) + { + cacheCallIndexNotLoop = parentRule.cacheCallIndexNotLoop; + } + } + } + class AssemblySpec + { + public string name; + public ObfuscationRule rule; + public List types = new List(); + } + + class TypeSpec + { + public string name; + public NameMatcher nameMatcher; + public ObfuscationRule rule; + public List methods = new List(); + } + + class MethodSpec + { + public string name; + public NameMatcher nameMatcher; + public ObfuscationRule rule; + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + disableObfuscation = false, + obfuscateCallInLoop = true, + cacheCallIndexInLoop = true, + cacheCallIndexNotLoop = false, + }; + + private ObfuscationRule _global; + private readonly List _whiteListAssemblies = new List(); + private readonly Dictionary _assemblySpecs = new Dictionary(); + + private readonly Dictionary _whiteListMethodCache = new Dictionary(MethodEqualityComparer.CompareDeclaringTypes); + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableObfuscationPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _toObfuscatedAssemblyNames = toObfuscatedAssemblyNames; + LoadConfigs(xmlConfigFiles); + InheritParentRules(); + } + + private void LoadConfigs(List configFiles) + { + if (configFiles == null || configFiles.Count == 0) + { + Debug.LogWarning($"ConfigurableObfuscationPolicy::LoadConfigs configFiles is empty, using default policy"); + return; + } + foreach (var configFile in configFiles) + { + if (string.IsNullOrEmpty(configFile)) + { + throw new Exception($"ObfusSettings.callObfusSettings.configFiles contains empty file name"); + } + LoadConfig(configFile); + } + } + + private void LoadConfig(string configFile) + { + if (string.IsNullOrEmpty(configFile)) + { + Debug.LogWarning($"ConfigurableObfuscationPolicy::LoadConfig configFile is empty, using default policy"); + return; + } + Debug.Log($"ConfigurableObfuscationPolicy::LoadConfig {configFile}"); + var doc = new XmlDocument(); + doc.Load(configFile); + var root = doc.DocumentElement; + if (root.Name != "obfuz") + { + throw new Exception($"Invalid xml file {configFile}, root name should be 'obfuz'"); + } + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "global": _global = ParseObfuscationRule(ele, true); break; + case "whitelist": ParseWhitelist(ele); break; + case "assembly": + { + AssemblySpec assSpec = ParseAssembly(ele); + string name = assSpec.name; + if (!_toObfuscatedAssemblyNames.Contains(name)) + { + throw new Exception($"Invalid xml file {configFile}, assembly name {name} isn't in toObfuscatedAssemblyNames"); + } + if (_assemblySpecs.ContainsKey(name)) + { + throw new Exception($"Invalid xml file {configFile}, assembly name {name} is duplicated"); + } + _assemblySpecs.Add(name, assSpec); + break; + } + default: throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + } + + private void InheritParentRules() + { + if (_global == null) + { + _global = s_default; + } + else + { + _global.InheritParent(s_default); + } + foreach (AssemblySpec assSpec in _assemblySpecs.Values) + { + assSpec.rule.InheritParent(_global); + foreach (TypeSpec typeSpec in assSpec.types) + { + typeSpec.rule.InheritParent(assSpec.rule); + foreach (MethodSpec methodSpec in typeSpec.methods) + { + methodSpec.rule.InheritParent(typeSpec.rule); + } + } + } + } + + private ObfuscationRule ParseObfuscationRule(XmlElement ele, bool parseWhitelist) + { + var rule = new ObfuscationRule(); + if (ele.HasAttribute("disableObfuscation")) + { + rule.disableObfuscation = ConfigUtil.ParseBool(ele.GetAttribute("disableObfuscation")); + } + if (ele.HasAttribute("obfuscateCallInLoop")) + { + rule.obfuscateCallInLoop = ConfigUtil.ParseBool(ele.GetAttribute("obfuscateCallInLoop")); + } + if (ele.HasAttribute("cacheCallIndexInLoop")) + { + rule.cacheCallIndexInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheCallIndexInLoop")); + } + if (ele.HasAttribute("cacheCallIndexNotLoop")) + { + rule.cacheCallIndexNotLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheCallIndexNotLoop")); + } + return rule; + } + + private void ParseWhitelist(XmlElement ruleEle) + { + foreach (XmlNode xmlNode in ruleEle.ChildNodes) + { + if (!(xmlNode is XmlElement childEle)) + { + continue; + } + switch (childEle.Name) + { + case "assembly": + { + var ass = ParseWhiteListtAssembly(childEle); + _whiteListAssemblies.Add(ass); + break; + } + default: throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + + private WhiteListAssembly ParseWhiteListtAssembly(XmlElement element) + { + var ass = new WhiteListAssembly(); + ass.name = element.GetAttribute("name"); + ass.nameMatcher = new NameMatcher(ass.name); + if (element.HasAttribute("obfuscateNone")) + { + ass.obfuscateNone = ConfigUtil.ParseBool(element.GetAttribute("obfuscateNone")); + } + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "type": + ass.types.Add(ParseWhiteListType(ele)); + break; + default: + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + return ass; + } + + private WhiteListType ParseWhiteListType(XmlElement element) + { + var type = new WhiteListType(); + type.name = element.GetAttribute("name"); + type.nameMatcher = new NameMatcher(type.name); + if (element.HasAttribute("obfuscateNone")) + { + type.obfuscateNone = ConfigUtil.ParseBool(element.GetAttribute("obfuscateNone")); + } + + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "method": + { + type.methods.Add(ParseWhiteListMethod(ele)); + break; + } + default: throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + + return type; + } + + private WhiteListMethod ParseWhiteListMethod(XmlElement element) + { + var method = new WhiteListMethod(); + method.name = element.GetAttribute("name"); + method.nameMatcher = new NameMatcher(method.name); + return method; + } + + private AssemblySpec ParseAssembly(XmlElement element) + { + var assemblySpec = new AssemblySpec(); + assemblySpec.name = element.GetAttribute("name"); + if (string.IsNullOrEmpty(assemblySpec.name)) + { + throw new Exception($"Invalid xml file, assembly name is empty"); + } + assemblySpec.rule = ParseObfuscationRule(element, false); + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "type": + assemblySpec.types.Add(ParseType(ele)); + break; + default: + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + return assemblySpec; + } + + private TypeSpec ParseType(XmlElement element) + { + var typeSpec = new TypeSpec(); + typeSpec.name = element.GetAttribute("name"); + typeSpec.nameMatcher = new NameMatcher(typeSpec.name); + if (string.IsNullOrEmpty(typeSpec.name)) + { + throw new Exception($"Invalid xml file, type name is empty"); + } + typeSpec.rule = ParseObfuscationRule(element, false); + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "method": + typeSpec.methods.Add(ParseMethod(ele)); + break; + default: + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + return typeSpec; + } + + private MethodSpec ParseMethod(XmlElement element) + { + var methodSpec = new MethodSpec(); + methodSpec.name = element.GetAttribute("name"); + methodSpec.nameMatcher = new NameMatcher(methodSpec.name); + if (string.IsNullOrEmpty(methodSpec.name)) + { + throw new Exception($"Invalid xml file, method name is empty"); + } + methodSpec.rule = ParseObfuscationRule(element, false); + return methodSpec; + } + + private ObfuscationRule ComputeMethodObfuscationRule(MethodDef method) + { + var assemblyName = method.DeclaringType.Module.Assembly.Name; + if (!_assemblySpecs.TryGetValue(assemblyName, out var assSpec)) + { + return _global; + } + string declaringTypeName = method.DeclaringType.FullName; + foreach (var typeSpec in assSpec.types) + { + if (typeSpec.nameMatcher.IsMatch(declaringTypeName)) + { + foreach (var methodSpec in typeSpec.methods) + { + if (methodSpec.nameMatcher.IsMatch(method.Name)) + { + return methodSpec.rule; + } + } + return typeSpec.rule; + } + } + return assSpec.rule; + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = ComputeMethodObfuscationRule(method); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedDynamicProxyCallInMethod(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return rule.disableObfuscation != true; + } + + public override ObfuscationCachePolicy GetMethodObfuscationCachePolicy(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return new ObfuscationCachePolicy() + { + cacheInLoop = rule.cacheCallIndexInLoop.Value, + cacheNotInLoop = rule.cacheCallIndexNotLoop.Value, + }; + } + + private bool ComputeIsInWhiteList(IMethod calledMethod) + { + ITypeDefOrRef declaringType = calledMethod.DeclaringType; + TypeSig declaringTypeSig = calledMethod.DeclaringType.ToTypeSig(); + declaringTypeSig = declaringTypeSig.RemovePinnedAndModifiers(); + switch (declaringTypeSig.ElementType) + { + case ElementType.ValueType: + case ElementType.Class: + { + break; + } + case ElementType.GenericInst: + { + if (MetaUtil.ContainsContainsGenericParameter(calledMethod)) + { + return true; + } + break; + } + default: return true; + } + + TypeDef typeDef = declaringType.ResolveTypeDef(); + + if (typeDef.IsDelegate || typeDef.IsEnum) + return true; + + string assName = typeDef.Module.Assembly.Name; + string typeFullName = typeDef.FullName; + string methodName = calledMethod.Name; + + // doesn't proxy call if the method is a constructor + if (methodName == ".ctor") + { + return true; + } + // special handle + // don't proxy call for List.Enumerator GetEnumerator() + if (methodName == "GetEnumerator") + { + return true; + } + + foreach (var ass in _whiteListAssemblies) + { + if (!ass.nameMatcher.IsMatch(assName)) + { + continue; + } + if (ass.obfuscateNone == true) + { + return true; + } + foreach (var type in ass.types) + { + if (!type.nameMatcher.IsMatch(typeFullName)) + { + continue; + } + if (type.obfuscateNone == true) + { + return true; + } + foreach (var method in type.methods) + { + if (method.nameMatcher.IsMatch(methodName)) + { + return true; + } + } + } + } + return false; + } + + private bool IsInWhiteList(IMethod method) + { + if (!_whiteListMethodCache.TryGetValue(method, out var isWhiteList)) + { + isWhiteList = ComputeIsInWhiteList(method); + _whiteListMethodCache.Add(method, isWhiteList); + } + return isWhiteList; + } + + public override bool NeedDynamicProxyCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir, bool currentInLoop) + { + if (IsInWhiteList(calledMethod)) + { + return false; + } + ObfuscationRule rule = GetMethodObfuscationRule(callerMethod); + if (currentInLoop && rule.obfuscateCallInLoop == false) + { + return false; + } + return true; + } + } +} diff --git a/Editor/ObfusPasses/CallObfus/DefaultProxyCallObfuscator.cs b/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs similarity index 92% rename from Editor/ObfusPasses/CallObfus/DefaultProxyCallObfuscator.cs rename to Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs index cadaf66..cf3bdce 100644 --- a/Editor/ObfusPasses/CallObfus/DefaultProxyCallObfuscator.cs +++ b/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs @@ -6,13 +6,13 @@ using Obfuz.Emit; namespace Obfuz.ObfusPasses.CallObfus { - public class DefaultProxyCallObfuscator : CallObfuscatorBase + public class DefaultCallProxyObfuscator : ObfuscatorBase { private readonly IRandom _random; private readonly IEncryptor _encryptor; private readonly CallProxyAllocator _proxyCallAllocator; - public DefaultProxyCallObfuscator(IRandom random, IEncryptor encryptor) + public DefaultCallProxyObfuscator(IRandom random, IEncryptor encryptor) { _random = random; _encryptor = encryptor; diff --git a/Editor/ObfusPasses/CallObfus/ICallObfusPolicy.cs b/Editor/ObfusPasses/CallObfus/ICallObfusPolicy.cs deleted file mode 100644 index df8d79b..0000000 --- a/Editor/ObfusPasses/CallObfus/ICallObfusPolicy.cs +++ /dev/null @@ -1,16 +0,0 @@ -using dnlib.DotNet; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Obfuz.ObfusPasses.CallObfus -{ - public interface ICallObfusPolicy - { - bool NeedDynamicProxyCallInMethod(MethodDef method); - - bool NeedDynamicProxyCalledMethod(IMethod method, bool callVir); - } -} diff --git a/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs b/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs new file mode 100644 index 0000000..3641781 --- /dev/null +++ b/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs @@ -0,0 +1,25 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.CallObfus +{ + + public struct ObfuscationCachePolicy + { + public bool cacheInLoop; + public bool cacheNotInLoop; + } + + public interface IObfuscationPolicy + { + bool NeedDynamicProxyCallInMethod(MethodDef method); + + ObfuscationCachePolicy GetMethodObfuscationCachePolicy(MethodDef method); + + bool NeedDynamicProxyCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir, bool currentInLoop); + } +} diff --git a/Editor/ObfusPasses/CallObfus/ICallObfuscator.cs b/Editor/ObfusPasses/CallObfus/IObfuscator.cs similarity index 90% rename from Editor/ObfusPasses/CallObfus/ICallObfuscator.cs rename to Editor/ObfusPasses/CallObfus/IObfuscator.cs index 73dabf1..691999e 100644 --- a/Editor/ObfusPasses/CallObfus/ICallObfuscator.cs +++ b/Editor/ObfusPasses/CallObfus/IObfuscator.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Obfuz.ObfusPasses.CallObfus { - public interface ICallObfuscator + public interface IObfuscator { void Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, List obfuscatedInstructions); diff --git a/Editor/ObfusPasses/CallObfus/ObfuscationPolicyBase.cs b/Editor/ObfusPasses/CallObfus/ObfuscationPolicyBase.cs new file mode 100644 index 0000000..58ff170 --- /dev/null +++ b/Editor/ObfusPasses/CallObfus/ObfuscationPolicyBase.cs @@ -0,0 +1,13 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.CallObfus +{ + public abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + public abstract bool NeedDynamicProxyCallInMethod(MethodDef method); + + public abstract ObfuscationCachePolicy GetMethodObfuscationCachePolicy(MethodDef method); + + public abstract bool NeedDynamicProxyCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir, bool currentInLoop); + } +} diff --git a/Editor/ObfusPasses/CallObfus/CallObfuscatorBase.cs b/Editor/ObfusPasses/CallObfus/ObfuscatorBase.cs similarity index 83% rename from Editor/ObfusPasses/CallObfus/CallObfuscatorBase.cs rename to Editor/ObfusPasses/CallObfus/ObfuscatorBase.cs index bc78bea..66ff0b1 100644 --- a/Editor/ObfusPasses/CallObfus/CallObfuscatorBase.cs +++ b/Editor/ObfusPasses/CallObfus/ObfuscatorBase.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Obfuz.ObfusPasses.CallObfus { - public abstract class CallObfuscatorBase : ICallObfuscator + public abstract class ObfuscatorBase : IObfuscator { public abstract void Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, List obfuscatedInstructions); public abstract void Done(); diff --git a/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs b/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs index 2fd8f3a..941b8cb 100644 --- a/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs +++ b/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs @@ -109,14 +109,14 @@ namespace Obfuz.ObfusPasses.ConstEncrypt public string name; public NameMatcher nameMatcher; public ObfuscationRule rule; - public List methodSpecs = new List(); + public List methods = new List(); } class AssemblySpec { public string name; public ObfuscationRule rule; - public List typeSpecs = new List(); + public List types = new List(); } private static readonly ObfuscationRule s_default = new ObfuscationRule() @@ -140,17 +140,35 @@ namespace Obfuz.ObfusPasses.ConstEncrypt private readonly Dictionary _assemblySpecs = new Dictionary(); private readonly Dictionary _methodRuleCache = new Dictionary(); - public ConfigurableEncryptPolicy(List toObfuscatedAssemblyNames, string xmlConfigFile) + public ConfigurableEncryptPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) { _toObfuscatedAssemblyNames = toObfuscatedAssemblyNames; - LoadConfig(xmlConfigFile); + LoadConfigs(xmlConfigFiles); InheritParentRules(); } + private void LoadConfigs(List configFiles) + { + if (configFiles == null || configFiles.Count == 0) + { + Debug.LogWarning($"ConfigurableObfuscationPolicy::LoadConfigs configFiles is empty, using default policy"); + return; + } + foreach (var configFile in configFiles) + { + if (string.IsNullOrEmpty(configFile)) + { + throw new Exception($"ObfuzSettings.constEncryptSettings.configFiles contains empty file name"); + } + LoadConfig(configFile); + } + } + private void LoadConfig(string configFile) { if (string.IsNullOrEmpty(configFile)) { + Debug.LogWarning($"ConfigurableObfuscationPolicy::LoadConfig configFile is empty, using default policy"); return; } Debug.Log($"ConfigurableObfuscationPolicy::LoadConfig {configFile}"); @@ -176,7 +194,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt string name = assSpec.name; if (!_toObfuscatedAssemblyNames.Contains(name)) { - throw new Exception($"Invalid xml file {configFile}, assembly name {name} is in toObfuscatedAssemblyNames"); + throw new Exception($"Invalid xml file {configFile}, assembly name {name} isn't in toObfuscatedAssemblyNames"); } if (_assemblySpecs.ContainsKey(name)) { @@ -203,10 +221,10 @@ namespace Obfuz.ObfusPasses.ConstEncrypt foreach (AssemblySpec assSpec in _assemblySpecs.Values) { assSpec.rule.InheritParent(_global); - foreach (TypeSpec typeSpec in assSpec.typeSpecs) + foreach (TypeSpec typeSpec in assSpec.types) { typeSpec.rule.InheritParent(assSpec.rule); - foreach (MethodSpec methodSpec in typeSpec.methodSpecs) + foreach (MethodSpec methodSpec in typeSpec.methods) { methodSpec.rule.InheritParent(typeSpec.rule); } @@ -214,109 +232,61 @@ namespace Obfuz.ObfusPasses.ConstEncrypt } } - private bool ParseBool(string str) - { - switch (str.ToLowerInvariant()) - { - case "1": - case "true": return true; - case "0": - case "false": return false; - default: throw new Exception($"Invalid bool value {str}"); - } - } - - private int? ParseNullableInt(string str) - { - if (string.IsNullOrEmpty(str)) - { - return null; - } - return int.Parse(str); - } - - private long? ParseNullableLong(string str) - { - if (string.IsNullOrEmpty(str)) - { - return null; - } - return long.Parse(str); - } - - private float? ParseNullableFloat(string str) - { - if (string.IsNullOrEmpty(str)) - { - return null; - } - return float.Parse(str); - } - - private double? ParseNullableDouble(string str) - { - if (string.IsNullOrEmpty(str)) - { - return null; - } - return double.Parse(str); - } - private ObfuscationRule ParseObfuscationRule(XmlElement ele, bool parseWhitelist) { var rule = new ObfuscationRule(); if (ele.HasAttribute("disableEncrypt")) { - rule.disableEncrypt = ParseBool(ele.GetAttribute("disableEncrypt")); + rule.disableEncrypt = ConfigUtil.ParseBool(ele.GetAttribute("disableEncrypt")); } if (ele.HasAttribute("encryptInt")) { - rule.encryptInt = ParseBool(ele.GetAttribute("encryptInt")); + rule.encryptInt = ConfigUtil.ParseBool(ele.GetAttribute("encryptInt")); } if (ele.HasAttribute("encryptLong")) { - rule.encryptLong = ParseBool(ele.GetAttribute("encryptLong")); + rule.encryptLong = ConfigUtil.ParseBool(ele.GetAttribute("encryptLong")); } if (ele.HasAttribute("encryptFloat")) { - rule.encryptFloat = ParseBool(ele.GetAttribute("encryptFloat")); + rule.encryptFloat = ConfigUtil.ParseBool(ele.GetAttribute("encryptFloat")); } if (ele.HasAttribute("encryptDouble")) { - rule.encryptDouble = ParseBool(ele.GetAttribute("encryptDouble")); + rule.encryptDouble = ConfigUtil.ParseBool(ele.GetAttribute("encryptDouble")); } if (ele.HasAttribute("encryptBytes")) { - rule.encryptArray = ParseBool(ele.GetAttribute("encryptArray")); + rule.encryptArray = ConfigUtil.ParseBool(ele.GetAttribute("encryptArray")); } if (ele.HasAttribute("encryptString")) { - rule.encryptString = ParseBool(ele.GetAttribute("encryptString")); + rule.encryptString = ConfigUtil.ParseBool(ele.GetAttribute("encryptString")); } if (ele.HasAttribute("encryptConstInLoop")) { - rule.encryptConstInLoop = ParseBool(ele.GetAttribute("encryptConstInLoop")); + rule.encryptConstInLoop = ConfigUtil.ParseBool(ele.GetAttribute("encryptConstInLoop")); } if (ele.HasAttribute("encryptStringInLoop")) { - rule.encryptStringInLoop = ParseBool(ele.GetAttribute("encryptStringInLoop")); + rule.encryptStringInLoop = ConfigUtil.ParseBool(ele.GetAttribute("encryptStringInLoop")); } if (ele.HasAttribute("cacheConstInLoop")) { - rule.cacheConstInLoop = ParseBool(ele.GetAttribute("cacheConstInLoop")); + rule.cacheConstInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheConstInLoop")); } if (ele.HasAttribute("cacheConstNotInLoop")) { - rule.cacheConstNotInLoop = ParseBool(ele.GetAttribute("cacheConstNotInLoop")); + rule.cacheConstNotInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheConstNotInLoop")); } if (ele.HasAttribute("cacheStringInLoop")) { - rule.cacheStringInLoop = ParseBool(ele.GetAttribute("cacheStringInLoop")); + rule.cacheStringInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheStringInLoop")); } if (ele.HasAttribute("cacheStringNotInLoop")) { - rule.cacheStringNotInLoop = ParseBool(ele.GetAttribute("cacheStringNotInLoop")); + rule.cacheStringNotInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheStringNotInLoop")); } if (parseWhitelist) { @@ -367,7 +337,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt { throw new Exception($"Invalid xml file, int-range {value} is invalid"); } - rule.notEncryptIntRanges.Add(new NumberRange(ParseNullableInt(parts[0]), ParseNullableInt(parts[1]))); + rule.notEncryptIntRanges.Add(new NumberRange(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1]))); break; } case "long-range": @@ -377,7 +347,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt { throw new Exception($"Invalid xml file, long-range {value} is invalid"); } - rule.notEncryptLongRanges.Add(new NumberRange(ParseNullableLong(parts[0]), ParseNullableLong(parts[1]))); + rule.notEncryptLongRanges.Add(new NumberRange(ConfigUtil.ParseNullableLong(parts[0]), ConfigUtil.ParseNullableLong(parts[1]))); break; } case "float-range": @@ -387,7 +357,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt { throw new Exception($"Invalid xml file, float-range {value} is invalid"); } - rule.notEncryptFloatRanges.Add(new NumberRange(ParseNullableFloat(parts[0]), ParseNullableFloat(parts[1]))); + rule.notEncryptFloatRanges.Add(new NumberRange(ConfigUtil.ParseNullableFloat(parts[0]), ConfigUtil.ParseNullableFloat(parts[1]))); break; } case "double-range": @@ -397,7 +367,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt { throw new Exception($"Invalid xml file, double-range {value} is invalid"); } - rule.notEncryptDoubleRanges.Add(new NumberRange(ParseNullableDouble(parts[0]), ParseNullableDouble(parts[1]))); + rule.notEncryptDoubleRanges.Add(new NumberRange(ConfigUtil.ParseNullableDouble(parts[0]), ConfigUtil.ParseNullableDouble(parts[1]))); break; } case "string-length-range": @@ -407,7 +377,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt { throw new Exception($"Invalid xml file, string-length-range {value} is invalid"); } - rule.notEncryptStringLengthRanges.Add(new NumberRange(ParseNullableInt(parts[0]), ParseNullableInt(parts[1]))); + rule.notEncryptStringLengthRanges.Add(new NumberRange(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1]))); break; } case "array-length-range": @@ -417,7 +387,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt { throw new Exception($"Invalid xml file, array-length-range {value} is invalid"); } - rule.notEncryptArrayLengthRanges.Add(new NumberRange(ParseNullableInt(parts[0]), ParseNullableInt(parts[1]))); + rule.notEncryptArrayLengthRanges.Add(new NumberRange(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1]))); break; } default: throw new Exception($"Invalid xml file, unknown whitelist type {type} in {childEle.Name} node"); @@ -447,7 +417,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt switch (ele.Name) { case "type": - assemblySpec.typeSpecs.Add(ParseType(ele)); + assemblySpec.types.Add(ParseType(ele)); break; default: throw new Exception($"Invalid xml file, unknown node {ele.Name}"); @@ -475,7 +445,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt switch (ele.Name) { case "method": - typeSpec.methodSpecs.Add(ParseMethod(ele)); + typeSpec.methods.Add(ParseMethod(ele)); break; default: throw new Exception($"Invalid xml file, unknown node {ele.Name}"); @@ -506,11 +476,11 @@ namespace Obfuz.ObfusPasses.ConstEncrypt return _global; } string declaringTypeName = method.DeclaringType.FullName; - foreach (var typeSpec in assSpec.typeSpecs) + foreach (var typeSpec in assSpec.types) { if (typeSpec.nameMatcher.IsMatch(declaringTypeName)) { - foreach (var methodSpec in typeSpec.methodSpecs) + foreach (var methodSpec in typeSpec.methods) { if (methodSpec.nameMatcher.IsMatch(method.Name)) { diff --git a/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs b/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs index 766039f..bb8913d 100644 --- a/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs +++ b/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs @@ -15,18 +15,18 @@ namespace Obfuz.ObfusPasses.ConstEncrypt public class ConstEncryptPass : BasicBlockObfuscationPassBase { - private readonly string _configFile; + private readonly List _configFiles; private IEncryptPolicy _dataObfuscatorPolicy; private IConstEncryptor _dataObfuscator; public ConstEncryptPass(ConstEncryptSettings settings) { - _configFile = settings.configFile; + _configFiles = settings.configFiles.ToList(); } public override void Start(ObfuscationPassContext ctx) { - _dataObfuscatorPolicy = new ConfigurableEncryptPolicy(ctx.toObfuscatedAssemblyNames, _configFile); + _dataObfuscatorPolicy = new ConfigurableEncryptPolicy(ctx.toObfuscatedAssemblyNames, _configFiles); _dataObfuscator = new DefaultConstEncryptor(); } @@ -40,7 +40,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt return _dataObfuscatorPolicy.NeedObfuscateMethod(method); } - protected override bool TryObfuscateInstruction(MethodDef method, Instruction inst, BasicBlock block, int instructionIndex, + protected override bool TryObfuscateInstruction(MethodDef method, Instruction inst, BasicBlock block, int instructionIndex, IList globalInstructions, List outputInstructions, List totalFinalInstructions) { bool currentInLoop = block.inLoop; @@ -127,7 +127,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt { if (((IMethod)inst.Operand).FullName == "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)") { - Instruction prevInst = block.instructions[instructionIndex - 1]; + Instruction prevInst = globalInstructions[instructionIndex - 1]; if (prevInst.OpCode.Code == Code.Ldtoken) { IField rvaField = (IField)prevInst.Operand; diff --git a/Editor/Settings/CallObfusSettings.cs b/Editor/Settings/CallObfusSettings.cs index c2e546e..280e287 100644 --- a/Editor/Settings/CallObfusSettings.cs +++ b/Editor/Settings/CallObfusSettings.cs @@ -3,11 +3,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using UnityEngine; namespace Obfuz.Settings { [Serializable] public class CallObfusSettings { + [Tooltip("config xml files")] + public string[] configFiles; } } diff --git a/Editor/Settings/ConstEncryptSettings.cs b/Editor/Settings/ConstEncryptSettings.cs index 8eb2426..867f255 100644 --- a/Editor/Settings/ConstEncryptSettings.cs +++ b/Editor/Settings/ConstEncryptSettings.cs @@ -10,7 +10,7 @@ namespace Obfuz.Settings [Serializable] public class ConstEncryptSettings { - [Tooltip("config xml file")] - public string configFile; + [Tooltip("config xml files")] + public string[] configFiles; } } diff --git a/Editor/Utils/ConfigUtil.cs b/Editor/Utils/ConfigUtil.cs new file mode 100644 index 0000000..93ac5d0 --- /dev/null +++ b/Editor/Utils/ConfigUtil.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public static class ConfigUtil + { + + public static bool ParseBool(string str) + { + switch (str.ToLowerInvariant()) + { + case "1": + case "true": return true; + case "0": + case "false": return false; + default: throw new Exception($"Invalid bool value {str}"); + } + } + + public static int? ParseNullableInt(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + return int.Parse(str); + } + + public static long? ParseNullableLong(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + return long.Parse(str); + } + + public static float? ParseNullableFloat(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + return float.Parse(str); + } + + public static double? ParseNullableDouble(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + return double.Parse(str); + } + } +}