完善CallObfus

backup
walon 2025-05-10 09:41:45 +08:00
parent 83c0b921e3
commit 8c48e6bf61
17 changed files with 712 additions and 187 deletions

View File

@ -37,7 +37,7 @@ namespace Obfuz.ObfusPasses
protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, BasicBlock block, int instructionIndex, protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, BasicBlock block, int instructionIndex,
List<Instruction> outputInstructions, List<Instruction> totalFinalInstructions); IList<Instruction> globalInstructions, List<Instruction> outputInstructions, List<Instruction> totalFinalInstructions);
private void ObfuscateData(MethodDef method) private void ObfuscateData(MethodDef method)
{ {
@ -52,7 +52,7 @@ namespace Obfuz.ObfusPasses
Instruction inst = instructions[i]; Instruction inst = instructions[i];
BasicBlock block = bbc.GetBasicBlockByInstruction(inst); BasicBlock block = bbc.GetBasicBlockByInstruction(inst);
outputInstructions.Clear(); 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. // 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 // we replace it with nop now, then remove it in CleanUpInstructionPass

View File

@ -13,19 +13,20 @@ using Obfuz.Settings;
namespace Obfuz.ObfusPasses.CallObfus namespace Obfuz.ObfusPasses.CallObfus
{ {
public class CallObfusPass : InstructionObfuscationPassBase public class CallObfusPass : BasicBlockObfuscationPassBase
{ {
private readonly List<string> _configFiles;
private readonly IRandom _random; private readonly IRandom _random;
private readonly IEncryptor _encryptor; private readonly IEncryptor _encryptor;
private readonly ICallObfusPolicy _dynamicProxyPolicy; private readonly IObfuscator _dynamicProxyObfuscator;
private readonly ICallObfuscator _dynamicProxyObfuscator; private IObfuscationPolicy _dynamicProxyPolicy;
public CallObfusPass(CallObfusSettings settings) public CallObfusPass(CallObfusSettings settings)
{ {
_configFiles = settings.configFiles.ToList();
_random = new RandomWithKey(new byte[] { 0x1, 0x2, 0x3, 0x4 }, 0x5); _random = new RandomWithKey(new byte[] { 0x1, 0x2, 0x3, 0x4 }, 0x5);
_encryptor = new DefaultEncryptor(new byte[] { 0x1A, 0x2B, 0x3C, 0x4D }); _encryptor = new DefaultEncryptor(new byte[] { 0x1A, 0x2B, 0x3C, 0x4D });
_dynamicProxyPolicy = new ConfigurableCallObfusPolicy(); _dynamicProxyObfuscator = new DefaultCallProxyObfuscator(_random, _encryptor);
_dynamicProxyObfuscator = new DefaultProxyCallObfuscator(_random, _encryptor);
} }
public override void Stop(ObfuscationPassContext ctx) public override void Stop(ObfuscationPassContext ctx)
@ -35,7 +36,7 @@ namespace Obfuz.ObfusPasses.CallObfus
public override void Start(ObfuscationPassContext ctx) public override void Start(ObfuscationPassContext ctx)
{ {
_dynamicProxyPolicy = new ConfigurableObfuscationPolicy(ctx.toObfuscatedAssemblyNames, _configFiles);
} }
protected override bool NeedObfuscateMethod(MethodDef method) protected override bool NeedObfuscateMethod(MethodDef method)
@ -43,8 +44,8 @@ namespace Obfuz.ObfusPasses.CallObfus
return _dynamicProxyPolicy.NeedDynamicProxyCallInMethod(method); return _dynamicProxyPolicy.NeedDynamicProxyCallInMethod(method);
} }
protected override bool TryObfuscateInstruction(MethodDef method, Instruction inst, IList<Instruction> instructions, int instructionIndex, protected override bool TryObfuscateInstruction(MethodDef callerMethod, Instruction inst, BasicBlock block,
List<Instruction> outputInstructions, List<Instruction> totalFinalInstructions) int instructionIndex, IList<Instruction> globalInstructions, List<Instruction> outputInstructions, List<Instruction> totalFinalInstructions)
{ {
IMethod calledMethod = inst.Operand as IMethod; IMethod calledMethod = inst.Operand as IMethod;
if (calledMethod == null || !calledMethod.IsMethod) if (calledMethod == null || !calledMethod.IsMethod)
@ -56,32 +57,35 @@ namespace Obfuz.ObfusPasses.CallObfus
return false; return false;
} }
bool callVir;
switch (inst.OpCode.Code) switch (inst.OpCode.Code)
{ {
case Code.Call: case Code.Call:
{ {
if (!_dynamicProxyPolicy.NeedDynamicProxyCalledMethod(calledMethod, false)) callVir = false;
{ break;
return false;
}
_dynamicProxyObfuscator.Obfuscate(method, calledMethod, false, outputInstructions);
return true;
} }
case Code.Callvirt: 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; return false;
} }
if (!_dynamicProxyPolicy.NeedDynamicProxyCalledMethod(calledMethod, true)) callVir = true;
{ break;
return false;
}
_dynamicProxyObfuscator.Obfuscate(method, calledMethod, true, outputInstructions);
return true;
} }
default: return false; 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;
} }
} }
} }

View File

@ -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);
}
}

View File

@ -138,7 +138,7 @@ namespace Obfuz.ObfusPasses.CallObfus
} }
case ThisArgType.ValueType: case ThisArgType.ValueType:
{ {
methodSig.Params.Insert(0, _module.CorLibTypes.UIntPtr); methodSig.Params.Insert(0, _module.CorLibTypes.IntPtr);
break; break;
} }
} }

View File

@ -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<T>.Enumerator GetEnumerator()
if (method.Name == "GetEnumerator")
{
return false;
}
return true;
}
}
}

View File

@ -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<string> _toObfuscatedAssemblyNames;
class WhiteListAssembly
{
public string name;
public NameMatcher nameMatcher;
public bool? obfuscateNone;
public List<WhiteListType> types = new List<WhiteListType>();
}
class WhiteListType
{
public string name;
public NameMatcher nameMatcher;
public bool? obfuscateNone;
public List<WhiteListMethod> methods = new List<WhiteListMethod>();
}
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<TypeSpec> types = new List<TypeSpec>();
}
class TypeSpec
{
public string name;
public NameMatcher nameMatcher;
public ObfuscationRule rule;
public List<MethodSpec> methods = new List<MethodSpec>();
}
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<WhiteListAssembly> _whiteListAssemblies = new List<WhiteListAssembly>();
private readonly Dictionary<string, AssemblySpec> _assemblySpecs = new Dictionary<string, AssemblySpec>();
private readonly Dictionary<IMethod, bool> _whiteListMethodCache = new Dictionary<IMethod, bool>(MethodEqualityComparer.CompareDeclaringTypes);
private readonly Dictionary<MethodDef, ObfuscationRule> _methodRuleCache = new Dictionary<MethodDef, ObfuscationRule>();
public ConfigurableObfuscationPolicy(List<string> toObfuscatedAssemblyNames, List<string> xmlConfigFiles)
{
_toObfuscatedAssemblyNames = toObfuscatedAssemblyNames;
LoadConfigs(xmlConfigFiles);
InheritParentRules();
}
private void LoadConfigs(List<string> 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<T>.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;
}
}
}

View File

@ -6,13 +6,13 @@ using Obfuz.Emit;
namespace Obfuz.ObfusPasses.CallObfus namespace Obfuz.ObfusPasses.CallObfus
{ {
public class DefaultProxyCallObfuscator : CallObfuscatorBase public class DefaultCallProxyObfuscator : ObfuscatorBase
{ {
private readonly IRandom _random; private readonly IRandom _random;
private readonly IEncryptor _encryptor; private readonly IEncryptor _encryptor;
private readonly CallProxyAllocator _proxyCallAllocator; private readonly CallProxyAllocator _proxyCallAllocator;
public DefaultProxyCallObfuscator(IRandom random, IEncryptor encryptor) public DefaultCallProxyObfuscator(IRandom random, IEncryptor encryptor)
{ {
_random = random; _random = random;
_encryptor = encryptor; _encryptor = encryptor;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace Obfuz.ObfusPasses.CallObfus namespace Obfuz.ObfusPasses.CallObfus
{ {
public interface ICallObfuscator public interface IObfuscator
{ {
void Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, List<Instruction> obfuscatedInstructions); void Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, List<Instruction> obfuscatedInstructions);

View File

@ -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);
}
}

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Obfuz.ObfusPasses.CallObfus 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<Instruction> obfuscatedInstructions); public abstract void Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, List<Instruction> obfuscatedInstructions);
public abstract void Done(); public abstract void Done();

View File

@ -109,14 +109,14 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
public string name; public string name;
public NameMatcher nameMatcher; public NameMatcher nameMatcher;
public ObfuscationRule rule; public ObfuscationRule rule;
public List<MethodSpec> methodSpecs = new List<MethodSpec>(); public List<MethodSpec> methods = new List<MethodSpec>();
} }
class AssemblySpec class AssemblySpec
{ {
public string name; public string name;
public ObfuscationRule rule; public ObfuscationRule rule;
public List<TypeSpec> typeSpecs = new List<TypeSpec>(); public List<TypeSpec> types = new List<TypeSpec>();
} }
private static readonly ObfuscationRule s_default = new ObfuscationRule() private static readonly ObfuscationRule s_default = new ObfuscationRule()
@ -140,17 +140,35 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
private readonly Dictionary<string, AssemblySpec> _assemblySpecs = new Dictionary<string, AssemblySpec>(); private readonly Dictionary<string, AssemblySpec> _assemblySpecs = new Dictionary<string, AssemblySpec>();
private readonly Dictionary<MethodDef, ObfuscationRule> _methodRuleCache = new Dictionary<MethodDef, ObfuscationRule>(); private readonly Dictionary<MethodDef, ObfuscationRule> _methodRuleCache = new Dictionary<MethodDef, ObfuscationRule>();
public ConfigurableEncryptPolicy(List<string> toObfuscatedAssemblyNames, string xmlConfigFile) public ConfigurableEncryptPolicy(List<string> toObfuscatedAssemblyNames, List<string> xmlConfigFiles)
{ {
_toObfuscatedAssemblyNames = toObfuscatedAssemblyNames; _toObfuscatedAssemblyNames = toObfuscatedAssemblyNames;
LoadConfig(xmlConfigFile); LoadConfigs(xmlConfigFiles);
InheritParentRules(); InheritParentRules();
} }
private void LoadConfigs(List<string> 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) private void LoadConfig(string configFile)
{ {
if (string.IsNullOrEmpty(configFile)) if (string.IsNullOrEmpty(configFile))
{ {
Debug.LogWarning($"ConfigurableObfuscationPolicy::LoadConfig configFile is empty, using default policy");
return; return;
} }
Debug.Log($"ConfigurableObfuscationPolicy::LoadConfig {configFile}"); Debug.Log($"ConfigurableObfuscationPolicy::LoadConfig {configFile}");
@ -176,7 +194,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
string name = assSpec.name; string name = assSpec.name;
if (!_toObfuscatedAssemblyNames.Contains(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)) if (_assemblySpecs.ContainsKey(name))
{ {
@ -203,10 +221,10 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
foreach (AssemblySpec assSpec in _assemblySpecs.Values) foreach (AssemblySpec assSpec in _assemblySpecs.Values)
{ {
assSpec.rule.InheritParent(_global); assSpec.rule.InheritParent(_global);
foreach (TypeSpec typeSpec in assSpec.typeSpecs) foreach (TypeSpec typeSpec in assSpec.types)
{ {
typeSpec.rule.InheritParent(assSpec.rule); typeSpec.rule.InheritParent(assSpec.rule);
foreach (MethodSpec methodSpec in typeSpec.methodSpecs) foreach (MethodSpec methodSpec in typeSpec.methods)
{ {
methodSpec.rule.InheritParent(typeSpec.rule); 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) private ObfuscationRule ParseObfuscationRule(XmlElement ele, bool parseWhitelist)
{ {
var rule = new ObfuscationRule(); var rule = new ObfuscationRule();
if (ele.HasAttribute("disableEncrypt")) if (ele.HasAttribute("disableEncrypt"))
{ {
rule.disableEncrypt = ParseBool(ele.GetAttribute("disableEncrypt")); rule.disableEncrypt = ConfigUtil.ParseBool(ele.GetAttribute("disableEncrypt"));
} }
if (ele.HasAttribute("encryptInt")) if (ele.HasAttribute("encryptInt"))
{ {
rule.encryptInt = ParseBool(ele.GetAttribute("encryptInt")); rule.encryptInt = ConfigUtil.ParseBool(ele.GetAttribute("encryptInt"));
} }
if (ele.HasAttribute("encryptLong")) if (ele.HasAttribute("encryptLong"))
{ {
rule.encryptLong = ParseBool(ele.GetAttribute("encryptLong")); rule.encryptLong = ConfigUtil.ParseBool(ele.GetAttribute("encryptLong"));
} }
if (ele.HasAttribute("encryptFloat")) if (ele.HasAttribute("encryptFloat"))
{ {
rule.encryptFloat = ParseBool(ele.GetAttribute("encryptFloat")); rule.encryptFloat = ConfigUtil.ParseBool(ele.GetAttribute("encryptFloat"));
} }
if (ele.HasAttribute("encryptDouble")) if (ele.HasAttribute("encryptDouble"))
{ {
rule.encryptDouble = ParseBool(ele.GetAttribute("encryptDouble")); rule.encryptDouble = ConfigUtil.ParseBool(ele.GetAttribute("encryptDouble"));
} }
if (ele.HasAttribute("encryptBytes")) if (ele.HasAttribute("encryptBytes"))
{ {
rule.encryptArray = ParseBool(ele.GetAttribute("encryptArray")); rule.encryptArray = ConfigUtil.ParseBool(ele.GetAttribute("encryptArray"));
} }
if (ele.HasAttribute("encryptString")) if (ele.HasAttribute("encryptString"))
{ {
rule.encryptString = ParseBool(ele.GetAttribute("encryptString")); rule.encryptString = ConfigUtil.ParseBool(ele.GetAttribute("encryptString"));
} }
if (ele.HasAttribute("encryptConstInLoop")) if (ele.HasAttribute("encryptConstInLoop"))
{ {
rule.encryptConstInLoop = ParseBool(ele.GetAttribute("encryptConstInLoop")); rule.encryptConstInLoop = ConfigUtil.ParseBool(ele.GetAttribute("encryptConstInLoop"));
} }
if (ele.HasAttribute("encryptStringInLoop")) if (ele.HasAttribute("encryptStringInLoop"))
{ {
rule.encryptStringInLoop = ParseBool(ele.GetAttribute("encryptStringInLoop")); rule.encryptStringInLoop = ConfigUtil.ParseBool(ele.GetAttribute("encryptStringInLoop"));
} }
if (ele.HasAttribute("cacheConstInLoop")) if (ele.HasAttribute("cacheConstInLoop"))
{ {
rule.cacheConstInLoop = ParseBool(ele.GetAttribute("cacheConstInLoop")); rule.cacheConstInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheConstInLoop"));
} }
if (ele.HasAttribute("cacheConstNotInLoop")) if (ele.HasAttribute("cacheConstNotInLoop"))
{ {
rule.cacheConstNotInLoop = ParseBool(ele.GetAttribute("cacheConstNotInLoop")); rule.cacheConstNotInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheConstNotInLoop"));
} }
if (ele.HasAttribute("cacheStringInLoop")) if (ele.HasAttribute("cacheStringInLoop"))
{ {
rule.cacheStringInLoop = ParseBool(ele.GetAttribute("cacheStringInLoop")); rule.cacheStringInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheStringInLoop"));
} }
if (ele.HasAttribute("cacheStringNotInLoop")) if (ele.HasAttribute("cacheStringNotInLoop"))
{ {
rule.cacheStringNotInLoop = ParseBool(ele.GetAttribute("cacheStringNotInLoop")); rule.cacheStringNotInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheStringNotInLoop"));
} }
if (parseWhitelist) if (parseWhitelist)
{ {
@ -367,7 +337,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
{ {
throw new Exception($"Invalid xml file, int-range {value} is invalid"); throw new Exception($"Invalid xml file, int-range {value} is invalid");
} }
rule.notEncryptIntRanges.Add(new NumberRange<int>(ParseNullableInt(parts[0]), ParseNullableInt(parts[1]))); rule.notEncryptIntRanges.Add(new NumberRange<int>(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1])));
break; break;
} }
case "long-range": case "long-range":
@ -377,7 +347,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
{ {
throw new Exception($"Invalid xml file, long-range {value} is invalid"); throw new Exception($"Invalid xml file, long-range {value} is invalid");
} }
rule.notEncryptLongRanges.Add(new NumberRange<long>(ParseNullableLong(parts[0]), ParseNullableLong(parts[1]))); rule.notEncryptLongRanges.Add(new NumberRange<long>(ConfigUtil.ParseNullableLong(parts[0]), ConfigUtil.ParseNullableLong(parts[1])));
break; break;
} }
case "float-range": case "float-range":
@ -387,7 +357,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
{ {
throw new Exception($"Invalid xml file, float-range {value} is invalid"); throw new Exception($"Invalid xml file, float-range {value} is invalid");
} }
rule.notEncryptFloatRanges.Add(new NumberRange<float>(ParseNullableFloat(parts[0]), ParseNullableFloat(parts[1]))); rule.notEncryptFloatRanges.Add(new NumberRange<float>(ConfigUtil.ParseNullableFloat(parts[0]), ConfigUtil.ParseNullableFloat(parts[1])));
break; break;
} }
case "double-range": case "double-range":
@ -397,7 +367,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
{ {
throw new Exception($"Invalid xml file, double-range {value} is invalid"); throw new Exception($"Invalid xml file, double-range {value} is invalid");
} }
rule.notEncryptDoubleRanges.Add(new NumberRange<double>(ParseNullableDouble(parts[0]), ParseNullableDouble(parts[1]))); rule.notEncryptDoubleRanges.Add(new NumberRange<double>(ConfigUtil.ParseNullableDouble(parts[0]), ConfigUtil.ParseNullableDouble(parts[1])));
break; break;
} }
case "string-length-range": case "string-length-range":
@ -407,7 +377,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
{ {
throw new Exception($"Invalid xml file, string-length-range {value} is invalid"); throw new Exception($"Invalid xml file, string-length-range {value} is invalid");
} }
rule.notEncryptStringLengthRanges.Add(new NumberRange<int>(ParseNullableInt(parts[0]), ParseNullableInt(parts[1]))); rule.notEncryptStringLengthRanges.Add(new NumberRange<int>(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1])));
break; break;
} }
case "array-length-range": case "array-length-range":
@ -417,7 +387,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
{ {
throw new Exception($"Invalid xml file, array-length-range {value} is invalid"); throw new Exception($"Invalid xml file, array-length-range {value} is invalid");
} }
rule.notEncryptArrayLengthRanges.Add(new NumberRange<int>(ParseNullableInt(parts[0]), ParseNullableInt(parts[1]))); rule.notEncryptArrayLengthRanges.Add(new NumberRange<int>(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1])));
break; break;
} }
default: throw new Exception($"Invalid xml file, unknown whitelist type {type} in {childEle.Name} node"); 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) switch (ele.Name)
{ {
case "type": case "type":
assemblySpec.typeSpecs.Add(ParseType(ele)); assemblySpec.types.Add(ParseType(ele));
break; break;
default: default:
throw new Exception($"Invalid xml file, unknown node {ele.Name}"); throw new Exception($"Invalid xml file, unknown node {ele.Name}");
@ -475,7 +445,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
switch (ele.Name) switch (ele.Name)
{ {
case "method": case "method":
typeSpec.methodSpecs.Add(ParseMethod(ele)); typeSpec.methods.Add(ParseMethod(ele));
break; break;
default: default:
throw new Exception($"Invalid xml file, unknown node {ele.Name}"); throw new Exception($"Invalid xml file, unknown node {ele.Name}");
@ -506,11 +476,11 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
return _global; return _global;
} }
string declaringTypeName = method.DeclaringType.FullName; string declaringTypeName = method.DeclaringType.FullName;
foreach (var typeSpec in assSpec.typeSpecs) foreach (var typeSpec in assSpec.types)
{ {
if (typeSpec.nameMatcher.IsMatch(declaringTypeName)) if (typeSpec.nameMatcher.IsMatch(declaringTypeName))
{ {
foreach (var methodSpec in typeSpec.methodSpecs) foreach (var methodSpec in typeSpec.methods)
{ {
if (methodSpec.nameMatcher.IsMatch(method.Name)) if (methodSpec.nameMatcher.IsMatch(method.Name))
{ {

View File

@ -15,18 +15,18 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
public class ConstEncryptPass : BasicBlockObfuscationPassBase public class ConstEncryptPass : BasicBlockObfuscationPassBase
{ {
private readonly string _configFile; private readonly List<string> _configFiles;
private IEncryptPolicy _dataObfuscatorPolicy; private IEncryptPolicy _dataObfuscatorPolicy;
private IConstEncryptor _dataObfuscator; private IConstEncryptor _dataObfuscator;
public ConstEncryptPass(ConstEncryptSettings settings) public ConstEncryptPass(ConstEncryptSettings settings)
{ {
_configFile = settings.configFile; _configFiles = settings.configFiles.ToList();
} }
public override void Start(ObfuscationPassContext ctx) public override void Start(ObfuscationPassContext ctx)
{ {
_dataObfuscatorPolicy = new ConfigurableEncryptPolicy(ctx.toObfuscatedAssemblyNames, _configFile); _dataObfuscatorPolicy = new ConfigurableEncryptPolicy(ctx.toObfuscatedAssemblyNames, _configFiles);
_dataObfuscator = new DefaultConstEncryptor(); _dataObfuscator = new DefaultConstEncryptor();
} }
@ -40,7 +40,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt
return _dataObfuscatorPolicy.NeedObfuscateMethod(method); 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<Instruction> globalInstructions,
List<Instruction> outputInstructions, List<Instruction> totalFinalInstructions) List<Instruction> outputInstructions, List<Instruction> totalFinalInstructions)
{ {
bool currentInLoop = block.inLoop; 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)") 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) if (prevInst.OpCode.Code == Code.Ldtoken)
{ {
IField rvaField = (IField)prevInst.Operand; IField rvaField = (IField)prevInst.Operand;

View File

@ -3,11 +3,14 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine;
namespace Obfuz.Settings namespace Obfuz.Settings
{ {
[Serializable] [Serializable]
public class CallObfusSettings public class CallObfusSettings
{ {
[Tooltip("config xml files")]
public string[] configFiles;
} }
} }

View File

@ -10,7 +10,7 @@ namespace Obfuz.Settings
[Serializable] [Serializable]
public class ConstEncryptSettings public class ConstEncryptSettings
{ {
[Tooltip("config xml file")] [Tooltip("config xml files")]
public string configFile; public string[] configFiles;
} }
} }

View File

@ -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);
}
}
}