diff --git a/com.code-philosophy.obfuz/Editor.meta b/com.code-philosophy.obfuz/Editor.meta new file mode 100644 index 0000000..e4fe3a4 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2326b426d539e084dbddf7f7c23ed1bd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Conf.meta b/com.code-philosophy.obfuz/Editor/Conf.meta new file mode 100644 index 0000000..d42ac4f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Conf.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 241df8eaf3a34dc47a0873c37ddb2695 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs b/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs new file mode 100644 index 0000000..aac0a66 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs @@ -0,0 +1,267 @@ +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 UnityEditor.VersionControl; +using UnityEngine; + +namespace Obfuz.Conf +{ + public interface IRule + { + void InheritParent(T parentRule); + } + + + public interface IMethodRule where R: IRule + { + string Name { get; set; } + NameMatcher NameMatcher { get; set; } + + R Rule { get; set; } + } + + public abstract class MethodRuleBase : IMethodRule where R : IRule + { + public string Name { get; set; } + public NameMatcher NameMatcher { get; set; } + + public R Rule { get; set; } + } + + public interface ITypeRule where T: IMethodRule where R : IRule + { + string Name { get; set; } + + NameMatcher NameMatcher { get; set; } + + R Rule { get; set; } + + List Methods { get; set; } + } + + public abstract class TypeRuleBase : ITypeRule where T : IMethodRule where R : IRule + { + public string Name { get; set; } + + public NameMatcher NameMatcher { get; set; } + + public R Rule { get; set; } + + public List Methods { get; set; } + } + + public interface IAssemblyRule where TType : ITypeRule where TMethod : IMethodRule where TRule : IRule + { + string Name { get; set; } + + TRule Rule { get; set; } + + List Types { get; set; } + } + public abstract class AssemblyRuleBase : IAssemblyRule where TType : ITypeRule where TMethod : IMethodRule where TRule : IRule + { + public string Name { get; set; } + + public TRule Rule { get; set; } + + public List Types { get; set; } + } + + public class XmlAssemblyTypeMethodRuleParser + where TMethod : IMethodRule, new() + where TType : ITypeRule, new() + where TAssembly : IAssemblyRule, new() + where TRule : IRule, new() + { + private readonly HashSet _toObfuscatedAssemblyNames; + private readonly Func _ruleParser; + private readonly Action _unknownNodeTypeHandler; + private readonly Dictionary _assemblySpecs = new Dictionary(); + + public XmlAssemblyTypeMethodRuleParser(IEnumerable toObfuscatedAssemblyNames, Func ruleParser, Action unknownNodeTypeHandler) + { + _toObfuscatedAssemblyNames = new HashSet(toObfuscatedAssemblyNames); + _ruleParser = ruleParser; + _unknownNodeTypeHandler = unknownNodeTypeHandler; + } + + public Dictionary AssemblySpecs => _assemblySpecs; + + public void LoadConfigs(IEnumerable configFiles) + { + foreach (var configFile in configFiles) + { + LoadConfig(configFile); + } + } + + public void LoadConfig(string configFile) + { + if (string.IsNullOrEmpty(configFile)) + { + throw new Exception($"Invalid xml file {configFile}, file name is empty"); + } + 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 "assembly": + { + TAssembly assSpec = ParseAssembly(configFile, ele); + _assemblySpecs.Add(assSpec.Name, assSpec); + break; + } + default: + { + if (_unknownNodeTypeHandler == null) + { + throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + _unknownNodeTypeHandler(configFile, ele); + break; + } + } + } + } + + private TAssembly ParseAssembly(string configFile, XmlElement ele) + { + var assemblySpec = new TAssembly(); + string name = ele.GetAttribute("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"); + } + assemblySpec.Name = name; + assemblySpec.Rule = _ruleParser(configFile, ele); + + var types = new List(); + assemblySpec.Types = types; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement childEle)) + { + continue; + } + switch (childEle.Name) + { + case "type": + { + types.Add(ParseType(configFile, childEle)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + return assemblySpec; + } + + private TType ParseType(string configFile, XmlElement element) + { + var typeSpec = new TType(); + + string name = element.GetAttribute("name"); + typeSpec.Name = name; + typeSpec.NameMatcher = new NameMatcher(name); + typeSpec.Rule = _ruleParser(configFile, element); + + var methods = new List(); + typeSpec.Methods = methods; + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "method": + { + methods.Add(ParseMethod(configFile, ele)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + } + return typeSpec; + } + + private TMethod ParseMethod(string configFile, XmlElement element) + { + var methodSpec = new TMethod(); + string name = element.GetAttribute("name"); + methodSpec.Name = name; + methodSpec.NameMatcher = new NameMatcher(name); + methodSpec.Rule = _ruleParser(configFile, element); + return methodSpec; + } + + public TRule GetMethodRule(MethodDef method, TRule defaultRule) + { + var assemblyName = method.DeclaringType.Module.Assembly.Name; + if (!_assemblySpecs.TryGetValue(assemblyName, out var assSpec)) + { + return defaultRule; + } + 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; + } + + public void InheritParentRules(TRule defaultRule) + { + foreach (TAssembly assSpec in _assemblySpecs.Values) + { + assSpec.Rule.InheritParent(defaultRule); + foreach (TType typeSpec in assSpec.Types) + { + typeSpec.Rule.InheritParent(assSpec.Rule); + foreach (TMethod methodSpec in typeSpec.Methods) + { + methodSpec.Rule.InheritParent(typeSpec.Rule); + } + } + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs.meta b/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs.meta new file mode 100644 index 0000000..84f5aa9 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36a3e142db81f6d4bb54938525e31973 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs b/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs new file mode 100644 index 0000000..591271e --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs @@ -0,0 +1,208 @@ +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 UnityEditor.VersionControl; +using UnityEngine; + +namespace Obfuz.Conf +{ + + + + public class XmlFieldRuleParser where R : class, new() + { + private readonly HashSet _toObfuscatedAssemblyNames; + private readonly Func _ruleParser; + private readonly Action _unknownNodeTypeHandler; + private readonly Dictionary _assemblySpecs = new Dictionary(); + + + private class FieldSpec + { + public string Name { get; set; } + public NameMatcher NameMatcher { get; set; } + + public R Rule { get; set; } + } + + private class TypeSpec + { + public string Name { get; set; } + + public NameMatcher NameMatcher { get; set; } + + public List Fields { get; set; } + } + + private class AssemblySpec + { + public string Name { get; set; } + + public List Types { get; set; } + } + + public XmlFieldRuleParser(IEnumerable toObfuscatedAssemblyNames, Func ruleParser, Action unknownNodeTypeHandler) + { + _toObfuscatedAssemblyNames = new HashSet(toObfuscatedAssemblyNames); + _ruleParser = ruleParser; + _unknownNodeTypeHandler = unknownNodeTypeHandler; + } + + public void LoadConfigs(IEnumerable configFiles) + { + foreach (var configFile in configFiles) + { + LoadConfig(configFile); + } + } + + public void LoadConfig(string configFile) + { + if (string.IsNullOrEmpty(configFile)) + { + throw new Exception($"Invalid xml file {configFile}, file name is empty"); + } + 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 "assembly": + { + AssemblySpec assSpec = ParseAssembly(configFile, ele); + _assemblySpecs.Add(assSpec.Name, assSpec); + break; + } + default: + { + if (_unknownNodeTypeHandler == null) + { + throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + _unknownNodeTypeHandler(configFile, ele); + break; + } + } + } + } + + private AssemblySpec ParseAssembly(string configFile, XmlElement ele) + { + var assemblySpec = new AssemblySpec(); + string name = ele.GetAttribute("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"); + } + assemblySpec.Name = name; + + var types = new List(); + assemblySpec.Types = types; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement childEle)) + { + continue; + } + switch (childEle.Name) + { + case "type": + { + types.Add(ParseType(configFile, childEle)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + return assemblySpec; + } + + private TypeSpec ParseType(string configFile, XmlElement element) + { + var typeSpec = new TypeSpec(); + + string name = element.GetAttribute("name"); + typeSpec.Name = name; + typeSpec.NameMatcher = new NameMatcher(name); + + var fields = new List(); + typeSpec.Fields = fields; + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "field": + { + fields.Add(ParseField(configFile, ele)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + } + return typeSpec; + } + + private FieldSpec ParseField(string configFile, XmlElement element) + { + var fieldSpec = new FieldSpec(); + string name = element.GetAttribute("name"); + fieldSpec.Name = name; + fieldSpec.NameMatcher = new NameMatcher(name); + fieldSpec.Rule = _ruleParser(configFile, element); + return fieldSpec; + } + + public R GetFieldRule(FieldDef field) + { + var assemblyName = field.DeclaringType.Module.Assembly.Name; + if (!_assemblySpecs.TryGetValue(assemblyName, out var assSpec)) + { + return null; + } + string declaringTypeName = field.DeclaringType.FullName; + foreach (var typeSpec in assSpec.Types) + { + if (typeSpec.NameMatcher.IsMatch(declaringTypeName)) + { + foreach (var fieldSpec in typeSpec.Fields) + { + if (fieldSpec.NameMatcher.IsMatch(field.Name)) + { + return fieldSpec.Rule; + } + } + } + } + return null; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs.meta b/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs.meta new file mode 100644 index 0000000..d8995b9 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1578270b9b81e1e4dba84d562c91090f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs b/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs new file mode 100644 index 0000000..6d0fc7e --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs @@ -0,0 +1,547 @@ +using dnlib.DotNet; +using Obfuz.ObfusPasses; +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 +{ + public class ConfigurablePassPolicy + { + class PassRule + { + public ObfuscationPassType? enablePasses; + public ObfuscationPassType? disablePasses; + public ObfuscationPassType? addPasses; + public ObfuscationPassType? removePasses; + public ObfuscationPassType finalPasses; + + public void InheritParent(PassRule parentRule, ObfuscationPassType globalEnabledPasses) + { + finalPasses = parentRule.finalPasses; + if (enablePasses != null) + { + finalPasses = enablePasses.Value; + } + if (disablePasses != null) + { + finalPasses = ~disablePasses.Value; + } + if (addPasses != null) + { + finalPasses |= addPasses.Value; + } + if (removePasses != null) + { + finalPasses &= ~removePasses.Value; + } + finalPasses &= globalEnabledPasses; + } + } + + class SpecBase + { + public string name; + public NameMatcher nameMatcher; + public PassRule rule; + } + + class MethodSpec : SpecBase + { + } + + class FieldSpec : SpecBase + { + } + + class PropertySpec : SpecBase + { + } + + class EventSpec : SpecBase + { + } + + class TypeSpec : SpecBase + { + public List fields = new List(); + public List methods = new List(); + public List properties = new List(); + public List events = new List(); + } + + class AssemblySpec + { + public string name; + public NameMatcher nameMatcher; + public PassRule rule; + public List types = new List(); + } + + private readonly ObfuscationPassType _enabledPasses; + private readonly HashSet _toObfuscatedAssemblyNames; + private readonly List _assemblySpecs = new List(); + private readonly PassRule _defaultPassRule; + + private string _curLoadingConfig; + + public ConfigurablePassPolicy(IEnumerable toObfuscatedAssemblyNames, ObfuscationPassType enabledPasses, List configFiles) + { + _toObfuscatedAssemblyNames = new HashSet(toObfuscatedAssemblyNames); + _enabledPasses = enabledPasses; + _defaultPassRule = new PassRule { finalPasses = enabledPasses }; + LoadConfigs(configFiles); + InheritParentRules(enabledPasses); + } + + private void LoadConfigs(IEnumerable configFiles) + { + foreach (var configFile in configFiles) + { + LoadConfig(configFile); + } + } + + private void InheritParentRules(ObfuscationPassType enablePasses) + { + var defaultRule = new PassRule + { + enablePasses = enablePasses, + finalPasses = enablePasses, + }; + foreach (AssemblySpec assSpec in _assemblySpecs) + { + assSpec.rule.InheritParent(defaultRule, enablePasses); + foreach (TypeSpec typeSpec in assSpec.types) + { + typeSpec.rule.InheritParent(assSpec.rule, enablePasses); + foreach (FieldSpec fieldSpec in typeSpec.fields) + { + fieldSpec.rule.InheritParent(typeSpec.rule, enablePasses); + } + foreach (MethodSpec methodSpec in typeSpec.methods) + { + methodSpec.rule.InheritParent(typeSpec.rule, enablePasses); + } + foreach (PropertySpec propertySpec in typeSpec.properties) + { + propertySpec.rule.InheritParent(typeSpec.rule, enablePasses); + } + foreach (EventSpec eventSpec in typeSpec.events) + { + eventSpec.rule.InheritParent(typeSpec.rule, enablePasses); + } + } + } + } + + public void LoadConfig(string configFile) + { + if (string.IsNullOrEmpty(configFile)) + { + throw new Exception($"Invalid xml file {configFile}, file name is empty"); + } + _curLoadingConfig = configFile; + + Debug.Log($"ConfigurablePassPolicy::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 "assembly": + { + AssemblySpec assSpec = ParseAssembly(ele); + _assemblySpecs.Add(assSpec); + break; + } + default: + { + throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + } + } + + (bool, ObfuscationPassType) ParseObfuscationType(string obfuscationPassTypesStr) + { + bool delta = false; + if (obfuscationPassTypesStr[0] == '+' || obfuscationPassTypesStr[0] == '-') + { + delta = true; + obfuscationPassTypesStr = obfuscationPassTypesStr.Substring(1); + } + ObfuscationPassType passType = ObfuscationPassType.None; + foreach (var passName in obfuscationPassTypesStr.Split('|')) + { + if (Enum.TryParse< ObfuscationPassType>(passName, out var pass)) + { + passType |= pass; + } + else + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, unknown pass type {passName}"); + } + } + return (delta, passType); + } + + private PassRule ParseRule(XmlElement ele) + { + var r = new PassRule(); + if (ele.HasAttribute("enable")) + { + string enablePassStr = ele.GetAttribute("enable"); + if (string.IsNullOrEmpty(enablePassStr)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, enable attribute is empty"); + } + var (delta, passType) = ParseObfuscationType(enablePassStr); + if (delta) + { + r.addPasses = passType; + } + else + { + r.enablePasses = passType; + } + } + if (ele.HasAttribute("disable")) + { + string disablePassStr = ele.GetAttribute("disable"); + if (string.IsNullOrEmpty(disablePassStr)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, disable attribute is empty"); + } + var (delta, passType) = ParseObfuscationType(disablePassStr); + if (delta) + { + r.removePasses = passType; + } + else + { + r.disablePasses = passType; + } + } + if (r.enablePasses != null && (r.disablePasses != null || r.addPasses != null || r.removePasses != null)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, enable and disable can't be used together"); + } + if (r.disablePasses != null && (r.enablePasses != null || r.addPasses != null || r.removePasses != null)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, disable and enable can't be used together"); + } + return r; + } + + private AssemblySpec ParseAssembly(XmlElement ele) + { + var assemblySpec = new AssemblySpec(); + string name = ele.GetAttribute("name"); + if (!_toObfuscatedAssemblyNames.Contains(name)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, assembly name {name} isn't in toObfuscatedAssemblyNames"); + } + assemblySpec.name = name; + assemblySpec.nameMatcher = new NameMatcher(name); + assemblySpec.rule = ParseRule(ele); + + + var types = assemblySpec.types; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement childEle)) + { + continue; + } + switch (childEle.Name) + { + case "type": + { + types.Add(ParseType(childEle)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + return assemblySpec; + } + + private TypeSpec ParseType(XmlElement element) + { + var typeSpec = new TypeSpec(); + + string name = element.GetAttribute("name"); + typeSpec.name = name; + typeSpec.nameMatcher = new NameMatcher(name); + typeSpec.rule = ParseRule(element); + + List fields = typeSpec.fields; + List methods = typeSpec.methods; + List properties = typeSpec.properties; + List events = typeSpec.events; + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "field": + { + fields.Add(ParseField(ele)); + break; + } + case "method": + { + methods.Add(ParseMethod(ele)); + break; + } + case "property": + { + properties.Add(ParseProperty(ele)); + break; + } + case "event": + { + events.Add(ParseEvent(ele)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + } + return typeSpec; + } + + private void ParseSpecObject(XmlElement element, SpecBase obj) + { + string name = element.GetAttribute("name"); + obj.name = name; + obj.nameMatcher = new NameMatcher(name); + obj.rule = ParseRule(element); + } + + private FieldSpec ParseField(XmlElement element) + { + var fieldSpec = new FieldSpec(); + ParseSpecObject(element, fieldSpec); + return fieldSpec; + } + + private MethodSpec ParseMethod(XmlElement element) + { + var methodSpec = new MethodSpec(); + ParseSpecObject(element, methodSpec); + return methodSpec; + } + + private PropertySpec ParseProperty(XmlElement element) + { + var propertySpec = new PropertySpec(); + ParseSpecObject(element, propertySpec); + return propertySpec; + } + + private EventSpec ParseEvent(XmlElement element) + { + var eventSpec = new EventSpec(); + ParseSpecObject(element, eventSpec); + return eventSpec; + } + + private readonly Dictionary _modulePassRuleCaches = new Dictionary(); + private readonly Dictionary _typePassRuleCaches = new Dictionary(); + private readonly Dictionary _methodPassRuleCaches = new Dictionary(); + private readonly Dictionary _fieldPassRuleCaches = new Dictionary(); + private readonly Dictionary _propertyPassRuleCaches = new Dictionary(); + private readonly Dictionary _eventPassRuleCaches = new Dictionary(); + + + private (AssemblySpec, PassRule) GetAssemblySpec(ModuleDef module) + { + if (!_modulePassRuleCaches.TryGetValue(module, out var result)) + { + result = (null, _defaultPassRule); + string assName = module.Assembly.Name; + foreach (var ass in _assemblySpecs) + { + if (ass.nameMatcher.IsMatch(assName)) + { + result = (ass, _defaultPassRule); + break; + } + } + _modulePassRuleCaches.Add(module, result); + } + return result; + } + + private (TypeSpec, PassRule) GetTypeSpec(TypeDef type) + { + if (!_typePassRuleCaches.TryGetValue(type, out var result)) + { + var assResult = GetAssemblySpec(type.Module); + result = (null, assResult.Item2); + if (assResult.Item1 != null) + { + string typeName = type.FullName; + foreach (var typeSpec in assResult.Item1.types) + { + if (typeSpec.nameMatcher.IsMatch(typeName)) + { + result = (typeSpec, typeSpec.rule); + break; + } + } + } + _typePassRuleCaches.Add(type, result); + } + return result; + } + + private (MethodSpec, PassRule) GetMethodSpec(MethodDef method) + { + if (!_methodPassRuleCaches.TryGetValue(method, out var result)) + { + var typeResult = GetTypeSpec(method.DeclaringType); + result = (null, typeResult.Item2); + if (typeResult.Item1 != null) + { + string methodName = method.Name; + foreach (var methodSpec in typeResult.Item1.methods) + { + if (methodSpec.nameMatcher.IsMatch(methodName)) + { + result = (methodSpec, methodSpec.rule); + break; + } + } + } + _methodPassRuleCaches.Add(method, result); + } + return result; + } + + private (FieldSpec, PassRule) GetFieldSpec(FieldDef field) + { + if (!_fieldPassRuleCaches.TryGetValue(field, out var result)) + { + var typeResult = GetTypeSpec(field.DeclaringType); + result = (null, typeResult.Item2); + if (typeResult.Item1 != null) + { + string fieldName = field.Name; + foreach (var fieldSpec in typeResult.Item1.fields) + { + if (fieldSpec.nameMatcher.IsMatch(fieldName)) + { + result = (fieldSpec, fieldSpec.rule); + break; + } + } + } + _fieldPassRuleCaches.Add(field, result); + } + return result; + } + + private (PropertySpec, PassRule) GetPropertySpec(PropertyDef property) + { + if (!_propertyPassRuleCaches.TryGetValue(property, out var result)) + { + var typeResult = GetTypeSpec(property.DeclaringType); + result = (null, typeResult.Item2); + if (typeResult.Item1 != null) + { + string propertyName = property.Name; + foreach (var propertySpec in typeResult.Item1.properties) + { + if (propertySpec.nameMatcher.IsMatch(propertyName)) + { + result = (propertySpec, propertySpec.rule); + break; + } + } + } + _propertyPassRuleCaches.Add(property, result); + } + return result; + } + + private (EventSpec, PassRule) GetEventSpec(EventDef eventDef) + { + if (!_eventPassRuleCaches.TryGetValue(eventDef, out var result)) + { + var typeResult = GetTypeSpec(eventDef.DeclaringType); + result = (null, typeResult.Item2); + if (typeResult.Item1 != null) + { + string eventName = eventDef.Name; + foreach (var eventSpec in typeResult.Item1.events) + { + if (eventSpec.nameMatcher.IsMatch(eventName)) + { + result = (eventSpec, eventSpec.rule); + break; + } + } + } + _eventPassRuleCaches.Add(eventDef, result); + } + return result; + } + + + public ObfuscationPassType GetAssemblyObfuscationPasses(ModuleDef module) + { + return GetAssemblySpec(module).Item2.finalPasses; + } + + public ObfuscationPassType GetTypeObfuscationPasses(TypeDef type) + { + return GetTypeSpec(type).Item2.finalPasses; + } + + public ObfuscationPassType GetMethodObfuscationPasses(MethodDef method) + { + return GetMethodSpec(method).Item2.finalPasses; + } + + public ObfuscationPassType GetFieldObfuscationPasses(FieldDef field) + { + return GetFieldSpec(field).Item2.finalPasses; + } + + public ObfuscationPassType GetPropertyObfuscationPasses(PropertyDef property) + { + return GetPropertySpec(property).Item2.finalPasses; + } + + public ObfuscationPassType GetEventObfuscationPasses(EventDef eventDef) + { + return GetEventSpec(eventDef).Item2.finalPasses; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs.meta new file mode 100644 index 0000000..9b85183 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 41044699810a34f4780e14de084bf7d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ConstValues.cs b/com.code-philosophy.obfuz/Editor/ConstValues.cs new file mode 100644 index 0000000..2792a00 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ConstValues.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Editor +{ + public static class ConstValues + { + public const string ObfuzInternalSymbolNamePrefix = "$Obfuz$"; + } +} diff --git a/com.code-philosophy.obfuz/Editor/ConstValues.cs.meta b/com.code-philosophy.obfuz/Editor/ConstValues.cs.meta new file mode 100644 index 0000000..33f4160 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ConstValues.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aee7817ed523a5e4ea42104013e8a775 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Data.meta b/com.code-philosophy.obfuz/Editor/Data.meta new file mode 100644 index 0000000..d162ae8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3319ebe75a42f3d4d996846ca09ed099 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs b/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs new file mode 100644 index 0000000..caf1725 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs @@ -0,0 +1,344 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Editor; +using Obfuz.Emit; +using Obfuz.Utils; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Obfuz.Data +{ + public class ModuleConstFieldAllocator : IGroupByModuleEntity + { + private ModuleDef _module; + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly RvaDataAllocator _rvaDataAllocator; + private readonly GroupByModuleEntityManager _moduleEntityManager; + private EncryptionScopeInfo _encryptionScope; + private RandomCreator _randomCreator; + private IEncryptor _encryptor; + + private TypeDef _holderTypeDef; + + class ConstFieldInfo + { + public FieldDef field; + public object value; + } + + class AnyComparer : IEqualityComparer + { + public new bool Equals(object x, object y) + { + if (x is byte[] xBytes && y is byte[] yBytes) + { + return StructuralComparisons.StructuralEqualityComparer.Equals(xBytes, yBytes); + } + return x.Equals(y); + } + + public static int ComputeHashCode(object obj) + { + return HashUtil.ComputePrimitiveOrStringOrBytesHashCode(obj); + } + + public int GetHashCode(object obj) + { + return ComputeHashCode(obj); + } + } + + private readonly Dictionary _allocatedFields = new Dictionary(new AnyComparer()); + private readonly Dictionary _field2Fields = new Dictionary(); + + private readonly List _holderTypeDefs = new List(); + private bool _done; + + + public ModuleConstFieldAllocator(EncryptionScopeProvider encryptionScopeProvider, RvaDataAllocator rvaDataAllocator, GroupByModuleEntityManager moduleEntityManager) + { + _encryptionScopeProvider = encryptionScopeProvider; + _rvaDataAllocator = rvaDataAllocator; + _moduleEntityManager = moduleEntityManager; + } + + public void Init(ModuleDef mod) + { + _module = mod; + _encryptionScope = _encryptionScopeProvider.GetScope(mod); + _randomCreator = _encryptionScope.localRandomCreator; + _encryptor = _encryptionScope.encryptor; + } + + const int maxFieldCount = 1000; + + + private TypeSig GetTypeSigOfValue(object value) + { + if (value is int) + return _module.CorLibTypes.Int32; + if (value is long) + return _module.CorLibTypes.Int64; + if (value is float) + return _module.CorLibTypes.Single; + if (value is double) + return _module.CorLibTypes.Double; + if (value is string) + return _module.CorLibTypes.String; + if (value is byte[]) + return new SZArraySig(_module.CorLibTypes.Byte); + throw new NotSupportedException($"Unsupported type: {value.GetType()}"); + } + + private ConstFieldInfo CreateConstFieldInfo(object value) + { + if (_holderTypeDef == null || _holderTypeDef.Fields.Count >= maxFieldCount) + { + _module.EnableTypeDefFindCache = false; + ITypeDefOrRef objectTypeRef = _module.Import(typeof(object)); + _holderTypeDef = new TypeDefUser($"{ConstValues.ObfuzInternalSymbolNamePrefix}ConstFieldHolder${_holderTypeDefs.Count}", objectTypeRef); + _module.Types.Add(_holderTypeDef); + _holderTypeDefs.Add(_holderTypeDef); + _module.EnableTypeDefFindCache = true; + } + + var field = new FieldDefUser($"{ConstValues.ObfuzInternalSymbolNamePrefix}RVA_Value{_holderTypeDef.Fields.Count}", new FieldSig(GetTypeSigOfValue(value)), FieldAttributes.Static | FieldAttributes.Public | FieldAttributes.InitOnly); + field.DeclaringType = _holderTypeDef; + return new ConstFieldInfo + { + field = field, + value = value, + }; + } + + private FieldDef AllocateAny(object value) + { + if (_done) + { + throw new Exception("can't Allocate after done"); + } + if (!_allocatedFields.TryGetValue(value, out var field)) + { + field = CreateConstFieldInfo(value); + _allocatedFields.Add(value, field); + _field2Fields.Add(field.field, field); + } + return field.field; + } + + public FieldDef Allocate(int value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(long value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(float value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(double value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(string value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(byte[] value) + { + return AllocateAny(value); + } + + private DefaultMetadataImporter GetModuleMetadataImporter() + { + return _moduleEntityManager.GetDefaultModuleMetadataImporter(_module, _encryptionScopeProvider); + } + + private void CreateCCtorOfRvaTypeDef(TypeDef type) + { + var cctor = new MethodDefUser(".cctor", + MethodSig.CreateStatic(_module.CorLibTypes.Void), + MethodImplAttributes.IL | MethodImplAttributes.Managed, + MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Private); + cctor.DeclaringType = type; + //_rvaTypeDef.Methods.Add(cctor); + var body = new CilBody(); + cctor.Body = body; + var ins = body.Instructions; + + //IMethod method = _module.Import(typeof(System.Runtime.CompilerServices.RuntimeHelpers).GetMethod("InitializeArray", new[] { typeof(Array), typeof(RuntimeFieldHandle) })); + //Assert.IsNotNull(method); + + + DefaultMetadataImporter importer = GetModuleMetadataImporter(); + // TODO. obfuscate init codes + foreach (var field in type.Fields) + { + ConstFieldInfo constInfo = _field2Fields[field]; + IRandom localRandom = _randomCreator(HashUtil.ComputePrimitiveOrStringOrBytesHashCode(constInfo.value)); + int ops = EncryptionUtil.GenerateEncryptionOpCodes(localRandom, _encryptor, 4); + int salt = localRandom.NextInt(); + switch (constInfo.value) + { + case int i: + { + int encryptedValue = _encryptor.Encrypt(i, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(_module, encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaInt)); + break; + } + case long l: + { + long encryptedValue = _encryptor.Encrypt(l, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(_module, encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaLong)); + break; + } + case float f: + { + float encryptedValue = _encryptor.Encrypt(f, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(_module, encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaFloat)); + break; + } + case double d: + { + double encryptedValue = _encryptor.Encrypt(d, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(_module, encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaDouble)); + break; + } + case string s: + { + byte[] encryptedValue = _encryptor.Encrypt(s, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(_module, encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + Assert.AreEqual(encryptedValue.Length, rvaData.size); + ins.Add(Instruction.CreateLdcI4(encryptedValue.Length)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaString)); + break; + } + case byte[] bs: + { + byte[] encryptedValue = _encryptor.Encrypt(bs, 0, bs.Length, ops, salt); + Assert.AreEqual(encryptedValue.Length, bs.Length); + RvaData rvaData = _rvaDataAllocator.Allocate(_module, encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(bs.Length)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaBytes)); + break; + } + default: throw new NotSupportedException($"Unsupported type: {constInfo.value.GetType()}"); + } + ins.Add(Instruction.Create(OpCodes.Stsfld, field)); + } + ins.Add(Instruction.Create(OpCodes.Ret)); + } + + public void Done() + { + if (_done) + { + throw new Exception("Already done"); + } + _done = true; + foreach (var typeDef in _holderTypeDefs) + { + CreateCCtorOfRvaTypeDef(typeDef); + } + } + } + + public class ConstFieldAllocator + { + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly RvaDataAllocator _rvaDataAllocator; + private readonly GroupByModuleEntityManager _moduleEntityManager; + + public ConstFieldAllocator(EncryptionScopeProvider encryptionScopeProvider, RvaDataAllocator rvaDataAllocator, GroupByModuleEntityManager moduleEntityManager) + { + _encryptionScopeProvider = encryptionScopeProvider; + _rvaDataAllocator = rvaDataAllocator; + _moduleEntityManager = moduleEntityManager; + } + + private ModuleConstFieldAllocator GetModuleAllocator(ModuleDef mod) + { + return _moduleEntityManager.GetEntity(mod, () => new ModuleConstFieldAllocator(_encryptionScopeProvider, _rvaDataAllocator, _moduleEntityManager)); + } + + public FieldDef Allocate(ModuleDef mod, int value) + { + return GetModuleAllocator(mod).Allocate(value); + } + + public FieldDef Allocate(ModuleDef mod, long value) + { + return GetModuleAllocator(mod).Allocate(value); + } + + public FieldDef Allocate(ModuleDef mod, float value) + { + return GetModuleAllocator(mod).Allocate(value); + } + + public FieldDef Allocate(ModuleDef mod, double value) + { + return GetModuleAllocator(mod).Allocate(value); + } + + public FieldDef Allocate(ModuleDef mod, byte[] value) + { + return GetModuleAllocator(mod).Allocate(value); + } + + public FieldDef Allocate(ModuleDef mod, string value) + { + return GetModuleAllocator(mod).Allocate(value); + } + + public void Done() + { + foreach (var moduleAllocator in _moduleEntityManager.GetEntities()) + { + moduleAllocator.Done(); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs.meta b/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs.meta new file mode 100644 index 0000000..8e64e42 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e75f5cdfd47370d4ea6c4dee7e55a881 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs b/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs new file mode 100644 index 0000000..3d4d8dd --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs @@ -0,0 +1,355 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine.Assertions; + +namespace Obfuz.Data +{ + public struct RvaData + { + public readonly FieldDef field; + public readonly int offset; + public readonly int size; + + public RvaData(FieldDef field, int offset, int size) + { + this.field = field; + this.offset = offset; + this.size = size; + } + } + + public class ModuleRvaDataAllocator : GroupByModuleEntityBase + { + // randomized + const int maxRvaDataSize = 0x1000; + + private ModuleDef _module; + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly GroupByModuleEntityManager _moduleEntityManager; + + private EncryptionScopeInfo _encryptionScope; + private IRandom _random; + + class RvaField + { + public FieldDef holderDataField; + public FieldDef runtimeValueField; + public int encryptionOps; + public uint size; + public List bytes; + public int salt; + + public void FillPaddingToSize(int newSize) + { + for (int i = bytes.Count; i < newSize; i++) + { + bytes.Add(0xAB); + } + } + + public void FillPaddingToEnd() + { + // fill with random value + for (int i = bytes.Count; i < size; i++) + { + bytes.Add(0xAB); + } + } + } + + private readonly List _rvaFields = new List(); + private RvaField _currentField; + + + private TypeDef _rvaTypeDef; + + private readonly Dictionary _dataHolderTypeBySizes = new Dictionary(); + private bool _done; + + public ModuleRvaDataAllocator(EncryptionScopeProvider encryptionScopeProvider, GroupByModuleEntityManager moduleEntityManager) + { + _encryptionScopeProvider = encryptionScopeProvider; + _moduleEntityManager = moduleEntityManager; + } + + public override void Init(ModuleDef mod) + { + _module = mod; + _encryptionScope = _encryptionScopeProvider.GetScope(mod); + _random = _encryptionScope.localRandomCreator(HashUtil.ComputeHash(mod.Name)); + } + + private (FieldDef, FieldDef) CreateDataHolderRvaField(TypeDef dataHolderType) + { + if (_rvaTypeDef == null) + { + _module.EnableTypeDefFindCache = false; + //_rvaTypeDef = _module.Find("$ObfuzRVA$", true); + //if (_rvaTypeDef != null) + //{ + // throw new Exception($"can't obfuscate a obfuscated assembly"); + //} + ITypeDefOrRef objectTypeRef = _module.Import(typeof(object)); + _rvaTypeDef = new TypeDefUser("$Obfuz$RVA$", objectTypeRef); + _module.Types.Add(_rvaTypeDef); + _module.EnableTypeDefFindCache = true; + } + + + var holderField = new FieldDefUser($"$RVA_Data{_rvaFields.Count}", new FieldSig(dataHolderType.ToTypeSig()), FieldAttributes.InitOnly | FieldAttributes.Static | FieldAttributes.HasFieldRVA); + holderField.DeclaringType = _rvaTypeDef; + + var runtimeValueField = new FieldDefUser($"$RVA_Value{_rvaFields.Count}", new FieldSig(new SZArraySig(_module.CorLibTypes.Byte)), FieldAttributes.Static | FieldAttributes.Public); + runtimeValueField.DeclaringType = _rvaTypeDef; + return (holderField, runtimeValueField); + } + + private TypeDef GetDataHolderType(int size) + { + size = (size + 15) & ~15; // align to 6 bytes + if (_dataHolderTypeBySizes.TryGetValue(size, out var type)) + return type; + var dataHolderType = new TypeDefUser($"$ObfuzRVA$DataHolder{size}", _module.Import(typeof(ValueType))); + dataHolderType.Attributes = TypeAttributes.Public | TypeAttributes.Sealed; + dataHolderType.Layout = TypeAttributes.ExplicitLayout; + dataHolderType.PackingSize = 1; + dataHolderType.ClassSize = (uint)size; + _dataHolderTypeBySizes.Add(size, dataHolderType); + _module.Types.Add(dataHolderType); + return dataHolderType; + } + + private static int AlignTo(int size, int alignment) + { + return (size + alignment - 1) & ~(alignment - 1); + } + + private RvaField CreateRvaField(int size) + { + TypeDef dataHolderType = GetDataHolderType(size); + var (holderDataField, runtimeValueField) = CreateDataHolderRvaField(dataHolderType); + var newRvaField = new RvaField + { + holderDataField = holderDataField, + runtimeValueField = runtimeValueField, + size = dataHolderType.ClassSize, + bytes = new List((int)dataHolderType.ClassSize), + encryptionOps = _random.NextInt(), + salt = _random.NextInt(), + }; + _rvaFields.Add(newRvaField); + return newRvaField; + } + + private RvaField GetRvaField(int preservedSize, int alignment) + { + if (_done) + { + throw new Exception("can't GetRvaField after done"); + } + Assert.IsTrue(preservedSize % alignment == 0); + // for big size, create a new field + if (preservedSize >= maxRvaDataSize) + { + return CreateRvaField(preservedSize); + } + + if (_currentField != null) + { + int offset = AlignTo(_currentField.bytes.Count, alignment); + + int expectedSize = offset + preservedSize; + if (expectedSize <= _currentField.size) + { + _currentField.FillPaddingToSize(offset); + return _currentField; + } + + _currentField.FillPaddingToEnd(); + } + _currentField = CreateRvaField(maxRvaDataSize); + return _currentField; + } + + public RvaData Allocate(int value) + { + RvaField field = GetRvaField(4, 4); + int offset = field.bytes.Count; + Assert.IsTrue(offset % 4 == 0); + field.bytes.AddRange(BitConverter.GetBytes(value)); + return new RvaData(field.runtimeValueField, offset, 4); + } + + public RvaData Allocate(long value) + { + RvaField field = GetRvaField(8, 8); + int offset = field.bytes.Count; + Assert.IsTrue(offset % 8 == 0); + field.bytes.AddRange(BitConverter.GetBytes(value)); + return new RvaData(field.runtimeValueField, offset, 8); + } + + public RvaData Allocate(float value) + { + RvaField field = GetRvaField(4, 4); + int offset = field.bytes.Count; + Assert.IsTrue(offset % 4 == 0); + field.bytes.AddRange(BitConverter.GetBytes(value)); + return new RvaData(field.runtimeValueField, offset, 4); + } + + public RvaData Allocate(double value) + { + RvaField field = GetRvaField(8, 8); + int offset = field.bytes.Count; + Assert.IsTrue(offset % 8 == 0); + field.bytes.AddRange(BitConverter.GetBytes(value)); + return new RvaData(field.runtimeValueField, offset, 8); + } + + public RvaData Allocate(string value) + { + byte[] bytes = Encoding.UTF8.GetBytes(value); + return Allocate(bytes); + } + + public RvaData Allocate(byte[] value) + { + RvaField field = GetRvaField(value.Length, 1); + int offset = field.bytes.Count; + field.bytes.AddRange(value); + return new RvaData(field.runtimeValueField, offset, value.Length); + } + + private void CreateCCtorOfRvaTypeDef() + { + if (_rvaTypeDef == null) + { + return; + } + ModuleDef mod = _rvaTypeDef.Module; + var cctorMethod = new MethodDefUser(".cctor", + MethodSig.CreateStatic(_module.CorLibTypes.Void), + MethodImplAttributes.IL | MethodImplAttributes.Managed, + MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Private); + cctorMethod.DeclaringType = _rvaTypeDef; + //_rvaTypeDef.Methods.Add(cctor); + var body = new CilBody(); + cctorMethod.Body = body; + var ins = body.Instructions; + + DefaultMetadataImporter importer = _moduleEntityManager.GetDefaultModuleMetadataImporter(mod, _encryptionScopeProvider); + foreach (var field in _rvaFields) + { + // ldc + // newarr + // dup + // stsfld + // ldtoken + // RuntimeHelpers.InitializeArray(array, fieldHandle); + ins.Add(Instruction.Create(OpCodes.Ldc_I4, (int)field.size)); + ins.Add(Instruction.Create(OpCodes.Newarr, field.runtimeValueField.FieldType.Next.ToTypeDefOrRef())); + ins.Add(Instruction.Create(OpCodes.Dup)); + ins.Add(Instruction.Create(OpCodes.Dup)); + ins.Add(Instruction.Create(OpCodes.Stsfld, field.runtimeValueField)); + ins.Add(Instruction.Create(OpCodes.Ldtoken, field.holderDataField)); + ins.Add(Instruction.Create(OpCodes.Call, importer.InitializedArrayMethod)); + + // EncryptionService.DecryptBlock(array, field.encryptionOps, field.salt); + ins.Add(Instruction.CreateLdcI4(field.encryptionOps)); + ins.Add(Instruction.Create(OpCodes.Ldc_I4, field.salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptBlock)); + + } + ins.Add(Instruction.Create(OpCodes.Ret)); + } + + private void SetFieldsRVA() + { + foreach (var field in _rvaFields) + { + Assert.IsTrue(field.bytes.Count <= field.size); + if (field.bytes.Count < field.size) + { + field.FillPaddingToEnd(); + } + byte[] data = field.bytes.ToArray(); + _encryptionScope.encryptor.EncryptBlock(data, field.encryptionOps, field.salt); + field.holderDataField.InitialValue = data; + } + } + + public void Done() + { + if (_done) + { + throw new Exception("can't call Done twice"); + } + _done = true; + SetFieldsRVA(); + CreateCCtorOfRvaTypeDef(); + } + } + + public class RvaDataAllocator + { + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly GroupByModuleEntityManager _moduleEntityManager; + + public RvaDataAllocator(EncryptionScopeProvider encryptionScopeProvider, GroupByModuleEntityManager moduleEntityManager) + { + _encryptionScopeProvider = encryptionScopeProvider; + _moduleEntityManager = moduleEntityManager; + } + + private ModuleRvaDataAllocator GetModuleRvaDataAllocator(ModuleDef mod) + { + return _moduleEntityManager.GetEntity(mod, () => new ModuleRvaDataAllocator(_encryptionScopeProvider, _moduleEntityManager)); + } + + public RvaData Allocate(ModuleDef mod, int value) + { + return GetModuleRvaDataAllocator(mod).Allocate(value); + } + + public RvaData Allocate(ModuleDef mod, long value) + { + return GetModuleRvaDataAllocator(mod).Allocate(value); + } + + public RvaData Allocate(ModuleDef mod, float value) + { + return GetModuleRvaDataAllocator(mod).Allocate(value); + } + + public RvaData Allocate(ModuleDef mod, double value) + { + return GetModuleRvaDataAllocator(mod).Allocate(value); + } + + public RvaData Allocate(ModuleDef mod, string value) + { + return GetModuleRvaDataAllocator(mod).Allocate(value); + } + + public RvaData Allocate(ModuleDef mod, byte[] value) + { + return GetModuleRvaDataAllocator(mod).Allocate(value); + } + + public void Done() + { + foreach (var allocator in _moduleEntityManager.GetEntities()) + { + allocator.Done(); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs.meta b/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs.meta new file mode 100644 index 0000000..c1f94e8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c00ca514f46605645bf40b0135e7e504 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Emit.meta b/com.code-philosophy.obfuz/Editor/Emit.meta new file mode 100644 index 0000000..d13f8ab --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Emit.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a513a192808ba5f47b1ef8a3ecf02533 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs b/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs new file mode 100644 index 0000000..acd80a7 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs @@ -0,0 +1,295 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Remoting.Messaging; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Emit +{ + public class BasicBlock + { + public readonly List instructions = new List(); + + public readonly List inBlocks = new List(); + + public readonly List outBlocks = new List(); + + public bool inLoop; + + public void AddTargetBasicBlock(BasicBlock target) + { + if (!outBlocks.Contains(target)) + { + outBlocks.Add(target); + } + if (!target.inBlocks.Contains(this)) + { + target.inBlocks.Add(this); + } + } + } + + public class BasicBlockCollection + { + private readonly MethodDef _method; + + private readonly List _blocks = new List(); + private readonly Dictionary _inst2BlockMap = new Dictionary(); + + public IList Blocks => _blocks; + + public BasicBlockCollection(MethodDef method) + { + _method = method; + HashSet splitPoints = BuildSplitPoint(method); + BuildBasicBlocks(method, splitPoints); + BuildInOutGraph(method); + + var loopBlocks = FindLoopBlocks(_blocks); + foreach (var block in loopBlocks) + { + block.inLoop = true; + } + } + + public BasicBlock GetBasicBlockByInstruction(Instruction inst) + { + return _inst2BlockMap[inst]; + } + + private HashSet BuildSplitPoint(MethodDef method) + { + var insts = method.Body.Instructions; + var splitPoints = new HashSet(); + foreach (ExceptionHandler eh in method.Body.ExceptionHandlers) + { + if (eh.TryStart != null) + { + splitPoints.Add(eh.TryStart); + } + if (eh.TryEnd != null) + { + splitPoints.Add(eh.TryEnd); + } + if (eh.HandlerStart != null) + { + splitPoints.Add(eh.HandlerStart); + } + if (eh.HandlerEnd != null) + { + splitPoints.Add(eh.HandlerEnd); + } + if (eh.FilterStart != null) + { + splitPoints.Add(eh.FilterStart); + } + } + + for (int i = 0, n = insts.Count; i < n; i++) + { + Instruction curInst = insts[i]; + Instruction nextInst = i + 1 < n ? insts[i + 1] : null; + switch (curInst.OpCode.FlowControl) + { + case FlowControl.Branch: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + break; + } + case FlowControl.Cond_Branch: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + if (curInst.Operand is Instruction targetInst) + { + splitPoints.Add(targetInst); + } + else if (curInst.Operand is Instruction[] targetInsts) + { + foreach (var target in targetInsts) + { + splitPoints.Add(target); + } + } + break; + } + case FlowControl.Return: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + break; + } + case FlowControl.Throw: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + break; + } + } + } + return splitPoints; + } + + + private void BuildBasicBlocks(MethodDef method, HashSet splitPoints) + { + var insts = method.Body.Instructions; + + + BasicBlock curBlock = new BasicBlock(); + foreach (Instruction inst in insts) + { + if (splitPoints.Contains(inst) && curBlock.instructions.Count > 0) + { + _blocks.Add(curBlock); + curBlock = new BasicBlock(); + } + curBlock.instructions.Add(inst); + _inst2BlockMap.Add(inst, curBlock); + } + if (curBlock.instructions.Count > 0) + { + _blocks.Add(curBlock); + } + } + + private void BuildInOutGraph(MethodDef method) + { + var insts = method.Body.Instructions; + for (int i = 0, n = _blocks.Count; i < n; i++) + { + BasicBlock curBlock = _blocks[i]; + BasicBlock nextBlock = i + 1 < n ? _blocks[i + 1] : null; + Instruction lastInst = curBlock.instructions.Last(); + switch (lastInst.OpCode.FlowControl) + { + case FlowControl.Branch: + { + Instruction targetInst = (Instruction)lastInst.Operand; + BasicBlock targetBlock = GetBasicBlockByInstruction(targetInst); + curBlock.AddTargetBasicBlock(targetBlock); + break; + } + case FlowControl.Cond_Branch: + { + if (lastInst.Operand is Instruction targetInst) + { + BasicBlock targetBlock = GetBasicBlockByInstruction(targetInst); + curBlock.AddTargetBasicBlock(targetBlock); + } + else if (lastInst.Operand is Instruction[] targetInsts) + { + foreach (var target in targetInsts) + { + BasicBlock targetBlock = GetBasicBlockByInstruction(target); + curBlock.AddTargetBasicBlock(targetBlock); + } + } + else + { + throw new Exception("Invalid operand type for conditional branch"); + } + if (nextBlock != null) + { + curBlock.AddTargetBasicBlock(nextBlock); + } + break; + } + case FlowControl.Return: + case FlowControl.Throw: + { + break; + } + } + } + } + + private static HashSet FindLoopBlocks(List allBlocks) + { + // Tarjan算法找强连通分量 + var sccList = FindStronglyConnectedComponents(allBlocks); + + // 筛选有效循环 + var loopBlocks = new HashSet(); + foreach (var scc in sccList) + { + // 有效循环需满足以下条件之一: + // 1. 分量包含多个块 + // 2. 单个块有自环(跳转自己) + if (scc.Count > 1 || + (scc.Count == 1 && scc[0].outBlocks.Contains(scc[0]))) + { + foreach (var block in scc) + { + loopBlocks.Add(block); + } + } + } + return loopBlocks; + } + + private static List> FindStronglyConnectedComponents(List allBlocks) + { + int index = 0; + var stack = new Stack(); + var indexes = new Dictionary(); + var lowLinks = new Dictionary(); + var onStack = new HashSet(); + var sccList = new List>(); + + foreach (var block in allBlocks.Where(b => !indexes.ContainsKey(b))) + { + StrongConnect(block); + } + + return sccList; + + void StrongConnect(BasicBlock v) + { + indexes[v] = index; + lowLinks[v] = index; + index++; + stack.Push(v); + onStack.Add(v); + + foreach (var w in v.outBlocks) + { + if (!indexes.ContainsKey(w)) + { + StrongConnect(w); + lowLinks[v] = System.Math.Min(lowLinks[v], lowLinks[w]); + } + else if (onStack.Contains(w)) + { + lowLinks[v] = System.Math.Min(lowLinks[v], indexes[w]); + } + } + + if (lowLinks[v] == indexes[v]) + { + var scc = new List(); + BasicBlock w; + do + { + w = stack.Pop(); + onStack.Remove(w); + scc.Add(w); + } while (!w.Equals(v)); + sccList.Add(scc); + } + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs.meta b/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs.meta new file mode 100644 index 0000000..dabfa3b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77c19c023bb7f77489998d994a3be1bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs b/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs new file mode 100644 index 0000000..8a3c30d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs @@ -0,0 +1,193 @@ +using dnlib.DotNet; +using System; +using UnityEngine.Assertions; + +namespace Obfuz.Emit +{ + public class EncryptionServiceMetadataImporter + { + private readonly ModuleDef _module; + private readonly Type _encryptionServiceType; + + private IMethod _encryptBlock; + private IMethod _decryptBlock; + private IMethod _encryptInt; + private IMethod _decryptInt; + private IMethod _encryptLong; + private IMethod _decryptLong; + private IMethod _encryptFloat; + private IMethod _decryptFloat; + private IMethod _encryptDouble; + private IMethod _decryptDouble; + private IMethod _encryptString; + private IMethod _decryptString; + private IMethod _encryptBytes; + private IMethod _decryptBytes; + + private IMethod _decryptFromRvaInt; + private IMethod _decryptFromRvaLong; + private IMethod _decryptFromRvaFloat; + private IMethod _decryptFromRvaDouble; + private IMethod _decryptFromRvaString; + private IMethod _decryptFromRvaBytes; + + public IMethod EncryptBlock => _encryptBlock; + public IMethod DecryptBlock => _decryptBlock; + + public IMethod EncryptInt => _encryptInt; + public IMethod DecryptInt => _decryptInt; + public IMethod EncryptLong => _encryptLong; + public IMethod DecryptLong => _decryptLong; + public IMethod EncryptFloat => _encryptFloat; + public IMethod DecryptFloat => _decryptFloat; + public IMethod EncryptDouble => _encryptDouble; + public IMethod DecryptDouble => _decryptDouble; + public IMethod EncryptString => _encryptString; + public IMethod DecryptString => _decryptString; + public IMethod EncryptBytes => _encryptBytes; + public IMethod DecryptBytes => _decryptBytes; + + public IMethod DecryptFromRvaInt => _decryptFromRvaInt; + public IMethod DecryptFromRvaLong => _decryptFromRvaLong; + public IMethod DecryptFromRvaFloat => _decryptFromRvaFloat; + public IMethod DecryptFromRvaDouble => _decryptFromRvaDouble; + public IMethod DecryptFromRvaBytes => _decryptFromRvaBytes; + public IMethod DecryptFromRvaString => _decryptFromRvaString; + + public EncryptionServiceMetadataImporter(ModuleDef mod, Type encryptionServiceType) + { + _module = mod; + _encryptionServiceType = encryptionServiceType; + _encryptBlock = mod.Import(encryptionServiceType.GetMethod("EncryptBlock", new[] { typeof(byte[]), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptBlock); + _decryptBlock = mod.Import(encryptionServiceType.GetMethod("DecryptBlock", new[] { typeof(byte[]), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptBlock); + _encryptInt = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptInt); + _decryptInt = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptInt); + _encryptLong = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(long), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptLong); + _decryptLong = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(long), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptLong); + _encryptFloat = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(float), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptFloat); + _decryptFloat = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(float), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFloat); + _encryptDouble = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(double), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptDouble); + _decryptDouble = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(double), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptDouble); + _encryptString = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(string), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptString); + _decryptString = mod.Import(encryptionServiceType.GetMethod("DecryptString", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptString); + _encryptBytes = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptBytes); + _decryptBytes = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptBytes); + + _decryptFromRvaInt = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaInt", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaInt); + _decryptFromRvaLong = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaLong", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaLong); + _decryptFromRvaFloat = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaFloat", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaFloat); + _decryptFromRvaDouble = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaDouble", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaDouble); + _decryptFromRvaBytes = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaBytes", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaBytes); + _decryptFromRvaString = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaString", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaString); + } + } + + public class DefaultMetadataImporter : GroupByModuleEntityBase + { + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private EncryptionScopeInfo _encryptionScope; + private EncryptionServiceMetadataImporter _defaultEncryptionServiceMetadataImporter; + + + private EncryptionServiceMetadataImporter _staticDefaultEncryptionServiceMetadataImporter; + private EncryptionServiceMetadataImporter _dynamicDefaultEncryptionServiceMetadataImporter; + + public DefaultMetadataImporter(EncryptionScopeProvider encryptionScopeProvider) + { + _encryptionScopeProvider = encryptionScopeProvider; + } + + public override void Init(ModuleDef mod) + { + _module = mod; + _encryptionScope = _encryptionScopeProvider.GetScope(mod); + var constUtilityType = typeof(ConstUtility); + + _castIntAsFloat = mod.Import(constUtilityType.GetMethod("CastIntAsFloat")); + Assert.IsNotNull(_castIntAsFloat, "CastIntAsFloat not found"); + _castLongAsDouble = mod.Import(constUtilityType.GetMethod("CastLongAsDouble")); + Assert.IsNotNull(_castLongAsDouble, "CastLongAsDouble not found"); + _castFloatAsInt = mod.Import(constUtilityType.GetMethod("CastFloatAsInt")); + Assert.IsNotNull(_castFloatAsInt, "CastFloatAsInt not found"); + _castDoubleAsLong = mod.Import(constUtilityType.GetMethod("CastDoubleAsLong")); + Assert.IsNotNull(_castDoubleAsLong, "CastDoubleAsLong not found"); + + _initializeArray = mod.Import(typeof(System.Runtime.CompilerServices.RuntimeHelpers).GetMethod("InitializeArray", new[] { typeof(Array), typeof(RuntimeFieldHandle) })); + Assert.IsNotNull(_initializeArray); + + _staticDefaultEncryptionServiceMetadataImporter = new EncryptionServiceMetadataImporter(mod, typeof(EncryptionService)); + _dynamicDefaultEncryptionServiceMetadataImporter = new EncryptionServiceMetadataImporter(mod, typeof(EncryptionService)); + if (_encryptionScopeProvider.IsDynamicSecretAssembly(mod)) + { + _defaultEncryptionServiceMetadataImporter = _dynamicDefaultEncryptionServiceMetadataImporter; + } + else + { + _defaultEncryptionServiceMetadataImporter = _staticDefaultEncryptionServiceMetadataImporter; + } + } + + public EncryptionServiceMetadataImporter GetEncryptionServiceMetadataImporterOfModule(ModuleDef mod) + { + return _encryptionScopeProvider.IsDynamicSecretAssembly(mod) ? _dynamicDefaultEncryptionServiceMetadataImporter : _staticDefaultEncryptionServiceMetadataImporter; + } + + private ModuleDef _module; + private IMethod _castIntAsFloat; + private IMethod _castLongAsDouble; + private IMethod _castFloatAsInt; + private IMethod _castDoubleAsLong; + private IMethod _initializeArray; + + public IMethod CastIntAsFloat => _castIntAsFloat; + public IMethod CastLongAsDouble => _castLongAsDouble; + public IMethod CastFloatAsInt => _castFloatAsInt; + public IMethod CastDoubleAsLong => _castDoubleAsLong; + + public IMethod InitializedArrayMethod => _initializeArray; + + public IMethod EncryptBlock => _defaultEncryptionServiceMetadataImporter.EncryptBlock; + public IMethod DecryptBlock => _defaultEncryptionServiceMetadataImporter.DecryptBlock; + + public IMethod EncryptInt => _defaultEncryptionServiceMetadataImporter.EncryptInt; + public IMethod DecryptInt => _defaultEncryptionServiceMetadataImporter.DecryptInt; + public IMethod EncryptLong => _defaultEncryptionServiceMetadataImporter.EncryptLong; + public IMethod DecryptLong => _defaultEncryptionServiceMetadataImporter.DecryptLong; + public IMethod EncryptFloat => _defaultEncryptionServiceMetadataImporter.EncryptFloat; + public IMethod DecryptFloat => _defaultEncryptionServiceMetadataImporter.DecryptFloat; + public IMethod EncryptDouble => _defaultEncryptionServiceMetadataImporter.EncryptDouble; + public IMethod DecryptDouble => _defaultEncryptionServiceMetadataImporter.DecryptDouble; + public IMethod EncryptString => _defaultEncryptionServiceMetadataImporter.EncryptString; + public IMethod DecryptString => _defaultEncryptionServiceMetadataImporter.DecryptString; + public IMethod EncryptBytes => _defaultEncryptionServiceMetadataImporter.EncryptBytes; + public IMethod DecryptBytes => _defaultEncryptionServiceMetadataImporter.DecryptBytes; + + public IMethod DecryptFromRvaInt => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaInt; + public IMethod DecryptFromRvaLong => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaLong; + public IMethod DecryptFromRvaFloat => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaFloat; + public IMethod DecryptFromRvaDouble => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaDouble; + public IMethod DecryptFromRvaBytes => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaBytes; + public IMethod DecryptFromRvaString => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaString; + + } +} diff --git a/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs.meta b/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs.meta new file mode 100644 index 0000000..c29c6a9 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76438ce96146edd469872feada7857ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs b/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs new file mode 100644 index 0000000..860bdf8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs @@ -0,0 +1,66 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Emit +{ + public interface IGroupByModuleEntity + { + void Init(ModuleDef mod); + } + + public abstract class GroupByModuleEntityBase : IGroupByModuleEntity + { + public abstract void Init(ModuleDef mod); + } + + public class GroupByModuleEntityManager + { + private readonly Dictionary<(ModuleDef, Type), IGroupByModuleEntity> _moduleEntityManagers = new Dictionary<(ModuleDef, Type), IGroupByModuleEntity>(); + + public T GetEntity(ModuleDef mod, Func creator = null) where T : IGroupByModuleEntity + { + var key = (mod, typeof(T)); + if (_moduleEntityManagers.TryGetValue(key, out var emitManager)) + { + return (T)emitManager; + } + else + { + T newEmitManager; + if (creator != null) + { + newEmitManager = creator(); + } + else + { + newEmitManager = (T)Activator.CreateInstance(typeof(T)); + } + newEmitManager.Init(mod); + _moduleEntityManagers[key] = newEmitManager; + return newEmitManager; + } + } + + public List GetEntities() where T: IGroupByModuleEntity + { + var managers = new List(); + foreach (var kv in _moduleEntityManagers) + { + if (kv.Key.Item2 == typeof(T)) + { + managers.Add((T)kv.Value); + } + } + return managers; + } + + public DefaultMetadataImporter GetDefaultModuleMetadataImporter(ModuleDef module, EncryptionScopeProvider encryptionScopeProvider) + { + return GetEntity(module, () => new DefaultMetadataImporter(encryptionScopeProvider)); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs.meta b/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs.meta new file mode 100644 index 0000000..a6b7602 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bfcb2b5a87851f469d201fc8978c109 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM.meta new file mode 100644 index 0000000..e8e65c1 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fec4187cc1b96d5439ff908bcecd988f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs new file mode 100644 index 0000000..19ded45 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs @@ -0,0 +1,25 @@ +namespace Obfuz.EncryptionVM +{ + public class EncryptionInstructionWithOpCode + { + public readonly ushort code; + + public readonly IEncryptionInstruction function; + + public EncryptionInstructionWithOpCode(ushort code, IEncryptionInstruction function) + { + this.code = code; + this.function = function; + } + + public int Encrypt(int value, int[] secretKey, int salt) + { + return function.Encrypt(value, secretKey, salt); + } + + public int Decrypt(int value, int[] secretKey, int salt) + { + return function.Decrypt(value, secretKey, salt); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs.meta new file mode 100644 index 0000000..cd63f21 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca9bd232ed2583f4bb5f330886a329e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs new file mode 100644 index 0000000..49808c4 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM +{ + public interface IEncryptionInstruction + { + int Encrypt(int value, int[] secretKey, int salt); + + int Decrypt(int value, int[] secretKey, int salt); + + void GenerateEncryptCode(List lines, string indent); + + void GenerateDecryptCode(List lines, string indent); + } + + public abstract class EncryptionInstructionBase : IEncryptionInstruction + { + public abstract int Encrypt(int value, int[] secretKey, int salt); + public abstract int Decrypt(int value, int[] secretKey, int salt); + + public abstract void GenerateEncryptCode(List lines, string indent); + public abstract void GenerateDecryptCode(List lines, string indent); + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs.meta new file mode 100644 index 0000000..ab7f3a6 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7b9d087de770a5488a9069ddf697c2f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions.meta new file mode 100644 index 0000000..fa56988 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 981355cf75a9d234883b2a15c446f478 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs new file mode 100644 index 0000000..2681fff --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class AddInstruction : EncryptionInstructionBase + { + private readonly int _addValue; + private readonly int _opKeyIndex; + + public AddInstruction(int addValue, int opKeyIndex) + { + _addValue = addValue; + _opKeyIndex = opKeyIndex; + } + public override int Encrypt(int value, int[] secretKey, int salt) + { + return ((value + secretKey[_opKeyIndex]) ^ salt) + _addValue; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + return ((value - _addValue) ^ salt) - secretKey[_opKeyIndex]; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = ((value + _secretKey[{_opKeyIndex}]) ^ salt) + {_addValue};"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value = ((value - {_addValue}) ^ salt) - _secretKey[{_opKeyIndex}];"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs.meta new file mode 100644 index 0000000..d1d2031 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6bdbdc5fd983f044a87e7b8ab8647aeb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs new file mode 100644 index 0000000..099a566 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class AddRotateXorInstruction : EncryptionInstructionBase + { + // x = x + p1 + secretKey[index1]; + // x = Rotate(x, p2) + // x = x ^ p3 ^ salt; + + private readonly int _addValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public AddRotateXorInstruction(int addValue, int index1, int rotateBitNum, int xorValue) + { + _addValue = addValue; + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value += _addValue + secretKey[_index1]; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + value ^= _xorValue ^ salt; + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + value ^= _xorValue ^ salt; + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value -= _addValue + secretKey[_index1]; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value += {_addValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value -= {_addValue} + _secretKey[{_index1}];"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs.meta new file mode 100644 index 0000000..6101fdb --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cda67c0dd0cadd24ea02c2988e34281a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs new file mode 100644 index 0000000..cc453a7 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class AddXorRotateInstruction : EncryptionInstructionBase + { + // x = x + p1 + secretKey[index1]; + // x = x ^ p3 ^ salt; + // x = Rotate(x, p2) + + private readonly int _addValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public AddXorRotateInstruction(int addValue, int index1, int xorValue, int rotateBitNum) + { + _addValue = addValue; + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value += _addValue + secretKey[_index1]; + value ^= _xorValue ^ salt; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value ^= _xorValue ^ salt; + value -= _addValue + secretKey[_index1]; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value += {_addValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint part1 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"value -= {_addValue} + _secretKey[{_index1}];"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs.meta new file mode 100644 index 0000000..abde16f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d806305e627be06469fb2d2c2cf98816 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs new file mode 100644 index 0000000..2d9dcd1 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class BitRotateInstruction : EncryptionInstructionBase + { + private readonly int _rotateBitNum; + private readonly int _opKeyIndex; + + public BitRotateInstruction(int rotateBitNum, int opKeyIndex) + { + _rotateBitNum = rotateBitNum; + _opKeyIndex = opKeyIndex; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + return ((int)(part1 | part2) ^ secretKey[_opKeyIndex]) + salt; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)((value - salt) ^ secretKey[_opKeyIndex]); + uint part1 = value2 >> _rotateBitNum; + uint part2 = value2 << (32 - _rotateBitNum); + return (int)(part1 | part2); + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = ((int)(part1 | part2) ^ _secretKey[{_opKeyIndex}]) + salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)((value - salt) ^ _secretKey[{_opKeyIndex}]);"); + lines.Add(indent + $"uint part1 = value2 >> {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = value2 << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs.meta new file mode 100644 index 0000000..b4b0b9a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bccff31b9f07fcf4f821cee671f82caf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs new file mode 100644 index 0000000..ed59848 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.EncryptionVM.Instructions +{ + + public class EncryptFunction : EncryptionInstructionBase + { + private readonly IEncryptionInstruction[] _instructions; + + public EncryptFunction(IEncryptionInstruction[] instructions) + { + _instructions = instructions; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + foreach (var instruction in _instructions) + { + value = instruction.Encrypt(value, secretKey, salt); + } + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + for (int i = _instructions.Length - 1; i >= 0; i--) + { + value = _instructions[i].Decrypt(value, secretKey, salt); + } + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + throw new NotImplementedException(); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + throw new NotImplementedException(); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs.meta new file mode 100644 index 0000000..0ee2250 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: feafdb30f7b6d5143a89c7659bc16171 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs new file mode 100644 index 0000000..2635f5e --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs @@ -0,0 +1,64 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class MultipleInstruction : EncryptionInstructionBase + { + private readonly int _multiValue; + private readonly int _revertMultiValue; + private readonly int _opKeyIndex; + + public MultipleInstruction(int addValue, int opKeyIndex) + { + _multiValue = addValue; + _opKeyIndex = opKeyIndex; + _revertMultiValue = (int)ModInverseOdd((uint)addValue); + Verify(); + } + + private void Verify() + { + int a = 1122334; + Assert.AreEqual(a, a * _multiValue * _revertMultiValue); + } + + public static uint ModInverseOdd(uint a) + { + if (a % 2 == 0) + throw new ArgumentException("Input must be an odd number.", nameof(a)); + + uint x = 1; // 初始解:x₀ = 1 (mod 2) + for (int i = 0; i < 5; i++) // 迭代5次(2^1 → 2^32) + { + int shift = 2 << i; // 当前模数为 2^(2^(i+1)) + ulong mod = 1UL << shift; // 使用 ulong 避免溢出 + ulong ax = (ulong)a * x; // 计算 a*x(64位避免截断) + ulong term = (2 - ax) % mod; + x = (uint)((x * term) % mod); // 更新 x,结果截断为 uint + } + return x; // 最终解为 x₅ mod 2^32 + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + return value * _multiValue + secretKey[_opKeyIndex] + salt; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + return (value - secretKey[_opKeyIndex] - salt) * _revertMultiValue; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = value * {_multiValue} + _secretKey[{_opKeyIndex}] + salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value = (value - _secretKey[{_opKeyIndex}] - salt) * {_revertMultiValue};"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs.meta new file mode 100644 index 0000000..478ba08 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd5fdfad694e0ae469bf6ca04c913220 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs new file mode 100644 index 0000000..75e4c5c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class MultipleRotateXorInstruction : EncryptionInstructionBase + { + // x = x * p1 + secretKey[index1]; + // x = Rotate(x, p2) + // x = x ^ p3 ^ salt; + + private readonly int _multipleValue; + private readonly int _revertMultipleValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public MultipleRotateXorInstruction(int multipleValue, int index1, int rotateBitNum, int xorValue) + { + _multipleValue = multipleValue; + _revertMultipleValue = (int)MultipleInstruction.ModInverseOdd((uint)multipleValue); + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value = value * _multipleValue + secretKey[_index1]; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + value ^= _xorValue ^ salt; + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + value ^= _xorValue ^ salt; + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value = (value - secretKey[_index1]) * _revertMultipleValue; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = value * {_multipleValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value = (value - _secretKey[{_index1}]) * {_revertMultipleValue};"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs.meta new file mode 100644 index 0000000..7f4e53a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e3c8b55b35ff1554489fa657a714f485 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs new file mode 100644 index 0000000..9733776 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class MultipleXorRotateInstruction : EncryptionInstructionBase + { + // x = x * p1 + secretKey[index1]; + // x = x ^ p3 ^ salt; + // x = Rotate(x, p2) + + private readonly int _multipleValue; + private readonly int _revertMultipleValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public MultipleXorRotateInstruction(int multipleValue, int index1, int xorValue, int rotateBitNum) + { + _multipleValue = multipleValue; + _revertMultipleValue = (int)MultipleInstruction.ModInverseOdd((uint)multipleValue); + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value = value * _multipleValue + secretKey[_index1]; + value ^= _xorValue ^ salt; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value ^= _xorValue ^ salt; + value = (value - secretKey[_index1]) * _revertMultipleValue; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = value * {_multipleValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"value = (value - _secretKey[{_index1}]) * {_revertMultipleValue};"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs.meta new file mode 100644 index 0000000..0e6a653 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: adc3dcde66795744fa4bdc753a2c599f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs new file mode 100644 index 0000000..10a9226 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class XorAddRotateInstruction : EncryptionInstructionBase + { + // x = x ^ p3 ^ salt; + // x = x + p1 + secretKey[index1]; + // x = Rotate(x, p2) + + private readonly int _addValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public XorAddRotateInstruction(int xorValue, int addValue, int index1, int rotateBitNum) + { + _addValue = addValue; + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value ^= _xorValue ^ salt; + value += _addValue + secretKey[_index1]; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value -= _addValue + secretKey[_index1]; + value ^= _xorValue ^ salt; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"value += {_addValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value -= {_addValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs.meta new file mode 100644 index 0000000..d1a6d08 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ad8f4dd724d7ff845b0dd65861054d37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs new file mode 100644 index 0000000..ec5fb9f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class XorInstruction : EncryptionInstructionBase + { + private readonly int _xorValue; + private readonly int _opKeyIndex; + + public XorInstruction(int xorValue, int opKeyIndex) + { + _xorValue = xorValue; + _opKeyIndex = opKeyIndex; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + return ((value ^ secretKey[_opKeyIndex]) + salt) ^ _xorValue; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + return ((value ^ _xorValue) - salt) ^ secretKey[_opKeyIndex]; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = ((value ^ _secretKey[{_opKeyIndex}]) + salt) ^ {_xorValue};"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value = ((value ^ {_xorValue}) - salt) ^ _secretKey[{_opKeyIndex}];"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs.meta new file mode 100644 index 0000000..e246896 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f16dd868e4473b45bfa9daaf7fabaf8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs new file mode 100644 index 0000000..7a10395 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class XorMultipleRotateInstruction : EncryptionInstructionBase + { + // x = x ^ p3 ^ salt; + // x = x * p1 + secretKey[index1]; + // x = Rotate(x, p2) + + private readonly int _multipleValue; + private readonly int _revertMultipleValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public XorMultipleRotateInstruction(int xorValue, int multipleValue, int index1, int rotateBitNum) + { + _multipleValue = multipleValue; + _revertMultipleValue = (int)MultipleInstruction.ModInverseOdd((uint)multipleValue); + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value ^= _xorValue ^ salt; + value = value * _multipleValue + secretKey[_index1]; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value = (value - secretKey[_index1]) * _revertMultipleValue; + value ^= _xorValue ^ salt; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"value = value * {_multipleValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value = (value - _secretKey[{_index1}]) * {_revertMultipleValue};"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs.meta new file mode 100644 index 0000000..e95af06 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3eb7e6d475cfc14459d3850c5964ba52 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs new file mode 100644 index 0000000..1ca1a59 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs @@ -0,0 +1,17 @@ +namespace Obfuz.EncryptionVM +{ + public class VirtualMachine + { + public const int SecretKeyLength = 1024; + + public readonly int version; + public readonly string codeGenerationSecretKey; + public readonly EncryptionInstructionWithOpCode[] opCodes; + + public VirtualMachine(int version, string codeGenerationSecretKey, EncryptionInstructionWithOpCode[] opCodes) + { + this.codeGenerationSecretKey = codeGenerationSecretKey; + this.opCodes = opCodes; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs.meta new file mode 100644 index 0000000..e932d33 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6970e037654dcb49912783a40f3e1ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs new file mode 100644 index 0000000..b05f000 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs @@ -0,0 +1,207 @@ +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.EncryptionVM +{ + public class VirtualMachineCodeGenerator + { + private readonly int _opCodeCount; + private readonly int _opCodeBits; + private readonly VirtualMachine _vm; + + public VirtualMachineCodeGenerator(string vmCodeGenerateSecretKey, int opCodeCount) + { + _opCodeCount = opCodeCount; + _opCodeBits = EncryptionUtil.GetBitCount(opCodeCount - 1); + _vm = new VirtualMachineCreator(vmCodeGenerateSecretKey).CreateVirtualMachine(opCodeCount); + } + + public VirtualMachineCodeGenerator(VirtualMachine vm) + { + _opCodeCount = vm.opCodes.Length; + _opCodeBits = EncryptionUtil.GetBitCount(_opCodeCount - 1); + _vm = vm; + } + + + public bool ValidateMatch(string outputFile) + { + if (!File.Exists(outputFile)) + { + return false; + } + string oldCode = NormalizeText(File.ReadAllText(outputFile, Encoding.UTF8)); + string newCode = NormalizeText(GenerateCode()); + return oldCode == newCode; + } + + private static string NormalizeText(string input) + { + return Regex.Replace(input, @"\s+", string.Empty); + } + + public void Generate(string outputFile) + { + FileUtil.CreateParentDir(outputFile); + + string code = GenerateCode(); + + File.WriteAllText(outputFile, code, Encoding.UTF8); + Debug.Log($"Generate EncryptionVM code to {outputFile}"); + UnityEditor.AssetDatabase.Refresh(); + } + + private string GenerateCode() + { + var lines = new List(); + AppendHeader(lines); + AppendEncryptCodes(lines); + AppendDecryptCodes(lines); + AppendTailer(lines); + return string.Join("\n", lines); + } + + private void AppendEncryptCodes(List lines) + { + lines.Add(@" + private int ExecuteEncrypt(int value, int opCode, int salt) + { + switch (opCode) + {"); + foreach (var opCode in _vm.opCodes) + { + lines.Add($@" case {opCode.code}: + {{ + // {opCode.function.GetType().Name}"); + AppendEncryptCode(lines, opCode.function); + lines.Add(@" return value; + }"); + } + + lines.Add(@" + default: + throw new System.Exception($""Invalid opCode:{opCode}""); + } + }"); + } + + private void AppendDecryptCodes(List lines) + { + lines.Add(@" + private int ExecuteDecrypt(int value, int opCode, int salt) + { + switch (opCode) + {"); + foreach (var opCode in _vm.opCodes) + { + lines.Add($@" case {opCode.code}: + {{ + // {opCode.function.GetType().Name}"); + AppendDecryptCode(lines, opCode.function); + lines.Add(@" return value; + }"); + } + + lines.Add(@" + default: + throw new System.Exception($""Invalid opCode:{opCode}""); + } + }"); + } + + private void AppendHeader(List lines) + { + + lines.Add($"/// This file is auto-generated by Obfuz. Do not modify it."); + lines.Add($"///"); + //lines.Add($"/// Created Time: {DateTime.Now}"); + + lines.Add($"/// Version: {_vm.version}"); + lines.Add($"/// SecretKey: {_vm.codeGenerationSecretKey}"); + lines.Add($"/// OpCodeCount: {_vm.opCodes.Length}"); + + lines.Add(@" +namespace Obfuz.EncryptionVM +{ + public class GeneratedEncryptionVirtualMachine : Obfuz.EncryptorBase + {"); + lines.Add($@" + private const int kOpCodeBits = {_opCodeBits}; + + private const int kOpCodeCount = {_opCodeCount}; + + private const int kOpCodeMask = {_opCodeCount - 1}; +"); + lines.Add(@" + + private readonly int[] _secretKey; + + public GeneratedEncryptionVirtualMachine(byte[] secretKey) + { + this._secretKey = ConvertToIntKey(secretKey); + } + + public override int OpCodeCount => kOpCodeCount; + + public override int Encrypt(int value, int opts, int salt) + { + uint uopts = (uint)opts; + uint revertOps = 0; + while (uopts != 0) + { + uint opCode = uopts & kOpCodeMask; + revertOps <<= kOpCodeBits; + revertOps |= opCode; + uopts >>= kOpCodeBits; + } + + while (revertOps != 0) + { + uint opCode = revertOps & kOpCodeMask; + value = ExecuteEncrypt(value, (int)opCode, salt); + revertOps >>= kOpCodeBits; + } + return value; + } + + public override int Decrypt(int value, int opts, int salt) + { + uint uopts = (uint)opts; + while (uopts != 0) + { + uint opCode = uopts & kOpCodeMask; + value = ExecuteDecrypt(value, (int)opCode, salt); + uopts >>= kOpCodeBits; + } + return value; + } +"); + } + + private void AppendTailer(List lines) + { + lines.Add(@" + } +} + +"); + } + + private void AppendEncryptCode(List lines, IEncryptionInstruction instruction) + { + instruction.GenerateEncryptCode(lines, " "); + } + + private void AppendDecryptCode(List lines, IEncryptionInstruction instruction) + { + instruction.GenerateDecryptCode(lines, " "); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs.meta new file mode 100644 index 0000000..956402c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2246e9d3369eb3c45bc19ae0748d76ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs new file mode 100644 index 0000000..8035ad0 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; +using Obfuz.EncryptionVM.Instructions; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using UnityEngine.Assertions; +using UnityEngine.UIElements; + +namespace Obfuz.EncryptionVM +{ + public class VirtualMachineCreator + { + private readonly string _vmGenerationSecretKey; + private readonly IRandom _random; + + public const int CodeGenerationSecretKeyLength = 1024; + + public const int VirtualMachineVersion = 1; + + public VirtualMachineCreator(string vmGenerationSecretKey) + { + _vmGenerationSecretKey = vmGenerationSecretKey; + byte[] byteGenerationSecretKey = KeyGenerator.GenerateKey(vmGenerationSecretKey, CodeGenerationSecretKeyLength); + int[] intGenerationSecretKey = KeyGenerator.ConvertToIntKey(byteGenerationSecretKey); + _random = new RandomWithKey(intGenerationSecretKey, 0); + } + + private readonly List> _instructionCreators = new List> + { + (r, len) => new AddInstruction(r.NextInt(), r.NextInt(len)), + (r, len) => new XorInstruction(r.NextInt(), r.NextInt(len)), + (r, len) => new BitRotateInstruction(r.NextInt(32), r.NextInt(len)), + (r, len) => new MultipleInstruction(r.NextInt() | 0x1, r.NextInt(len)), + (r, len) => new AddRotateXorInstruction(r.NextInt(), r.NextInt(len), r.NextInt(32), r.NextInt()), + (r, len) => new AddXorRotateInstruction(r.NextInt(), r.NextInt(len), r.NextInt(), r.NextInt(32)), + (r, len) => new XorAddRotateInstruction(r.NextInt(), r.NextInt(), r.NextInt(len), r.NextInt(32)), + (r, len) => new MultipleRotateXorInstruction(r.NextInt() | 0x1, r.NextInt(len), r.NextInt(32), r.NextInt()), + (r, len) => new MultipleXorRotateInstruction(r.NextInt() | 0x1, r.NextInt(len), r.NextInt(), r.NextInt(32)), + (r, len) => new XorMultipleRotateInstruction(r.NextInt(), r.NextInt() | 0x1, r.NextInt(len), r.NextInt(32)), + }; + + private IEncryptionInstruction CreateRandomInstruction(int intSecretKeyLength) + { + return _instructionCreators[_random.NextInt(_instructionCreators.Count)](_random, intSecretKeyLength); + } + + private EncryptionInstructionWithOpCode CreateEncryptOpCode(ushort code) + { + IEncryptionInstruction inst = CreateRandomInstruction(VirtualMachine.SecretKeyLength / sizeof(int)); + return new EncryptionInstructionWithOpCode(code, inst); + } + + public VirtualMachine CreateVirtualMachine(int opCodeCount) + { + if (opCodeCount < 64) + { + throw new System.Exception("OpCode count should be >= 64"); + } + if ((opCodeCount & (opCodeCount - 1)) != 0) + { + throw new System.Exception("OpCode count should be power of 2"); + } + var opCodes = new EncryptionInstructionWithOpCode[opCodeCount]; + for (int i = 0; i < opCodes.Length; i++) + { + opCodes[i] = CreateEncryptOpCode((ushort)i); + } + return new VirtualMachine(VirtualMachineVersion, _vmGenerationSecretKey, opCodes); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs.meta new file mode 100644 index 0000000..3124bb7 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77d95ff5cf0b3aa4e96a055e37c381ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs new file mode 100644 index 0000000..a8e25c8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs @@ -0,0 +1,96 @@ +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using System.Text; +using UnityEngine; +using UnityEngine.Assertions; +using UnityEngine.UIElements; + +namespace Obfuz.EncryptionVM +{ + + public class VirtualMachineSimulator : EncryptorBase + { + private readonly EncryptionInstructionWithOpCode[] _opCodes; + private readonly int[] _secretKey; + + public override int OpCodeCount => _opCodes.Length; + + public VirtualMachineSimulator(VirtualMachine vm, byte[] byteSecretKey) + { + _opCodes = vm.opCodes; + _secretKey = KeyGenerator.ConvertToIntKey(byteSecretKey); + + VerifyInstructions(); + } + + private void VerifyInstructions() + { + int value = 0x11223344; + for (int i = 0; i < _opCodes.Length; i++) + { + int encryptedValue = _opCodes[i].Encrypt(value, _secretKey, i); + int decryptedValue = _opCodes[i].Decrypt(encryptedValue, _secretKey, i); + //Debug.Log($"instruction type:{_opCodes[i].function.GetType()}"); + Assert.AreEqual(value, decryptedValue); + } + + int ops = 11223344; + int salt = 789; + Assert.AreEqual(1, Decrypt(Encrypt(1, ops, salt), ops, salt)); + Assert.AreEqual(1L, Decrypt(Encrypt(1L, ops, salt), ops, salt)); + Assert.AreEqual(1.0f, Decrypt(Encrypt(1.0f, ops, salt), ops, salt)); + Assert.AreEqual(1.0, Decrypt(Encrypt(1.0, ops, salt), ops, salt)); + + byte[] strBytes = Encrypt("abcdef", ops, salt); + Assert.AreEqual("abcdef", DecryptString(strBytes, 0, strBytes.Length, ops, salt)); + var arr = new byte[100]; + for (int i = 0; i < arr.Length ; i++) + { + arr[i] = (byte)i; + } + EncryptBlock(arr, ops, salt); + DecryptBlock(arr, ops, salt); + for (int i = 0; i < arr.Length; i++) + { + Assert.AreEqual(i, arr[i]); + } + } + + private List DecodeOps(uint ops) + { + var codes = new List(); + while (ops != 0) + { + uint code = ops % (uint)_opCodes.Length; + codes.Add(code); + ops /= (uint)_opCodes.Length; + } + return codes; + } + + public override int Encrypt(int value, int ops, int salt) + { + var codes = DecodeOps((uint)ops); + for (int i = codes.Count - 1; i >= 0; i--) + { + var opCode = _opCodes[codes[i]]; + value = opCode.Encrypt(value, _secretKey, salt); + } + return value; + } + + public override int Decrypt(int value, int ops, int salt) + { + var codes = DecodeOps((uint)ops); + foreach (var code in codes) + { + var opCode = _opCodes[code]; + value = opCode.Decrypt(value, _secretKey, salt); + } + return value; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs.meta b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs.meta new file mode 100644 index 0000000..532a498 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f86f4d6faf49764a915d5c675091375 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs b/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs new file mode 100644 index 0000000..16d875f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs @@ -0,0 +1,20 @@ +using Obfuz.ObfusPasses; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz +{ + public interface IObfuscationPass + { + ObfuscationPassType Type { get; } + + void Start(); + + void Stop(); + + void Process(); + } +} diff --git a/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs.meta b/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs.meta new file mode 100644 index 0000000..cd501ad --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7003f9503025794b8aa775d9ade335c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses.meta new file mode 100644 index 0000000..ed7f92c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 120b2dcffd582e84dbb92003240824d1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs new file mode 100644 index 0000000..916a9ef --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs @@ -0,0 +1,87 @@ +using dnlib.DotNet.Emit; +using dnlib.DotNet; +using System.Collections.Generic; +using System.Linq; +using Obfuz.Emit; + +namespace Obfuz.ObfusPasses +{ + public abstract class BasicBlockObfuscationPassBase : ObfuscationPassBase + { + protected abstract bool NeedObfuscateMethod(MethodDef method); + + public override void Process() + { + var ctx = ObfuscationPassContext.Current; + ObfuscationMethodWhitelist whiteList = ctx.whiteList; + ConfigurablePassPolicy passPolicy = ctx.passPolicy; + foreach (ModuleDef mod in ctx.modulesToObfuscate) + { + if (whiteList.IsInWhiteList(mod) || !Support(passPolicy.GetAssemblyObfuscationPasses(mod))) + { + continue; + } + // ToArray to avoid modify list exception + foreach (TypeDef type in mod.GetTypes().ToArray()) + { + if (whiteList.IsInWhiteList(type) || !Support(passPolicy.GetTypeObfuscationPasses(type))) + { + continue; + } + // ToArray to avoid modify list exception + foreach (MethodDef method in type.Methods.ToArray()) + { + if (!method.HasBody || ctx.whiteList.IsInWhiteList(method) || !Support(passPolicy.GetMethodObfuscationPasses(method)) || !NeedObfuscateMethod(method)) + { + continue; + } + // TODO if isGeneratedBy Obfuscator, continue + ObfuscateData(method); + } + } + } + } + + + protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, BasicBlock block, int instructionIndex, + IList globalInstructions, List outputInstructions, List totalFinalInstructions); + + private void ObfuscateData(MethodDef method) + { + BasicBlockCollection bbc = new BasicBlockCollection(method); + + IList instructions = method.Body.Instructions; + + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + BasicBlock block = bbc.GetBasicBlockByInstruction(inst); + outputInstructions.Clear(); + 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 + inst.OpCode = outputInstructions[0].OpCode; + inst.Operand = outputInstructions[0].Operand; + totalFinalInstructions.Add(inst); + for (int k = 1; k < outputInstructions.Count; k++) + { + totalFinalInstructions.Add(outputInstructions[k]); + } + } + else + { + totalFinalInstructions.Add(inst); + } + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs.meta new file mode 100644 index 0000000..027d02b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae83aaf003665614092aabceabff3cf8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus.meta new file mode 100644 index 0000000..a5ed333 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cf68e45551825c547b137f6e5189937e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs new file mode 100644 index 0000000..9b32359 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs @@ -0,0 +1,92 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Utils; +using Obfuz.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using UnityEngine.Assertions; +using Obfuz.Settings; + +namespace Obfuz.ObfusPasses.CallObfus +{ + public class CallObfusPass : BasicBlockObfuscationPassBase + { + private readonly List _configFiles; + private readonly int _obfuscationLevel; + private IObfuscator _dynamicProxyObfuscator; + private IObfuscationPolicy _dynamicProxyPolicy; + + public override ObfuscationPassType Type => ObfuscationPassType.CallObfus; + + public CallObfusPass(CallObfuscationSettings settings) + { + _configFiles = settings.ruleFiles.ToList(); + _obfuscationLevel = settings.obfuscationLevel; + } + + public override void Stop() + { + _dynamicProxyObfuscator.Done(); + } + + public override void Start() + { + var ctx = ObfuscationPassContext.Current; + _dynamicProxyObfuscator = new DefaultCallProxyObfuscator(ctx.encryptionScopeProvider, ctx.constFieldAllocator, ctx.moduleEntityManager, _obfuscationLevel); + _dynamicProxyPolicy = new ConfigurableObfuscationPolicy(ctx.assembliesToObfuscate, _configFiles); + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return _dynamicProxyPolicy.NeedObfuscateCallInMethod(method); + } + + 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) + { + return false; + } + if (MetaUtil.ContainsContainsGenericParameter(calledMethod)) + { + return false; + } + + bool callVir; + switch (inst.OpCode.Code) + { + case Code.Call: + { + callVir = false; + break; + } + case Code.Callvirt: + { + if (instructionIndex > 0 && globalInstructions[instructionIndex - 1].OpCode.Code == Code.Constrained) + { + return false; + } + callVir = true; + break; + } + default: return false; + } + + if (!_dynamicProxyPolicy.NeedObfuscateCalledMethod(callerMethod, calledMethod, callVir, block.inLoop)) + { + return false; + } + + ObfuscationCachePolicy cachePolicy = _dynamicProxyPolicy.GetMethodObfuscationCachePolicy(callerMethod); + bool cachedCallIndex = block.inLoop ? cachePolicy.cacheInLoop : cachePolicy.cacheNotInLoop; + _dynamicProxyObfuscator.Obfuscate(callerMethod, calledMethod, callVir, cachedCallIndex, outputInstructions); + return true; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs.meta new file mode 100644 index 0000000..b15eb81 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 112178b770868274fb8119a4997a3420 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs new file mode 100644 index 0000000..1164db6 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs @@ -0,0 +1,293 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Editor; +using Obfuz.Emit; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MethodImplAttributes = dnlib.DotNet.MethodImplAttributes; +using TypeAttributes = dnlib.DotNet.TypeAttributes; + +namespace Obfuz.ObfusPasses.CallObfus +{ + public struct ProxyCallMethodData + { + public readonly MethodDef proxyMethod; + public readonly int encryptOps; + public readonly int salt; + public readonly int encryptedIndex; + public readonly int index; + + public ProxyCallMethodData(MethodDef proxyMethod, int encryptOps, int salt, int encryptedIndex, int index) + { + this.proxyMethod = proxyMethod; + this.encryptOps = encryptOps; + this.salt = salt; + this.encryptedIndex = encryptedIndex; + this.index = index; + } + } + + class ModuleCallProxyAllocator : IGroupByModuleEntity + { + private ModuleDef _module; + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly int _encryptionLevel; + + private EncryptionScopeInfo _encryptionScope; + private bool _done; + + class MethodKey : IEquatable + { + public readonly IMethod _method; + public readonly bool _callVir; + private readonly int _hashCode; + + public MethodKey(IMethod method, bool callVir) + { + _method = method; + _callVir = callVir; + _hashCode = HashUtil.CombineHash(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method), callVir ? 1 : 0); + } + + public override int GetHashCode() + { + return _hashCode; + } + + public bool Equals(MethodKey other) + { + return MethodEqualityComparer.CompareDeclaringTypes.Equals(_method, other._method) && _callVir == other._callVir; + } + } + + class MethodProxyInfo + { + public MethodDef proxyMethod; + + public int index; + public int encryptedOps; + public int salt; + public int encryptedIndex; + } + + private readonly Dictionary _methodProxys = new Dictionary(); + + + const int maxProxyMethodPerDispatchMethod = 1000; + + class CallInfo + { + public IMethod method; + public bool callVir; + } + + class DispatchMethodInfo + { + public MethodDef methodDef; + public List methods = new List(); + } + + private readonly Dictionary> _dispatchMethods = new Dictionary>(SignatureEqualityComparer.Instance); + + + private TypeDef _proxyTypeDef; + + public ModuleCallProxyAllocator(EncryptionScopeProvider encryptionScopeProvider, int encryptionLevel) + { + _encryptionScopeProvider = encryptionScopeProvider; + _encryptionLevel = encryptionLevel; + } + + public void Init(ModuleDef mod) + { + _module = mod; + _encryptionScope = _encryptionScopeProvider.GetScope(mod); + } + + private TypeDef CreateProxyTypeDef() + { + var typeDef = new TypeDefUser($"{ConstValues.ObfuzInternalSymbolNamePrefix}ProxyCall", _module.CorLibTypes.Object.ToTypeDefOrRef()); + typeDef.Attributes = TypeAttributes.NotPublic | TypeAttributes.Sealed; + _module.EnableTypeDefFindCache = false; + _module.Types.Add(typeDef); + _module.EnableTypeDefFindCache = true; + return typeDef; + } + + private MethodDef CreateDispatchMethodInfo(MethodSig methodSig) + { + if (_proxyTypeDef == null) + { + _proxyTypeDef = CreateProxyTypeDef(); + } + MethodDef methodDef = new MethodDefUser($"{ConstValues.ObfuzInternalSymbolNamePrefix}ProxyCall$Dispatch${_proxyTypeDef.Methods.Count}", methodSig, + MethodImplAttributes.IL | MethodImplAttributes.Managed, + MethodAttributes.Static | MethodAttributes.Public); + methodDef.DeclaringType = _proxyTypeDef; + return methodDef; + } + + private MethodSig CreateDispatchMethodSig(IMethod method) + { + MethodSig methodSig = MetaUtil.ToSharedMethodSig(_module.CorLibTypes, MetaUtil.GetInflatedMethodSig(method)); + //MethodSig methodSig = MetaUtil.GetInflatedMethodSig(method).Clone(); + //methodSig.Params + switch (MetaUtil.GetThisArgType(method)) + { + case ThisArgType.Class: + { + methodSig.Params.Insert(0, _module.CorLibTypes.Object); + break; + } + case ThisArgType.ValueType: + { + methodSig.Params.Insert(0, _module.CorLibTypes.IntPtr); + break; + } + } + // extra param for index + methodSig.Params.Add(_module.CorLibTypes.Int32); + return MethodSig.CreateStatic(methodSig.RetType, methodSig.Params.ToArray()); + } + + private int GenerateSalt(IRandom random) + { + return random.NextInt(); + } + + private int GenerateEncryptOps(IRandom random) + { + return EncryptionUtil.GenerateEncryptionOpCodes(random, _encryptionScope.encryptor, _encryptionLevel); + } + + private DispatchMethodInfo GetDispatchMethod(IMethod method) + { + MethodSig methodSig = CreateDispatchMethodSig(method); + if (!_dispatchMethods.TryGetValue(methodSig, out var dispatchMethods)) + { + dispatchMethods = new List(); + _dispatchMethods.Add(methodSig, dispatchMethods); + } + if (dispatchMethods.Count == 0 || dispatchMethods.Last().methods.Count >= maxProxyMethodPerDispatchMethod) + { + var newDispatchMethodInfo = new DispatchMethodInfo + { + methodDef = CreateDispatchMethodInfo(methodSig), + }; + dispatchMethods.Add(newDispatchMethodInfo); + } + return dispatchMethods.Last(); + } + + private IRandom CreateRandomForMethod(IMethod method, bool callVir) + { + int seed = MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method); + return _encryptionScope.localRandomCreator(seed); + } + + public ProxyCallMethodData Allocate(IMethod method, bool callVir) + { + if (_done) + { + throw new Exception("can't Allocate after done"); + } + var key = new MethodKey(method, callVir); + if (!_methodProxys.TryGetValue(key, out var proxyInfo)) + { + var methodDispatcher = GetDispatchMethod(method); + + int index = methodDispatcher.methods.Count; + IRandom localRandom = CreateRandomForMethod(method, callVir); + int encryptOps = GenerateEncryptOps(localRandom); + int salt = GenerateSalt(localRandom); + int encryptedIndex = _encryptionScope.encryptor.Encrypt(index, encryptOps, salt); + proxyInfo = new MethodProxyInfo() + { + proxyMethod = methodDispatcher.methodDef, + index = index, + encryptedOps = encryptOps, + salt = salt, + encryptedIndex = encryptedIndex, + }; + methodDispatcher.methods.Add(new CallInfo { method = method, callVir = callVir}); + _methodProxys.Add(key, proxyInfo); + } + return new ProxyCallMethodData(proxyInfo.proxyMethod, proxyInfo.encryptedOps, proxyInfo.salt, proxyInfo.encryptedIndex, proxyInfo.index); + } + + public void Done() + { + if (_done) + { + throw new Exception("Already done"); + } + _done = true; + + foreach (DispatchMethodInfo dispatchMethod in _dispatchMethods.Values.SelectMany(ms => ms)) + { + var methodDef = dispatchMethod.methodDef; + var methodSig = methodDef.MethodSig; + + + var body = new CilBody(); + methodDef.Body = body; + var ins = body.Instructions; + + foreach (Parameter param in methodDef.Parameters) + { + ins.Add(Instruction.Create(OpCodes.Ldarg, param)); + } + + var switchCases = new List(); + var switchInst = Instruction.Create(OpCodes.Switch, switchCases); + ins.Add(switchInst); + var ret = Instruction.Create(OpCodes.Ret); + foreach (CallInfo ci in dispatchMethod.methods) + { + var callTargetMethod = Instruction.Create(ci.callVir ? OpCodes.Callvirt : OpCodes.Call, ci.method); + switchCases.Add(callTargetMethod); + ins.Add(callTargetMethod); + ins.Add(Instruction.Create(OpCodes.Br, ret)); + } + ins.Add(ret); + } + } + } + + public class CallProxyAllocator + { + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private GroupByModuleEntityManager _moduleEntityManager; + private readonly int _encryptionLevel; + + public CallProxyAllocator(EncryptionScopeProvider encryptionScopeProvider, GroupByModuleEntityManager moduleEntityManager, int encryptionLevel) + { + _encryptionScopeProvider = encryptionScopeProvider; + _moduleEntityManager = moduleEntityManager; + _encryptionLevel = encryptionLevel; + } + + private ModuleCallProxyAllocator GetModuleAllocator(ModuleDef mod) + { + return _moduleEntityManager.GetEntity(mod, () => new ModuleCallProxyAllocator(_encryptionScopeProvider, _encryptionLevel)); + } + + public ProxyCallMethodData Allocate(ModuleDef mod, IMethod method, bool callVir) + { + ModuleCallProxyAllocator allocator = GetModuleAllocator(mod); + return allocator.Allocate(method, callVir); + } + + public void Done() + { + foreach (var allocator in _moduleEntityManager.GetEntities()) + { + allocator.Done(); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs.meta new file mode 100644 index 0000000..a6a06f8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16b960455f093854d927c2dbd47a4826 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs new file mode 100644 index 0000000..4deb16a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs @@ -0,0 +1,411 @@ +using dnlib.DotNet; +using Obfuz.Conf; +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 + { + class WhiteListAssembly + { + public string name; + public NameMatcher nameMatcher; + public bool? obfuscate; + public List types = new List(); + } + + class WhiteListType + { + public string name; + public NameMatcher nameMatcher; + public bool? obfuscate; + public List methods = new List(); + } + + class WhiteListMethod + { + public string name; + public NameMatcher nameMatcher; + public bool? obfuscate; + } + + class ObfuscationRule : IRule + { + 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 : AssemblyRuleBase + { + } + + class TypeSpec : TypeRuleBase + { + } + + class MethodSpec : MethodRuleBase + { + + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + disableObfuscation = false, + obfuscateCallInLoop = true, + cacheCallIndexInLoop = true, + cacheCallIndexNotLoop = false, + }; + + private readonly XmlAssemblyTypeMethodRuleParser _configParser; + + private ObfuscationRule _global; + private readonly List _whiteListAssemblies = new List(); + + private readonly Dictionary _whiteListMethodCache = new Dictionary(MethodEqualityComparer.CompareDeclaringTypes); + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableObfuscationPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _configParser = new XmlAssemblyTypeMethodRuleParser(toObfuscatedAssemblyNames, + ParseObfuscationRule, ParseGlobalElement); + LoadConfigs(xmlConfigFiles); + } + + private void LoadConfigs(List configFiles) + { + _configParser.LoadConfigs(configFiles); + + if (_global == null) + { + _global = s_default; + } + else + { + _global.InheritParent(s_default); + } + _configParser.InheritParentRules(_global); + InheritWhitelistRules(); + } + + private void InheritWhitelistRules() + { + foreach (var ass in _whiteListAssemblies) + { + foreach (var type in ass.types) + { + if (type.obfuscate == null) + { + type.obfuscate = ass.obfuscate; + } + foreach (var method in type.methods) + { + if (method.obfuscate == null) + { + method.obfuscate = type.obfuscate; + } + } + } + } + } + + private void ParseGlobalElement(string configFile, XmlElement ele) + { + switch (ele.Name) + { + case "global": _global = ParseObfuscationRule(configFile, ele); break; + case "whitelist": ParseWhitelist(ele); break; + default: throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + + private ObfuscationRule ParseObfuscationRule(string configFile, XmlElement ele) + { + 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 = ParseWhiteListAssembly(childEle); + _whiteListAssemblies.Add(ass); + break; + } + default: throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + + private WhiteListAssembly ParseWhiteListAssembly(XmlElement element) + { + var ass = new WhiteListAssembly(); + ass.name = element.GetAttribute("name"); + ass.nameMatcher = new NameMatcher(ass.name); + + ass.obfuscate = ConfigUtil.ParseNullableBool(element.GetAttribute("obfuscate")) ?? false; + + 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); + type.obfuscate = ConfigUtil.ParseNullableBool(element.GetAttribute("obfuscate")); + + 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); + method.obfuscate = ConfigUtil.ParseNullableBool(element.GetAttribute("obfuscate")); + return method; + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = _configParser.GetMethodRule(method, s_default); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedObfuscateCallInMethod(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 IsSpecialNotObfuscatedMethod(TypeDef typeDef, IMethod method) + { + if (typeDef.IsDelegate || typeDef.IsEnum) + return true; + + string methodName = method.Name; + + // doesn't proxy call if the method is a constructor + if (methodName == ".ctor") + { + return true; + } + + if (typeDef.Name == "EncryptionService`1") + { + return true; + } + // special handle + // don't proxy call for List.Enumerator GetEnumerator() + if (methodName == "GetEnumerator") + { + return true; + } + return false; + } + + 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 (IsSpecialNotObfuscatedMethod(typeDef, calledMethod)) + { + return true; + } + + string assName = typeDef.Module.Assembly.Name; + string typeFullName = typeDef.FullName; + string methodName = calledMethod.Name; + foreach (var ass in _whiteListAssemblies) + { + if (!ass.nameMatcher.IsMatch(assName)) + { + continue; + } + foreach (var type in ass.types) + { + if (!type.nameMatcher.IsMatch(typeFullName)) + { + continue; + } + foreach (var method in type.methods) + { + if (method.nameMatcher.IsMatch(methodName)) + { + return !method.obfuscate.Value; + } + } + return !type.obfuscate.Value; + } + return !ass.obfuscate.Value; + } + return false; + } + + private bool IsInWhiteList(IMethod method) + { + if (!_whiteListMethodCache.TryGetValue(method, out var isWhiteList)) + { + isWhiteList = ComputeIsInWhiteList(method); + _whiteListMethodCache.Add(method, isWhiteList); + } + return isWhiteList; + } + + private bool IsTypeSelfAndParentPublic(TypeDef type) + { + if (type.DeclaringType != null && !IsTypeSelfAndParentPublic(type.DeclaringType)) + { + return false; + } + + return type.IsPublic; + } + + public override bool NeedObfuscateCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir, bool currentInLoop) + { + if (IsInWhiteList(calledMethod)) + { + return false; + } + + // mono has more strict access control, calls non-public method will raise exception. + if (PlatformUtil.IsMonoBackend()) + { + MethodDef calledMethodDef = calledMethod.ResolveMethodDef(); + if (calledMethodDef != null && (!calledMethodDef.IsPublic || !IsTypeSelfAndParentPublic(calledMethodDef.DeclaringType))) + { + return false; + } + } + ObfuscationRule rule = GetMethodObfuscationRule(callerMethod); + if (currentInLoop && rule.obfuscateCallInLoop == false) + { + return false; + } + return true; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs.meta new file mode 100644 index 0000000..c970eec --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9ea12b16c4b296459db8a60fb1615d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs new file mode 100644 index 0000000..e171909 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs @@ -0,0 +1,53 @@ +using dnlib.DotNet.Emit; +using dnlib.DotNet; +using System.Collections.Generic; +using Obfuz.Utils; +using Obfuz.Emit; +using Obfuz.Data; +using UnityEngine; + +namespace Obfuz.ObfusPasses.CallObfus +{ + public class DefaultCallProxyObfuscator : ObfuscatorBase + { + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly ConstFieldAllocator _constFieldAllocator; + private readonly CallProxyAllocator _proxyCallAllocator; + private readonly GroupByModuleEntityManager _moduleEntityManager; + + public DefaultCallProxyObfuscator(EncryptionScopeProvider encryptionScopeProvider, ConstFieldAllocator constFieldAllocator, GroupByModuleEntityManager moduleEntityManager, int encryptionLevel) + { + _encryptionScopeProvider = encryptionScopeProvider; + _constFieldAllocator = constFieldAllocator; + _moduleEntityManager = moduleEntityManager; + _proxyCallAllocator = new CallProxyAllocator(encryptionScopeProvider, moduleEntityManager, encryptionLevel); + } + + public override void Done() + { + _proxyCallAllocator.Done(); + } + + public override void Obfuscate(MethodDef callerMethod, IMethod calledMethod, bool callVir, bool needCacheCall, List obfuscatedInstructions) + { + + MethodSig sharedMethodSig = MetaUtil.ToSharedMethodSig(calledMethod.Module.CorLibTypes, MetaUtil.GetInflatedMethodSig(calledMethod)); + ProxyCallMethodData proxyCallMethodData = _proxyCallAllocator.Allocate(callerMethod.Module, calledMethod, callVir); + DefaultMetadataImporter importer = _moduleEntityManager.GetDefaultModuleMetadataImporter(callerMethod.Module, _encryptionScopeProvider); + + if (needCacheCall) + { + FieldDef cacheField = _constFieldAllocator.Allocate(callerMethod.Module, proxyCallMethodData.index); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + } + else + { + obfuscatedInstructions.Add(Instruction.CreateLdcI4(proxyCallMethodData.encryptedIndex)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(proxyCallMethodData.encryptOps)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(proxyCallMethodData.salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptInt)); + } + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, proxyCallMethodData.proxyMethod)); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs.meta new file mode 100644 index 0000000..78ae2a2 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e13ba01b03439e049af0e09367825cde +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs new file mode 100644 index 0000000..43c505f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs @@ -0,0 +1,34 @@ +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 NeedObfuscateCallInMethod(MethodDef method); + + ObfuscationCachePolicy GetMethodObfuscationCachePolicy(MethodDef method); + + bool NeedObfuscateCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir, bool currentInLoop); + } + + public abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + public abstract bool NeedObfuscateCallInMethod(MethodDef method); + + public abstract ObfuscationCachePolicy GetMethodObfuscationCachePolicy(MethodDef method); + + public abstract bool NeedObfuscateCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir, bool currentInLoop); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs.meta new file mode 100644 index 0000000..96009d1 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6af3cd881fdefd14d9a55b77088dd5a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs new file mode 100644 index 0000000..4135b0d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs @@ -0,0 +1,23 @@ +using dnlib.DotNet.Emit; +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 IObfuscator + { + void Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, bool needCacheCall, List obfuscatedInstructions); + + void Done(); + } + + public abstract class ObfuscatorBase : IObfuscator + { + public abstract void Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, bool needCacheCall, List obfuscatedInstructions); + public abstract void Done(); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs.meta new file mode 100644 index 0000000..c75fd40 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4156317478f8b1d438ef6d5a280d409f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp.meta new file mode 100644 index 0000000..37096d7 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2764442d8fc2b914dbc39dcfa2699698 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs new file mode 100644 index 0000000..1a526eb --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs @@ -0,0 +1,46 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.CleanUp +{ + public class CleanUpInstructionPass : ObfuscationPassBase + { + public override ObfuscationPassType Type => ObfuscationPassType.None; + + public override void Start() + { + } + + public override void Stop() + { + + } + + public override void Process() + { + var ctx = ObfuscationPassContext.Current; + foreach (ModuleDef mod in ctx.modulesToObfuscate) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + if (method.HasBody) + { + CilBody body = method.Body; + body.SimplifyBranches(); + body.OptimizeMacros(); + body.OptimizeBranches(); + // TODO remove dup + } + } + } + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs.meta new file mode 100644 index 0000000..cc97432 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78cc056cd929d70409a0f0737b571a6d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs new file mode 100644 index 0000000..2855136 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs @@ -0,0 +1,71 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.CleanUp +{ + public class RemoveObfuzAttributesPass : ObfuscationPassBase + { + public override ObfuscationPassType Type => ObfuscationPassType.None; + + public override void Start() + { + } + + public override void Stop() + { + + } + + + private void RemoveObfuzAttributes(IHasCustomAttribute provider) + { + CustomAttributeCollection customAttributes = provider.CustomAttributes; + if (customAttributes.Count == 0) + return; + var toRemove = new List(); + customAttributes.RemoveAll("Obfuz.ObfuzIgnoreAttribute"); + customAttributes.RemoveAll("Obfuz.EncryptFieldAttribute"); + } + + public override void Process() + { + var ctx = ObfuscationPassContext.Current; + foreach (ModuleDef mod in ctx.modulesToObfuscate) + { + RemoveObfuzAttributes(mod); + foreach (TypeDef type in mod.GetTypes()) + { + RemoveObfuzAttributes(type); + foreach (FieldDef field in type.Fields) + { + RemoveObfuzAttributes(field); + } + foreach (MethodDef method in type.Methods) + { + RemoveObfuzAttributes(method); + foreach (Parameter param in method.Parameters) + { + if (param.ParamDef != null) + { + RemoveObfuzAttributes(param.ParamDef); + } + } + } + foreach (PropertyDef property in type.Properties) + { + RemoveObfuzAttributes(property); + } + foreach (EventDef eventDef in type.Events) + { + RemoveObfuzAttributes(eventDef); + } + } + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs.meta new file mode 100644 index 0000000..ff6039e --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b475010a7656a0439ca8664a3d2dbc0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt.meta new file mode 100644 index 0000000..1fdfee0 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18104d0c3c665ea489e566eec67f2aea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs new file mode 100644 index 0000000..3b3445c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs @@ -0,0 +1,489 @@ +using dnlib.DotNet; +using Obfuz.Conf; +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.ConstEncrypt +{ + + public class ConfigurableEncryptPolicy : EncryptPolicyBase + { + class ObfuscationRule : IRule + { + public bool? disableEncrypt; + public bool? encryptInt; + public bool? encryptLong; + public bool? encryptFloat; + public bool? encryptDouble; + public bool? encryptArray; + public bool? encryptString; + + public bool? encryptConstInLoop; + public bool? encryptStringInLoop; + + public bool? cacheConstInLoop; + public bool? cacheConstNotInLoop; + public bool? cacheStringInLoop; + public bool? cacheStringNotInLoop; + + public void InheritParent(ObfuscationRule parentRule) + { + if (disableEncrypt == null) + disableEncrypt = parentRule.disableEncrypt; + if (encryptInt == null) + encryptInt = parentRule.encryptInt; + if (encryptLong == null) + encryptLong = parentRule.encryptLong; + if (encryptFloat == null) + encryptFloat = parentRule.encryptFloat; + if (encryptDouble == null) + encryptDouble = parentRule.encryptDouble; + if (encryptArray == null) + encryptArray = parentRule.encryptArray; + if (encryptString == null) + encryptString = parentRule.encryptString; + + if (encryptConstInLoop == null) + encryptConstInLoop = parentRule.encryptConstInLoop; + if (encryptStringInLoop == null) + encryptStringInLoop = parentRule.encryptStringInLoop; + + if (cacheConstInLoop == null) + cacheConstInLoop = parentRule.cacheConstInLoop; + if (cacheConstNotInLoop == null) + cacheConstNotInLoop = parentRule.cacheConstNotInLoop; + if (cacheStringInLoop == null) + cacheStringInLoop = parentRule.cacheStringInLoop; + if (cacheStringNotInLoop == null) + cacheStringNotInLoop = parentRule.cacheStringNotInLoop; + } + } + + class MethodSpec : MethodRuleBase + { + } + + class TypeSpec : TypeRuleBase + { + } + + class AssemblySpec : AssemblyRuleBase + { + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + disableEncrypt = false, + encryptInt = true, + encryptLong = true, + encryptFloat = true, + encryptDouble = true, + encryptArray = true, + encryptString = true, + encryptConstInLoop = true, + encryptStringInLoop = true, + cacheConstInLoop = true, + cacheConstNotInLoop = false, + cacheStringInLoop = true, + cacheStringNotInLoop = true, + }; + + private ObfuscationRule _global; + + public HashSet notEncryptInts = new HashSet(); + public HashSet notEncryptLongs = new HashSet(); + public HashSet notEncryptStrings = new HashSet(); + public List> notEncryptIntRanges = new List>(); + public List> notEncryptLongRanges = new List>(); + public List> notEncryptFloatRanges = new List>(); + public List> notEncryptDoubleRanges = new List>(); + public List> notEncryptArrayLengthRanges = new List>(); + public List> notEncryptStringLengthRanges = new List>(); + + private readonly XmlAssemblyTypeMethodRuleParser _xmlParser; + + private readonly Dictionary _assemblySpecs = new Dictionary(); + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableEncryptPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _xmlParser = new XmlAssemblyTypeMethodRuleParser( + toObfuscatedAssemblyNames, ParseObfuscationRule, ParseGlobalElement); + LoadConfigs(xmlConfigFiles); + } + + private void LoadConfigs(List configFiles) + { + _xmlParser.LoadConfigs(configFiles); + if (_global == null) + { + _global = s_default; + } + else + { + _global.InheritParent(s_default); + } + _xmlParser.InheritParentRules(_global); + } + + private void ParseGlobalElement(string configFile, XmlElement ele) + { + switch (ele.Name) + { + case "global": _global = ParseObfuscationRule(configFile, ele); break; + case "whitelist": ParseWhitelist(configFile, ele); break; + default: throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + + private ObfuscationRule ParseObfuscationRule(string configFile, XmlElement ele) + { + var rule = new ObfuscationRule(); + if (ele.HasAttribute("disableEncrypt")) + { + rule.disableEncrypt = ConfigUtil.ParseBool(ele.GetAttribute("disableEncrypt")); + } + if (ele.HasAttribute("encryptInt")) + { + rule.encryptInt = ConfigUtil.ParseBool(ele.GetAttribute("encryptInt")); + } + if (ele.HasAttribute("encryptLong")) + { + rule.encryptLong = ConfigUtil.ParseBool(ele.GetAttribute("encryptLong")); + } + if (ele.HasAttribute("encryptFloat")) + { + rule.encryptFloat = ConfigUtil.ParseBool(ele.GetAttribute("encryptFloat")); + } + if (ele.HasAttribute("encryptDouble")) + { + rule.encryptDouble = ConfigUtil.ParseBool(ele.GetAttribute("encryptDouble")); + } + if (ele.HasAttribute("encryptBytes")) + { + rule.encryptArray = ConfigUtil.ParseBool(ele.GetAttribute("encryptArray")); + } + if (ele.HasAttribute("encryptString")) + { + rule.encryptString = ConfigUtil.ParseBool(ele.GetAttribute("encryptString")); + } + + if (ele.HasAttribute("encryptConstInLoop")) + { + rule.encryptConstInLoop = ConfigUtil.ParseBool(ele.GetAttribute("encryptConstInLoop")); + } + if (ele.HasAttribute("encryptStringInLoop")) + { + rule.encryptStringInLoop = ConfigUtil.ParseBool(ele.GetAttribute("encryptStringInLoop")); + } + if (ele.HasAttribute("cacheConstInLoop")) + { + rule.cacheConstInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheConstInLoop")); + } + if (ele.HasAttribute("cacheConstNotInLoop")) + { + rule.cacheConstNotInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheConstNotInLoop")); + } + if (ele.HasAttribute("cacheStringInLoop")) + { + rule.cacheStringInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheStringInLoop")); + } + if (ele.HasAttribute("cacheStringNotInLoop")) + { + rule.cacheStringNotInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheStringNotInLoop")); + } + return rule; + } + + private void ParseWhitelist(string configFile, XmlElement childEle) + { + string type = childEle.GetAttribute("type"); + if (string.IsNullOrEmpty(type)) + { + throw new Exception($"Invalid xml file, whitelist type is empty"); + } + string value = childEle.InnerText; + switch (type) + { + case "int": + { + notEncryptInts.AddRange(value.Split(',').Select(s => int.Parse(s.Trim()))); + break; + } + case "long": + { + notEncryptLongs.AddRange(value.Split(',').Select(s => long.Parse(s.Trim()))); + break; + } + case "string": + { + notEncryptStrings.AddRange(value.Split(',').Select(s => s.Trim())); + break; + } + case "int-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, int-range {value} is invalid"); + } + notEncryptIntRanges.Add(new NumberRange(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1]))); + break; + } + case "long-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, long-range {value} is invalid"); + } + notEncryptLongRanges.Add(new NumberRange(ConfigUtil.ParseNullableLong(parts[0]), ConfigUtil.ParseNullableLong(parts[1]))); + break; + } + case "float-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, float-range {value} is invalid"); + } + notEncryptFloatRanges.Add(new NumberRange(ConfigUtil.ParseNullableFloat(parts[0]), ConfigUtil.ParseNullableFloat(parts[1]))); + break; + } + case "double-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, double-range {value} is invalid"); + } + notEncryptDoubleRanges.Add(new NumberRange(ConfigUtil.ParseNullableDouble(parts[0]), ConfigUtil.ParseNullableDouble(parts[1]))); + break; + } + case "string-length-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, string-length-range {value} is invalid"); + } + notEncryptStringLengthRanges.Add(new NumberRange(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1]))); + break; + } + case "array-length-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, array-length-range {value} is invalid"); + } + 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"); + } + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = _xmlParser.GetMethodRule(method, _global); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedObfuscateMethod(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return rule.disableEncrypt != true; + } + + public override ConstCachePolicy GetMethodConstCachePolicy(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return new ConstCachePolicy + { + cacheConstInLoop = rule.cacheConstInLoop.Value, + cacheConstNotInLoop = rule.cacheConstNotInLoop.Value, + cacheStringInLoop = rule.cacheStringInLoop.Value, + cacheStringNotInLoop = rule.cacheStringNotInLoop.Value, + }; + } + + public override bool NeedObfuscateInt(MethodDef method, bool currentInLoop, int value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptInt == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + if (notEncryptInts.Contains(value)) + { + return false; + } + foreach (var range in notEncryptIntRanges) + { + if (range.min != null && value < range.min) + { + continue; + } + if (range.max != null && value > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateLong(MethodDef method, bool currentInLoop, long value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptLong == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + if (notEncryptLongs.Contains(value)) + { + return false; + } + foreach (var range in notEncryptLongRanges) + { + if (range.min != null && value < range.min) + { + continue; + } + if (range.max != null && value > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateFloat(MethodDef method, bool currentInLoop, float value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptFloat == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + foreach (var range in notEncryptFloatRanges) + { + if (range.min != null && value < range.min) + { + continue; + } + if (range.max != null && value > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateDouble(MethodDef method, bool currentInLoop, double value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptDouble == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + foreach (var range in notEncryptDoubleRanges) + { + if (range.min != null && value < range.min) + { + continue; + } + if (range.max != null && value > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateString(MethodDef method, bool currentInLoop, string value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptString == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + if (notEncryptStrings.Contains(value)) + { + return false; + } + foreach (var range in notEncryptStringLengthRanges) + { + if (range.min != null && value.Length < range.min) + { + continue; + } + if (range.max != null && value.Length > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateArray(MethodDef method, bool currentInLoop, byte[] array) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptArray == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + foreach (var range in notEncryptArrayLengthRanges) + { + if (range.min != null && array.Length < range.min) + { + continue; + } + if (range.max != null && array.Length > range.max) + { + continue; + } + return false; + } + return true; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs.meta new file mode 100644 index 0000000..73c07be --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da25453bc1fda394097c052af7733260 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs new file mode 100644 index 0000000..b39d304 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs @@ -0,0 +1,152 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Settings; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + + public class ConstEncryptPass : BasicBlockObfuscationPassBase + { + private readonly List _configFiles; + private readonly int _encryptionLevel; + private IEncryptPolicy _dataObfuscatorPolicy; + private IConstEncryptor _dataObfuscator; + public override ObfuscationPassType Type => ObfuscationPassType.ConstEncrypt; + + public ConstEncryptPass(ConstEncryptionSettings settings) + { + _configFiles = settings.ruleFiles.ToList(); + _encryptionLevel = settings.encryptionLevel; + } + + public override void Start() + { + var ctx = ObfuscationPassContext.Current; + _dataObfuscatorPolicy = new ConfigurableEncryptPolicy(ctx.assembliesToObfuscate, _configFiles); + _dataObfuscator = new DefaultConstEncryptor(ctx.encryptionScopeProvider, ctx.rvaDataAllocator, ctx.constFieldAllocator, ctx.moduleEntityManager, _encryptionLevel); + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return _dataObfuscatorPolicy.NeedObfuscateMethod(method); + } + + protected override bool TryObfuscateInstruction(MethodDef method, Instruction inst, BasicBlock block, int instructionIndex, IList globalInstructions, + List outputInstructions, List totalFinalInstructions) + { + bool currentInLoop = block.inLoop; + ConstCachePolicy constCachePolicy = _dataObfuscatorPolicy.GetMethodConstCachePolicy(method); + bool needCache = currentInLoop ? constCachePolicy.cacheConstInLoop : constCachePolicy.cacheConstNotInLoop; + switch (inst.OpCode.Code) + { + case Code.Ldc_I4: + case Code.Ldc_I4_S: + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4_M1: + { + int value = inst.GetLdcI4Value(); + if (_dataObfuscatorPolicy.NeedObfuscateInt(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateInt(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Ldc_I8: + { + long value = (long)inst.Operand; + if (_dataObfuscatorPolicy.NeedObfuscateLong(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateLong(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Ldc_R4: + { + float value = (float)inst.Operand; + if (_dataObfuscatorPolicy.NeedObfuscateFloat(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateFloat(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Ldc_R8: + { + double value = (double)inst.Operand; + if (_dataObfuscatorPolicy.NeedObfuscateDouble(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateDouble(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Ldstr: + { + string value = (string)inst.Operand; + if (_dataObfuscatorPolicy.NeedObfuscateString(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateString(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Call: + { + //if (((IMethod)inst.Operand).FullName == "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)") + //{ + // Instruction prevInst = globalInstructions[instructionIndex - 1]; + // if (prevInst.OpCode.Code == Code.Ldtoken) + // { + // IField rvaField = (IField)prevInst.Operand; + // FieldDef ravFieldDef = rvaField.ResolveFieldDefThrow(); + // byte[] data = ravFieldDef.InitialValue; + // if (data != null && _dataObfuscatorPolicy.NeedObfuscateArray(method, currentInLoop, data)) + // { + // if (_encryptedRvaFields.Add(ravFieldDef)) + // { + + // } + + // // remove prev ldtoken instruction + // Assert.AreEqual(Code.Ldtoken, totalFinalInstructions.Last().OpCode.Code); + // //totalFinalInstructions.RemoveAt(totalFinalInstructions.Count - 1); + // // dup arr argument for decryption operation + // totalFinalInstructions.Insert(totalFinalInstructions.Count - 1, Instruction.Create(OpCodes.Dup)); + // totalFinalInstructions.Add(inst.Clone()); + // //bool needCache = currentInLoop ? constCachePolicy.cacheStringInLoop : constCachePolicy.cacheStringNotInLoop; + // bool needCache = false; + // _dataObfuscator.ObfuscateBytes(method, needCache, data, outputInstructions); + // return true; + // } + // } + //} + return false; + } + default: return false; + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs.meta new file mode 100644 index 0000000..da7fdfb --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aa0e9191126d4e24c92546b6af2c52cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs new file mode 100644 index 0000000..75f98dc --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs @@ -0,0 +1,204 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Data; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using NUnit.Framework; +using System.Text; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + public class DefaultConstEncryptor : IConstEncryptor + { + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly RvaDataAllocator _rvaDataAllocator; + private readonly ConstFieldAllocator _constFieldAllocator; + private readonly GroupByModuleEntityManager _moduleEntityManager; + private readonly int _encryptionLevel; + + public DefaultConstEncryptor(EncryptionScopeProvider encryptionScopeProvider, RvaDataAllocator rvaDataAllocator, ConstFieldAllocator constFieldAllocator, GroupByModuleEntityManager moduleEntityManager, int encryptionLevel) + { + _encryptionScopeProvider = encryptionScopeProvider; + _rvaDataAllocator = rvaDataAllocator; + _constFieldAllocator = constFieldAllocator; + _moduleEntityManager = moduleEntityManager; + _encryptionLevel = encryptionLevel; + } + + private IRandom CreateRandomForValue(EncryptionScopeInfo encryptionScope, int value) + { + return encryptionScope.localRandomCreator(value); + } + + private int GenerateEncryptionOperations(EncryptionScopeInfo encryptionScope, IRandom random) + { + return EncryptionUtil.GenerateEncryptionOpCodes(random, encryptionScope.encryptor, _encryptionLevel); + } + + public int GenerateSalt(IRandom random) + { + return random.NextInt(); + } + + private DefaultMetadataImporter GetModuleMetadataImporter(MethodDef method) + { + return _moduleEntityManager.GetDefaultModuleMetadataImporter(method.Module, _encryptionScopeProvider); + } + + public void ObfuscateInt(MethodDef method, bool needCacheValue, int value, List obfuscatedInstructions) + { + if (needCacheValue) + { + FieldDef cacheField = _constFieldAllocator.Allocate(method.Module, value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + EncryptionScopeInfo encryptionScope = _encryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + int encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(method.Module, encryptedValue); + + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaInt)); + } + + public void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List obfuscatedInstructions) + { + if (needCacheValue) + { + FieldDef cacheField = _constFieldAllocator.Allocate(method.Module, value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + EncryptionScopeInfo encryptionScope = _encryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + long encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(method.Module, encryptedValue); + + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaLong)); + } + + public void ObfuscateFloat(MethodDef method, bool needCacheValue, float value, List obfuscatedInstructions) + { + if (needCacheValue) + { + FieldDef cacheField = _constFieldAllocator.Allocate(method.Module, value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + EncryptionScopeInfo encryptionScope = _encryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + float encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(method.Module, encryptedValue); + + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaFloat)); + } + + public void ObfuscateDouble(MethodDef method, bool needCacheValue, double value, List obfuscatedInstructions) + { + if (needCacheValue) + { + FieldDef cacheField = _constFieldAllocator.Allocate(method.Module, value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + EncryptionScopeInfo encryptionScope = _encryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + double encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + RvaData rvaData = _rvaDataAllocator.Allocate(method.Module, encryptedValue); + + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaDouble)); + } + + public void ObfuscateBytes(MethodDef method, bool needCacheValue, byte[] value, List obfuscatedInstructions) + { + throw new NotSupportedException("ObfuscateBytes is not supported yet."); + //if (needCacheValue) + //{ + // FieldDef cacheField = _constFieldAllocator.Allocate(method.Module, value); + // obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + // return; + //} + + //int ops = GenerateEncryptionOperations(); + //int salt = GenerateSalt(); + //byte[] encryptedValue = _encryptor.Encrypt(value, 0, value.Length, ops, salt); + //Assert.IsTrue(encryptedValue.Length % 4 == 0); + //RvaData rvaData = _rvaDataAllocator.Allocate(method.Module, encryptedValue); + + //DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + //obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + //obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + //// should use value.Length, can't use rvaData.size, because rvaData.size is align to 4, it's not the actual length. + //obfuscatedInstructions.Add(Instruction.CreateLdcI4(value.Length)); + //obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + //obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + //obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaBytes)); + } + + public void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions) + { + if (needCacheValue) + { + FieldDef cacheField = _constFieldAllocator.Allocate(method.Module, value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + EncryptionScopeInfo encryptionScope = _encryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + int stringByteLength = Encoding.UTF8.GetByteCount(value); + byte[] encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + Assert.AreEqual(stringByteLength, encryptedValue.Length); + RvaData rvaData = _rvaDataAllocator.Allocate(method.Module, encryptedValue); + + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + // should use stringByteLength, can't use rvaData.size, because rvaData.size is align to 4, it's not the actual length. + obfuscatedInstructions.Add(Instruction.CreateLdcI4(stringByteLength)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaString)); + } + + public void Done() + { + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs.meta new file mode 100644 index 0000000..1e0977a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c6a0ecde97527e4694731e4d4de129a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs new file mode 100644 index 0000000..7043aec --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs @@ -0,0 +1,32 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + public interface IConstEncryptor + { + void ObfuscateInt(MethodDef method, bool needCacheValue, int value, List obfuscatedInstructions); + + void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List obfuscatedInstructions); + + void ObfuscateFloat(MethodDef method, bool needCacheValue, float value, List obfuscatedInstructions); + + void ObfuscateDouble(MethodDef method, bool needCacheValue, double value, List obfuscatedInstructions); + + void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions); + + void ObfuscateBytes(MethodDef method, bool needCacheValue, byte[] value, List obfuscatedInstructions); + } + + public abstract class ConstEncryptorBase : IConstEncryptor + { + public abstract void ObfuscateBytes(MethodDef method, bool needCacheValue, byte[] value, List obfuscatedInstructions); + public abstract void ObfuscateDouble(MethodDef method, bool needCacheValue, double value, List obfuscatedInstructions); + public abstract void ObfuscateFloat(MethodDef method, bool needCacheValue, float value, List obfuscatedInstructions); + public abstract void ObfuscateInt(MethodDef method, bool needCacheValue, int value, List obfuscatedInstructions); + public abstract void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List obfuscatedInstructions); + public abstract void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs.meta new file mode 100644 index 0000000..7f8d587 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ccbcdadf1913b6498eaee53abac5d0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs new file mode 100644 index 0000000..1db7f0e --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs @@ -0,0 +1,48 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + public struct ConstCachePolicy + { + public bool cacheConstInLoop; + public bool cacheConstNotInLoop; + public bool cacheStringInLoop; + public bool cacheStringNotInLoop; + } + + public interface IEncryptPolicy + { + bool NeedObfuscateMethod(MethodDef method); + + ConstCachePolicy GetMethodConstCachePolicy(MethodDef method); + + bool NeedObfuscateInt(MethodDef method, bool currentInLoop, int value); + + bool NeedObfuscateLong(MethodDef method, bool currentInLoop, long value); + + bool NeedObfuscateFloat(MethodDef method, bool currentInLoop, float value); + + bool NeedObfuscateDouble(MethodDef method, bool currentInLoop, double value); + + bool NeedObfuscateString(MethodDef method, bool currentInLoop, string value); + + bool NeedObfuscateArray(MethodDef method, bool currentInLoop, byte[] array); + } + + public abstract class EncryptPolicyBase : IEncryptPolicy + { + public abstract bool NeedObfuscateMethod(MethodDef method); + public abstract ConstCachePolicy GetMethodConstCachePolicy(MethodDef method); + public abstract bool NeedObfuscateDouble(MethodDef method, bool currentInLoop, double value); + public abstract bool NeedObfuscateFloat(MethodDef method, bool currentInLoop, float value); + public abstract bool NeedObfuscateInt(MethodDef method, bool currentInLoop, int value); + public abstract bool NeedObfuscateLong(MethodDef method, bool currentInLoop, long value); + public abstract bool NeedObfuscateString(MethodDef method, bool currentInLoop, string value); + public abstract bool NeedObfuscateArray(MethodDef method, bool currentInLoop, byte[] array); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs.meta new file mode 100644 index 0000000..0a44790 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18e57864070430a44ac561bdd7d00b2e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus.meta new file mode 100644 index 0000000..24080ec --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9fdb2c243b1ea0f489e67233fda287c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs new file mode 100644 index 0000000..468bfce --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs @@ -0,0 +1,35 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.ExprObfus +{ + public class ExprObfusPass : InstructionObfuscationPassBase + { + public override ObfuscationPassType Type => ObfuscationPassType.ExprObfus; + + public override void Start() + { + + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return false; + } + + protected override bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, List outputInstructions, List totalFinalInstructions) + { + return false; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs.meta new file mode 100644 index 0000000..f108ce3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 477e081ffc0072e4fa1a06100269e4a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt.meta new file mode 100644 index 0000000..24c7b1d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0b789725c4848bd4fb4b3ce1f2e2a9c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs new file mode 100644 index 0000000..15ce929 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs @@ -0,0 +1,44 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public class ConfigurableEncryptPolicy : EncryptPolicyBase + { + class ObfuscationRule + { + + } + + private readonly XmlFieldRuleParser _configParser; + + public ConfigurableEncryptPolicy(List toObfuscatedAssemblyNames, List configFiles) + { + _configParser = new XmlFieldRuleParser(toObfuscatedAssemblyNames, ParseRule, null); + _configParser.LoadConfigs(configFiles); + } + + private ObfuscationRule ParseRule(string configFile, XmlElement ele) + { + return new ObfuscationRule(); + } + + public override bool NeedEncrypt(FieldDef field) + { + if (MetaUtil.HasEncryptFieldAttribute(field)) + { + return true; + } + if (MetaUtil.HasObfuzIgnoreAttribute(field) || MetaUtil.HasObfuzIgnoreAttributeInSelfOrParent(field.DeclaringType)) + { + return false; + } + var rule = _configParser.GetFieldRule(field); + return rule != null; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs.meta new file mode 100644 index 0000000..bcc4d60 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b17fa09ce58526459f2b9e375c31cad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs new file mode 100644 index 0000000..05c9c63 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs @@ -0,0 +1,182 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public class DefaultFieldEncryptor : FieldEncryptorBase + { + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly GroupByModuleEntityManager _moduleEntityManager; + private readonly int _encryptionLevel; + + public DefaultFieldEncryptor(EncryptionScopeProvider encryptionScopeProvider, GroupByModuleEntityManager moduleEntityManager, int encryptionLevel) + { + _encryptionScopeProvider = encryptionScopeProvider; + _moduleEntityManager = moduleEntityManager; + _encryptionLevel = encryptionLevel; + } + + private DefaultMetadataImporter GetMetadataImporter(MethodDef method) + { + return _moduleEntityManager.GetDefaultModuleMetadataImporter(method.Module, _encryptionScopeProvider); + } + + class FieldEncryptInfo + { + public int encryptOps; + public int salt; + public ElementType fieldType; + public long xorValueForZero; + } + + private readonly Dictionary _fieldEncryptInfoCache = new Dictionary(); + + + private long CalcXorValueForZero(IEncryptor encryptor, ElementType type, int encryptOps, int salt) + { + switch (type) + { + case ElementType.I4: + case ElementType.U4: + case ElementType.R4: + return encryptor.Encrypt(0, encryptOps, salt); + case ElementType.I8: + case ElementType.U8: + case ElementType.R8: + return encryptor.Encrypt(0L, encryptOps, salt); + default: + throw new NotSupportedException($"Unsupported field type: {type} for encryption"); + } + } + + + private IRandom CreateRandomForField(RandomCreator randomCreator, FieldDef field) + { + return randomCreator(FieldEqualityComparer.CompareDeclaringTypes.GetHashCode(field)); + } + + private int GenerateEncryptionOperations(IRandom random, IEncryptor encryptor) + { + return EncryptionUtil.GenerateEncryptionOpCodes(random, encryptor, _encryptionLevel); + } + + public int GenerateSalt(IRandom random) + { + return random.NextInt(); + } + + private FieldEncryptInfo GetFieldEncryptInfo(FieldDef field) + { + if (_fieldEncryptInfoCache.TryGetValue(field, out var info)) + { + return info; + } + EncryptionScopeInfo encryptionScope = _encryptionScopeProvider.GetScope(field.Module); + + IRandom random = CreateRandomForField(encryptionScope.localRandomCreator, field); + IEncryptor encryptor = encryptionScope.encryptor; + int encryptOps = GenerateEncryptionOperations(random, encryptor); + int salt = GenerateSalt(random); + ElementType fieldType = field.FieldSig.Type.RemovePinnedAndModifiers().ElementType; + long xorValueForZero = CalcXorValueForZero(encryptor, fieldType, encryptOps, salt); + + info = new FieldEncryptInfo + { + encryptOps = encryptOps, + salt = salt, + fieldType = fieldType, + xorValueForZero = xorValueForZero, + }; + _fieldEncryptInfoCache[field] = info; + return info; + } + + public override void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction) + { + DefaultMetadataImporter importer = GetMetadataImporter(method); + EncryptionServiceMetadataImporter encryptionServiceMetadataImporter = importer.GetEncryptionServiceMetadataImporterOfModule(field.Module); + FieldEncryptInfo fei = GetFieldEncryptInfo(field); + if (fei.fieldType == ElementType.I4 || fei.fieldType == ElementType.U4 || fei.fieldType == ElementType.R4) + { + // value has been put on stack + + // encrypt + outputInstructions.Add(Instruction.CreateLdcI4(fei.encryptOps)); + outputInstructions.Add(Instruction.CreateLdcI4(fei.salt)); + outputInstructions.Add(Instruction.Create(OpCodes.Call, encryptionServiceMetadataImporter.EncryptInt)); + // xor + outputInstructions.Add(Instruction.CreateLdcI4((int)fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + } + else if (fei.fieldType == ElementType.I8 || fei.fieldType == ElementType.U8 || fei.fieldType == ElementType.R8) + { + // value has been put on stack + + // encrypt + outputInstructions.Add(Instruction.CreateLdcI4(fei.encryptOps)); + outputInstructions.Add(Instruction.CreateLdcI4(fei.salt)); + outputInstructions.Add(Instruction.Create(OpCodes.Call, encryptionServiceMetadataImporter.EncryptLong)); + // xor + outputInstructions.Add(Instruction.Create(OpCodes.Ldc_I8, fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + } + else + { + Assert.IsTrue(false, $"Unsupported field type: {fei.fieldType} for encryption"); + } + + outputInstructions.Add(currentInstruction.Clone()); + } + + public override void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction) + { + outputInstructions.Add(currentInstruction.Clone()); + DefaultMetadataImporter importer = GetMetadataImporter(method); + EncryptionServiceMetadataImporter encryptionServiceMetadataImporter = importer.GetEncryptionServiceMetadataImporterOfModule(field.Module); + FieldEncryptInfo fei = GetFieldEncryptInfo(field); + if (fei.fieldType == ElementType.I4 || fei.fieldType == ElementType.U4 || fei.fieldType == ElementType.R4) + { + // value has been put on stack + // xor + if (fei.fieldType == ElementType.R4) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastFloatAsInt)); + } + outputInstructions.Add(Instruction.CreateLdcI4((int)fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + + // decrypt + outputInstructions.Add(Instruction.CreateLdcI4(fei.encryptOps)); + outputInstructions.Add(Instruction.CreateLdcI4(fei.salt)); + outputInstructions.Add(Instruction.Create(OpCodes.Call, encryptionServiceMetadataImporter.DecryptInt)); + } + else if (fei.fieldType == ElementType.I8 || fei.fieldType == ElementType.U8 || fei.fieldType == ElementType.R8) + { + // value has been put on stack + // xor + if (fei.fieldType == ElementType.R8) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastDoubleAsLong)); + } + outputInstructions.Add(Instruction.Create(OpCodes.Ldc_I8, fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + + // decrypt + outputInstructions.Add(Instruction.CreateLdcI4(fei.encryptOps)); + outputInstructions.Add(Instruction.CreateLdcI4(fei.salt)); + outputInstructions.Add(Instruction.Create(OpCodes.Call, encryptionServiceMetadataImporter.DecryptLong)); + } + else + { + Assert.IsTrue(false, $"Unsupported field type: {fei.fieldType} for decryption"); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs.meta new file mode 100644 index 0000000..905f446 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6707a66ae63e2c498d55088c6e8ef4a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs new file mode 100644 index 0000000..054044e --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs @@ -0,0 +1,108 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz; +using Obfuz.Settings; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + + public class FieldEncryptPass : InstructionObfuscationPassBase + { + private readonly List _configFiles; + private readonly int _encryptionLevel; + private IEncryptPolicy _encryptionPolicy; + private IFieldEncryptor _memoryEncryptor; + + public override ObfuscationPassType Type => ObfuscationPassType.FieldEncrypt; + + public FieldEncryptPass(FieldEncryptionSettings settings) + { + _configFiles = settings.ruleFiles.ToList(); + _encryptionLevel = settings.encryptionLevel; + } + + protected override bool ForceProcessAllAssembliesAndIgnoreAllPolicy => true; + + public override void Start() + { + var ctx = ObfuscationPassContext.Current; + _memoryEncryptor = new DefaultFieldEncryptor(ctx.encryptionScopeProvider, ctx.moduleEntityManager, _encryptionLevel); + _encryptionPolicy = new ConfigurableEncryptPolicy(ctx.assembliesToObfuscate, _configFiles); + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return true; + } + + private bool IsSupportedFieldType(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.I4: + case ElementType.I8: + case ElementType.U4: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + return true; + default: return false; + } + } + + protected override bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, List outputInstructions, List totalFinalInstructions) + { + Code code = inst.OpCode.Code; + if (!(inst.Operand is IField field) || !field.IsField) + { + return false; + } + FieldDef fieldDef = field.ResolveFieldDefThrow(); + if (!IsSupportedFieldType(fieldDef.FieldSig.Type) || !_encryptionPolicy.NeedEncrypt(fieldDef)) + { + return false; + } + switch (code) + { + case Code.Ldfld: + { + _memoryEncryptor.Decrypt(callingMethod, fieldDef, outputInstructions, inst); + break; + } + case Code.Stfld: + { + _memoryEncryptor.Encrypt(callingMethod, fieldDef, outputInstructions, inst); + break; + } + case Code.Ldsfld: + { + _memoryEncryptor.Decrypt(callingMethod, fieldDef, outputInstructions, inst); + break; + } + case Code.Stsfld: + { + _memoryEncryptor.Encrypt(callingMethod, fieldDef, outputInstructions, inst); + break; + } + case Code.Ldflda: + case Code.Ldsflda: + { + throw new System.Exception($"You shouldn't get reference to memory encryption field: {field}"); + } + default: return false; + } + //Debug.Log($"memory encrypt field: {field}"); + return true; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs.meta new file mode 100644 index 0000000..1e60f28 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3da24d0f1f1fc7449cbd0e7ddd03aa2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs new file mode 100644 index 0000000..a1a7705 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs @@ -0,0 +1,19 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public interface IEncryptPolicy + { + bool NeedEncrypt(FieldDef field); + } + + public abstract class EncryptPolicyBase : IEncryptPolicy + { + public abstract bool NeedEncrypt(FieldDef field); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs.meta new file mode 100644 index 0000000..d5c1047 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a48d0500d0737404cad9c9ef23a9467c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs new file mode 100644 index 0000000..7c9f7c3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs @@ -0,0 +1,29 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public class MemoryEncryptionContext + { + public ModuleDef module; + + public Instruction currentInstruction; + } + + public interface IFieldEncryptor + { + void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + + void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + } + + public abstract class FieldEncryptorBase : IFieldEncryptor + { + public abstract void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + public abstract void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs.meta new file mode 100644 index 0000000..dca5071 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a3ec14fca5169d479529d21b2eeada1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs new file mode 100644 index 0000000..4028c41 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs @@ -0,0 +1,85 @@ +using dnlib.DotNet.Emit; +using dnlib.DotNet; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.ObfusPasses +{ + public abstract class InstructionObfuscationPassBase : ObfuscationPassBase + { + protected virtual bool ForceProcessAllAssembliesAndIgnoreAllPolicy => false; + + protected abstract bool NeedObfuscateMethod(MethodDef method); + + public override void Process() + { + var ctx = ObfuscationPassContext.Current; + var modules = ForceProcessAllAssembliesAndIgnoreAllPolicy ? ctx.allObfuscationRelativeModules : ctx.modulesToObfuscate; + ObfuscationMethodWhitelist whiteList = ctx.whiteList; + ConfigurablePassPolicy passPolicy = ctx.passPolicy; + foreach (ModuleDef mod in modules) + { + if (!ForceProcessAllAssembliesAndIgnoreAllPolicy && (whiteList.IsInWhiteList(mod) || !Support(passPolicy.GetAssemblyObfuscationPasses(mod)))) + { + continue; + } + // ToArray to avoid modify list exception + foreach (TypeDef type in mod.GetTypes().ToArray()) + { + if (!ForceProcessAllAssembliesAndIgnoreAllPolicy && (whiteList.IsInWhiteList(type) || !Support(passPolicy.GetTypeObfuscationPasses(type)))) + { + continue; + } + // ToArray to avoid modify list exception + foreach (MethodDef method in type.Methods.ToArray()) + { + if (!method.HasBody || (!ForceProcessAllAssembliesAndIgnoreAllPolicy && (ctx.whiteList.IsInWhiteList(method) || !Support(passPolicy.GetMethodObfuscationPasses(method)) || !NeedObfuscateMethod(method)))) + { + continue; + } + // TODO if isGeneratedBy Obfuscator, continue + ObfuscateData(method); + } + } + } + } + + + protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, + List outputInstructions, List totalFinalInstructions); + + private void ObfuscateData(MethodDef method) + { + IList instructions = method.Body.Instructions; + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + outputInstructions.Clear(); + if (TryObfuscateInstruction(method, inst, instructions, i, 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 + inst.OpCode = outputInstructions[0].OpCode; + inst.Operand = outputInstructions[0].Operand; + totalFinalInstructions.Add(inst); + for (int k = 1; k < outputInstructions.Count; k++) + { + totalFinalInstructions.Add(outputInstructions[k]); + } + } + else + { + totalFinalInstructions.Add(inst); + } + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs.meta new file mode 100644 index 0000000..ba2497a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0cad4b764050f44f8c9b225056a4f49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs new file mode 100644 index 0000000..b3aaf0c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs @@ -0,0 +1,20 @@ +using Obfuz.Emit; + +namespace Obfuz.ObfusPasses +{ + public abstract class ObfuscationPassBase : IObfuscationPass + { + public abstract ObfuscationPassType Type { get; } + + public bool Support(ObfuscationPassType passType) + { + return passType.HasFlag(Type); + } + + public abstract void Start(); + + public abstract void Stop(); + + public abstract void Process(); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs.meta new file mode 100644 index 0000000..335ee27 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f3e7e1d2a3ad3a4fb1a81e97730b5a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs new file mode 100644 index 0000000..ec28ad3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs @@ -0,0 +1,25 @@ +using System; + +namespace Obfuz.ObfusPasses +{ + [Flags] + public enum ObfuscationPassType + { + None = 0, + + ConstEncrypt = 0x1, + FieldEncrypt = 0x2, + + SymbolObfus = 0x100, + CallObfus = 0x200, + ExprObfus = 0x400, + ControlFlowObfus = 0x800, + + AllObfus = SymbolObfus | CallObfus | ExprObfus | ControlFlowObfus, + AllEncrypt = ConstEncrypt | FieldEncrypt, + + MethodBodyObfusOrEncrypt = ConstEncrypt | CallObfus | ExprObfus | ControlFlowObfus, + + All = ~0, + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs.meta new file mode 100644 index 0000000..b03ea20 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5addd02f6f3dc0a4d888a0f74bd5ce4d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus.meta new file mode 100644 index 0000000..331b3d1 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b746569f7c0d9754fa6f2925538eddbd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs new file mode 100644 index 0000000..184ce1b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs @@ -0,0 +1,33 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public interface INameMaker + { + void AddPreservedName(TypeDef typeDef, string name); + + void AddPreservedNamespace(TypeDef typeDef, string name); + + void AddPreservedName(MethodDef methodDef, string name); + + void AddPreservedName(FieldDef fieldDef, string name); + + void AddPreservedName(PropertyDef propertyDef, string name); + + void AddPreservedName(EventDef eventDef, string name); + + string GetNewName(TypeDef typeDef, string originalName); + + string GetNewNamespace(TypeDef typeDef, string originalNamespace, bool reuse); + + string GetNewName(MethodDef methodDef, string originalName); + + string GetNewName(ParamDef param, string originalName); + + string GetNewName(FieldDef fieldDef, string originalName); + + string GetNewName(PropertyDef propertyDef, string originalName); + + string GetNewName(EventDef eventDef, string originalName); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs.meta new file mode 100644 index 0000000..8062197 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c24d29f654d00b44bb6aa3b4bf222dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs new file mode 100644 index 0000000..2a39e45 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs @@ -0,0 +1,17 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public interface IObfuscationPolicy + { + bool NeedRename(TypeDef typeDef); + + bool NeedRename(MethodDef methodDef); + + bool NeedRename(FieldDef fieldDef); + + bool NeedRename(PropertyDef propertyDef); + + bool NeedRename(EventDef eventDef); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs.meta new file mode 100644 index 0000000..ffe0fee --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd640b26c1d868544a7a91a0f986fdde +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers.meta new file mode 100644 index 0000000..9cd6e53 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c970ffd992fbc154aaa37a2c48c24d5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs new file mode 100644 index 0000000..b7b4e3f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs @@ -0,0 +1,29 @@ +using dnlib.DotNet; +using System.Text; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public class DebugNameMaker : NameMakerBase + { + private class TestNameScope : NameScopeBase + { + private int _nextIndex; + protected override void BuildNewName(StringBuilder nameBuilder, string originalName, string lastName) + { + if (string.IsNullOrEmpty(lastName)) + { + nameBuilder.Append($"${originalName}"); + } + else + { + nameBuilder.Append($"${originalName}{_nextIndex++}"); + } + } + } + + protected override INameScope CreateNameScope() + { + return new TestNameScope(); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs.meta new file mode 100644 index 0000000..11b505b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abc1adfad5c7754499ceed4d4646eb58 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs new file mode 100644 index 0000000..9fb5a98 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs @@ -0,0 +1,9 @@ +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public interface INameScope + { + void AddPreservedName(string name); + + string GetNewName(string originalName, bool reuse); + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs.meta new file mode 100644 index 0000000..4c63253 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c3884d338faf564eab48d58f02adc39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs new file mode 100644 index 0000000..157aeab --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs @@ -0,0 +1,103 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public abstract class NameMakerBase : INameMaker + { + + private readonly Dictionary _nameScopes = new Dictionary(); + + private readonly object _namespaceScope = new object(); + + protected abstract INameScope CreateNameScope(); + + protected INameScope GetNameScope(object key) + { + if (!_nameScopes.TryGetValue(key, out var nameScope)) + { + nameScope = CreateNameScope(); + _nameScopes[key] = nameScope; + } + return nameScope; + } + + public void AddPreservedName(TypeDef typeDef, string name) + { + GetNameScope(typeDef.Module).AddPreservedName(name); + } + + public void AddPreservedName(MethodDef methodDef, string name) + { + GetNameScope(methodDef.DeclaringType).AddPreservedName(name); + } + + public void AddPreservedName(FieldDef fieldDef, string name) + { + GetNameScope(fieldDef.DeclaringType).AddPreservedName(name); + } + + public void AddPreservedName(PropertyDef propertyDef, string name) + { + GetNameScope(propertyDef.DeclaringType).AddPreservedName(name); + } + + public void AddPreservedName(EventDef eventDef, string name) + { + GetNameScope(eventDef.DeclaringType).AddPreservedName(name); + } + + public void AddPreservedNamespace(TypeDef typeDef, string name) + { + GetNameScope(_namespaceScope).AddPreservedName(name); + } + + private string GetDefaultNewName(object scope, string originName) + { + return GetNameScope(scope).GetNewName(originName, false); + } + + public string GetNewNamespace(TypeDef typeDef, string originalNamespace, bool reuse) + { + if (string.IsNullOrEmpty(originalNamespace)) + { + return string.Empty; + } + return GetNameScope(_namespaceScope).GetNewName(originalNamespace, reuse); + } + + public string GetNewName(TypeDef typeDef, string originalName) + { + return GetDefaultNewName(typeDef.Module, originalName); + } + + public string GetNewName(MethodDef methodDef, string originalName) + { + return (methodDef.IsVirtual ? ">" : "") + GetDefaultNewName(methodDef.DeclaringType, originalName); + } + + public virtual string GetNewName(ParamDef param, string originalName) + { + return "1"; + } + + public string GetNewName(FieldDef fieldDef, string originalName) + { + return GetDefaultNewName(fieldDef.DeclaringType, originalName); + } + + public string GetNewName(PropertyDef propertyDef, string originalName) + { + return GetDefaultNewName(propertyDef.DeclaringType, originalName); + } + + public string GetNewName(EventDef eventDef, string originalName) + { + return GetDefaultNewName(eventDef.DeclaringType, originalName); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs.meta new file mode 100644 index 0000000..d3ca8a0 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 205da3a8ebfd4ae4ba72d27db4b92d3f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs new file mode 100644 index 0000000..b0ba913 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Remoting.Messaging; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public static class NameMakerFactory + { + public static INameMaker CreateDebugNameMaker() + { + return new DebugNameMaker(); + } + + public static INameMaker CreateNameMakerBaseASCIICharSet(string namePrefix) + { + var words = new List(); + for (int i = 0; i < 26; i++) + { + words.Add(((char)('a' + i)).ToString()); + words.Add(((char)('A' + i)).ToString()); + } + return new WordSetNameMaker(namePrefix, words); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs.meta new file mode 100644 index 0000000..e1fb22c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afa0e87123ec9854b806098330c4980a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs new file mode 100644 index 0000000..610f845 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs @@ -0,0 +1,35 @@ +using Microsoft.SqlServer.Server; +using System.Collections.Generic; +using System.Text; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + + public class NameScope : NameScopeBase + { + private readonly string _namePrefix; + private readonly List _wordSet; + private int _nextIndex; + + public NameScope(string namePrefix, List wordSet) + { + _namePrefix = namePrefix; + _wordSet = wordSet; + _nextIndex = 0; + } + + protected override void BuildNewName(StringBuilder nameBuilder, string originalName, string lastName) + { + nameBuilder.Append(_namePrefix); + for (int i = _nextIndex++; ;) + { + nameBuilder.Append(_wordSet[i % _wordSet.Count]); + i = i / _wordSet.Count; + if (i == 0) + { + break; + } + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs.meta new file mode 100644 index 0000000..e224237 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a35c5c4b49c98a84f94b690c26900c33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs new file mode 100644 index 0000000..4f3d213 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Text; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public abstract class NameScopeBase : INameScope + { + + private readonly Dictionary _nameMap = new Dictionary(); + + private readonly HashSet _preservedNames = new HashSet(); + + + public void AddPreservedName(string name) + { + if (!string.IsNullOrEmpty(name)) + { + _preservedNames.Add(name); + } + } + + + protected abstract void BuildNewName(StringBuilder nameBuilder, string originalName, string lastName); + + private string CreateNewName(string originalName) + { + var nameBuilder = new StringBuilder(); + string lastName = null; + while (true) + { + nameBuilder.Clear(); + BuildNewName(nameBuilder, originalName, lastName); + string newName = nameBuilder.ToString(); + lastName = newName; + if (_preservedNames.Add(newName)) + { + return newName; + } + } + } + + public string GetNewName(string originalName, bool reuse) + { + if (!reuse) + { + return CreateNewName(originalName); + } + if (_nameMap.TryGetValue(originalName, out var newName)) + { + return newName; + } + newName = CreateNewName(originalName); + _nameMap[originalName] = newName; + return newName; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs.meta new file mode 100644 index 0000000..17853c8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 26e6ae1f35e7f094c844cf1567b88a19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs new file mode 100644 index 0000000..8e8b5db --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs @@ -0,0 +1,26 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + + public class WordSetNameMaker : NameMakerBase + { + private readonly string _namePrefix; + private readonly List _wordSet; + + public WordSetNameMaker(string namePrefix, List wordSet) + { + _namePrefix = namePrefix; + _wordSet = wordSet; + } + + protected override INameScope CreateNameScope() + { + return new NameScope(_namePrefix, _wordSet); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs.meta new file mode 100644 index 0000000..23706b3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47c92aea40a66e34b92f9eb5c0d380ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies.meta new file mode 100644 index 0000000..c16875c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 98e496436c90c0a4f82711af059471c7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs new file mode 100644 index 0000000..ba6e9c6 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs @@ -0,0 +1,68 @@ +using dnlib.DotNet; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + public class CacheRenamePolicy : ObfuscationPolicyBase + { + private readonly IObfuscationPolicy _underlyingPolicy; + + private readonly Dictionary _computeCache = new Dictionary(); + + public CacheRenamePolicy(IObfuscationPolicy underlyingPolicy) + { + _underlyingPolicy = underlyingPolicy; + } + + public override bool NeedRename(TypeDef typeDef) + { + if (!_computeCache.TryGetValue(typeDef, out var value)) + { + value = _underlyingPolicy.NeedRename(typeDef); + _computeCache[typeDef] = value; + } + return value; + } + + public override bool NeedRename(MethodDef methodDef) + { + if (!_computeCache.TryGetValue(methodDef, out var value)) + { + value = _underlyingPolicy.NeedRename(methodDef); + _computeCache[methodDef] = value; + } + return value; + } + + public override bool NeedRename(FieldDef fieldDef) + { + if (!_computeCache.TryGetValue(fieldDef, out var value)) + { + value = _underlyingPolicy.NeedRename(fieldDef); + _computeCache[fieldDef] = value; + } + return value; + } + + public override bool NeedRename(PropertyDef propertyDef) + { + if (!_computeCache.TryGetValue(propertyDef, out var value)) + { + value = _underlyingPolicy.NeedRename(propertyDef); + _computeCache[propertyDef] = value; + } + return value; + } + + public override bool NeedRename(EventDef eventDef) + { + if (!_computeCache.TryGetValue(eventDef, out var value)) + { + value = _underlyingPolicy.NeedRename(eventDef); + _computeCache[eventDef] = value; + } + return value; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs.meta new file mode 100644 index 0000000..ab4330f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c319b2ad62ad8794f9a8bc234c856d7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs new file mode 100644 index 0000000..a84a587 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs @@ -0,0 +1,40 @@ +using dnlib.DotNet; +using System.Linq; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + public class CombineRenamePolicy : IObfuscationPolicy + { + private readonly IObfuscationPolicy[] _policies; + + public CombineRenamePolicy(params IObfuscationPolicy[] policies) + { + _policies = policies; + } + + public bool NeedRename(TypeDef typeDef) + { + return _policies.All(policy => policy.NeedRename(typeDef)); + } + + public bool NeedRename(MethodDef methodDef) + { + return _policies.All(policy => policy.NeedRename(methodDef)); + } + + public bool NeedRename(FieldDef fieldDef) + { + return _policies.All(policy => policy.NeedRename(fieldDef)); + } + + public bool NeedRename(PropertyDef propertyDef) + { + return _policies.All(policy => policy.NeedRename(propertyDef)); + } + + public bool NeedRename(EventDef eventDef) + { + return _policies.All(policy => policy.NeedRename(eventDef)); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs.meta new file mode 100644 index 0000000..fcf6ea9 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97be4546adeb71947bf644949c3a9e82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs new file mode 100644 index 0000000..7122956 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs @@ -0,0 +1,699 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using UnityEngine; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + + public class ConfigurableRenamePolicy : ObfuscationPolicyBase + { + enum RuleType + { + Assembly = 1, + Type = 2, + Method = 3, + Field = 4, + Property = 5, + Event = 6, + } + + enum ModifierType + { + None = 0x0, + Private = 0x1, + Protected = 0x2, + Public = 0x3, + } + + class MethodRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public bool? obfuscateName; + } + + class FieldRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public bool? obfuscateName; + } + + class PropertyRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public bool? obfuscateName; + public bool? obfuscateGetter; + public bool? obfuscateSetter; + } + + class EventRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public bool? obfuscateName; + public bool? obfuscateAdd; + public bool? obfuscateRemove; + public bool? obfuscateFire; + } + + class TypeRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public ClassType? classType; + public bool? obfuscateName; + public bool? obfuscateNamespace; + public List fields; + public List methods; + public List properties; + public List events; + } + + class AssemblyRuleSpec + { + public string assemblyName; + public bool? obfuscateName; + public List types; + } + + private readonly Dictionary _assemblyRuleSpecs = new Dictionary(); + + private AssemblyRuleSpec ParseAssembly(XmlElement ele) + { + string assemblyName = ele.GetAttribute("name"); + if (string.IsNullOrEmpty(assemblyName)) + { + throw new Exception($"Invalid xml file, assembly name is empty"); + } + if (!_obfuscationAssemblyNames.Contains(assemblyName)) + { + throw new Exception($"unknown assembly name:{assemblyName}, not in ObfuzSettings.obfuscationAssemblyNames"); + } + if (_assemblyRuleSpecs.ContainsKey(assemblyName)) + { + throw new Exception($"Invalid xml file, duplicate assembly name {assemblyName}"); + } + var rule = new AssemblyRuleSpec() + { + assemblyName = assemblyName, + obfuscateName = ConfigUtil.ParseNullableBool(ele.GetAttribute("obName")), + types = new List(), + }; + + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement childElement)) + { + continue; + } + if (childElement.Name != "type") + { + throw new Exception($"Invalid xml file, unknown node {childElement.Name}"); + } + TypeRuleSpec type = ParseType(childElement); + rule.types.Add(type); + } + return rule; + } + + private enum ClassType + { + None = 0x0, + Class = 0x1, + Struct = 0x2, + Interface = 0x4, + Enum = 0x8, + Delegate = 0x10, + } + + private ClassType? ParseClassType(string classType) + { + if (string.IsNullOrEmpty(classType)) + { + return null; + } + + ClassType type = ClassType.None; + foreach (var s in classType.Split('|')) + { + switch (s) + { + case "class": type |= ClassType.Class; break; + case "struct": type |= ClassType.Struct; break; + case "interface": type |= ClassType.Interface; break; + case "enum": type |= ClassType.Enum; break; + case "delegate": type |= ClassType.Delegate; break; + default: throw new Exception($"Invalid class type {s}"); + } + } + return type; + } + + private ModifierType? ParseModifierType(string modifierType) + { + if (string.IsNullOrEmpty(modifierType)) + { + return null; + } + ModifierType type = ModifierType.None; + foreach (var s in modifierType.Split('|')) + { + switch (s) + { + case "public": type |= ModifierType.Public; break; + case "protected": type |= ModifierType.Protected; break; + case "private": type |= ModifierType.Private; break; + default: throw new Exception($"Invalid modifier type {s}"); + } + } + return type; + } + + private TypeRuleSpec ParseType(XmlElement element) + { + var rule = new TypeRuleSpec(); + + rule.nameMatcher = new NameMatcher(element.GetAttribute("name")); + rule.obfuscateName = ConfigUtil.ParseNullableBool(element.GetAttribute("obName")); + rule.obfuscateNamespace = ConfigUtil.ParseNullableBool(element.GetAttribute("obNamespace")); + rule.modifierType = ParseModifierType(element.GetAttribute("modifier")); + rule.classType = ParseClassType(element.GetAttribute("classType")); + + //rule.nestTypeRuleSpecs = new List(); + rule.fields = new List(); + rule.methods = new List(); + rule.properties = new List(); + rule.events = new List(); + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement childElement)) + { + continue; + } + switch (childElement.Name) + { + case "field": + { + var fieldRuleSpec = new FieldRuleSpec(); + fieldRuleSpec.nameMatcher = new NameMatcher(childElement.GetAttribute("name")); + fieldRuleSpec.modifierType = ParseModifierType(childElement.GetAttribute("modifier")); + fieldRuleSpec.obfuscateName = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obName")); + rule.fields.Add(fieldRuleSpec); + break; + } + case "method": + { + var methodRuleSpec = new MethodRuleSpec(); + methodRuleSpec.nameMatcher = new NameMatcher(childElement.GetAttribute("name")); + methodRuleSpec.modifierType = ParseModifierType(childElement.GetAttribute("modifier")); + methodRuleSpec.obfuscateName = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obName")); + rule.methods.Add(methodRuleSpec); + break; + } + case "property": + { + var propertyRulerSpec = new PropertyRuleSpec(); + propertyRulerSpec.nameMatcher = new NameMatcher(childElement.GetAttribute("name")); + propertyRulerSpec.modifierType = ParseModifierType(childElement.GetAttribute("modifier")); + propertyRulerSpec.obfuscateName = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obName")); + propertyRulerSpec.obfuscateGetter = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obGetter")); + propertyRulerSpec.obfuscateSetter = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obSetter")); + rule.properties.Add(propertyRulerSpec); + break; + } + case "event": + { + var eventRuleSpec = new EventRuleSpec(); + eventRuleSpec.nameMatcher = new NameMatcher(childElement.GetAttribute("name")); + eventRuleSpec.modifierType = ParseModifierType(childElement.GetAttribute("modifier")); + eventRuleSpec.obfuscateName = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obName")); + eventRuleSpec.obfuscateAdd = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obAdd")); + eventRuleSpec.obfuscateRemove = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obRemove")); + eventRuleSpec.obfuscateFire = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obFire")); + rule.events.Add(eventRuleSpec); + break; + } + default: throw new Exception($"Invalid xml file, unknown node {childElement.Name} in type node"); + } + } + return rule; + } + + private void LoadXmls(List xmlFiles) + { + var rawAssemblySpecElements = new List(); + foreach (string file in xmlFiles) + { + LoadRawXml(file, rawAssemblySpecElements); + } + ResolveAssemblySpecs(rawAssemblySpecElements); + } + + private void ResolveAssemblySpecs(List rawAssemblySpecElements) + { + foreach (XmlElement ele in rawAssemblySpecElements) + { + var assemblyRule = ParseAssembly(ele); + _assemblyRuleSpecs.Add(assemblyRule.assemblyName, assemblyRule); + } + } + + private void LoadRawXml(string xmlFile, List rawAssemblyElements) + { + Debug.Log($"ObfuscateRule::LoadXml {xmlFile}"); + var doc = new XmlDocument(); + doc.Load(xmlFile); + var root = doc.DocumentElement; + if (root.Name != "obfuz") + { + throw new Exception($"Invalid xml file {xmlFile}, root name should be 'obfuz'"); + } + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement element)) + { + continue; + } + switch (element.Name) + { + case "assembly": + { + rawAssemblyElements.Add(element); + break; + } + default: + { + throw new Exception($"Invalid xml file {xmlFile}, unknown node {element.Name}"); + } + } + } + } + + private ModifierType ComputeModifierType(TypeAttributes visibility) + { + if (visibility == TypeAttributes.NotPublic || visibility == TypeAttributes.NestedPrivate) + { + return ModifierType.Private; + } + if (visibility == TypeAttributes.Public || visibility == TypeAttributes.NestedPublic) + { + return ModifierType.Public; + } + return ModifierType.Protected; + } + + private ModifierType ComputeModifierType(FieldAttributes access) + { + if (access == FieldAttributes.Private || access == FieldAttributes.PrivateScope) + { + return ModifierType.Private; + } + if (access == FieldAttributes.Public) + { + return ModifierType.Public; + } + return ModifierType.Protected; + } + + //private ModifierType ComputeModifierType(MethodAttributes access) + //{ + // if (access == MethodAttributes.Private || access == MethodAttributes.PrivateScope) + // { + // return ModifierType.Private; + // } + // if (access == MethodAttributes.Public) + // { + // return ModifierType.Public; + // } + // return ModifierType.Protected; + //} + + private bool MatchModifier(ModifierType? modifierType, TypeDef typeDef) + { + return modifierType == null || (modifierType & ComputeModifierType(typeDef.Visibility)) != 0; + } + + private bool MatchModifier(ModifierType? modifierType, FieldDef fieldDef) + { + return modifierType == null || (modifierType & ComputeModifierType(fieldDef.Access)) != 0; + } + + private bool MatchModifier(ModifierType? modifierType, MethodDef methodDef) + { + return modifierType == null || (modifierType & ComputeModifierType((FieldAttributes)methodDef.Access)) != 0; + } + + private bool MatchModifier(ModifierType? modifierType, PropertyDef propertyDef) + { + return modifierType == null || (modifierType & ComputeModifierType((FieldAttributes)propertyDef.Attributes)) != 0; + } + + private bool MatchModifier(ModifierType? modifierType, EventDef eventDef) + { + return modifierType == null || (modifierType & ComputeModifierType((FieldAttributes)eventDef.Attributes)) != 0; + } + + private class MethodComputeCache + { + public bool obfuscateName = true; + public bool obfuscateParam = true; + public bool obfuscateBody = true; + } + + //private class TypeDefComputeCache + //{ + // public bool obfuscateName = true; + // public bool obfuscateNamespace = true; + + // public readonly Dictionary methods = new Dictionary(); + + // public readonly HashSet notObfuscatedFields = new HashSet(); + + // public readonly HashSet notObfuscatedProperties = new HashSet(); + + // public readonly HashSet notObfuscatedEvents = new HashSet(); + //} + + private readonly Dictionary _typeSpecCache = new Dictionary(); + private readonly Dictionary _methodSpecCache = new Dictionary(); + private readonly Dictionary _fieldSpecCache = new Dictionary(); + private readonly Dictionary _propertySpecCache = new Dictionary(); + private readonly Dictionary _eventSpecCache = new Dictionary(); + + + private readonly HashSet _obfuscationAssemblyNames; + + public ConfigurableRenamePolicy(List obfuscationAssemblyNames, List xmlFiles) + { + _obfuscationAssemblyNames = new HashSet(obfuscationAssemblyNames); + LoadXmls(xmlFiles); + } + + private void BuildDefaultTypeMemberCache(TypeDef typeDef, TypeRuleSpec typeRule) + { + foreach (var fieldDef in typeDef.Fields) + { + var fieldRule = new FieldRuleSpec() + { + obfuscateName = typeRule.obfuscateName, + }; + _fieldSpecCache.Add(fieldDef, fieldRule); + } + foreach (var eventDef in typeDef.Events) + { + var eventRule = new EventRuleSpec() + { + obfuscateName = typeRule.obfuscateName, + obfuscateAdd = typeRule.obfuscateName, + obfuscateRemove = typeRule.obfuscateName, + obfuscateFire = typeRule.obfuscateName, + }; + _eventSpecCache.Add(eventDef, eventRule); + } + foreach (var propertyDef in typeDef.Properties) + { + var propertyRule = new PropertyRuleSpec() + { + obfuscateName = typeRule.obfuscateName, + obfuscateGetter = typeRule.obfuscateName, + obfuscateSetter = typeRule.obfuscateName, + }; + _propertySpecCache.Add(propertyDef, propertyRule); + } + foreach (MethodDef methodDef in typeDef.Methods) + { + var methodRule = new MethodRuleSpec() + { + obfuscateName = typeRule.obfuscateName, + }; + _methodSpecCache.Add(methodDef, methodRule); + } + } + + private bool MatchClassType(ClassType? classType, TypeDef typeDef) + { + if (classType == null) + { + return true; + } + if (typeDef.IsInterface && (classType & ClassType.Interface) != 0) + { + return true; + } + if (typeDef.IsEnum && (classType & ClassType.Enum) != 0) + { + return true; + } + if (typeDef.IsDelegate && (classType & ClassType.Delegate) != 0) + { + return true; + } + if (typeDef.IsValueType && !typeDef.IsEnum && (classType & ClassType.Struct) != 0) + { + return true; + } + if (!typeDef.IsValueType && !typeDef.IsInterface && !typeDef.IsDelegate && (classType & ClassType.Class) != 0) + { + return true; + } + return false; + } + + private TypeRuleSpec GetOrCreateTypeDefRenameComputeCache(TypeDef typeDef) + { + if (_typeSpecCache.TryGetValue(typeDef, out var typeRule)) + { + return typeRule; + } + typeRule = new TypeRuleSpec(); + _typeSpecCache.Add(typeDef, typeRule); + + if (!_assemblyRuleSpecs.TryGetValue(typeDef.Module.Assembly.Name, out var assemblyRuleSpec)) + { + typeRule.obfuscateName = true; + typeRule.obfuscateNamespace = true; + BuildDefaultTypeMemberCache(typeDef, typeRule); + return typeRule; + } + + typeRule.obfuscateName = assemblyRuleSpec.obfuscateName ?? true; + typeRule.obfuscateNamespace = assemblyRuleSpec.obfuscateName ?? true; + + if (typeDef.DeclaringType != null) + { + TypeRuleSpec declaringTypeSpec = GetOrCreateTypeDefRenameComputeCache(typeDef.DeclaringType); + if (declaringTypeSpec.obfuscateName != null) + { + typeRule.obfuscateName = declaringTypeSpec.obfuscateName; + } + if (declaringTypeSpec.obfuscateNamespace != null) + { + typeRule.obfuscateNamespace = declaringTypeSpec.obfuscateNamespace; + } + } + + string typeName = typeDef.FullName; + bool findMatch = false; + foreach (var typeSpec in assemblyRuleSpec.types) + { + if (!typeSpec.nameMatcher.IsMatch(typeName) || !MatchModifier(typeSpec.modifierType, typeDef) || !MatchClassType(typeSpec.classType, typeDef)) + { + continue; + } + findMatch = true; + if (typeSpec.obfuscateName != null) + { + typeRule.obfuscateName = typeSpec.obfuscateName; + } + if (typeSpec.obfuscateNamespace != null) + { + typeRule.obfuscateNamespace = typeSpec.obfuscateNamespace; + } + + + foreach (var fieldDef in typeDef.Fields) + { + var fieldRule = new FieldRuleSpec() + { + obfuscateName = typeRule.obfuscateName, + }; + _fieldSpecCache.Add(fieldDef, fieldRule); + foreach (var fieldSpec in typeSpec.fields) + { + if (fieldSpec.nameMatcher.IsMatch(fieldDef.Name) && MatchModifier(fieldSpec.modifierType, fieldDef)) + { + if (fieldSpec.obfuscateName != null) + { + fieldRule.obfuscateName = fieldSpec.obfuscateName; + } + break; + } + } + } + + var methodObfuscateFromPropertyOrEvent = new Dictionary(); + + foreach (var eventDef in typeDef.Events) + { + var eventRule = new EventRuleSpec() + { + obfuscateName = typeRule.obfuscateName, + obfuscateAdd = typeRule.obfuscateName, + obfuscateRemove = typeRule.obfuscateName, + obfuscateFire = typeRule.obfuscateName, + }; + _eventSpecCache.Add(eventDef, eventRule); + foreach (var eventSpec in typeSpec.events) + { + if (!eventSpec.nameMatcher.IsMatch(eventDef.Name) || !MatchModifier(eventSpec.modifierType, eventDef)) + { + continue; + } + if (eventSpec.obfuscateName != null) + { + eventRule.obfuscateName = eventSpec.obfuscateName; + } + if (eventSpec.obfuscateAdd != null) + { + eventRule.obfuscateAdd = eventSpec.obfuscateAdd; + } + if (eventSpec.obfuscateRemove != null) + { + eventRule.obfuscateRemove = eventSpec.obfuscateRemove; + } + if (eventSpec.obfuscateFire != null) + { + eventRule.obfuscateFire = eventSpec.obfuscateFire; + } + if (eventDef.AddMethod != null && eventRule.obfuscateAdd != null) + { + methodObfuscateFromPropertyOrEvent.Add(eventDef.AddMethod, eventRule.obfuscateAdd.Value); + } + if (eventDef.RemoveMethod != null && eventRule.obfuscateRemove != null) + { + methodObfuscateFromPropertyOrEvent.Add(eventDef.RemoveMethod, eventRule.obfuscateRemove.Value); + } + if (eventDef.InvokeMethod != null && eventRule.obfuscateFire != null) + { + methodObfuscateFromPropertyOrEvent.Add(eventDef.InvokeMethod, eventRule.obfuscateFire.Value); + } + break; + } + } + foreach (var propertyDef in typeDef.Properties) + { + var propertyRule = new PropertyRuleSpec() + { + obfuscateName = typeRule.obfuscateName, + obfuscateGetter = typeRule.obfuscateName, + obfuscateSetter = typeRule.obfuscateName, + }; + _propertySpecCache.Add(propertyDef, propertyRule); + foreach (var propertySpec in typeSpec.properties) + { + if (!propertySpec.nameMatcher.IsMatch(propertyDef.Name) || !MatchModifier(propertySpec.modifierType, propertyDef)) + { + continue; + } + if (propertySpec.obfuscateName != null) + { + propertyRule.obfuscateName = propertySpec.obfuscateName; + } + if (propertySpec.obfuscateGetter != null) + { + propertyRule.obfuscateGetter = propertySpec.obfuscateGetter; + } + if (propertySpec.obfuscateSetter != null) + { + propertyRule.obfuscateSetter = propertySpec.obfuscateSetter; + } + + if (propertyDef.GetMethod != null && propertyRule.obfuscateGetter != null) + { + methodObfuscateFromPropertyOrEvent.Add(propertyDef.GetMethod, propertyRule.obfuscateGetter.Value); + } + if (propertyDef.SetMethod != null && propertyRule.obfuscateSetter != null) + { + methodObfuscateFromPropertyOrEvent.Add(propertyDef.SetMethod, propertyRule.obfuscateSetter.Value); + } + break; + } + } + foreach (MethodDef methodDef in typeDef.Methods) + { + var methodRule = new MethodRuleSpec() + { + obfuscateName = typeRule.obfuscateName, + }; + _methodSpecCache.Add(methodDef, methodRule); + if (methodObfuscateFromPropertyOrEvent.TryGetValue(methodDef, out var obfuscateName)) + { + methodRule.obfuscateName = obfuscateName; + } + foreach (MethodRuleSpec methodSpec in typeSpec.methods) + { + if (!methodSpec.nameMatcher.IsMatch(methodDef.Name) || !MatchModifier(methodSpec.modifierType, methodDef)) + { + continue; + } + if (methodSpec.obfuscateName != null) + { + methodRule.obfuscateName = methodSpec.obfuscateName; + } + break; + } + } + } + if (!findMatch) + { + BuildDefaultTypeMemberCache(typeDef, typeRule); + } + + return typeRule; + } + + public override bool NeedRename(TypeDef typeDef) + { + var cache = GetOrCreateTypeDefRenameComputeCache(typeDef); + return cache.obfuscateName != false; + } + + public override bool NeedRename(MethodDef methodDef) + { + TypeDef typeDef = methodDef.DeclaringType; + GetOrCreateTypeDefRenameComputeCache(typeDef); + return _methodSpecCache[methodDef].obfuscateName != false; + } + + public override bool NeedRename(FieldDef fieldDef) + { + TypeDef typeDef = fieldDef.DeclaringType; + GetOrCreateTypeDefRenameComputeCache(typeDef); + return _fieldSpecCache[fieldDef].obfuscateName != false; + } + + public override bool NeedRename(PropertyDef propertyDef) + { + TypeDef typeDef = propertyDef.DeclaringType; + GetOrCreateTypeDefRenameComputeCache(typeDef); + return _propertySpecCache[propertyDef].obfuscateName != false; + } + + public override bool NeedRename(EventDef eventDef) + { + TypeDef typeDef = eventDef.DeclaringType; + GetOrCreateTypeDefRenameComputeCache(typeDef); + return _eventSpecCache[eventDef].obfuscateName != false; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs.meta new file mode 100644 index 0000000..e6b4fee --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c063bc949939fe44972c3d99870527e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs new file mode 100644 index 0000000..6a271ad --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs @@ -0,0 +1,33 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + public abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + + public virtual bool NeedRename(TypeDef typeDef) + { + return true; + } + + public virtual bool NeedRename(MethodDef methodDef) + { + return true; + } + + public virtual bool NeedRename(FieldDef fieldDef) + { + return true; + } + + public virtual bool NeedRename(PropertyDef propertyDef) + { + return true; + } + + public virtual bool NeedRename(EventDef eventDef) + { + return true; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs.meta new file mode 100644 index 0000000..2d0bf05 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ab98107a2ef9624b9b8a53061f682c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs new file mode 100644 index 0000000..1d0cb6b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs @@ -0,0 +1,50 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + internal class SupportPassPolicy : ObfuscationPolicyBase + { + private readonly ConfigurablePassPolicy _policy; + + + private bool Support(ObfuscationPassType passType) + { + return passType.HasFlag(ObfuscationPassType.SymbolObfus); + } + + public SupportPassPolicy(ConfigurablePassPolicy policy) + { + _policy = policy; + } + + public override bool NeedRename(TypeDef typeDef) + { + return Support(_policy.GetTypeObfuscationPasses(typeDef)); + } + + public override bool NeedRename(MethodDef methodDef) + { + return Support(_policy.GetMethodObfuscationPasses(methodDef)); + } + + public override bool NeedRename(FieldDef fieldDef) + { + return Support(_policy.GetFieldObfuscationPasses(fieldDef)); + } + + public override bool NeedRename(PropertyDef propertyDef) + { + return Support(_policy.GetPropertyObfuscationPasses(propertyDef)); + } + + public override bool NeedRename(EventDef eventDef) + { + return Support(_policy.GetEventObfuscationPasses(eventDef)); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs.meta new file mode 100644 index 0000000..bf7d3b8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 584dd4d4e9b9fa64090611d84b50980b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs new file mode 100644 index 0000000..de0854d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs @@ -0,0 +1,83 @@ +using dnlib.DotNet; +using Obfuz.Utils; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + public class SystemRenamePolicy : ObfuscationPolicyBase + { + public override bool NeedRename(TypeDef typeDef) + { + string name = typeDef.Name; + if (name == "" || name == "ObfuzIgnoreAttribute") + { + return false; + } + if (MetaUtil.HasObfuzIgnoreAttributeInSelfOrParent(typeDef)) + { + return false; + } + return true; + } + + public override bool NeedRename(MethodDef methodDef) + { + if (methodDef.DeclaringType.IsDelegate) + { + return false; + } + if (methodDef.Name == ".ctor" || methodDef.Name == ".cctor") + { + return false; + } + + if (MetaUtil.HasObfuzIgnoreAttribute(methodDef) || MetaUtil.HasObfuzIgnoreAttributeInSelfOrParent(methodDef.DeclaringType)) + { + return false; + } + return true; + } + + public override bool NeedRename(FieldDef fieldDef) + { + if (fieldDef.DeclaringType.IsDelegate) + { + return false; + } + if (MetaUtil.HasObfuzIgnoreAttribute(fieldDef) || MetaUtil.HasObfuzIgnoreAttributeInSelfOrParent(fieldDef.DeclaringType)) + { + return false; + } + if (fieldDef.DeclaringType.IsEnum && fieldDef.Name == "value__") + { + return false; + } + return true; + } + + public override bool NeedRename(PropertyDef propertyDef) + { + if (propertyDef.DeclaringType.IsDelegate) + { + return false; + } + if (MetaUtil.HasObfuzIgnoreAttribute(propertyDef) || MetaUtil.HasObfuzIgnoreAttributeInSelfOrParent(propertyDef.DeclaringType)) + { + return false; + } + return true; + } + + public override bool NeedRename(EventDef eventDef) + { + if (eventDef.DeclaringType.IsDelegate) + { + return false; + } + if (MetaUtil.HasObfuzIgnoreAttribute(eventDef) || MetaUtil.HasObfuzIgnoreAttributeInSelfOrParent(eventDef.DeclaringType)) + { + return false; + } + return true; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs.meta new file mode 100644 index 0000000..b7d45e3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b7b98f2ff075c04aa9bd989f8797f00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs new file mode 100644 index 0000000..9878973 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs @@ -0,0 +1,97 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + + public class UnityRenamePolicy : ObfuscationPolicyBase + { + private static HashSet s_monoBehaviourEvents = new HashSet { + "Awake", + "OnEnable", + "Start", + "FixedUpdate", + "Update", + "LateUpdate", + "OnDisable", + "OnDestroy", + "OnApplicationQuit", + "OnTriggerEnter", + "OnTriggerExit", + "OnTriggerStay", + "OnCollisionEnter", + "OnCollisionExit", + "OnCollisionStay", + "OnMouseDown", + "OnMouseUp", + "OnMouseEnter", + "OnMouseExit", + "OnMouseOver", + "OnMouseDrag", + "OnBecameVisible", + "OnBecameInvisible", + "OnGUI", + "OnPreRender", + "OnPostRender", + "OnRenderObject", + "OnDrawGizmos", + "OnDrawGizmosSelected", + "OnValidate", + "OnAnimatorIK", + "OnAnimatorMove", + "OnApplicationFocus", + "OnApplicationPause", + "OnAudioFilterRead", + "OnJointBreak", + "OnParticleCollision", + "OnTransformChildrenChanged", + "OnTransformParentChanged", + "OnRectTransformDimensionsChange", + "OnWillRenderObject" +}; + public override bool NeedRename(TypeDef typeDef) + { + if (MetaUtil.IsScriptOrSerializableType(typeDef)) + { + return false; + } + if (typeDef.FullName.StartsWith("UnitySourceGeneratedAssemblyMonoScriptTypes_")) + { + return false; + } + return true; + } + + public override bool NeedRename(MethodDef methodDef) + { + if (MetaUtil.IsInheritFromUnityObject(methodDef.DeclaringType)) + { + return !s_monoBehaviourEvents.Contains(methodDef.Name); + } + if (methodDef.DeclaringType.FullName.StartsWith("UnitySourceGeneratedAssemblyMonoScriptTypes_")) + { + return false; + } + return true; + } + + public override bool NeedRename(FieldDef fieldDef) + { + TypeDef typeDef = fieldDef.DeclaringType; + if (MetaUtil.IsScriptOrSerializableType(typeDef)) + { + return !MetaUtil.IsSerializableField(fieldDef); + } + if (fieldDef.DeclaringType.FullName.StartsWith("UnitySourceGeneratedAssemblyMonoScriptTypes_")) + { + return false; + } + return true; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs.meta new file mode 100644 index 0000000..f8f6f99 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4357f35c667599246b2481b093f41f04 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs new file mode 100644 index 0000000..791f95d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs @@ -0,0 +1,706 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using UnityEngine; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + + public class RenameRecordMap + { + private enum RenameStatus + { + NotRenamed, + Renamed, + } + + private class RenameRecord + { + public RenameStatus status; + public string signature; + public string oldName; + public string newName; + public string oldStackTraceSignature; // only for MethodDef + public object renameMappingData; + } + + private class RenameMappingField + { + public RenameStatus status; + public string signature; + public string newName; + } + + private class RenameMappingMethod + { + public RenameStatus status; + public string signature; + public string newName; + } + + private class RenameMappingMethodParam + { + public RenameStatus status; + public int index; + public string newName; + } + + private class RenameMappingProperty + { + public RenameStatus status; + public string signature; + public string newName; + } + + private class RenameMappingEvent + { + public RenameStatus status; + public string signature; + public string newName; + } + + private class RenameMappingType + { + public RenameStatus status; + public string oldFullName; + public string newFullName; + + public Dictionary fields = new Dictionary(); + public Dictionary methods = new Dictionary(); + public Dictionary properties = new Dictionary(); + public Dictionary events = new Dictionary(); + } + + private class RenameMappingAssembly + { + public string assName; + + public Dictionary types = new Dictionary(); + } + + private readonly string _mappingFile; + private readonly Dictionary _assemblies = new Dictionary(); + + + private readonly Dictionary _modRenames = new Dictionary(); + private readonly Dictionary _typeRenames = new Dictionary(); + private readonly Dictionary _methodRenames = new Dictionary(); + private readonly Dictionary _paramRenames = new Dictionary(); + private readonly Dictionary _fieldRenames = new Dictionary(); + private readonly Dictionary _propertyRenames = new Dictionary(); + private readonly Dictionary _eventRenames = new Dictionary(); + private readonly Dictionary _virtualMethodGroups = new Dictionary(); + + + public RenameRecordMap(string mappingFile) + { + _mappingFile = mappingFile; + } + + public void Init(List assemblies, INameMaker nameMaker) + { + LoadXmlMappingFile(_mappingFile); + foreach (ModuleDef mod in assemblies) + { + string name = mod.Assembly.Name; + + RenameMappingAssembly rma = _assemblies.GetValueOrDefault(name); + + _modRenames.Add(mod, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = name, + oldName = name, + newName = null, + renameMappingData = rma, + }); + + foreach (TypeDef type in mod.GetTypes()) + { + nameMaker.AddPreservedName(type, name); + nameMaker.AddPreservedNamespace(type, type.Namespace); + string fullTypeName = type.FullName; + RenameMappingType rmt = rma?.types.GetValueOrDefault(fullTypeName); + if (rmt != null) + { + var (newNamespace, newName) = MetaUtil.SplitNamespaceAndName(rmt.newFullName); + nameMaker.AddPreservedNamespace(type, newNamespace); + nameMaker.AddPreservedName(type, newName); + } + + _typeRenames.Add(type, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = fullTypeName, + oldName = fullTypeName, + newName = null, + renameMappingData = rmt, + }); + foreach (MethodDef method in type.Methods) + { + nameMaker.AddPreservedName(method, method.Name); + string methodSig = TypeSigUtil.ComputeMethodDefSignature(method); + nameMaker.AddPreservedName(method, method.Name); + + RenameMappingMethod rmm = rmt?.methods.GetValueOrDefault(methodSig); + if (rmm != null) + { + nameMaker.AddPreservedName(method, rmm.newName); + } + _methodRenames.Add(method, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = methodSig, + oldName = method.Name, + newName = null, + renameMappingData = rmm, + oldStackTraceSignature = MetaUtil.CreateMethodDefIl2CppStackTraceSignature(method), + }); + foreach (Parameter param in method.Parameters) + { + if (param.ParamDef != null) + { + _paramRenames.Add(param.ParamDef, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = param.Name, + oldName = param.Name, + newName = null, + }); + } + } + } + foreach (FieldDef field in type.Fields) + { + nameMaker.AddPreservedName(field, field.Name); + string fieldSig = TypeSigUtil.ComputeFieldDefSignature(field); + RenameMappingField rmf = rmt?.fields.GetValueOrDefault(fieldSig); + if (rmf != null) + { + nameMaker.AddPreservedName(field, rmf.newName); + } + _fieldRenames.Add(field, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = fieldSig, + oldName = field.Name, + newName = null, + renameMappingData = rmf, + }); + } + foreach (PropertyDef property in type.Properties) + { + nameMaker.AddPreservedName(property, property.Name); + string propertySig = TypeSigUtil.ComputePropertyDefSignature(property); + RenameMappingProperty rmp = rmt?.properties.GetValueOrDefault(propertySig); + if (rmp != null) + { + nameMaker.AddPreservedName(property, rmp.newName); + } + _propertyRenames.Add(property, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = propertySig, + oldName = property.Name, + newName = null, + renameMappingData = rmp, + }); + } + foreach (EventDef eventDef in type.Events) + { + nameMaker.AddPreservedName(eventDef, eventDef.Name); + string eventSig = TypeSigUtil.ComputeEventDefSignature(eventDef); + RenameMappingEvent rme = rmt?.events.GetValueOrDefault(eventSig); + if (rme != null) + { + nameMaker.AddPreservedName(eventDef, rme.newName); + } + _eventRenames.Add(eventDef, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = eventSig, + oldName = eventDef.Name, + newName = null, + renameMappingData = rme, + }); + } + } + } + } + + private void LoadXmlMappingFile(string mappingFile) + { + if (string.IsNullOrEmpty(mappingFile) || !File.Exists(mappingFile)) + { + return; + } + var doc = new XmlDocument(); + doc.Load(mappingFile); + var root = doc.DocumentElement; + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement element)) + { + continue; + } + LoadAssemblyMapping(element); + } + } + + private void LoadAssemblyMapping(XmlElement ele) + { + if (ele.Name != "assembly") + { + throw new System.Exception($"Invalid node name: {ele.Name}. Expected 'assembly'."); + } + + var assemblyName = ele.Attributes["name"].Value; + var rma = new RenameMappingAssembly + { + assName = assemblyName, + }; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement element)) + { + continue; + } + if (element.Name != "type") + { + throw new System.Exception($"Invalid node name: {element.Name}. Expected 'type'."); + } + LoadTypeMapping(element, rma); + } + _assemblies.Add(assemblyName, rma); + } + + private void LoadTypeMapping(XmlElement ele, RenameMappingAssembly ass) + { + var typeName = ele.Attributes["fullName"].Value; + var newTypeName = ele.Attributes["newFullName"].Value; + var rmt = new RenameMappingType + { + oldFullName = typeName, + newFullName = newTypeName, + status = (RenameStatus)System.Enum.Parse(typeof(RenameStatus), ele.Attributes["status"].Value), + }; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement c)) + { + continue; + } + switch (node.Name) + { + case "field": LoadFieldMapping(c, rmt); break; + case "event": LoadEventMapping(c, rmt); break; + case "property": LoadPropertyMapping(c, rmt); break; + case "method": LoadMethodMapping(c, rmt); break; + default: throw new System.Exception($"Invalid node name:{node.Name}"); + } + } + ass.types.Add(typeName, rmt); + } + + private void LoadMethodMapping(XmlElement ele, RenameMappingType type) + { + string signature = ele.Attributes["signature"].Value; + string newName = ele.Attributes["newName"].Value; + var rmm = new RenameMappingMethod + { + signature = signature, + newName = newName, + status = RenameStatus.Renamed, + }; + type.methods.Add(signature, rmm); + } + + private void LoadFieldMapping(XmlElement ele, RenameMappingType type) + { + string signature = ele.Attributes["signature"].Value; + string newName = ele.Attributes["newName"].Value; + var rmf = new RenameMappingField + { + signature = signature, + newName = newName, + status = RenameStatus.Renamed, + }; + type.fields.Add(signature, rmf); + } + + private void LoadPropertyMapping(XmlElement ele, RenameMappingType type) + { + string signature = ele.Attributes["signature"].Value; + string newName = ele.Attributes["newName"].Value; + var rmp = new RenameMappingProperty + { + signature = signature, + newName = newName, + status = RenameStatus.Renamed, + }; + type.properties.Add(signature, rmp); + } + + private void LoadEventMapping(XmlElement ele, RenameMappingType type) + { + string signature = ele.Attributes["signature"].Value; + string newName = ele.Attributes["newName"].Value; + var rme = new RenameMappingEvent + { + signature = signature, + newName = newName, + status = RenameStatus.Renamed, + }; + type.events.Add(signature, rme); + } + + private List GetSortedValueList(Dictionary dic, Comparison comparer) + { + var list = dic.Values.ToList(); + list.Sort(comparer); + return list; + } + + public void WriteXmlMappingFile() + { + if (string.IsNullOrEmpty(_mappingFile)) + { + return; + } + var doc = new XmlDocument(); + var root = doc.CreateElement("mapping"); + doc.AppendChild(root); + + var sortedModRenames = _modRenames.ToList(); + sortedModRenames.Sort((a, b) => a.Value.oldName.CompareTo(b.Value.oldName)); + foreach (var kvp in sortedModRenames) + { + ModuleDef mod = kvp.Key; + RenameRecord record = kvp.Value; + var assemblyNode = doc.CreateElement("assembly"); + assemblyNode.SetAttribute("name", mod.Assembly.Name); + foreach (TypeDef type in mod.GetTypes()) + { + WriteTypeMapping(assemblyNode, type); + } + root.AppendChild(assemblyNode); + } + + var sortedAsses = GetSortedValueList(_assemblies, (a, b) => a.assName.CompareTo(b.assName)); + foreach (RenameMappingAssembly ass in sortedAsses) + { + if (_modRenames.Keys.Any(m => m.Assembly.Name == ass.assName)) + { + continue; + } + var assemblyNode = doc.CreateElement("assembly"); + assemblyNode.SetAttribute("name", ass.assName); + + var sortedTypes = GetSortedValueList(ass.types, (a, b) => a.oldFullName.CompareTo(b.oldFullName)); + foreach (var type in sortedTypes) + { + WriteTypeMapping(assemblyNode, type.oldFullName, type); + } + root.AppendChild(assemblyNode); + } + Directory.CreateDirectory(Path.GetDirectoryName(_mappingFile)); + doc.Save(_mappingFile); + Debug.Log($"Mapping file saved to {Path.GetFullPath(_mappingFile)}"); + } + + private void WriteTypeMapping(XmlElement assNode, TypeDef type) + { + _typeRenames.TryGetValue(type, out var record); + var typeNode = assNode.OwnerDocument.CreateElement("type"); + typeNode.SetAttribute("fullName", record?.signature ?? type.FullName); + typeNode.SetAttribute("newFullName", record != null && record.status == RenameStatus.Renamed ? record.newName : ""); + typeNode.SetAttribute("status", record != null ? record.status.ToString() : RenameStatus.NotRenamed.ToString()); + + foreach (FieldDef field in type.Fields) + { + WriteFieldMapping(typeNode, field); + } + foreach (PropertyDef property in type.Properties) + { + WritePropertyMapping(typeNode, property); + } + foreach (EventDef eventDef in type.Events) + { + WriteEventMapping(typeNode, eventDef); + } + foreach (MethodDef method in type.Methods) + { + WriteMethodMapping(typeNode, method); + } + if ((record != null && record.status == RenameStatus.Renamed) || typeNode.ChildNodes.Count > 0) + { + assNode.AppendChild(typeNode); + } + } + + private void WriteTypeMapping(XmlElement assNode, string fullName, RenameMappingType type) + { + var typeNode = assNode.OwnerDocument.CreateElement("type"); + typeNode.SetAttribute("fullName", fullName); + typeNode.SetAttribute("newFullName", type.status == RenameStatus.Renamed ? type.newFullName : ""); + typeNode.SetAttribute("status", type.status.ToString()); + + foreach (var e in type.fields) + { + string signature = e.Key; + RenameMappingField field = e.Value; + WriteFieldMapping(typeNode, e.Key, e.Value); + } + foreach (var e in type.properties) + { + WritePropertyMapping(typeNode, e.Key, e.Value); + } + foreach (var e in type.events) + { + WriteEventMapping(typeNode, e.Key, e.Value); + } + foreach (var e in type.methods) + { + WriteMethodMapping(typeNode, e.Key, e.Value); + } + + assNode.AppendChild(typeNode); + } + + private void WriteFieldMapping(XmlElement typeEle, FieldDef field) + { + if (!_fieldRenames.TryGetValue(field, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var fieldNode = typeEle.OwnerDocument.CreateElement("field"); + fieldNode.SetAttribute("signature", record?.signature); + fieldNode.SetAttribute("newName", record.newName); + //fieldNode.SetAttribute("status", record.status.ToString()); + typeEle.AppendChild(fieldNode); + } + + private void WriteFieldMapping(XmlElement typeEle, string signature, RenameMappingField field) + { + var fieldNode = typeEle.OwnerDocument.CreateElement("field"); + fieldNode.SetAttribute("signature", signature); + fieldNode.SetAttribute("newName", field.newName); + //fieldNode.SetAttribute("status", record.status.ToString()); + typeEle.AppendChild(fieldNode); + } + + private void WritePropertyMapping(XmlElement typeEle, PropertyDef property) + { + if (!_propertyRenames.TryGetValue(property, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var propertyNode = typeEle.OwnerDocument.CreateElement("property"); + propertyNode.SetAttribute("signature", record.signature); + propertyNode.SetAttribute("newName", record.newName); + //propertyNode.SetAttribute("status", record.status.ToString()); + typeEle.AppendChild(propertyNode); + } + + private void WritePropertyMapping(XmlElement typeEle, string signature, RenameMappingProperty property) + { + var propertyNode = typeEle.OwnerDocument.CreateElement("property"); + propertyNode.SetAttribute("signature", signature); + propertyNode.SetAttribute("newName", property.newName); + //propertyNode.SetAttribute("status", record.status.ToString()); + typeEle.AppendChild(propertyNode); + } + + private void WriteEventMapping(XmlElement typeEle, EventDef eventDef) + { + if (!_eventRenames.TryGetValue(eventDef, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var eventNode = typeEle.OwnerDocument.CreateElement("event"); + eventNode.SetAttribute("signature", record.signature); + eventNode.SetAttribute("newName", record.newName); + typeEle.AppendChild(eventNode); + } + + private void WriteEventMapping(XmlElement typeEle, string signature, RenameMappingEvent eventDef) + { + var eventNode = typeEle.OwnerDocument.CreateElement("event"); + eventNode.SetAttribute("signature", signature); + eventNode.SetAttribute("newName", eventDef.newName); + typeEle.AppendChild(eventNode); + } + + private void WriteMethodMapping(XmlElement typeEle, MethodDef method) + { + if (!_methodRenames.TryGetValue(method, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var methodNode = typeEle.OwnerDocument.CreateElement("method"); + methodNode.SetAttribute("signature", record.signature); + methodNode.SetAttribute("newName", record.newName); + methodNode.SetAttribute("oldStackTraceSignature", record.oldStackTraceSignature); + methodNode.SetAttribute("newStackTraceSignature", MetaUtil.CreateMethodDefIl2CppStackTraceSignature(method)); + //methodNode.SetAttribute("status", record != null ? record.status.ToString() : RenameStatus.NotRenamed.ToString()); + foreach (Parameter param in method.Parameters) + { + if (param.ParamDef != null) + { + WriteMethodParamMapping(methodNode, param.ParamDef); + } + } + typeEle.AppendChild(methodNode); + } + + private void WriteMethodMapping(XmlElement typeEle, string signature, RenameMappingMethod method) + { + var methodNode = typeEle.OwnerDocument.CreateElement("method"); + methodNode.SetAttribute("signature", signature); + methodNode.SetAttribute("newName", method.newName); + typeEle.AppendChild(methodNode); + } + + private void WriteMethodParamMapping(XmlElement methodEle, ParamDef param) + { + if (!_paramRenames.TryGetValue(param, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var paramNode = methodEle.OwnerDocument.CreateElement("param"); + paramNode.SetAttribute("index", param.Sequence.ToString()); + paramNode.SetAttribute("newName", record.newName); + //paramNode.SetAttribute("status", record.status.ToString()); + methodEle.AppendChild(paramNode); + } + + public void AddRename(ModuleDef mod, string newName) + { + RenameRecord record = _modRenames[mod]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void AddRename(TypeDef type, string newName) + { + RenameRecord record = _typeRenames[type]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void AddRename(MethodDef method, string newName) + { + RenameRecord record = _methodRenames[method]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void InitAndAddRename(VirtualMethodGroup methodGroup, string newName) + { + RenameRecord methodRecord = _methodRenames[methodGroup.methods[0]]; + _virtualMethodGroups.Add(methodGroup, new RenameRecord + { + status = RenameStatus.Renamed, + signature = methodRecord.signature, + oldName = methodRecord.oldName, + newName = newName, + }); + } + + public void AddRename(FieldDef field, string newName) + { + RenameRecord record = _fieldRenames[field]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void AddRename(PropertyDef property, string newName) + { + RenameRecord record = _propertyRenames[property]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void AddRename(EventDef eventDef, string newName) + { + RenameRecord record = _eventRenames[eventDef]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public bool TryGetExistRenameMapping(TypeDef type, out string newNamespace, out string newName) + { + if (_typeRenames.TryGetValue(type, out var record) && record.renameMappingData != null) + { + var rmt = (RenameMappingType)record.renameMappingData; + (newNamespace, newName) = MetaUtil.SplitNamespaceAndName(rmt.newFullName); + return true; + } + newNamespace = null; + newName = null; + return false; + } + + public bool TryGetExistRenameMapping(MethodDef method, out string newName) + { + if (_methodRenames.TryGetValue(method, out var record) && record.renameMappingData != null) + { + newName = ((RenameMappingMethod)record.renameMappingData).newName; + return true; + } + newName = null; + return false; + } + + public bool TryGetExistRenameMapping(FieldDef field, out string newName) + { + if (_fieldRenames.TryGetValue(field, out var record) && record.renameMappingData != null) + { + newName = ((RenameMappingField)record.renameMappingData).newName; + return true; + } + newName = null; + return false; + } + + public bool TryGetExistRenameMapping(PropertyDef property, out string newName) + { + if (_propertyRenames.TryGetValue(property, out var record) && record.renameMappingData != null) + { + newName = ((RenameMappingProperty)record.renameMappingData).newName; + return true; + } + newName = null; + return false; + } + + public bool TryGetExistRenameMapping(EventDef eventDef, out string newName) + { + if (_eventRenames.TryGetValue(eventDef, out var record) && record.renameMappingData != null) + { + newName = ((RenameMappingEvent)record.renameMappingData).newName; + return true; + } + newName = null; + return false; + } + + public bool TryGetRename(VirtualMethodGroup group, out string newName) + { + if (_virtualMethodGroups.TryGetValue(group, out var record)) + { + newName = record.newName; + return true; + } + newName = null; + return false; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs.meta new file mode 100644 index 0000000..2a91341 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3ffbd28624d87c4382f62eb4fc19c70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs new file mode 100644 index 0000000..2501f1d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs @@ -0,0 +1,36 @@ +using Obfuz.Settings; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public class SymbolObfusPass : ObfuscationPassBase + { + private SymbolRename _symbolRename; + + public override ObfuscationPassType Type => ObfuscationPassType.SymbolObfus; + + public SymbolObfusPass(SymbolObfuscationSettings settings) + { + _symbolRename = new SymbolRename(settings); + } + + public override void Start() + { + _symbolRename.Init(); + } + + public override void Stop() + { + _symbolRename.Save(); + } + + public override void Process() + { + _symbolRename.Process(); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs.meta new file mode 100644 index 0000000..a86c04b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4c9a37b51ade6b46a299be7ad2155d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs new file mode 100644 index 0000000..cda7d4f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs @@ -0,0 +1,759 @@ +using dnlib.DotNet; +using Obfuz.ObfusPasses.SymbolObfus; +using Obfuz.ObfusPasses.SymbolObfus.NameMakers; +using Obfuz.ObfusPasses.SymbolObfus.Policies; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.OleDb; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public class SymbolRename + { + private readonly bool _useConsistentNamespaceObfuscation; + private readonly List _obfuscationRuleFiles; + private readonly string _mappingXmlPath; + + private AssemblyCache _assemblyCache; + + private List _toObfuscatedModules; + private List _obfuscatedAndNotObfuscatedModules; + private HashSet _toObfuscatedModuleSet; + private IObfuscationPolicy _renamePolicy; + private INameMaker _nameMaker; + private readonly Dictionary> _customAttributeArgumentsWithTypeByMods = new Dictionary>(); + private readonly RenameRecordMap _renameRecordMap; + private readonly VirtualMethodGroupCalculator _virtualMethodGroupCalculator; + + class CustomAttributeInfo + { + public CustomAttributeCollection customAttributes; + public int index; + public List arguments; + public List namedArguments; + } + + public SymbolRename(SymbolObfuscationSettings settings) + { + _useConsistentNamespaceObfuscation = settings.useConsistentNamespaceObfuscation; + _mappingXmlPath = settings.symbolMappingFile; + _obfuscationRuleFiles = settings.ruleFiles.ToList(); + _renameRecordMap = new RenameRecordMap(settings.debug ? null : settings.symbolMappingFile); + _virtualMethodGroupCalculator = new VirtualMethodGroupCalculator(); + _nameMaker = settings.debug ? NameMakerFactory.CreateDebugNameMaker() : NameMakerFactory.CreateNameMakerBaseASCIICharSet(settings.obfuscatedNamePrefix); + } + + public void Init() + { + var ctx = ObfuscationPassContext.Current; + _assemblyCache = ctx.assemblyCache; + _toObfuscatedModules = ctx.modulesToObfuscate; + _obfuscatedAndNotObfuscatedModules = ctx.allObfuscationRelativeModules; + _toObfuscatedModuleSet = new HashSet(ctx.modulesToObfuscate); + var obfuscateRuleConfig = new ConfigurableRenamePolicy(ctx.assembliesToObfuscate, _obfuscationRuleFiles); + _renamePolicy = new CacheRenamePolicy(new CombineRenamePolicy(new SupportPassPolicy(ctx.passPolicy), new SystemRenamePolicy(), new UnityRenamePolicy(), obfuscateRuleConfig)); + BuildCustomAttributeArguments(); + } + + private void CollectCArgumentWithTypeOf(IHasCustomAttribute meta, List customAttributes) + { + int index = 0; + foreach (CustomAttribute ca in meta.CustomAttributes) + { + List arguments = null; + if (ca.ConstructorArguments.Any(a => MetaUtil.MayRenameCustomDataType(a.Type.ElementType))) + { + arguments = ca.ConstructorArguments.ToList(); + } + List namedArguments = null; + if (ca.NamedArguments.Any(a => MetaUtil.MayRenameCustomDataType(a.Type.ElementType))) + { + namedArguments = ca.NamedArguments.ToList(); + } + if (arguments != null | namedArguments != null) + { + customAttributes.Add(new CustomAttributeInfo + { + customAttributes = meta.CustomAttributes, + index = index, + arguments = arguments, + namedArguments = namedArguments + }); + } + ++index; + } + } + + private void BuildCustomAttributeArguments() + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + var customAttributes = new List(); + CollectCArgumentWithTypeOf(mod, customAttributes); + foreach (TypeDef type in mod.GetTypes()) + { + CollectCArgumentWithTypeOf(type, customAttributes); + foreach (FieldDef field in type.Fields) + { + CollectCArgumentWithTypeOf(field, customAttributes); + } + foreach (MethodDef method in type.Methods) + { + CollectCArgumentWithTypeOf(method, customAttributes); + foreach (Parameter param in method.Parameters) + { + if (param.ParamDef != null) + { + CollectCArgumentWithTypeOf(param.ParamDef, customAttributes); + } + } + } + foreach (PropertyDef property in type.Properties) + { + CollectCArgumentWithTypeOf(property, customAttributes); + } + foreach (EventDef eventDef in type.Events) + { + CollectCArgumentWithTypeOf (eventDef, customAttributes); + } + } + + _customAttributeArgumentsWithTypeByMods.Add(mod, customAttributes); + } + } + + public void Process() + { + _renameRecordMap.Init(_toObfuscatedModules, _nameMaker); + + RenameTypes(); + RenameFields(); + RenameMethods(); + RenameProperties(); + RenameEvents(); + } + + class RefTypeDefMetas + { + public readonly List typeRefs = new List(); + + public readonly List customAttributes = new List(); + } + + private void BuildRefTypeDefMetasMap(Dictionary refTypeDefMetasMap) + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (TypeRef typeRef in mod.GetTypeRefs()) + { + if (typeRef.DefinitionAssembly.IsCorLib()) + { + continue; + } + TypeDef typeDef = typeRef.ResolveThrow(); + if (!refTypeDefMetasMap.TryGetValue(typeDef, out var typeDefMetas)) + { + typeDefMetas = new RefTypeDefMetas(); + refTypeDefMetasMap.Add(typeDef, typeDefMetas); + } + typeDefMetas.typeRefs.Add(typeRef); + } + } + + foreach (CustomAttributeInfo cai in _customAttributeArgumentsWithTypeByMods.Values.SelectMany(cas => cas)) + { + CustomAttribute ca = cai.customAttributes[cai.index]; + TypeDef typeDef = MetaUtil.GetTypeDefOrGenericTypeBaseThrowException(ca.Constructor.DeclaringType); + if (!refTypeDefMetasMap.TryGetValue(typeDef, out var typeDefMetas)) + { + typeDefMetas = new RefTypeDefMetas(); + refTypeDefMetasMap.Add(typeDef, typeDefMetas); + } + typeDefMetas.customAttributes.Add(ca); + } + } + + private void RetargetTypeRefInCustomAttributes() + { + foreach (CustomAttributeInfo cai in _customAttributeArgumentsWithTypeByMods.Values.SelectMany(cas => cas)) + { + CustomAttribute ca = cai.customAttributes[cai.index]; + bool anyChange = false; + if (cai.arguments != null) + { + for (int i = 0; i < cai.arguments.Count; i++) + { + CAArgument oldArg = cai.arguments[i]; + if (MetaUtil.TryRetargetTypeRefInArgument(oldArg, out CAArgument newArg)) + { + anyChange = true; + cai.arguments[i] = newArg; + } + } + } + if (cai.namedArguments != null) + { + for (int i = 0; i < cai.namedArguments.Count; i++) + { + if (MetaUtil.TryRetargetTypeRefInNamedArgument(cai.namedArguments[i])) + { + anyChange = true; + } + } + } + if (anyChange) + { + cai.customAttributes[cai.index] = new CustomAttribute(ca.Constructor, + cai.arguments != null ? cai.arguments : ca.ConstructorArguments, + cai.namedArguments != null ? cai.namedArguments : ca.NamedArguments); + } + } + } + + private readonly Dictionary _refTypeRefMetasMap = new Dictionary(); + + private void RenameTypes() + { + Debug.Log("RenameTypes begin"); + + RetargetTypeRefInCustomAttributes(); + + BuildRefTypeDefMetasMap(_refTypeRefMetasMap); + _assemblyCache.EnableTypeDefCache = false; + + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + if (_renamePolicy.NeedRename(type)) + { + Rename(type, _refTypeRefMetasMap.GetValueOrDefault(type)); + } + } + } + + // clean cache + _assemblyCache.EnableTypeDefCache = true; + Debug.Log("Rename Types end"); + } + + + class RefFieldMetas + { + public readonly List fieldRefs = new List(); + public readonly List customAttributes = new List(); + } + + + private void BuildHierarchyFields(TypeDef type, List fields) + { + while (type != null) + { + fields.AddRange(type.Fields); + type = MetaUtil.GetBaseTypeDef(type); + } + } + + private IEnumerable WalkMemberRefs(ModuleDef mod) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + if (!method.HasBody) + { + continue; + } + foreach (var instr in method.Body.Instructions) + { + if (instr.Operand is MemberRef memberRef) + { + yield return memberRef; + } + } + } + } + } + + private void BuildRefFieldMetasMap(Dictionary refFieldMetasMap) + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (MemberRef memberRef in WalkMemberRefs(mod)) + { + if (!memberRef.IsFieldRef) + { + continue; + } + + IMemberRefParent parent = memberRef.Class; + TypeDef parentTypeDef = MetaUtil.GetMemberRefTypeDefParentOrNull(parent); + if (parentTypeDef == null) + { + continue; + } + foreach (FieldDef field in parentTypeDef.Fields) + { + if (field.Name == memberRef.Name && TypeEqualityComparer.Instance.Equals(field.FieldSig.Type, memberRef.FieldSig.Type)) + { + if (!refFieldMetasMap.TryGetValue(field, out var fieldMetas)) + { + fieldMetas = new RefFieldMetas(); + refFieldMetasMap.Add(field, fieldMetas); + } + fieldMetas.fieldRefs.Add(memberRef); + break; + } + } + } + } + foreach (var e in _refTypeRefMetasMap) + { + TypeDef typeDef = e.Key; + var hierarchyFields = new List(); + BuildHierarchyFields(typeDef, hierarchyFields); + RefTypeDefMetas typeDefMetas = e.Value; + foreach (CustomAttribute ca in typeDefMetas.customAttributes) + { + foreach (var arg in ca.NamedArguments) + { + if (arg.IsProperty) + { + continue; + } + foreach (FieldDef field in hierarchyFields) + { + // FIXME. field of Generic Base Type may not be same + if (field.Name == arg.Name && TypeEqualityComparer.Instance.Equals(field.FieldType, arg.Type)) + { + if (!refFieldMetasMap.TryGetValue(field, out var fieldMetas)) + { + fieldMetas = new RefFieldMetas(); + refFieldMetasMap.Add(field, fieldMetas); + } + fieldMetas.customAttributes.Add(ca); + break; + } + } + } + } + } + } + + private void RenameFields() + { + Debug.Log("Rename fields begin"); + var refFieldMetasMap = new Dictionary(); + BuildRefFieldMetasMap(refFieldMetasMap); + + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (FieldDef field in type.Fields) + { + if (_renamePolicy.NeedRename(field)) + { + Rename(field, refFieldMetasMap.GetValueOrDefault(field)); + } + } + } + } + Debug.Log("Rename fields end"); + } + + class RefMethodMetas + { + public readonly List memberRefs = new List(); + } + + private void BuildRefMethodMetasMap(Dictionary refMethodMetasMap) + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (MemberRef memberRef in WalkMemberRefs(mod)) + { + if (!memberRef.IsMethodRef) + { + continue; + } + + IMemberRefParent parent = memberRef.Class; + TypeDef parentTypeDef = MetaUtil.GetMemberRefTypeDefParentOrNull(parent); + if (parentTypeDef == null) + { + continue; + } + foreach (MethodDef method in parentTypeDef.Methods) + { + if (method.Name == memberRef.Name && new SigComparer(default).Equals(method.MethodSig, memberRef.MethodSig)) + { + if (!refMethodMetasMap.TryGetValue(method, out var refMethodMetas)) + { + refMethodMetas = new RefMethodMetas(); + refMethodMetasMap.Add(method, refMethodMetas); + } + refMethodMetas.memberRefs.Add(memberRef); + break; + } + } + } + } + } + + private void RenameMethods() + { + Debug.Log("Rename methods begin"); + Debug.Log("Rename not virtual methods begin"); + var virtualMethods = new List(); + var refMethodMetasMap = new Dictionary(); + BuildRefMethodMetasMap(refMethodMetasMap); + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + if (method.IsVirtual) + { + continue; + } + if (_renamePolicy.NeedRename(method)) + { + Rename(method, refMethodMetasMap.GetValueOrDefault(method)); + } + } + } + } + + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + _virtualMethodGroupCalculator.CalculateType(type); + foreach (MethodDef method in type.Methods) + { + if (method.IsVirtual) + { + virtualMethods.Add(method); + } + } + } + } + + Debug.Log("Rename not virtual methods end"); + + + Debug.Log("Rename virtual methods begin"); + var visitedVirtualMethods = new HashSet(); + var groupNeedRenames = new Dictionary(); + foreach (var method in virtualMethods) + { + if (!visitedVirtualMethods.Add(method)) + { + continue; + } + VirtualMethodGroup group = _virtualMethodGroupCalculator.GetMethodGroup(method); + if (!groupNeedRenames.TryGetValue(group, out var needRename)) + { + needRename = group.methods.All(m => _toObfuscatedModuleSet.Contains(m.DeclaringType.Module) && _renamePolicy.NeedRename(m)); + groupNeedRenames.Add(group, needRename); + if (needRename) + { + _renameRecordMap.InitAndAddRename(group, _renameRecordMap.TryGetExistRenameMapping(method, out var nn) ? nn : _nameMaker.GetNewName(method, method.Name)); + } + } + if (!needRename) + { + continue; + } + if (_renameRecordMap.TryGetRename(group, out var newName)) + { + Rename(method, refMethodMetasMap.GetValueOrDefault(method), newName); + } + else + { + throw new Exception($"group:{group} method:{method} not found in rename record map"); + } + } + Debug.Log("Rename virtual methods end"); + Debug.Log("Rename methods end"); + } + + class RefPropertyMetas + { + public List customAttributes = new List(); + } + + private void BuildHierarchyProperties(TypeDef type, List properties) + { + while (type != null) + { + properties.AddRange(type.Properties); + type = MetaUtil.GetBaseTypeDef(type); + } + } + + private void BuildRefPropertyMetasMap(Dictionary refPropertyMetasMap) + { + foreach (var e in _refTypeRefMetasMap) + { + TypeDef typeDef = e.Key; + var hierarchyProperties = new List(); + BuildHierarchyProperties(typeDef, hierarchyProperties); + RefTypeDefMetas typeDefMetas = e.Value; + foreach (CustomAttribute ca in typeDefMetas.customAttributes) + { + foreach (var arg in ca.NamedArguments) + { + if (arg.IsField) + { + continue; + } + foreach (PropertyDef field in hierarchyProperties) + { + // FIXME. field of Generic Base Type may not be same + if (field.Name == arg.Name && TypeEqualityComparer.Instance.Equals(arg.Type, field.PropertySig.RetType)) + { + if (!refPropertyMetasMap.TryGetValue(field, out var fieldMetas)) + { + fieldMetas = new RefPropertyMetas(); + refPropertyMetasMap.Add(field, fieldMetas); + } + fieldMetas.customAttributes.Add(ca); + break; + } + } + } + } + } + } + + private void RenameProperties() + { + Debug.Log("Rename properties begin"); + var refPropertyMetasMap = new Dictionary(); + BuildRefPropertyMetasMap(refPropertyMetasMap); + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (PropertyDef property in type.Properties) + { + if (_renamePolicy.NeedRename(property)) + { + Rename(property, refPropertyMetasMap.GetValueOrDefault(property)); + } + } + } + } + Debug.Log("Rename properties end"); + } + + private void RenameEvents() + { + Debug.Log("Rename events begin"); + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (EventDef eventDef in type.Events) + { + if (_renamePolicy.NeedRename(eventDef)) + { + Rename(eventDef); + } + } + } + } + Debug.Log("Rename events begin"); + } + + //private void Rename(ModuleDef mod) + //{ + // string oldName = mod.Assembly.Name; + // string newName = _renameRecordMap.TryGetExistRenameMapping(mod, out var n) ? n : _nameMaker.GetNewName(mod, oldName); + // _renameRecordMap.AddRename(mod, newName); + // mod.Assembly.Name = newName; + // mod.Name = $"{newName}.dll"; + // //Debug.Log($"rename module. oldName:{oldName} newName:{newName}"); + // foreach (AssemblyReferenceInfo ass in GetReferenceMeAssemblies(mod)) + // { + // foreach (AssemblyRef assRef in ass.module.GetAssemblyRefs()) + // { + // if (assRef.Name == oldName) + // { + // _renameRecordMap.AddRename(mod, newName); + // assRef.Name = newName; + // // Debug.Log($"rename assembly:{ass.name} ref oldName:{oldName} newName:{newName}"); + // } + // } + // } + //} + + private void Rename(TypeDef type, RefTypeDefMetas refTypeDefMeta) + { + string moduleName = MetaUtil.GetModuleNameWithoutExt(type.Module.Name); + string oldFullName = type.FullName; + string oldNamespace = type.Namespace; + + string oldName = type.Name; + + string newNamespace; + string newName; + if (_renameRecordMap.TryGetExistRenameMapping(type, out var nns, out var nn)) + { + newNamespace = nns; + newName = nn; + } + else + { + newNamespace = _nameMaker.GetNewNamespace(type, oldNamespace, _useConsistentNamespaceObfuscation); + newName = _nameMaker.GetNewName(type, oldName); + } + + if (refTypeDefMeta != null) + { + foreach (TypeRef typeRef in refTypeDefMeta.typeRefs) + { + Assert.AreEqual(typeRef.FullName, oldFullName); + Assert.IsTrue(typeRef.DefinitionAssembly.Name == moduleName); + if (!string.IsNullOrEmpty(oldNamespace)) + { + typeRef.Namespace = newNamespace; + } + typeRef.Name = newName; + //Debug.Log($"rename assembly:{typeRef.Module.Name} reference {oldFullName} => {typeRef.FullName}"); + } + } + type.Name = newName; + type.Namespace = newNamespace; + string newFullName = type.FullName; + _renameRecordMap.AddRename(type, newFullName); + //Debug.Log($"rename typedef. assembly:{type.Module.Name} oldName:{oldFullName} => newName:{newFullName}"); + } + + private void Rename(FieldDef field, RefFieldMetas fieldMetas) + { + string oldName = field.Name; + string newName = _renameRecordMap.TryGetExistRenameMapping(field, out var nn) ? nn : _nameMaker.GetNewName(field, oldName); + if (fieldMetas != null) + { + foreach (var memberRef in fieldMetas.fieldRefs) + { + memberRef.Name = newName; + //Debug.Log($"rename assembly:{memberRef.Module.Name} reference {field.FullName} => {memberRef.FullName}"); + } + foreach (var ca in fieldMetas.customAttributes) + { + foreach (var arg in ca.NamedArguments) + { + if (arg.Name == oldName) + { + arg.Name = newName; + } + } + } + } + //Debug.Log($"rename field. {field} => {newName}"); + _renameRecordMap.AddRename(field, newName); + field.Name = newName; + + } + + private void Rename(MethodDef method, RefMethodMetas refMethodMetas) + { + string oldName = method.Name; + string newName = _renameRecordMap.TryGetExistRenameMapping(method, out var nn) ? nn : _nameMaker.GetNewName(method, oldName); + Rename(method, refMethodMetas, newName); + } + + private void Rename(MethodDef method, RefMethodMetas refMethodMetas, string newName) + { + ModuleDefMD mod = (ModuleDefMD)method.DeclaringType.Module; + RenameMethodParams(method); + RenameMethodBody(method); + if (refMethodMetas != null) + { + foreach (MemberRef memberRef in refMethodMetas.memberRefs) + { + string oldMethodFullName = memberRef.ToString(); + memberRef.Name = newName; + //Debug.Log($"rename assembly:{memberRef.Module.Name} method:{oldMethodFullName} => {memberRef}"); + } + } + _renameRecordMap.AddRename(method, newName); + method.Name = newName; + } + + private void RenameMethodBody(MethodDef method) + { + if (method.Body == null) + { + return; + } + } + + private void RenameMethodParams(MethodDef method) + { + foreach (Parameter param in method.Parameters) + { + if (param.ParamDef != null) + { + Rename(param.ParamDef); + } + } + } + + private void Rename(ParamDef param) + { + string newName = _nameMaker.GetNewName(param, param.Name); + param.Name = newName; + } + + private void Rename(EventDef eventDef) + { + string oldName = eventDef.Name; + string newName = _renameRecordMap.TryGetExistRenameMapping(eventDef, out var nn) ? nn : _nameMaker.GetNewName(eventDef, eventDef.Name); + _renameRecordMap.AddRename(eventDef, newName); + eventDef.Name = newName; + } + + private void Rename(PropertyDef property, RefPropertyMetas refPropertyMetas) + { + string oldName = property.Name; + string newName = _renameRecordMap.TryGetExistRenameMapping(property, out var nn) ? nn : _nameMaker.GetNewName(property, oldName); + + if (refPropertyMetas != null) + { + foreach (var ca in refPropertyMetas.customAttributes) + { + foreach (var arg in ca.NamedArguments) + { + if (arg.Name == oldName) + { + arg.Name = newName; + } + } + } + } + _renameRecordMap.AddRename(property, newName); + property.Name = newName; + } + + public void Save() + { + Directory.CreateDirectory(Path.GetDirectoryName(_mappingXmlPath)); + _renameRecordMap.WriteXmlMappingFile(); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs.meta new file mode 100644 index 0000000..739a842 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec29be12f08e90741a59970f029c2eec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs new file mode 100644 index 0000000..03cb1b3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs @@ -0,0 +1,100 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + + public class VirtualMethodGroup + { + public List methods; + } + + public class VirtualMethodGroupCalculator + { + + private class TypeFlatMethods + { + public HashSet flatMethods = new HashSet(); + + + public bool TryFindMatchVirtualMethod(MethodDef method, out MethodDef matchMethodDef) + { + foreach (var parentOrInterfaceMethod in flatMethods) + { + if (parentOrInterfaceMethod.Name == method.Name && parentOrInterfaceMethod.GetParamCount() == method.GetParamCount()) + { + matchMethodDef = parentOrInterfaceMethod; + return true; + } + } + matchMethodDef = null; + return false; + } + } + + + private readonly Dictionary _methodGroups = new Dictionary(); + private readonly Dictionary _visitedTypes = new Dictionary(); + + + + public VirtualMethodGroup GetMethodGroup(MethodDef methodDef) + { + if (_methodGroups.TryGetValue(methodDef, out var group)) + { + return group; + } + return null; + } + + public void CalculateType(TypeDef typeDef) + { + if (_visitedTypes.ContainsKey(typeDef)) + { + return; + } + + var typeMethods = new TypeFlatMethods(); + + if (typeDef.BaseType != null) + { + TypeDef baseTypeDef = MetaUtil.GetTypeDefOrGenericTypeBaseThrowException(typeDef.BaseType); + CalculateType(baseTypeDef); + typeMethods.flatMethods.AddRange(_visitedTypes[baseTypeDef].flatMethods); + foreach (var intfType in typeDef.Interfaces) + { + TypeDef intfTypeDef = MetaUtil.GetTypeDefOrGenericTypeBaseThrowException(intfType.Interface); + CalculateType(intfTypeDef); + typeMethods.flatMethods.AddRange(_visitedTypes[intfTypeDef].flatMethods); + } + } + foreach (var method in typeDef.Methods) + { + if (!method.IsVirtual) + { + continue; + } + if (typeMethods.TryFindMatchVirtualMethod(method, out var matchMethodDef)) + { + var group = _methodGroups[matchMethodDef]; + group.methods.Add(method); + _methodGroups.Add(method, group); + } + else + { + _methodGroups.Add(method, new VirtualMethodGroup() { methods = new List { method } }); + } + if (method.IsNewSlot) + { + typeMethods.flatMethods.Add(method); + } + } + _visitedTypes.Add(typeDef, typeMethods); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs.meta b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs.meta new file mode 100644 index 0000000..097106a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 649aeb6306500a04f8b7a3e01f5aaf0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs b/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs new file mode 100644 index 0000000..87ca94f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs @@ -0,0 +1,75 @@ +using dnlib.DotNet; +using Obfuz.Editor; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz +{ + public class ObfuscationMethodWhitelist + { + private bool ShouldBeIgnoredByCustomAttribute(IHasCustomAttribute obj) + { + return MetaUtil.HasObfuzIgnoreAttribute(obj); + } + + public bool IsInWhiteList(ModuleDef module) + { + string modName = module.Assembly.Name; + if (modName == "Obfuz.Runtime") + { + return true; + } + if (ShouldBeIgnoredByCustomAttribute(module)) + { + return true; + } + return false; + } + + public bool IsInWhiteList(MethodDef method) + { + if (IsInWhiteList(method.DeclaringType)) + { + return true; + } + if (method.Name.StartsWith(ConstValues.ObfuzInternalSymbolNamePrefix)) + { + return true; + } + if (ShouldBeIgnoredByCustomAttribute(method)) + { + return true; + } + return false; + } + + public bool IsInWhiteList(TypeDef type) + { + if (type.Name.StartsWith(ConstValues.ObfuzInternalSymbolNamePrefix)) + { + return true; + } + if (IsInWhiteList(type.Module)) + { + return true; + } + if (MetaUtil.HasObfuzIgnoreAttributeInSelfOrParent(type)) + { + return true; + } + if (type.DeclaringType != null && IsInWhiteList(type.DeclaringType)) + { + return true; + } + if (type.FullName == "Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine") + { + return true; + } + return false; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs.meta b/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs.meta new file mode 100644 index 0000000..0ef3bf3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8824d47fdc31cef41a899294491c8844 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs b/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs new file mode 100644 index 0000000..f4b5c74 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs @@ -0,0 +1,83 @@ +using dnlib.DotNet; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.ObfusPasses; +using Obfuz.ObfusPasses.SymbolObfus; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz +{ + public delegate IRandom RandomCreator(int seed); + + public class EncryptionScopeInfo + { + public readonly IEncryptor encryptor; + public readonly RandomCreator localRandomCreator; + + public EncryptionScopeInfo(IEncryptor encryptor, RandomCreator localRandomCreator) + { + this.encryptor = encryptor; + this.localRandomCreator = localRandomCreator; + } + } + + public class EncryptionScopeProvider + { + private readonly EncryptionScopeInfo _defaultStaticScope; + private readonly EncryptionScopeInfo _defaultDynamicScope; + private readonly HashSet _dynamicSecretAssemblyNames; + + public EncryptionScopeProvider(EncryptionScopeInfo defaultStaticScope, EncryptionScopeInfo defaultDynamicScope, HashSet dynamicSecretAssemblyNames) + { + _defaultStaticScope = defaultStaticScope; + _defaultDynamicScope = defaultDynamicScope; + _dynamicSecretAssemblyNames = dynamicSecretAssemblyNames; + } + + public EncryptionScopeInfo GetScope(ModuleDef module) + { + if (_dynamicSecretAssemblyNames.Contains(module.Assembly.Name)) + { + return _defaultDynamicScope; + } + else + { + return _defaultStaticScope; + } + } + + public bool IsDynamicSecretAssembly(ModuleDef module) + { + return _dynamicSecretAssemblyNames.Contains(module.Assembly.Name); + } + } + + public class ObfuscationPassContext + { + public static ObfuscationPassContext Current { get; set; } + + + public GroupByModuleEntityManager moduleEntityManager; + + public AssemblyCache assemblyCache; + + public List modulesToObfuscate; + public List allObfuscationRelativeModules; + + public List assembliesToObfuscate; + public List nonObfuscatedButReferencingObfuscatedAssemblies; + + public string obfuscatedAssemblyOutputPath; + + public EncryptionScopeProvider encryptionScopeProvider; + public ConstFieldAllocator constFieldAllocator; + public RvaDataAllocator rvaDataAllocator; + public ObfuscationMethodWhitelist whiteList; + public ConfigurablePassPolicy passPolicy; + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs.meta b/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs.meta new file mode 100644 index 0000000..827ddc4 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 196d07f0366ce4b46bf0333fa4918d40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Obfuscator.cs b/com.code-philosophy.obfuz/Editor/Obfuscator.cs new file mode 100644 index 0000000..6b7bcb8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Obfuscator.cs @@ -0,0 +1,339 @@ +using dnlib.DotNet; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.EncryptionVM; +using Obfuz.ObfusPasses; +using Obfuz.ObfusPasses.CleanUp; +using Obfuz.ObfusPasses.SymbolObfus; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz +{ + + public class Obfuscator + { + private readonly string _obfuscatedAssemblyOutputPath; + + private readonly List _assembliesToObfuscate; + private readonly List _nonObfuscatedButReferencingObfuscatedAssemblies; + private readonly List _assemblySearchPaths; + + private readonly ConfigurablePassPolicy _passPolicy; + + private readonly Pipeline _pipeline1 = new Pipeline(); + private readonly Pipeline _pipeline2 = new Pipeline(); + + private readonly byte[] _defaultStaticByteSecretKey; + private readonly byte[] _defaultDynamicByteSecret; + private readonly HashSet _assembliesUsingDynamicSecretKeys; + + private readonly int _randomSeed; + private readonly string _encryptionVmGenerationSecretKey; + private readonly int _encryptionVmOpCodeCount; + private readonly string _encryptionVmCodeFile; + + private ObfuscationPassContext _ctx; + + public Obfuscator(ObfuscatorBuilder builder) + { + _defaultStaticByteSecretKey = KeyGenerator.GenerateKey(builder.DefaultStaticSecretKey, VirtualMachine.SecretKeyLength); + _defaultDynamicByteSecret = KeyGenerator.GenerateKey(builder.DefaultDynamicSecretKey, VirtualMachine.SecretKeyLength); + _assembliesUsingDynamicSecretKeys = new HashSet(builder.AssembliesUsingDynamicSecretKeys); + + + _randomSeed = builder.RandomSeed; + _encryptionVmGenerationSecretKey = builder.EncryptionVmGenerationSecretKey; + _encryptionVmOpCodeCount = builder.EncryptionVmOpCodeCount; + _encryptionVmCodeFile = builder.EncryptionVmCodeFile; + + _assembliesToObfuscate = builder.AssembliesToObfuscate; + _nonObfuscatedButReferencingObfuscatedAssemblies = builder.NonObfuscatedButReferencingObfuscatedAssemblies; + _obfuscatedAssemblyOutputPath = builder.ObfuscatedAssemblyOutputPath; + _assemblySearchPaths = builder.AssemblySearchPaths; + + _passPolicy = new ConfigurablePassPolicy(_assembliesToObfuscate, builder.EnableObfuscationPasses, builder.ObfuscationPassRuleConfigFiles); + + foreach (var pass in builder.ObfuscationPasses) + { + if (pass is SymbolObfusPass symbolObfusPass) + { + _pipeline2.AddPass(pass); + } + else + { + _pipeline1.AddPass(pass); + } + } + _pipeline1.AddPass(new CleanUpInstructionPass()); + _pipeline2.AddPass(new RemoveObfuzAttributesPass()); + } + + public void Run() + { + Debug.Log($"Obfuscator Run. begin"); + FileUtil.RecreateDir(_obfuscatedAssemblyOutputPath); + RunPipeline(_pipeline1); + _assemblySearchPaths.Insert(0, _obfuscatedAssemblyOutputPath); + RunPipeline(_pipeline2); + Debug.Log($"Obfuscator Run. end"); + } + + private void RunPipeline(Pipeline pipeline) + { + if (pipeline.Empty) + { + return; + } + OnPreObfuscation(pipeline); + DoObfuscation(pipeline); + OnPostObfuscation(pipeline); + } + + private IEncryptor CreateEncryptionVirtualMachine(byte[] secretKey) + { + var vmCreator = new VirtualMachineCreator(_encryptionVmGenerationSecretKey); + var vm = vmCreator.CreateVirtualMachine(_encryptionVmOpCodeCount); + var vmGenerator = new VirtualMachineCodeGenerator(vm); + + if (!File.Exists(_encryptionVmCodeFile)) + { + throw new Exception($"EncryptionVm CodeFile:`{_encryptionVmCodeFile}` not exists! Please run `Obfuz/GenerateVm` to generate it!"); + } + if (!vmGenerator.ValidateMatch(_encryptionVmCodeFile)) + { + throw new Exception($"EncryptionVm CodeFile:`{_encryptionVmCodeFile}` not match with encryptionVM settings! Please run `Obfuz/GenerateVm` to update it!"); + } + var vms = new VirtualMachineSimulator(vm, secretKey); + + var generatedVmTypes = AppDomain.CurrentDomain.GetAssemblies() + .Select(assembly => assembly.GetType("Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine")) + .Where(type => type != null) + .ToList(); + if (generatedVmTypes.Count == 0) + { + throw new Exception($"class Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine not found in any assembly! Please run `Obfuz/GenerateVm` to generate it!"); + } + if (generatedVmTypes.Count > 1) + { + throw new Exception($"class Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine found in multiple assemblies! Please retain only one!"); + } + + var gvmInstance = (IEncryptor)Activator.CreateInstance(generatedVmTypes[0], new object[] { secretKey } ); + + VerifyVm(vm, vms, gvmInstance); + + return vms; + } + + private void VerifyVm(VirtualMachine vm, VirtualMachineSimulator vms, IEncryptor gvm) + { + int testInt = 11223344; + long testLong = 1122334455667788L; + float testFloat = 1234f; + double testDouble = 1122334455.0; + string testString = "hello,world"; + for (int i = 0; i < vm.opCodes.Length; i++) + { + int ops = i * vm.opCodes.Length + i; + //int salt = i; + //int ops = -1135538782; + int salt = -879409147; + { + int encryptedIntOfVms = vms.Encrypt(testInt, ops, salt); + int decryptedIntOfVms = vms.Decrypt(encryptedIntOfVms, ops, salt); + if (decryptedIntOfVms != testInt) + { + throw new Exception($"VirtualMachineSimulator decrypt failed! opCode:{i}, originalValue:{testInt} decryptedValue:{decryptedIntOfVms}"); + } + int encryptedValueOfGvm = gvm.Encrypt(testInt, ops, salt); + int decryptedValueOfGvm = gvm.Decrypt(encryptedValueOfGvm, ops, salt); + if (encryptedValueOfGvm != encryptedIntOfVms) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testInt} encryptedValue VirtualMachineSimulator:{encryptedIntOfVms} GeneratedEncryptionVirtualMachine:{encryptedValueOfGvm}"); + } + if (decryptedValueOfGvm != testInt) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt failed! opCode:{i}, originalValue:{testInt} decryptedValue:{decryptedValueOfGvm}"); + } + } + { + long encryptedLongOfVms = vms.Encrypt(testLong, ops, salt); + long decryptedLongOfVms = vms.Decrypt(encryptedLongOfVms, ops, salt); + if (decryptedLongOfVms != testLong) + { + throw new Exception($"VirtualMachineSimulator decrypt long failed! opCode:{i}, originalValue:{testLong} decryptedValue:{decryptedLongOfVms}"); + } + long encryptedValueOfGvm = gvm.Encrypt(testLong, ops, salt); + long decryptedValueOfGvm = gvm.Decrypt(encryptedValueOfGvm, ops, salt); + if (encryptedValueOfGvm != encryptedLongOfVms) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testLong} encryptedValue VirtualMachineSimulator:{encryptedLongOfVms} GeneratedEncryptionVirtualMachine:{encryptedValueOfGvm}"); + } + if (decryptedValueOfGvm != testLong) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt long failed! opCode:{i}, originalValue:{testLong} decryptedValue:{decryptedValueOfGvm}"); + } + } + { + float encryptedFloatOfVms = vms.Encrypt(testFloat, ops, salt); + float decryptedFloatOfVms = vms.Decrypt(encryptedFloatOfVms, ops, salt); + if (decryptedFloatOfVms != testFloat) + { + throw new Exception("encryptedFloat not match"); + } + float encryptedValueOfGvm = gvm.Encrypt(testFloat, ops, salt); + float decryptedValueOfGvm = gvm.Decrypt(encryptedFloatOfVms, ops, salt); + if (encryptedFloatOfVms != encryptedValueOfGvm) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testFloat} encryptedValue"); + } + if (decryptedValueOfGvm != testFloat) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt float failed! opCode:{i}, originalValue:{testFloat}"); + } + } + { + double encryptedFloatOfVms = vms.Encrypt(testDouble, ops, salt); + double decryptedFloatOfVms = vms.Decrypt(encryptedFloatOfVms, ops, salt); + if (decryptedFloatOfVms != testDouble) + { + throw new Exception("encryptedFloat not match"); + } + double encryptedValueOfGvm = gvm.Encrypt(testDouble, ops, salt); + double decryptedValueOfGvm = gvm.Decrypt(encryptedFloatOfVms, ops, salt); + if (encryptedFloatOfVms != encryptedValueOfGvm) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testDouble} encryptedValue"); + } + if (decryptedValueOfGvm != testDouble) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt float failed! opCode:{i}, originalValue:{testDouble}"); + } + } + + { + byte[] encryptedStrOfVms = vms.Encrypt(testString, ops, salt); + string decryptedStrOfVms = vms.DecryptString(encryptedStrOfVms, 0, encryptedStrOfVms.Length, ops, salt); + if (decryptedStrOfVms != testString) + { + throw new Exception($"VirtualMachineSimulator decrypt string failed! opCode:{i}, originalValue:{testString} decryptedValue:{decryptedStrOfVms}"); + } + byte[] encryptedStrOfGvm = gvm.Encrypt(testString, ops, salt); + string decryptedStrOfGvm = gvm.DecryptString(encryptedStrOfGvm, 0, encryptedStrOfGvm.Length, ops, salt); + if (!encryptedStrOfGvm.SequenceEqual(encryptedStrOfVms)) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testString} encryptedValue VirtualMachineSimulator:{encryptedStrOfVms} GeneratedEncryptionVirtualMachine:{encryptedStrOfGvm}"); + } + if (decryptedStrOfGvm != testString) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt string failed! opCode:{i}, originalValue:{testString} decryptedValue:{decryptedStrOfGvm}"); + } + } + } + } + + private EncryptionScopeInfo CreateEncryptionScope(byte[] byteSecret) + { + int[] intSecretKey = KeyGenerator.ConvertToIntKey(byteSecret); + IEncryptor encryption = CreateEncryptionVirtualMachine(byteSecret); + RandomCreator localRandomCreator = (seed) => new RandomWithKey(intSecretKey, _randomSeed ^ seed); + return new EncryptionScopeInfo(encryption, localRandomCreator); + } + + private EncryptionScopeProvider CreateEncryptionScopeProvider() + { + var defaultStaticScope = CreateEncryptionScope(_defaultStaticByteSecretKey); + var defaultDynamicScope = CreateEncryptionScope(_defaultDynamicByteSecret); + foreach (string dynamicAssName in _assembliesUsingDynamicSecretKeys) + { + if (!_assembliesToObfuscate.Contains(dynamicAssName)) + { + throw new Exception($"Dynamic secret assembly `{dynamicAssName}` should be in the toObfuscatedAssemblyNames list!"); + } + } + return new EncryptionScopeProvider(defaultStaticScope, defaultDynamicScope, _assembliesUsingDynamicSecretKeys); + } + + private void OnPreObfuscation(Pipeline pipeline) + { + AssemblyCache assemblyCache = new AssemblyCache(new PathAssemblyResolver(_assemblySearchPaths.ToArray())); + List modulesToObfuscate = new List(); + List allObfuscationRelativeModules = new List(); + LoadAssemblies(assemblyCache, modulesToObfuscate, allObfuscationRelativeModules); + + EncryptionScopeProvider encryptionScopeProvider = CreateEncryptionScopeProvider(); + var moduleEntityManager = new GroupByModuleEntityManager(); + var rvaDataAllocator = new RvaDataAllocator(encryptionScopeProvider, moduleEntityManager); + var constFieldAllocator = new ConstFieldAllocator(encryptionScopeProvider, rvaDataAllocator, moduleEntityManager); + _ctx = new ObfuscationPassContext + { + assemblyCache = assemblyCache, + modulesToObfuscate = modulesToObfuscate, + allObfuscationRelativeModules = allObfuscationRelativeModules, + assembliesToObfuscate = _assembliesToObfuscate, + nonObfuscatedButReferencingObfuscatedAssemblies = _nonObfuscatedButReferencingObfuscatedAssemblies, + obfuscatedAssemblyOutputPath = _obfuscatedAssemblyOutputPath, + moduleEntityManager = moduleEntityManager, + + encryptionScopeProvider = encryptionScopeProvider, + + rvaDataAllocator = rvaDataAllocator, + constFieldAllocator = constFieldAllocator, + whiteList = new ObfuscationMethodWhitelist(), + passPolicy = _passPolicy, + }; + ObfuscationPassContext.Current = _ctx; + pipeline.Start(); + } + + private void LoadAssemblies(AssemblyCache assemblyCache, List modulesToObfuscate, List allObfuscationRelativeModules) + { + foreach (string assName in _assembliesToObfuscate.Concat(_nonObfuscatedButReferencingObfuscatedAssemblies)) + { + ModuleDefMD mod = assemblyCache.TryLoadModule(assName); + if (mod == null) + { + Debug.Log($"assembly: {assName} not found! ignore."); + continue; + } + if (_assembliesToObfuscate.Contains(assName)) + { + modulesToObfuscate.Add(mod); + } + allObfuscationRelativeModules.Add(mod); + } + } + + private void WriteAssemblies() + { + foreach (ModuleDef mod in _ctx.allObfuscationRelativeModules) + { + string assNameWithExt = mod.Name; + string outputFile = $"{_obfuscatedAssemblyOutputPath}/{assNameWithExt}"; + mod.Write(outputFile); + Debug.Log($"save module. name:{mod.Assembly.Name} output:{outputFile}"); + } + } + + private void DoObfuscation(Pipeline pipeline) + { + pipeline.Run(); + } + + private void OnPostObfuscation(Pipeline pipeline) + { + pipeline.Stop(); + + _ctx.constFieldAllocator.Done(); + _ctx.rvaDataAllocator.Done(); + WriteAssemblies(); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Obfuscator.cs.meta b/com.code-philosophy.obfuz/Editor/Obfuscator.cs.meta new file mode 100644 index 0000000..08291f3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Obfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca9c4e108bde2184885e599f2bd19a00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs b/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs new file mode 100644 index 0000000..3a3943c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs @@ -0,0 +1,216 @@ +using Obfuz.EncryptionVM; +using Obfuz.ObfusPasses; +using Obfuz.ObfusPasses.CallObfus; +using Obfuz.ObfusPasses.ConstEncrypt; +using Obfuz.ObfusPasses.ExprObfus; +using Obfuz.ObfusPasses.FieldEncrypt; +using Obfuz.ObfusPasses.SymbolObfus; +using Obfuz.Settings; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; + +namespace Obfuz +{ + public class ObfuscatorBuilder + { + private string _defaultStaticSecretKey; + private string _defaultStaticSecretKeyOutputPath; + private string _defaultDynamicSecretKey; + private string _defaultDynamicSecretKeyOutputPath; + private List _assembliesUsingDynamicSecretKeys = new List(); + + private int _randomSeed; + private string _encryptionVmGenerationSecretKey; + private int _encryptionVmOpCodeCount; + private string _encryptionVmCodeFile; + + private List _assembliesToObfuscate = new List(); + private List _nonObfuscatedButReferencingObfuscatedAssemblies = new List(); + private List _assemblySearchPaths = new List(); + + private string _obfuscatedAssemblyOutputPath; + private List _obfuscationPassRuleConfigFiles; + + private ObfuscationPassType _enabledObfuscationPasses; + private List _obfuscationPasses = new List(); + + public string DefaultStaticSecretKey + { + get => _defaultStaticSecretKey; + set => _defaultStaticSecretKey = value; + } + + public string DefaultStaticSecretKeyOutputPath + { + get => _defaultStaticSecretKeyOutputPath; + set => _defaultStaticSecretKeyOutputPath = value; + } + + public string DefaultDynamicSecretKey + { + get => _defaultDynamicSecretKey; + set => _defaultDynamicSecretKey = value; + } + + public string DefaultDynamicSecretKeyOutputPath + { + get => _defaultDynamicSecretKeyOutputPath; + set => _defaultDynamicSecretKeyOutputPath = value; + } + + public List AssembliesUsingDynamicSecretKeys + { + get => _assembliesUsingDynamicSecretKeys; + set => _assembliesUsingDynamicSecretKeys = value; + } + + public int RandomSeed + { + get => _randomSeed; + set => _randomSeed = value; + } + + public string EncryptionVmGenerationSecretKey + { + get => _encryptionVmGenerationSecretKey; + set => _encryptionVmGenerationSecretKey = value; + } + + public int EncryptionVmOpCodeCount + { + get => _encryptionVmOpCodeCount; + set => _encryptionVmOpCodeCount = value; + } + + public string EncryptionVmCodeFile + { + get => _encryptionVmCodeFile; + set => _encryptionVmCodeFile = value; + } + + public List AssembliesToObfuscate + { + get => _assembliesToObfuscate; + set => _assembliesToObfuscate = value; + } + + public List NonObfuscatedButReferencingObfuscatedAssemblies + { + get => _nonObfuscatedButReferencingObfuscatedAssemblies; + set => _nonObfuscatedButReferencingObfuscatedAssemblies = value; + } + + public List AssemblySearchPaths + { + get => _assemblySearchPaths; + set => _assemblySearchPaths = value; + } + + public string ObfuscatedAssemblyOutputPath + { + get => _obfuscatedAssemblyOutputPath; + set => _obfuscatedAssemblyOutputPath = value; + } + + public ObfuscationPassType EnableObfuscationPasses + { + get => _enabledObfuscationPasses; + set => _enabledObfuscationPasses = value; + } + + public List ObfuscationPassRuleConfigFiles + { + get => _obfuscationPassRuleConfigFiles; + set => _obfuscationPassRuleConfigFiles = value; + } + + public List ObfuscationPasses + { + get => _obfuscationPasses; + set => _obfuscationPasses = value; + } + + public void InsertTopPriorityAssemblySearchPaths(List assemblySearchPaths) + { + _assemblySearchPaths.InsertRange(0, assemblySearchPaths); + } + + public ObfuscatorBuilder AddPass(IObfuscationPass pass) + { + _obfuscationPasses.Add(pass); + return this; + } + + public Obfuscator Build() + { + return new Obfuscator(this); + } + + public static List BuildUnityAssemblySearchPaths() + { + string applicationContentsPath = EditorApplication.applicationContentsPath; + return new List + { +#if UNITY_2021_1_OR_NEWER + Path.Combine(applicationContentsPath, "UnityReferenceAssemblies/unity-4.8-api/Facades"), + Path.Combine(applicationContentsPath, "UnityReferenceAssemblies/unity-4.8-api"), +#elif UNITY_2020 || UNITY_2019 + Path.Combine(applicationContentsPath, "MonoBleedingEdge/lib/mono/4.7.1-api/Facades"), + Path.Combine(applicationContentsPath, "MonoBleedingEdge/lib/mono/4.7.1-api"), +#else +#error "Unsupported Unity version" +#endif + Path.Combine(applicationContentsPath, "Managed/UnityEngine"), + }; + } + + public static ObfuscatorBuilder FromObfuzSettings(ObfuzSettings settings, BuildTarget target, bool searchPathIncludeUnityEditorInstallLocation) + { + List searchPaths = searchPathIncludeUnityEditorInstallLocation ? + BuildUnityAssemblySearchPaths().Concat(settings.assemblySettings.additionalAssemblySearchPaths).ToList() + : settings.assemblySettings.additionalAssemblySearchPaths.ToList(); + var builder = new ObfuscatorBuilder + { + _defaultStaticSecretKey = settings.secretSettings.defaultStaticSecretKey, + _defaultStaticSecretKeyOutputPath = settings.secretSettings.DefaultStaticSecretKeyOutputPath, + _defaultDynamicSecretKey = settings.secretSettings.defaultDynamicSecretKey, + _defaultDynamicSecretKeyOutputPath = settings.secretSettings.DefaultDynamicSecretKeyOutputPath, + _assembliesUsingDynamicSecretKeys = settings.secretSettings.assembliesUsingDynamicSecretKeys.ToList(), + _randomSeed = settings.secretSettings.randomSeed, + _encryptionVmGenerationSecretKey = settings.encryptionVMSettings.codeGenerationSecretKey, + _encryptionVmOpCodeCount = settings.encryptionVMSettings.encryptionOpCodeCount, + _encryptionVmCodeFile = settings.encryptionVMSettings.codeOutputPath, + _assembliesToObfuscate = settings.assemblySettings.assembliesToObfuscate.ToList(), + _nonObfuscatedButReferencingObfuscatedAssemblies = settings.assemblySettings.nonObfuscatedButReferencingObfuscatedAssemblies.ToList(), + _assemblySearchPaths = searchPaths, + _obfuscatedAssemblyOutputPath = settings.GetObfuscatedAssemblyOutputPath(target), + _enabledObfuscationPasses = settings.obfuscationPassSettings.enabledPasses, + _obfuscationPassRuleConfigFiles = settings.obfuscationPassSettings.ruleFiles.ToList(), + }; + ObfuscationPassType obfuscationPasses = settings.obfuscationPassSettings.enabledPasses; + if (obfuscationPasses.HasFlag(ObfuscationPassType.ConstEncrypt)) + { + builder.AddPass(new ConstEncryptPass(settings.constEncryptSettings)); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.FieldEncrypt)) + { + builder.AddPass(new FieldEncryptPass(settings.fieldEncryptSettings)); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.CallObfus)) + { + builder.AddPass(new CallObfusPass(settings.callObfusSettings)); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.ExprObfus)) + { + builder.AddPass(new ExprObfusPass()); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.SymbolObfus)) + { + builder.AddPass(new SymbolObfusPass(settings.symbolObfusSettings)); + } + return builder; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs.meta b/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs.meta new file mode 100644 index 0000000..261993b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77e279242d764ed4d996db96f35e8570 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef b/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef new file mode 100644 index 0000000..e1b3933 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Obfuz.Editor", + "rootNamespace": "", + "references": [ + "GUID:4140bd2e2764f1f47ab93125ecb61942" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef.meta b/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef.meta new file mode 100644 index 0000000..d010c09 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 66e09fc524ec6594b8d6ca1d91aa1a41 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Pipeline.cs b/com.code-philosophy.obfuz/Editor/Pipeline.cs new file mode 100644 index 0000000..9e92d46 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Pipeline.cs @@ -0,0 +1,43 @@ +using Obfuz.ObfusPasses; +using System.Collections.Generic; + +namespace Obfuz +{ + public class Pipeline + { + private readonly List _passes = new List(); + + public bool Empty => _passes.Count == 0; + + public Pipeline AddPass(IObfuscationPass pass) + { + _passes.Add(pass); + return this; + } + + public void Start() + { + foreach (var pass in _passes) + { + pass.Start(); + } + } + + public void Stop() + { + + foreach (var pass in _passes) + { + pass.Stop(); + } + } + + public void Run() + { + foreach (var pass in _passes) + { + pass.Process(); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Pipeline.cs.meta b/com.code-philosophy.obfuz/Editor/Pipeline.cs.meta new file mode 100644 index 0000000..fd3558c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Pipeline.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0915452219e923d428b9a408cf413844 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings.meta b/com.code-philosophy.obfuz/Editor/Settings.meta new file mode 100644 index 0000000..64d3c0a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4939d1f80df50b34c85ffc8fbe530887 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs b/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs new file mode 100644 index 0000000..ec4898d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class AssemblySettings + { + + [Tooltip("name of assemblies to obfuscate")] + public string[] assembliesToObfuscate; + + [Tooltip("name of assemblies not obfuscated but reference assemblies to obfuscated ")] + public string[] nonObfuscatedButReferencingObfuscatedAssemblies; + + [Tooltip("additional assembly search paths")] + public string[] additionalAssemblySearchPaths; + + public string[] GetObfuscationRelativeAssemblyNames() + { + return assembliesToObfuscate + .Concat(nonObfuscatedButReferencingObfuscatedAssemblies) + .ToArray(); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs.meta new file mode 100644 index 0000000..8a13940 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 735c01fddc6f0c54a83e1feb9a60e13a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs new file mode 100644 index 0000000..3ea3a1f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class CallObfuscationSettings + { + [Tooltip("The obfuscation level for the obfuscation. Higher levels provide more security but may impact performance.")] + [Range(1, 4)] + public int obfuscationLevel = 1; + + [Tooltip("rule config xml files")] + public string[] ruleFiles; + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs.meta new file mode 100644 index 0000000..ce268f1 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae4ddc19ecf8d9940b444f5b66c23efa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs new file mode 100644 index 0000000..794d685 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class ConstEncryptionSettings + { + [Tooltip("The encryption level for the obfuscation. Higher levels provide more security but may impact performance.")] + [Range(1, 4)] + public int encryptionLevel = 1; + + [Tooltip("config xml files")] + public string[] ruleFiles; + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs.meta new file mode 100644 index 0000000..d5cef40 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b189f76d7da58e4b9403b4fe87265b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs new file mode 100644 index 0000000..04a7a22 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class EncryptionVMSettings + { + [Tooltip("secret key for generating encryption virtual machine source code")] + public string codeGenerationSecretKey = "Obfuz"; + + [Tooltip("encryption OpCode count, should be power of 2 and >= 64")] + public int encryptionOpCodeCount = 256; + + [Tooltip("encryption virtual machine source code output path")] + public string codeOutputPath = "Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs"; + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs.meta new file mode 100644 index 0000000..0257381 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0e626d510710c540bdc36b4dca93340 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs new file mode 100644 index 0000000..0b41afe --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class FieldEncryptionSettings + { + [Tooltip("The encryption level for the obfuscation. Higher levels provide more security but may impact performance.")] + [Range(1, 4)] + public int encryptionLevel = 1; + + [Tooltip("rule config xml files")] + public string[] ruleFiles; + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs.meta new file mode 100644 index 0000000..fbc0e2e --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56cff1f5683d9114e8f13cee917ea582 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs new file mode 100644 index 0000000..837c8b0 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs @@ -0,0 +1,16 @@ +using Obfuz.ObfusPasses; +using System; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class ObfuscationPassSettings + { + [Tooltip("enable obfuscation pass")] + public ObfuscationPassType enabledPasses = ObfuscationPassType.All; + + [Tooltip("rule config xml files")] + public string[] ruleFiles; + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs.meta new file mode 100644 index 0000000..147fed6 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9cb41a6fb7293ce47807e432d80f2b2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs new file mode 100644 index 0000000..70fb15d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.IO; +using System.Runtime.Remoting.Messaging; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace Obfuz.Settings +{ + + + public class ObfuzSettings : ScriptableObject + { + [Tooltip("enable Obfuz")] + public bool enable = true; + + [Tooltip("assembly settings")] + public AssemblySettings assemblySettings; + + [Tooltip("obfuscation pass settings")] + public ObfuscationPassSettings obfuscationPassSettings; + + [Tooltip("secret settings")] + public SecretSettings secretSettings; + + [Tooltip("encryption virtual machine settings")] + public EncryptionVMSettings encryptionVMSettings; + + [Tooltip("symbol obfuscation settings")] + public SymbolObfuscationSettings symbolObfusSettings; + + [Tooltip("const encryption settings")] + public ConstEncryptionSettings constEncryptSettings; + + [Tooltip("field encryption settings")] + public FieldEncryptionSettings fieldEncryptSettings; + + [Tooltip("call obfuscation settings")] + public CallObfuscationSettings callObfusSettings; + + public string ObfuzRootDir => $"Library/Obfuz"; + + public string GetObfuscatedAssemblyOutputPath(BuildTarget target) + { + return $"{ObfuzRootDir}/{target}/ObfuscatedAssemblies"; + } + + public string GetOriginalAssemblyBackupDir(BuildTarget target) + { + return $"{ObfuzRootDir}/{target}/OriginalAssemblies"; + } + + private static ObfuzSettings s_Instance; + + public static ObfuzSettings Instance + { + get + { + if (!s_Instance) + { + LoadOrCreate(); + } + return s_Instance; + } + } + + protected static string SettingsPath => "ProjectSettings/Obfuz.asset"; + + private static ObfuzSettings LoadOrCreate() + { + string filePath = SettingsPath; + var arr = InternalEditorUtility.LoadSerializedFileAndForget(filePath); + //Debug.Log($"typeof arr:{arr?.GetType()} arr[0]:{(arr != null && arr.Length > 0 ? arr[0].GetType(): null)}"); + + if (arr != null && arr.Length > 0 && arr[0] is ObfuzSettings obfuzSettings) + { + s_Instance = obfuzSettings; + } + else + { + s_Instance ??= CreateInstance(); + } + return s_Instance; + } + + public static void Save() + { + if (!s_Instance) + { + Debug.LogError("Cannot save ScriptableSingleton: no instance!"); + return; + } + + string filePath = SettingsPath; + string directoryName = Path.GetDirectoryName(filePath); + Directory.CreateDirectory(directoryName); + UnityEngine.Object[] obj = new ObfuzSettings[1] { s_Instance }; + InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, true); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs.meta new file mode 100644 index 0000000..8bad58f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c414eef017e565c4db1442ec64ec52fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs b/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs new file mode 100644 index 0000000..3a5c9e2 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs @@ -0,0 +1,104 @@ +using System; +using System.Reflection; +using UnityEditor; +using UnityEditor.Presets; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Obfuz.Settings +{ + public class ObfuzSettingsProvider : SettingsProvider + { + + private static ObfuzSettingsProvider s_provider; + + [SettingsProvider] + public static SettingsProvider CreateMyCustomSettingsProvider() + { + if (s_provider == null) + { + s_provider = new ObfuzSettingsProvider(); + using (var so = new SerializedObject(ObfuzSettings.Instance)) + { + s_provider.keywords = GetSearchKeywordsFromSerializedObject(so); + } + } + return s_provider; + } + + + private SerializedObject _serializedObject; + private SerializedProperty _enable; + private SerializedProperty _assemblySettings; + private SerializedProperty _obfuscationPassSettings; + private SerializedProperty _secretSettings; + private SerializedProperty _encryptionVMSettings; + + private SerializedProperty _symbolObfusSettings; + private SerializedProperty _constEncryptSettings; + private SerializedProperty _fieldEncryptSettings; + private SerializedProperty _callObfusSettings; + + public ObfuzSettingsProvider() : base("Project/Obfuz", SettingsScope.Project) + { + } + + public override void OnActivate(string searchContext, VisualElement rootElement) + { + InitGUI(); + } + + public override void OnDeactivate() + { + base.OnDeactivate(); + ObfuzSettings.Save(); + } + + private void InitGUI() + { + var setting = ObfuzSettings.Instance; + _serializedObject?.Dispose(); + _serializedObject = new SerializedObject(setting); + _enable = _serializedObject.FindProperty("enable"); + _assemblySettings = _serializedObject.FindProperty("assemblySettings"); + _obfuscationPassSettings = _serializedObject.FindProperty("obfuscationPassSettings"); + _secretSettings = _serializedObject.FindProperty("secretSettings"); + + _encryptionVMSettings = _serializedObject.FindProperty("encryptionVMSettings"); + + _symbolObfusSettings = _serializedObject.FindProperty("symbolObfusSettings"); + _constEncryptSettings = _serializedObject.FindProperty("constEncryptSettings"); + _fieldEncryptSettings = _serializedObject.FindProperty("fieldEncryptSettings"); + _callObfusSettings = _serializedObject.FindProperty("callObfusSettings"); + } + + public override void OnGUI(string searchContext) + { + if (_serializedObject == null||!_serializedObject.targetObject) + { + InitGUI(); + } + _serializedObject.Update(); + EditorGUI.BeginChangeCheck(); + + EditorGUILayout.PropertyField(_enable); + EditorGUILayout.PropertyField(_assemblySettings); + EditorGUILayout.PropertyField(_obfuscationPassSettings); + EditorGUILayout.PropertyField(_secretSettings); + + EditorGUILayout.PropertyField(_encryptionVMSettings); + + EditorGUILayout.PropertyField(_symbolObfusSettings); + EditorGUILayout.PropertyField(_constEncryptSettings); + EditorGUILayout.PropertyField(_fieldEncryptSettings); + EditorGUILayout.PropertyField(_callObfusSettings); + + + if (EditorGUI.EndChangeCheck()) + { + _serializedObject.ApplyModifiedProperties(); + ObfuzSettings.Save(); + } + } + } +} \ No newline at end of file diff --git a/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs.meta new file mode 100644 index 0000000..471477b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f020d09993a1aa41bae3258ec33d5fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs new file mode 100644 index 0000000..09c2143 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class SecretSettings + { + + [Tooltip("default static secret key")] + public string defaultStaticSecretKey = "Code Philosophy-Static"; + + [Tooltip("default dynamic secret key")] + public string defaultDynamicSecretKey = "Code Philosophy-Dynamic"; + + [Tooltip("secret key output path")] + public string secretKeyOutputPath = $"Assets/Resources/Obfuz"; + + [Tooltip("random seed")] + public int randomSeed = 0; + + [Tooltip("name of assemblies those use dynamic secret key")] + public string[] assembliesUsingDynamicSecretKeys; + + public string DefaultStaticSecretKeyOutputPath => Path.Combine(secretKeyOutputPath, "defaultStaticSecretKey.bytes"); + + public string DefaultDynamicSecretKeyOutputPath => Path.Combine(secretKeyOutputPath, "defaultDynamicSecretKey.bytes"); + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs.meta new file mode 100644 index 0000000..7e26a78 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ac4fe2a6df113444b67412254452a00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs new file mode 100644 index 0000000..6e1d765 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class SymbolObfuscationSettings + { + public bool debug; + + [Tooltip("prefix for obfuscated name to avoid name confliction with original name")] + public string obfuscatedNamePrefix = "$"; + + [Tooltip("obfuscate same namespace to one name")] + public bool useConsistentNamespaceObfuscation = true; + + [Tooltip("symbol mapping file path")] + public string symbolMappingFile = "Assets/Obfuz/SymbolObfus/symbol-mapping.xml"; + + [Tooltip("rule files")] + public string[] ruleFiles; + } +} diff --git a/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs.meta b/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs.meta new file mode 100644 index 0000000..58779d6 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2484a8a12a689df46b5eb7fc4ccac81f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Unity.meta b/com.code-philosophy.obfuz/Editor/Unity.meta new file mode 100644 index 0000000..8456b1d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bc9b206fbf6a69f4c99a6ec9b0b27c69 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs new file mode 100644 index 0000000..b8f2397 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs @@ -0,0 +1,8 @@ +namespace Obfuz.Unity +{ + public class ObfuscationBeginEventArgs + { + public string scriptAssembliesPath; + public string obfuscatedScriptAssembliesPath; + } +} diff --git a/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs.meta b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs.meta new file mode 100644 index 0000000..a2d5e11 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e3d38018839d4844bf1e15631946e65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs new file mode 100644 index 0000000..ac2fa76 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs @@ -0,0 +1,9 @@ +namespace Obfuz.Unity +{ + public class ObfuscationEndEventArgs + { + public bool success; + public string originalScriptAssembliesPath; + public string obfuscatedScriptAssembliesPath; + } +} diff --git a/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs.meta b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs.meta new file mode 100644 index 0000000..fe37681 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b198ad99d0a9c145b1bc2b29b25b8ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs new file mode 100644 index 0000000..4770a57 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor.Build; +using UnityEditor; +using UnityEditor.Build.Reporting; +using UnityEngine; +using UnityEditor.Compilation; +using Obfuz.Utils; +using FileUtil = Obfuz.Utils.FileUtil; +using Obfuz.Settings; + +namespace Obfuz.Unity +{ + +#if UNITY_2019_1_OR_NEWER + public class ObfuscationProcess : IPostBuildPlayerScriptDLLs + { + public int callbackOrder => 10000; + + public static event Action OnObfuscationBegin; + + public static event Action OnObfuscationEnd; + + public void OnPostBuildPlayerScriptDLLs(BuildReport report) + { +#if !UNITY_2022_1_OR_NEWER + RunObfuscate(report.files); +#else + RunObfuscate(report.GetFiles()); +#endif + } + + private static void BackupOriginalDlls(string srcDir, string dstDir, HashSet dllNames) + { + FileUtil.RecreateDir(dstDir); + foreach (string dllName in dllNames) + { + string srcFile = Path.Combine(srcDir, dllName + ".dll"); + string dstFile = Path.Combine(dstDir, dllName + ".dll"); + if (File.Exists(srcFile)) + { + File.Copy(srcFile, dstFile, true); + Debug.Log($"BackupOriginalDll {srcFile} -> {dstFile}"); + } + } + } + + private static void RunObfuscate(BuildFile[] files) + { + ObfuzSettings settings = ObfuzSettings.Instance; + if (!settings.enable) + { + Debug.Log("Obfuscation is disabled."); + return; + } + + Debug.Log("Obfuscation begin..."); + var buildTarget = EditorUserBuildSettings.activeBuildTarget; + + var obfuscationRelativeAssemblyNames = new HashSet(settings.assemblySettings.GetObfuscationRelativeAssemblyNames()); + string stagingAreaTempManagedDllDir = Path.GetDirectoryName(files.First(file => file.path.EndsWith(".dll")).path); + string backupPlayerScriptAssembliesPath = settings.GetOriginalAssemblyBackupDir(buildTarget); + BackupOriginalDlls(stagingAreaTempManagedDllDir, backupPlayerScriptAssembliesPath, obfuscationRelativeAssemblyNames); + + string applicationContentsPath = EditorApplication.applicationContentsPath; + + var obfuscatorBuilder = ObfuscatorBuilder.FromObfuzSettings(settings, buildTarget, false); + + var assemblySearchDirs = new List + { + stagingAreaTempManagedDllDir, + }; + obfuscatorBuilder.InsertTopPriorityAssemblySearchPaths(assemblySearchDirs); + + + OnObfuscationBegin?.Invoke(new ObfuscationBeginEventArgs + { + scriptAssembliesPath = stagingAreaTempManagedDllDir, + obfuscatedScriptAssembliesPath = obfuscatorBuilder.ObfuscatedAssemblyOutputPath, + }); + bool succ = false; + + try + { + Obfuscator obfuz = obfuscatorBuilder.Build(); + obfuz.Run(); + + foreach (var dllName in obfuscationRelativeAssemblyNames) + { + string src = $"{obfuscatorBuilder.ObfuscatedAssemblyOutputPath}/{dllName}.dll"; + string dst = $"{stagingAreaTempManagedDllDir}/{dllName}.dll"; + + if (!File.Exists(src)) + { + Debug.LogWarning($"obfuscation assembly not found! skip copy. path:{src}"); + continue; + } + File.Copy(src, dst, true); + Debug.Log($"obfuscate dll:{dst}"); + } + succ = true; + } + catch (Exception e) + { + succ = false; + Debug.LogException(e); + Debug.LogError($"Obfuscation failed."); + } + OnObfuscationEnd?.Invoke(new ObfuscationEndEventArgs + { + success = succ, + originalScriptAssembliesPath = backupPlayerScriptAssembliesPath, + obfuscatedScriptAssembliesPath = stagingAreaTempManagedDllDir, + }); + + Debug.Log("Obfuscation end."); + } + } +#endif + } diff --git a/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs.meta b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs.meta new file mode 100644 index 0000000..de6b391 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc7a8b1e20c66164699de44d0a302cb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs b/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs new file mode 100644 index 0000000..d0162c3 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs @@ -0,0 +1,64 @@ +using Obfuz.EncryptionVM; +using Obfuz.Settings; +using Obfuz.Utils; +using System.IO; +using UnityEditor; +using UnityEngine; +using FileUtil = Obfuz.Utils.FileUtil; + +namespace Obfuz.Unity +{ + public static class ObfuzMenu + { + + [MenuItem("Obfuz/Settings...", priority = 1)] + public static void OpenSettings() => SettingsService.OpenProjectSettings("Project/Obfuz"); + + [MenuItem("Obfuz/GenerateEncryptionVM", priority = 62)] + public static void GenerateEncryptionVM() + { + EncryptionVMSettings settings = ObfuzSettings.Instance.encryptionVMSettings; + var generator = new VirtualMachineCodeGenerator(settings.codeGenerationSecretKey, settings.encryptionOpCodeCount); + generator.Generate(settings.codeOutputPath); + } + + [MenuItem("Obfuz/GenerateSecretKeyFile", priority = 63)] + public static void SaveSecretFile() + { + SecretSettings settings = ObfuzSettings.Instance.secretSettings; + + var staticSecretBytes = KeyGenerator.GenerateKey(settings.defaultStaticSecretKey, VirtualMachine.SecretKeyLength); + SaveKey(staticSecretBytes, settings.DefaultStaticSecretKeyOutputPath); + Debug.Log($"Save static secret key to {settings.DefaultStaticSecretKeyOutputPath}"); + var dynamicSecretBytes = KeyGenerator.GenerateKey(settings.defaultDynamicSecretKey, VirtualMachine.SecretKeyLength); + SaveKey(dynamicSecretBytes, settings.DefaultDynamicSecretKeyOutputPath); + Debug.Log($"Save dynamic secret key to {settings.DefaultDynamicSecretKeyOutputPath}"); + AssetDatabase.Refresh(); + } + + private static void SaveKey(byte[] secret, string secretOutputPath) + { + FileUtil.CreateParentDir(secretOutputPath); + File.WriteAllBytes(secretOutputPath, secret); + } + + [MenuItem("Obfuz/Documents/Quick Start")] + public static void OpenQuickStart() => Application.OpenURL("https://obfuz.doc.code-philosophy.com/docs/beginner/quickstart"); + + [MenuItem("Obfuz/Documents/FAQ")] + public static void OpenFAQ() => Application.OpenURL("https://obfuz.doc.code-philosophy.com/docs/help/faq"); + + [MenuItem("Obfuz/Documents/Common Errors")] + public static void OpenCommonErrors() => Application.OpenURL("https://obfuz.doc.code-philosophy.com/docs/help/commonerrors"); + + [MenuItem("Obfuz/Documents/Bug Report")] + public static void OpenBugReport() => Application.OpenURL("https://obfuz.doc.code-philosophy.com/docs/help/issue"); + + [MenuItem("Obfuz/Documents/GitHub")] + public static void OpenGitHub() => Application.OpenURL("https://github.com/focus-creative-games/obfuz"); + + [MenuItem("Obfuz/Documents/About")] + public static void OpenAbout() => Application.OpenURL("https://obfuz.doc.code-philosophy.com/docs/intro"); + } + +} \ No newline at end of file diff --git a/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs.meta b/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs.meta new file mode 100644 index 0000000..ae1f6f8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce8d804a6c4640e45a3d5c98b30c0c31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils.meta b/com.code-philosophy.obfuz/Editor/Utils.meta new file mode 100644 index 0000000..8a3e253 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ade28aaad1116b143a4027071e71010f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs b/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs new file mode 100644 index 0000000..ecaa587 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs @@ -0,0 +1,91 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public class AssemblyCache + { + private readonly IAssemblyResolver _assemblyPathResolver; + private readonly ModuleContext _modCtx; + private readonly AssemblyResolver _asmResolver; + private bool _enableTypeDefCache; + + + public ModuleContext ModCtx => _modCtx; + + public Dictionary LoadedModules { get; } = new Dictionary(); + + public AssemblyCache(IAssemblyResolver assemblyResolver) + { + _enableTypeDefCache = true; + _assemblyPathResolver = assemblyResolver; + _modCtx = ModuleDef.CreateModuleContext(); + _asmResolver = (AssemblyResolver)_modCtx.AssemblyResolver; + _asmResolver.EnableTypeDefCache = _enableTypeDefCache; + _asmResolver.UseGAC = false; + } + + public bool EnableTypeDefCache + { + get => _enableTypeDefCache; + set + { + _enableTypeDefCache = value; + _asmResolver.EnableTypeDefCache = value; + foreach (var mod in LoadedModules.Values) + { + mod.EnableTypeDefFindCache = value; + } + } + } + + + public ModuleDefMD TryLoadModule(string moduleName) + { + string dllPath = _assemblyPathResolver.ResolveAssembly(moduleName); + if (string.IsNullOrEmpty(dllPath)) + { + return null; + } + return LoadModule(moduleName); + } + + public ModuleDefMD LoadModule(string moduleName) + { + // Debug.Log($"load module:{moduleName}"); + if (LoadedModules.TryGetValue(moduleName, out var mod)) + { + return mod; + } + string assemblyPath = _assemblyPathResolver.ResolveAssembly(moduleName); + if (string.IsNullOrEmpty(assemblyPath)) + { + throw new FileNotFoundException($"Assembly {moduleName} not found"); + } + mod = DoLoadModule(assemblyPath); + LoadedModules.Add(moduleName, mod); + + + foreach (var refAsm in mod.GetAssemblyRefs()) + { + LoadModule(refAsm.Name); + } + + return mod; + } + + private ModuleDefMD DoLoadModule(string dllPath) + { + //Debug.Log($"do load module:{dllPath}"); + ModuleDefMD mod = ModuleDefMD.Load(File.ReadAllBytes(dllPath), _modCtx); + mod.EnableTypeDefFindCache = _enableTypeDefCache; + _asmResolver.AddToCache(mod); + return mod; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs.meta new file mode 100644 index 0000000..bb92229 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce64ad992f9807d4994d4f41a54b170b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs b/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs new file mode 100644 index 0000000..7eb9491 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public abstract class AssemblyResolverBase : IAssemblyResolver + { + public abstract string ResolveAssembly(string assemblyName); + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs.meta new file mode 100644 index 0000000..4a7f4a0 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c7332848ca18498459e6248d06bc5b31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs b/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs new file mode 100644 index 0000000..f7165ab --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public static class CollectionExtensions + { + public static void AddRange(this HashSet values, IEnumerable newValues) + { + foreach (var value in newValues) + { + values.Add(value); + } + } + + public static V GetValueOrDefault(this Dictionary dic, K key) + { + return dic.TryGetValue(key, out V v) ? v : default(V); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs.meta new file mode 100644 index 0000000..ba4aa2a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 58fc4438f86bc174aba662f1d7058f45 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs b/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs new file mode 100644 index 0000000..3dc39fe --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs @@ -0,0 +1,76 @@ +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 bool? ParseNullableBool(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + 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); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs.meta new file mode 100644 index 0000000..bba160a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff35a8e07f37adf4483eaf5cc5da5c78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs b/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs new file mode 100644 index 0000000..5785800 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.Utils +{ + public static class EncryptionUtil + { + public static int GetBitCount(int value) + { + int count = 0; + while (value > 0) + { + count++; + value >>= 1; + } + return count; + } + + public static int GenerateEncryptionOpCodes(IRandom random, IEncryptor encryptor, int encryptionLevel) + { + if (encryptionLevel <= 0 || encryptionLevel > 4) + { + throw new ArgumentException($"Invalid encryption level: {encryptionLevel}, should be in range [1,4]"); + } + int vmOpCodeCount = encryptor.OpCodeCount; + long ops = 0; + for (int i = 0; i < encryptionLevel; i++) + { + long newOps = ops * vmOpCodeCount; + // don't use 0 + int op = random.NextInt(1, vmOpCodeCount); + newOps |= (uint)op; + if (newOps > uint.MaxValue) + { + Debug.LogWarning($"OpCode overflow. encryptionLevel:{encryptionLevel}, vmOpCodeCount:{vmOpCodeCount}"); + } + else + { + ops = newOps; + } + } + return (int)ops; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs.meta new file mode 100644 index 0000000..5c82b2b --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b8fc4c92fa6f0b40a9734b347cd265c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs b/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs new file mode 100644 index 0000000..ccd210d --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public static class FileUtil + { + public static void CreateParentDir(string path) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } + + public static void RemoveDir(string dir, bool log = false) + { + if (log) + { + UnityEngine.Debug.Log($"removeDir dir:{dir}"); + } + + int maxTryCount = 5; + for (int i = 0; i < maxTryCount; ++i) + { + try + { + if (!Directory.Exists(dir)) + { + return; + } + foreach (var file in Directory.GetFiles(dir)) + { + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); + } + foreach (var subDir in Directory.GetDirectories(dir)) + { + RemoveDir(subDir); + } + Directory.Delete(dir, true); + break; + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"removeDir:{dir} with exception:{e}. try count:{i}"); + Thread.Sleep(100); + } + } + } + + public static void RecreateDir(string dir) + { + if (Directory.Exists(dir)) + { + RemoveDir(dir, true); + } + Directory.CreateDirectory(dir); + } + + private static void CopyWithCheckLongFile(string srcFile, string dstFile) + { + var maxPathLength = 255; +#if UNITY_EDITOR_OSX + maxPathLength = 1024; +#endif + if (srcFile.Length > maxPathLength) + { + UnityEngine.Debug.LogError($"srcFile:{srcFile} path is too long. skip copy!"); + return; + } + if (dstFile.Length > maxPathLength) + { + UnityEngine.Debug.LogError($"dstFile:{dstFile} path is too long. skip copy!"); + return; + } + File.Copy(srcFile, dstFile); + } + + public static void CopyDir(string src, string dst, bool log = false) + { + if (log) + { + UnityEngine.Debug.Log($"copyDir {src} => {dst}"); + } + RemoveDir(dst); + Directory.CreateDirectory(dst); + foreach (var file in Directory.GetFiles(src)) + { + CopyWithCheckLongFile(file, $"{dst}/{Path.GetFileName(file)}"); + } + foreach (var subDir in Directory.GetDirectories(src)) + { + CopyDir(subDir, $"{dst}/{Path.GetFileName(subDir)}"); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs.meta new file mode 100644 index 0000000..c8523ce --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6385af8bd061b142a3d7dcf41ab7e79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs b/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs new file mode 100644 index 0000000..a9f89be --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs @@ -0,0 +1,126 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + /// + /// Replaces generic type/method var with its generic argument + /// + public sealed class GenericArgumentContext + { + List typeArgsStack; + List methodArgsStack; + + public GenericArgumentContext(IList typeArgsStack, IList methodArgsStack) + { + this.typeArgsStack = typeArgsStack?.ToList(); + this.methodArgsStack = methodArgsStack?.ToList(); + } + + + + /// + /// Replaces a generic type/method var with its generic argument (if any). If + /// isn't a generic type/method var or if it can't + /// be resolved, it itself is returned. Else the resolved type is returned. + /// + /// Type signature + /// New which is never null unless + /// is null + public TypeSig Resolve(TypeSig typeSig) + { + if (!typeSig.ContainsGenericParameter) + { + return typeSig; + } + typeSig = typeSig.RemovePinnedAndModifiers(); + switch (typeSig.ElementType) + { + case ElementType.Ptr: return new PtrSig(Resolve(typeSig.Next)); + case ElementType.ByRef: return new PtrSig(Resolve(typeSig.Next)); + + case ElementType.SZArray: return new PtrSig(Resolve(typeSig.Next)); + case ElementType.Array: + { + var ara = (ArraySig)typeSig; + return new ArraySig(Resolve(typeSig.Next), ara.Rank, ara.Sizes, ara.LowerBounds); + } + + case ElementType.Var: + { + GenericVar genericVar = (GenericVar)typeSig; + var newSig = Resolve(typeArgsStack, genericVar.Number, true); + if (newSig == null) + { + throw new Exception(); + } + return newSig; + } + + case ElementType.MVar: + { + GenericMVar genericVar = (GenericMVar)typeSig; + var newSig = Resolve(methodArgsStack, genericVar.Number, true); + if (newSig == null) + { + throw new Exception(); + } + return newSig; + } + case ElementType.GenericInst: + { + var gia = (GenericInstSig)typeSig; + return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => Resolve(ga)).ToList()); + } + + case ElementType.FnPtr: + { + var fptr = (FnPtrSig)typeSig; + var cs = fptr.Signature; + CallingConventionSig ccs; + switch (cs) + { + case MethodSig ms: + { + ccs = new MethodSig(ms.GetCallingConvention(), ms.GenParamCount, Resolve(ms.RetType), ms.Params.Select(p => Resolve(p)).ToList()); + break; + } + case PropertySig ps: + { + ccs = new PropertySig(ps.HasThis, Resolve(ps.RetType)); + break; + } + case GenericInstMethodSig gims: + { + ccs = new GenericInstMethodSig(gims.GenericArguments.Select(ga => Resolve(ga)).ToArray()); + break; + } + default: throw new NotSupportedException(cs.ToString()); + } + return new FnPtrSig(ccs); + } + + case ElementType.ValueArray: + { + var vas = (ValueArraySig)typeSig; + return new ValueArraySig(Resolve(vas.Next), vas.Size); + } + default: return typeSig; + } + } + + private TypeSig Resolve(List args, uint number, bool isTypeVar) + { + var typeSig = args[(int)number]; + var gvar = typeSig as GenericSig; + if (gvar is null || gvar.IsTypeVar != isTypeVar) + return typeSig; + return gvar; + } + } + +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs.meta new file mode 100644 index 0000000..fd13834 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cdefb4e144f6a98418c7bd02eab51039 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs b/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs new file mode 100644 index 0000000..5b0d823 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs @@ -0,0 +1,67 @@ +using dnlib.DotNet; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine.UIElements; + +namespace Obfuz.Utils +{ + public static class HashUtil + { + public static int CombineHash(int hash1, int hash2) + { + return hash1 * 1566083941 + hash2; + } + + public static int ComputeHash(List sigs) + { + int hash = 135781321; + TypeEqualityComparer tc = TypeEqualityComparer.Instance; + foreach (var sig in sigs) + { + hash = hash * 1566083941 + tc.GetHashCode(sig); + } + return hash; + } + + public static unsafe int ComputeHash(string s) + { + fixed (char* ptr = s) + { + int num = 352654597; + int num2 = num; + int* ptr2 = (int*)ptr; + int num3; + for (num3 = s.Length; num3 > 2; num3 -= 4) + { + num = ((num << 5) + num + (num >> 27)) ^ *ptr2; + num2 = ((num2 << 5) + num2 + (num2 >> 27)) ^ ptr2[1]; + ptr2 += 2; + } + + if (num3 > 0) + { + num = ((num << 5) + num + (num >> 27)) ^ *ptr2; + } + + return num + num2 * 1566083941; + } + } + + public static int ComputePrimitiveOrStringOrBytesHashCode(object obj) + { + if (obj is byte[] bytes) + { + return StructuralComparisons.StructuralEqualityComparer.GetHashCode(bytes); + } + if (obj is string s) + { + return HashUtil.ComputeHash(s); + } + return obj.GetHashCode(); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs.meta new file mode 100644 index 0000000..d89f8f0 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b4cd05dd413bfa4ebb9fcbe591b1486 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs b/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs new file mode 100644 index 0000000..4a7578a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public interface IAssemblyResolver + { + string ResolveAssembly(string assemblyName); + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs.meta new file mode 100644 index 0000000..917a107 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0254d9726a78e146af99a61641b47d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs b/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs new file mode 100644 index 0000000..e3a68b8 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs @@ -0,0 +1,13 @@ +namespace Obfuz.Utils +{ + public interface IRandom + { + int NextInt(int min, int max); + + int NextInt(int max); + + int NextInt(); + + long NextLong(); + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs.meta new file mode 100644 index 0000000..086b073 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 959c821ff51056c4ebca3f89aeeff03d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs b/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs new file mode 100644 index 0000000..31aa40c --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs @@ -0,0 +1,41 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public static class KeyGenerator + { + public static byte[] GenerateKey(string initialString, int keyLength) + { + byte[] initialBytes = Encoding.UTF8.GetBytes(initialString); + using (var sha512 = SHA512.Create()) + { + byte[] hash = sha512.ComputeHash(initialBytes); + byte[] key = new byte[keyLength]; + int bytesCopied = 0; + while (bytesCopied < key.Length) + { + if (bytesCopied > 0) + { + // 再次哈希之前的哈希值以生成更多数据 + hash = sha512.ComputeHash(hash); + } + int bytesToCopy = Math.Min(hash.Length, key.Length - bytesCopied); + Buffer.BlockCopy(hash, 0, key, bytesCopied, bytesToCopy); + bytesCopied += bytesToCopy; + } + return key; + } + } + + public static int[] ConvertToIntKey(byte[] key) + { + return EncryptorBase.ConvertToIntKey(key); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs.meta new file mode 100644 index 0000000..7d11ff0 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5441ac16fd88a8848af862be23bd2ecb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs b/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs new file mode 100644 index 0000000..711e957 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs @@ -0,0 +1,813 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using UnityEngine.Assertions; +using UnityEngine.UIElements; + +namespace Obfuz.Utils +{ + + public static class MetaUtil + { + public static string GetModuleNameWithoutExt(string moduleName) + { + return Path.GetFileNameWithoutExtension(moduleName); + } + + public static (string, string) SplitNamespaceAndName(string fullName) + { + int index = fullName.LastIndexOf('/'); + if (index == -1) + { + int index2 = fullName.IndexOf('.'); + return index2 >= 0 ? (fullName.Substring(0, index2), fullName.Substring(index2 + 1)) : ("", fullName); + } + return ("", fullName.Substring(index + 1)); + } + + + public static TypeDef GetBaseTypeDef(TypeDef type) + { + ITypeDefOrRef baseType = type.BaseType; + if (baseType == null) + { + return null; + } + TypeDef baseTypeDef = baseType.ResolveTypeDef(); + if (baseTypeDef != null) + { + return baseTypeDef; + } + if (baseType is TypeSpec baseTypeSpec) + { + GenericInstSig genericIns = baseTypeSpec.TypeSig.ToGenericInstSig(); + return genericIns.GenericType.TypeDefOrRef.ResolveTypeDefThrow(); + } + else + { + throw new Exception($"GetBaseTypeDef: {type} fail"); + } + } + + public static TypeDef GetTypeDefOrGenericTypeBaseThrowException(ITypeDefOrRef type) + { + if (type.IsTypeDef) + { + return (TypeDef)type; + } + if (type.IsTypeRef) + { + return type.ResolveTypeDefThrow(); + } + if (type.IsTypeSpec) + { + GenericInstSig gis = type.TryGetGenericInstSig(); + return gis.GenericType.ToTypeDefOrRef().ResolveTypeDefThrow(); + } + throw new NotSupportedException($"{type}"); + } + + public static TypeDef GetTypeDefOrGenericTypeBaseOrNull(ITypeDefOrRef type) + { + if (type.IsTypeDef) + { + return (TypeDef)type; + } + if (type.IsTypeRef) + { + return type.ResolveTypeDefThrow(); + } + if (type.IsTypeSpec) + { + GenericInstSig gis = type.TryGetGenericInstSig(); + return gis.GenericType.ToTypeDefOrRef().ResolveTypeDefThrow(); + } + return null; + } + + public static TypeDef GetMemberRefTypeDefParentOrNull(IMemberRefParent parent) + { + if (parent is TypeDef typeDef) + { + return typeDef; + } + if (parent is TypeRef typeRef) + { + return typeRef.ResolveTypeDefThrow(); + } + if (parent is TypeSpec typeSpec) + { + GenericInstSig genericIns = typeSpec.TypeSig.ToGenericInstSig(); + return genericIns.GenericType.TypeDefOrRef.ResolveTypeDefThrow(); + } + return null; + } + + public static bool IsInheritFromUnityObject(TypeDef typeDef) + { + TypeDef cur = typeDef; + while (true) + { + cur = GetBaseTypeDef(cur); + if (cur == null) + { + return false; + } + if (cur.Name == "Object" && cur.Namespace == "UnityEngine" && cur.Module.Name == "UnityEngine.CoreModule.dll") + { + return true; + } + } + } + + + + public static bool IsScriptOrSerializableType(TypeDef type) + { + if (type.ContainsGenericParameter) + { + return false; + } + if (type.IsSerializable) + { + return true; + } + + for (TypeDef parentType = GetBaseTypeDef(type); parentType != null; parentType = GetBaseTypeDef(parentType)) + { + if ((parentType.Name == "MonoBehaviour" || parentType.Name == "ScriptableObject") + && parentType.Namespace == "UnityEngine" + && parentType.Module.Assembly.Name == "UnityEngine.CoreModule") + { + return true; + } + } + + return false; + } + + public static bool IsSerializableTypeSig(TypeSig typeSig) + { + typeSig = typeSig.RemovePinnedAndModifiers(); + switch (typeSig.ElementType) + { + case ElementType.Boolean: + case ElementType.Char: + case ElementType.I1: + case ElementType.U1: + case ElementType.I2: + case ElementType.U2: + case ElementType.I4: + case ElementType.U4: + case ElementType.I8: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + case ElementType.String: + return true; + case ElementType.Class: + return IsScriptOrSerializableType(typeSig.ToTypeDefOrRef().ResolveTypeDefThrow()); + case ElementType.ValueType: + { + TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDefThrow(); + if (typeDef.IsEnum) + { + return true; + } + return typeDef.IsSerializable; + } + case ElementType.GenericInst: + { + GenericInstSig genericIns = typeSig.ToGenericInstSig(); + TypeDef typeDef = genericIns.GenericType.ToTypeDefOrRef().ResolveTypeDefThrow(); + return typeDef.FullName == "System.Collections.Generic.List`1" && IsSerializableTypeSig(genericIns.GenericArguments[0]); + } + case ElementType.SZArray: + { + return IsSerializableTypeSig(typeSig.RemovePinnedAndModifiers().Next); + } + default: + return false; + } + } + + public static bool IsSerializableField(FieldDef field) + { + if (field.IsStatic) + { + return false; + } + var fieldSig = field.FieldSig.Type; + if (field.IsPublic) + { + return IsSerializableTypeSig(fieldSig); + } + if (field.CustomAttributes.Any(c => c.TypeFullName == "UnityEngine.SerializeField")) + { + //UnityEngine.Debug.Assert(IsSerializableTypeSig(fieldSig)); + return true; + } + return false; + } + + public static bool MayRenameCustomDataType(ElementType type) + { + return type == ElementType.Class || type == ElementType.ValueType || type == ElementType.Object || type == ElementType.SZArray; + } + + public static TypeSig RetargetTypeRefInTypeSig(TypeSig type) + { + TypeSig next = type.Next; + TypeSig newNext = next != null ? RetargetTypeRefInTypeSig(next) : null; + if (type.IsModifier || type.IsPinned) + { + if (next == newNext) + { + return type; + } + if (type is CModReqdSig cmrs) + { + return new CModReqdSig(cmrs.Modifier, newNext); + } + if (type is CModOptSig cmos) + { + return new CModOptSig(cmos.Modifier, newNext); + } + if (type is PinnedSig ps) + { + return new PinnedSig(newNext); + } + throw new System.NotSupportedException(type.ToString()); + } + switch (type.ElementType) + { + case ElementType.Ptr: + { + if (next == newNext) + { + return type; + } + return new PtrSig(newNext); + } + case ElementType.ValueType: + case ElementType.Class: + { + var vts = type as ClassOrValueTypeSig; + TypeDef typeDef = vts.TypeDefOrRef.ResolveTypeDefThrow(); + if (typeDef == vts.TypeDefOrRef) + { + return type; + } + return type.IsClassSig ? (TypeSig)new ClassSig(typeDef) : new ValueTypeSig(typeDef); + } + case ElementType.Array: + { + if (next == newNext) + { + return type; + } + return new ArraySig(newNext); + } + case ElementType.SZArray: + { + if (next == newNext) + { + return type; + } + return new SZArraySig(newNext); + } + case ElementType.GenericInst: + { + var gis = type as GenericInstSig; + ClassOrValueTypeSig genericType = gis.GenericType; + ClassOrValueTypeSig newGenericType = (ClassOrValueTypeSig)RetargetTypeRefInTypeSig(genericType); + bool anyChange = genericType != newGenericType; + var genericArgs = new List(); + foreach (var arg in gis.GenericArguments) + { + TypeSig newArg = RetargetTypeRefInTypeSig(arg); + anyChange |= newArg != genericType; + genericArgs.Add(newArg); + } + if (!anyChange) + { + return type; + } + return new GenericInstSig(newGenericType, genericArgs); + } + case ElementType.FnPtr: + { + var fp = type as FnPtrSig; + MethodSig methodSig = fp.MethodSig; + TypeSig newReturnType = RetargetTypeRefInTypeSig(methodSig.RetType); + bool anyChange = newReturnType != methodSig.RetType; + var newArgs = new List(); + foreach (TypeSig arg in methodSig.Params) + { + TypeSig newArg = RetargetTypeRefInTypeSig(arg); + anyChange |= newArg != newReturnType; + } + if (!anyChange) + { + return type; + } + var newParamsAfterSentinel = new List(); + foreach (TypeSig arg in methodSig.ParamsAfterSentinel) + { + TypeSig newArg = RetargetTypeRefInTypeSig(arg); + anyChange |= newArg != arg; + newParamsAfterSentinel.Add(newArg); + } + + var newMethodSig = new MethodSig(methodSig.CallingConvention, methodSig.GenParamCount, newReturnType, newArgs, newParamsAfterSentinel); + return new FnPtrSig(newMethodSig); + } + case ElementType.ByRef: + { + if (next == newNext) + { + return type; + } + return new ByRefSig(newNext); + } + default: + { + return type; + } + } + } + + + public static object RetargetTypeRefInTypeSigOfValue(object oldValue) + { + if (oldValue == null) + { + return null; + } + string typeName = oldValue.GetType().FullName; + if (oldValue.GetType().IsPrimitive) + { + return oldValue; + } + if (oldValue is string || oldValue is UTF8String) + { + return oldValue; + } + if (oldValue is TypeSig typeSig) + { + return RetargetTypeRefInTypeSig(typeSig); + } + if (oldValue is CAArgument caValue) + { + TypeSig newType = RetargetTypeRefInTypeSig(caValue.Type); + object newValue = RetargetTypeRefInTypeSigOfValue(caValue.Value); + if (newType != caValue.Type || newValue != caValue.Value) + { + return new CAArgument(newType, newValue); + } + return oldValue; + } + if (oldValue is List oldArr) + { + bool anyChange = false; + var newArr = new List(); + foreach (CAArgument oldArg in oldArr) + { + if (TryRetargetTypeRefInArgument(oldArg, out var newArg)) + { + anyChange = true; + newArr.Add(newArg); + } + else + { + newArr.Add(oldArg); + } + } + return anyChange ? newArr : oldArr; + } + throw new NotSupportedException($"type:{oldValue.GetType()} value:{oldValue}"); + } + + + + public static bool TryRetargetTypeRefInArgument(CAArgument oldArg, out CAArgument newArg) + { + TypeSig newType = RetargetTypeRefInTypeSig(oldArg.Type); + object newValue = RetargetTypeRefInTypeSigOfValue(oldArg.Value); + if (newType != oldArg.Type || oldArg.Value != newValue) + { + newArg = new CAArgument(newType, newValue); + return true; + } + newArg = default; + return false; + } + + public static bool TryRetargetTypeRefInNamedArgument(CANamedArgument arg) + { + bool anyChange = false; + TypeSig newType = RetargetTypeRefInTypeSig(arg.Type); + if (newType != arg.Type) + { + anyChange = true; + arg.Type = newType; + } + if (TryRetargetTypeRefInArgument(arg.Argument, out var newArg)) + { + arg.Argument = newArg; + anyChange = true; + } + return anyChange; + } + + //public static bool ContainsContainsGenericParameter1(MethodDef method) + //{ + // Assert.IsTrue(!(method.DeclaringType.ContainsGenericParameter || method.MethodSig.ContainsGenericParameter)); + // return false; + //} + + public static bool ContainsContainsGenericParameter1(MethodSpec methodSpec) + { + if (methodSpec.GenericInstMethodSig.ContainsGenericParameter) + { + return true; + } + IMethodDefOrRef method = methodSpec.Method; + if (method.IsMethodDef) + { + return false;// ContainsContainsGenericParameter1((MethodDef)method); + } + if (method.IsMemberRef) + { + return ContainsContainsGenericParameter1((MemberRef)method); + } + throw new Exception($"unknown method: {method}"); + } + + public static bool ContainsContainsGenericParameter1(MemberRef memberRef) + { + IMemberRefParent parent = memberRef.Class; + if (parent is TypeSpec typeSpec) + { + return typeSpec.ContainsGenericParameter; + } + return false; + } + + public static bool ContainsContainsGenericParameter(IMethod method) + { + Assert.IsTrue(method.IsMethod); + if (method is MethodDef methodDef) + { + return false; + } + + if (method is MethodSpec methodSpec) + { + return ContainsContainsGenericParameter1(methodSpec); + } + if (method is MemberRef memberRef) + { + return ContainsContainsGenericParameter1(memberRef); + } + throw new Exception($"unknown method: {method}"); + } + + + + public static TypeSig Inflate(TypeSig sig, GenericArgumentContext ctx) + { + if (!sig.ContainsGenericParameter) + { + return sig; + } + return ctx.Resolve(sig); + } + + + public static MethodSig InflateMethodSig(MethodSig methodSig, GenericArgumentContext genericArgumentContext) + { + var newReturnType = Inflate(methodSig.RetType, genericArgumentContext); + var newParams = new List(); + foreach (var param in methodSig.Params) + { + newParams.Add(Inflate(param, genericArgumentContext)); + } + var newParamsAfterSentinel = new List(); + if (methodSig.ParamsAfterSentinel != null) + { + throw new NotSupportedException($"methodSig.ParamsAfterSentinel is not supported: {methodSig}"); + //foreach (var param in methodSig.ParamsAfterSentinel) + //{ + // newParamsAfterSentinel.Add(Inflate(param, genericArgumentContext)); + //} + } + return new MethodSig(methodSig.CallingConvention, methodSig.GenParamCount, newReturnType, newParams, null); + } + + public static IList GetGenericArguments(IMemberRefParent type) + { + if (type is TypeDef typeDef) + { + return null; + } + if (type is TypeRef typeRef) + { + return null; + } + if (type is TypeSpec typeSpec) + { + GenericInstSig genericInstSig = typeSpec.TypeSig.ToGenericInstSig(); + return genericInstSig?.GenericArguments; + } + throw new NotSupportedException($"type:{type}"); + } + + public static MethodSig GetInflatedMethodSig(IMethod method) + { + if (method is MethodDef methodDef) + { + return methodDef.MethodSig; + } + if (method is MemberRef memberRef) + { + return InflateMethodSig(memberRef.MethodSig, new GenericArgumentContext(GetGenericArguments(memberRef.Class), null)); + } + if (method is MethodSpec methodSpec) + { + var genericInstMethodSig = methodSpec.GenericInstMethodSig; + if (methodSpec.Method is MethodDef methodDef2) + { + return InflateMethodSig(methodDef2.MethodSig, new GenericArgumentContext(null, genericInstMethodSig.GenericArguments)); + } + if (methodSpec.Method is MemberRef memberRef2) + { + return InflateMethodSig(memberRef2.MethodSig, new GenericArgumentContext(GetGenericArguments(memberRef2.Class), genericInstMethodSig.GenericArguments)); + } + + } + throw new NotSupportedException($" method: {method}"); + } + + public static ThisArgType GetThisArgType(IMethod method) + { + if (!method.MethodSig.HasThis) + { + return ThisArgType.None; + } + if (method is MethodDef methodDef) + { + return methodDef.DeclaringType.IsValueType ? ThisArgType.ValueType : ThisArgType.Class; + } + if (method is MemberRef memberRef) + { + TypeDef typeDef = MetaUtil.GetMemberRefTypeDefParentOrNull(memberRef.Class); + if (typeDef == null) + { + return ThisArgType.Class; + } + return typeDef.IsValueType ? ThisArgType.ValueType : ThisArgType.Class; + } + if (method is MethodSpec methodSpec) + { + return GetThisArgType(methodSpec.Method); + } + throw new NotSupportedException($" method: {method}"); + } + + public static MethodSig ToSharedMethodSig(ICorLibTypes corTypes, MethodSig methodSig) + { + var newReturnType = methodSig.RetType; + var newParams = new List(); + foreach (var param in methodSig.Params) + { + newParams.Add(ToShareTypeSig(corTypes, param)); + } + if (methodSig.ParamsAfterSentinel != null) + { + //foreach (var param in methodSig.ParamsAfterSentinel) + //{ + // newParamsAfterSentinel.Add(ToShareTypeSig(corTypes, param)); + //} + throw new NotSupportedException($"methodSig.ParamsAfterSentinel is not supported: {methodSig}"); + } + return new MethodSig(methodSig.CallingConvention, methodSig.GenParamCount, newReturnType, newParams, null); + } + + public static TypeSig ToShareTypeSig(ICorLibTypes corTypes, TypeSig typeSig) + { + var a = typeSig.RemovePinnedAndModifiers(); + switch (a.ElementType) + { + case ElementType.Void: return corTypes.Void; + case ElementType.Boolean: return corTypes.Byte; + case ElementType.Char: return corTypes.UInt16; + case ElementType.I1: return corTypes.SByte; + case ElementType.U1: return corTypes.Byte; + case ElementType.I2: return corTypes.Int16; + case ElementType.U2: return corTypes.UInt16; + case ElementType.I4: return corTypes.Int32; + case ElementType.U4: return corTypes.UInt32; + case ElementType.I8: return corTypes.Int64; + case ElementType.U8: return corTypes.UInt64; + case ElementType.R4: return corTypes.Single; + case ElementType.R8: return corTypes.Double; + case ElementType.String: return corTypes.Object; + case ElementType.TypedByRef: return corTypes.TypedReference; + case ElementType.I: return corTypes.IntPtr; + case ElementType.U: return corTypes.UIntPtr; + case ElementType.Object: return corTypes.Object; + case ElementType.Sentinel: return typeSig; + case ElementType.Ptr: return corTypes.UIntPtr; + case ElementType.ByRef: return corTypes.UIntPtr; + case ElementType.SZArray: return typeSig; + case ElementType.Array: return typeSig; + case ElementType.ValueType: + { + TypeDef typeDef = a.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{a} definition could not be found"); + } + if (typeDef.IsEnum) + { + return ToShareTypeSig(corTypes, typeDef.GetEnumUnderlyingType()); + } + return typeSig; + } + case ElementType.Var: + case ElementType.MVar: + case ElementType.Class: return corTypes.Object; + case ElementType.GenericInst: + { + var gia = (GenericInstSig)a; + TypeDef typeDef = gia.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{a} definition could not be found"); + } + if (typeDef.IsEnum) + { + return ToShareTypeSig(corTypes, typeDef.GetEnumUnderlyingType()); + } + if (!typeDef.IsValueType) + { + return corTypes.Object; + } + // il2cpp will raise error when try to share generic value type + return typeSig; + //return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => ToShareTypeSig(corTypes, ga)).ToList()); + } + case ElementType.FnPtr: return corTypes.UIntPtr; + case ElementType.ValueArray: return typeSig; + case ElementType.Module: return typeSig; + default: + throw new NotSupportedException(typeSig.ToString()); + } + } + + + public static void AppendIl2CppStackTraceNameOfTypeSig(StringBuilder sb, TypeSig typeSig) + { + typeSig = typeSig.RemovePinnedAndModifiers(); + + switch (typeSig.ElementType) + { + case ElementType.Void: sb.Append("Void"); break; + case ElementType.Boolean: sb.Append("Boolean"); break; + case ElementType.Char: sb.Append("Char"); break; + case ElementType.I1: sb.Append("SByte"); break; + case ElementType.U1: sb.Append("Byte"); break; + case ElementType.I2: sb.Append("Int16"); break; + case ElementType.U2: sb.Append("UInt16"); break; + case ElementType.I4: sb.Append("Int32"); break; + case ElementType.U4: sb.Append("UInt32"); break; + case ElementType.I8: sb.Append("Int64"); break; + case ElementType.U8: sb.Append("UInt64"); break; + case ElementType.R4: sb.Append("Single"); break; + case ElementType.R8: sb.Append("Double"); break; + case ElementType.String: sb.Append("String"); break; + case ElementType.Ptr: AppendIl2CppStackTraceNameOfTypeSig(sb, typeSig.Next); sb.Append("*"); break; + case ElementType.ByRef: AppendIl2CppStackTraceNameOfTypeSig(sb, typeSig.Next); sb.Append("&"); break; + case ElementType.ValueType: + case ElementType.Class: + { + var classOrValueTypeSig = (ClassOrValueTypeSig)typeSig; + TypeDef typeDef = classOrValueTypeSig.TypeDefOrRef.ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{classOrValueTypeSig} definition could not be found"); + } + sb.Append(typeDef.Name); + break; + } + case ElementType.GenericInst: + { + var genericInstSig = (GenericInstSig)typeSig; + AppendIl2CppStackTraceNameOfTypeSig(sb, genericInstSig.GenericType); + break; + } + case ElementType.Var: + case ElementType.MVar: + { + var varSig = (GenericSig)typeSig; + sb.Append(varSig.GenericParam.Name); + break; + } + case ElementType.I: sb.Append("IntPtr"); break; + case ElementType.U: sb.Append("UIntPtr"); break; + case ElementType.FnPtr: sb.Append("IntPtr"); break; + case ElementType.Object: sb.Append("Object"); break; + case ElementType.SZArray: + { + var szArraySig = (SZArraySig)typeSig; + AppendIl2CppStackTraceNameOfTypeSig(sb, szArraySig.Next); + sb.Append("[]"); + break; + } + default: + throw new NotSupportedException(typeSig.ToString()); + } + } + + public static TypeDef GetRootDeclaringType(TypeDef type) + { + TypeDef cur = type; + while (true) + { + TypeDef declaringType = cur.DeclaringType; + if (declaringType == null) + { + return cur; + } + cur = declaringType; + } + } + + public static string CreateMethodDefIl2CppStackTraceSignature(MethodDef method) + { + var result = new StringBuilder(); + TypeDef declaringType = method.DeclaringType; + + string namespaze = GetRootDeclaringType(declaringType).Namespace; + if (!string.IsNullOrEmpty(namespaze)) + { + result.Append(namespaze); + result.Append("."); + } + result.Append(declaringType.Name); + result.Append(":"); + result.Append(method.Name); + result.Append("("); + + int index = 0; + foreach (TypeSig p in method.GetParams()) + { + if (index > 0) + { + result.Append(", "); + } + AppendIl2CppStackTraceNameOfTypeSig(result, p); + ++index; + } + result.Append(")"); + return result.ToString(); + } + + public static bool HasObfuzIgnoreAttribute(IHasCustomAttribute obj) + { + return obj.CustomAttributes.Any(ca => ca.AttributeType.FullName == "Obfuz.ObfuzIgnoreAttribute"); + } + + public static bool HasCompilerGeneratedAttribute(IHasCustomAttribute obj) + { + return obj.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute"); + } + + public static bool HasEncryptFieldAttribute(IHasCustomAttribute obj) + { + return obj.CustomAttributes.Any(ca => ca.AttributeType.FullName == "Obfuz.EncryptFieldAttribute"); + } + + public static bool HasObfuzIgnoreAttributeInSelfOrParent(TypeDef typeDef) + { + while (true) + { + if (HasObfuzIgnoreAttribute(typeDef)) + { + return true; + } + typeDef = typeDef.DeclaringType; + if (typeDef == null) + { + return false; + } + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs.meta new file mode 100644 index 0000000..9ebbd42 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ded544371a7eb524caa1ccef3daebe55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs b/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs new file mode 100644 index 0000000..ea06a66 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs @@ -0,0 +1,41 @@ +using System.Text.RegularExpressions; + +namespace Obfuz.Utils +{ + public class NameMatcher + { + private readonly string _str; + private readonly Regex _regex; + + public string NameOrPattern => _str; + + public NameMatcher(string nameOrPattern) + { + if (string.IsNullOrEmpty(nameOrPattern)) + { + nameOrPattern = "*"; + } + _str = nameOrPattern; + _regex = nameOrPattern.Contains("*") || nameOrPattern.Contains("?") ? new Regex(WildcardToRegex(nameOrPattern)) : null; + } + + public static string WildcardToRegex(string pattern) + { + return "^" + Regex.Escape(pattern). + Replace("\\*", ".*"). + Replace("\\?", ".") + "$"; + } + + public bool IsMatch(string name) + { + if (_regex != null) + { + return _regex.IsMatch(name); + } + else + { + return _str == name; + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs.meta new file mode 100644 index 0000000..6abe4e1 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3a646fe086ecbd4a8dbf36a395ada71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs b/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs new file mode 100644 index 0000000..90d55e0 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs @@ -0,0 +1,14 @@ +namespace Obfuz.Utils +{ + public class NumberRange where T : struct + { + public readonly T? min; + public readonly T? max; + + public NumberRange(T? min, T? max) + { + this.min = min; + this.max = max; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs.meta new file mode 100644 index 0000000..f1b17e1 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d147a6853ce57c4d88529fb73823435 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs b/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs new file mode 100644 index 0000000..11c0f47 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.Utils +{ + public class PathAssemblyResolver : AssemblyResolverBase + { + private readonly string[] _searchPaths; + + public PathAssemblyResolver(params string[] searchPaths) + { + _searchPaths = searchPaths; + } + + public override string ResolveAssembly(string assemblyName) + { + foreach(var path in _searchPaths) + { + string assPath = Path.Combine(path, assemblyName + ".dll"); + if (File.Exists(assPath)) + { + //Debug.Log($"resolve {assemblyName} at {assPath}"); + return assPath; + } + } + return null; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs.meta new file mode 100644 index 0000000..1ae9c58 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a7681737885f604e885ee39d0bedd74 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs b/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs new file mode 100644 index 0000000..3a38d23 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; + +namespace Obfuz.Utils +{ + public static class PlatformUtil + { + public static bool IsMonoBackend() + { + return PlayerSettings.GetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup) + == ScriptingImplementation.Mono2x; + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs.meta new file mode 100644 index 0000000..ec195e6 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 85d01014c084c56498d292d3b16351d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs b/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs new file mode 100644 index 0000000..27d1001 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs @@ -0,0 +1,58 @@ +using Obfuz.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public class RandomWithKey : IRandom + { + private const long a = 1664525; + private const long c = 1013904223; + private const long m = 4294967296; // 2^32 + + private readonly int[] _key; + + private int _nextIndex; + + private int _seed; + + public RandomWithKey(int[] key, int seed) + { + _key = key; + _seed = seed; + } + + public int NextInt(int min, int max) + { + return min + NextInt(max - min); + } + + public int NextInt(int max) + { + return (int)((uint)NextInt() % (uint)max); + } + + private int GetNextSalt() + { + if (_nextIndex >= _key.Length) + { + _nextIndex = 0; + } + return _key[_nextIndex++]; + } + + public int NextInt() + { + _seed = (int)((a * _seed + c) % m); + return _seed ^ GetNextSalt(); + } + + public long NextLong() + { + return ((long)NextInt() << 32) | (uint)NextInt(); + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs.meta new file mode 100644 index 0000000..41b7f75 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e16d7eb75fe2354d96eca5bb01358a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs b/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs new file mode 100644 index 0000000..3f008cc --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs @@ -0,0 +1,9 @@ +namespace Obfuz.Utils +{ + public enum ThisArgType + { + None, + ValueType, + Class, + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs.meta new file mode 100644 index 0000000..c73587a --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d5a6303cdb66374f95187ca31b5e82f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs b/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs new file mode 100644 index 0000000..11c722f --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs @@ -0,0 +1,198 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.Utils +{ + public static class TypeSigUtil + { + public static string ComputeTypeDefSignature(TypeDef type) + { + return type.FullName; + } + + public static string ComputeMethodDefSignature(MethodDef method) + { + var result = new StringBuilder(); + ComputeTypeSigName(method.MethodSig.RetType, result); + result.Append(" "); + result.Append(method.DeclaringType.FullName); + result.Append("::"); + result.Append(method.Name); + if (method.HasGenericParameters) + { + result.Append($"`{method.GenericParameters.Count}"); + } + result.Append("("); + for (int i = 0; i < method.Parameters.Count; i++) + { + if (i > 0) + { + result.Append(", "); + } + ComputeTypeSigName(method.Parameters[i].Type, result); + } + result.Append(")"); + return result.ToString(); + } + + public static string ComputeFieldDefSignature(FieldDef field) + { + var result = new StringBuilder(); + ComputeTypeSigName(field.FieldSig.Type, result); + result.Append(" "); + result.Append(field.Name); + return result.ToString(); + } + + public static string ComputePropertyDefSignature(PropertyDef property) + { + var result = new StringBuilder(); + ComputeTypeSigName(property.PropertySig.RetType, result); + result.Append(" "); + result.Append(property.Name); + return result.ToString(); + } + + public static string ComputeEventDefSignature(EventDef eventDef) + { + var result = new StringBuilder(); + ComputeTypeSigName(eventDef.EventType.ToTypeSig(), result); + result.Append(" "); + result.Append(eventDef.Name); + return result.ToString(); + } + + public static string ComputeMethodSpecSignature(TypeSig type) + { + var sb = new StringBuilder(); + ComputeTypeSigName(type, sb); + return sb.ToString(); + } + + public static void ComputeTypeSigName(TypeSig type, StringBuilder result) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.Void: result.Append("void"); break; + case ElementType.Boolean: result.Append("bool"); break; + case ElementType.Char: result.Append("char"); break; + case ElementType.I1: result.Append("sbyte"); break; + case ElementType.U1: result.Append("byte"); break; + case ElementType.I2: result.Append("short"); break; + case ElementType.U2: result.Append("ushort"); break; + case ElementType.I4: result.Append("int"); break; + case ElementType.U4: result.Append("uint"); break; + case ElementType.I8: result.Append("long"); break; + case ElementType.U8: result.Append("ulong"); break; + case ElementType.R4: result.Append("float"); break; + case ElementType.R8: result.Append("double"); break; + case ElementType.String: result.Append("string"); break; + case ElementType.Ptr: + ComputeTypeSigName(((PtrSig)type).Next, result); + result.Append("*"); + break; + case ElementType.ByRef: + ComputeTypeSigName(((ByRefSig)type).Next, result); + result.Append("&"); + break; + case ElementType.ValueType: + case ElementType.Class: + { + var valueOrClassType = type.ToClassOrValueTypeSig(); + var typeDef = valueOrClassType.ToTypeDefOrRef().ResolveTypeDefThrow(); + if (typeDef.Module.IsCoreLibraryModule != true) + { + result.Append($"[{typeDef.Module.Assembly.Name}]"); + } + result.Append(typeDef.FullName); + break; + } + case ElementType.GenericInst: + { + var genInst = (GenericInstSig)type; + ComputeTypeSigName(genInst.GenericType, result); + result.Append("<"); + for (int i = 0; i < genInst.GenericArguments.Count; i++) + { + if (i > 0) + { + result.Append(","); + } + ComputeTypeSigName(genInst.GenericArguments[i], result); + } + result.Append(">"); + break; + } + case ElementType.SZArray: + ComputeTypeSigName(((SZArraySig)type).Next, result); + result.Append("[]"); + break; + case ElementType.Array: + { + var arraySig = (ArraySig)type; + ComputeTypeSigName(arraySig.Next, result); + result.Append("["); + for (int i = 0; i < arraySig.Rank; i++) + { + if (i > 0) + { + result.Append(","); + } + //result.Append(arraySig.Sizes[i]); + } + result.Append("]"); + break; + } + case ElementType.FnPtr: + { + var fnPtr = (FnPtrSig)type; + result.Append("("); + MethodSig ms = fnPtr.MethodSig; + ComputeTypeSigName(ms.RetType, result); + result.Append("("); + for (int i = 0; i < ms.Params.Count; i++) + { + if (i > 0) + { + result.Append(","); + } + ComputeTypeSigName(ms.Params[i], result); + } + result.Append(")*"); + break; + } + case ElementType.TypedByRef: + result.Append("typedref"); + break; + case ElementType.I: + result.Append("nint"); + break; + case ElementType.U: + result.Append("nuint"); + break; + case ElementType.Object: + result.Append("object"); + break; + case ElementType.Var: + { + var var = (GenericVar)type; + result.Append($"!{var.Number}"); + break; + } + case ElementType.MVar: + { + var mvar = (GenericMVar)type; + result.Append($"!!{mvar.Number}"); + break; + } + default: throw new NotSupportedException($"[ComputeTypeSigName] not support :{type}"); + + } + } + } +} diff --git a/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs.meta b/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs.meta new file mode 100644 index 0000000..fd92512 --- /dev/null +++ b/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 83e1214102577b449a933438c41c97bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/LICENSE b/com.code-philosophy.obfuz/LICENSE new file mode 100644 index 0000000..093e599 --- /dev/null +++ b/com.code-philosophy.obfuz/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Code Philosophy(代码哲学) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/com.code-philosophy.obfuz/LICENSE.meta b/com.code-philosophy.obfuz/LICENSE.meta new file mode 100644 index 0000000..3bb31e6 --- /dev/null +++ b/com.code-philosophy.obfuz/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4431597180c05fb46839ded925d40a19 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Plugins.meta b/com.code-philosophy.obfuz/Plugins.meta new file mode 100644 index 0000000..c04aee9 --- /dev/null +++ b/com.code-philosophy.obfuz/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f98a5dd310a82a64c9cc45cc139773dc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Plugins/dnlib.dll b/com.code-philosophy.obfuz/Plugins/dnlib.dll new file mode 100644 index 0000000..ba1f36d Binary files /dev/null and b/com.code-philosophy.obfuz/Plugins/dnlib.dll differ diff --git a/com.code-philosophy.obfuz/Plugins/dnlib.dll.meta b/com.code-philosophy.obfuz/Plugins/dnlib.dll.meta new file mode 100644 index 0000000..5bde396 --- /dev/null +++ b/com.code-philosophy.obfuz/Plugins/dnlib.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 355f9a0beb9535e4792b15e532d17460 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime.meta b/com.code-philosophy.obfuz/Runtime.meta new file mode 100644 index 0000000..a93405f --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cd1451b864839ad41b5463274b1d171c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/ConstUtility.cs b/com.code-philosophy.obfuz/Runtime/ConstUtility.cs new file mode 100644 index 0000000..467cc2b --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/ConstUtility.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Obfuz +{ + public static class ConstUtility + { + public static int GetInt(byte[] data, int offset) + { + return BitConverter.ToInt32(data, offset); + } + + public static long GetLong(byte[] data, int offset) + { + return BitConverter.ToInt64(data, offset); + } + + public static float GetFloat(byte[] data, int offset) + { + return BitConverter.ToSingle(data, offset); + } + + public static double GetDouble(byte[] data, int offset) + { + return BitConverter.ToDouble(data, offset); + } + + public static string GetString(byte[] data, int offset, int length) + { + return Encoding.UTF8.GetString(data, offset, length); + } + + public static byte[] GetBytes(byte[] data, int offset, int length) + { + byte[] result = new byte[length]; + Array.Copy(data, offset, result, 0, length); + return result; + } + + public static int[] GetInts(byte[] data, int offset, int byteLength) + { + Assert.IsTrue(byteLength % 4 == 0); + int[] result = new int[byteLength >> 2]; + Buffer.BlockCopy(data, offset, result, 0, byteLength); + return result; + } + + public static void InitializeArray(Array array, byte[] data, int offset, int length) + { + Buffer.BlockCopy(data, offset, array, 0, length); + } + + public static unsafe int CastFloatAsInt(float value) + { + int* intValue = (int*)&value; + return *intValue; + } + + public static unsafe float CastIntAsFloat(int value) + { + float* floatValue = (float*)&value; + return *floatValue; + } + + public static unsafe long CastDoubleAsLong(double value) + { + long* longValue = (long*)&value; + return *longValue; + } + + public static unsafe double CastLongAsDouble(long value) + { + double* doubleValue = (double*)&value; + return *doubleValue; + } + } +} diff --git a/com.code-philosophy.obfuz/Runtime/ConstUtility.cs.meta b/com.code-philosophy.obfuz/Runtime/ConstUtility.cs.meta new file mode 100644 index 0000000..f474205 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/ConstUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 48bd592d1a1339643be1fafe4b97c941 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs b/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs new file mode 100644 index 0000000..cf8eecb --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz +{ + [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + public class EncryptFieldAttribute : Attribute + { + } +} diff --git a/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs.meta b/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs.meta new file mode 100644 index 0000000..a76ea9d --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30f22110938816d4cb7e9cc9a176fd1e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs b/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs new file mode 100644 index 0000000..fe215ef --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs @@ -0,0 +1,31 @@ +namespace Obfuz +{ + public interface IEncryptionScope + { + + } + + public abstract class EncryptionScopeBase : IEncryptionScope + { + public void ForcePreserveAOT() + { + EncryptionService.Encrypt(0, 0, 0); + } + } + + public struct DefaultDynamicEncryptionScope : IEncryptionScope + { + public void ForcePreserveAOT() + { + EncryptionService.Encrypt(0, 0, 0); + } + } + + public struct DefaultStaticEncryptionScope: IEncryptionScope + { + public void ForcePreserveAOT() + { + EncryptionService.Encrypt(0, 0, 0); + } + } +} diff --git a/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs.meta b/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs.meta new file mode 100644 index 0000000..064f875 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d729fe7cb7d0bc43a69f1ba09f99061 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/EncryptionService.cs b/com.code-philosophy.obfuz/Runtime/EncryptionService.cs new file mode 100644 index 0000000..ccd0cea --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/EncryptionService.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz +{ + + public static class EncryptionService where T : IEncryptionScope + { + // for compatibility with Mono because Mono will raise FieldAccessException when try access private field + public static IEncryptor _encryptor; + + public static IEncryptor Encryptor + { + get => _encryptor; + set { _encryptor = value; } + } + + public static void EncryptBlock(byte[] data, int ops, int salt) + { + _encryptor.EncryptBlock(data, ops, salt); + } + + public static void DecryptBlock(byte[] data, int ops, int salt) + { + _encryptor.DecryptBlock(data, ops, salt); + } + + public static int Encrypt(int value, int opts, int salt) + { + return _encryptor.Encrypt(value, opts, salt); + } + + public static int Decrypt(int value, int opts, int salt) + { + return _encryptor.Decrypt(value, opts, salt); + } + + public static long Encrypt(long value, int opts, int salt) + { + return _encryptor.Encrypt(value, opts, salt); + } + + public static long Decrypt(long value, int opts, int salt) + { + return _encryptor.Decrypt(value, opts, salt); + } + + public static float Encrypt(float value, int opts, int salt) + { + return _encryptor.Encrypt(value, opts, salt); + } + + public static float Decrypt(float value, int opts, int salt) + { + return _encryptor.Decrypt(value, opts, salt); + } + + public static double Encrypt(double value, int opts, int salt) + { + return _encryptor.Encrypt(value, opts, salt); + } + + public static double Decrypt(double value, int opts, int salt) + { + return _encryptor.Decrypt(value, opts, salt); + } + + public static byte[] Encrypt(byte[] value, int offset, int length, int opts, int salt) + { + return _encryptor.Encrypt(value, offset, length, opts, salt); + } + + public static byte[] Decrypt(byte[] value, int offset, int byteLength, int ops, int salt) + { + return _encryptor.Decrypt(value, offset, byteLength, ops, salt); + } + + public static byte[] Encrypt(string value, int ops, int salt) + { + return _encryptor.Encrypt(value, ops, salt); + } + + public static string DecryptString(byte[] value, int offset, int stringBytesLength, int ops, int salt) + { + return _encryptor.DecryptString(value, offset, stringBytesLength, ops, salt); + } + + + public static int DecryptFromRvaInt(byte[] data, int offset, int ops, int salt) + { + int encryptedValue = ConstUtility.GetInt(data, offset); + return Decrypt(encryptedValue, ops, salt); + } + + public static long DecryptFromRvaLong(byte[] data, int offset, int ops, int salt) + { + long encryptedValue = ConstUtility.GetLong(data, offset); + return Decrypt(encryptedValue, ops, salt); + } + + public static float DecryptFromRvaFloat(byte[] data, int offset, int ops, int salt) + { + float encryptedValue = ConstUtility.GetFloat(data, offset); + return Decrypt(encryptedValue, ops, salt); + } + + public static double DecryptFromRvaDouble(byte[] data, int offset, int ops, int salt) + { + double encryptedValue = ConstUtility.GetDouble(data, offset); + return Decrypt(encryptedValue, ops, salt); + } + + public static string DecryptFromRvaString(byte[] data, int offset, int length, int ops, int salt) + { + return DecryptString(data, offset, length, ops, salt); + } + + public static byte[] DecryptFromRvaBytes(byte[] data, int offset, int bytesLength, int ops, int salt) + { + return Decrypt(data, offset, bytesLength, ops, salt); + } + } +} diff --git a/com.code-philosophy.obfuz/Runtime/EncryptionService.cs.meta b/com.code-philosophy.obfuz/Runtime/EncryptionService.cs.meta new file mode 100644 index 0000000..d7364ef --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/EncryptionService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbbeb7501a0d84542828cb1aa7103d1b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs b/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs new file mode 100644 index 0000000..007e4b9 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs @@ -0,0 +1,258 @@ +using JetBrains.Annotations; +using System; +using System.Text; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.Assertions; + +namespace Obfuz +{ + public abstract class EncryptorBase : IEncryptor + { + public abstract int OpCodeCount { get; } + + public static int[] ConvertToIntKey(byte[] key) + { + Assert.AreEqual(0, key.Length % 4); + int align4Length = key.Length / 4; + int[] intKey = new int[align4Length]; + Buffer.BlockCopy(key, 0, intKey, 0, key.Length); + return intKey; + } + + public abstract int Encrypt(int value, int opts, int salt); + public abstract int Decrypt(int value, int opts, int salt); + + public virtual long Encrypt(long value, int opts, int salt) + { + int low = (int)value; + int high = (int)(value >> 32); + int encryptedLow = Encrypt(low, opts, salt); + int encryptedHigh = Encrypt(high, opts, salt); + return ((long)encryptedHigh << 32) | (uint)encryptedLow; + } + + public virtual long Decrypt(long value, int opts, int salt) + { + int low = (int)value; + int high = (int)(value >> 32); + int decryptedLow = Decrypt(low, opts, salt); + int decryptedHigh = Decrypt(high, opts, salt); + return ((long)decryptedHigh << 32) | (uint)decryptedLow; + } + + public virtual unsafe float Encrypt(float value, int opts, int salt) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + return value; + } + ref int intValue = ref *(int*)&value; + int xorValue = ((1 << 23) - 1) & Decrypt(0xABCD, opts, salt); + intValue ^= xorValue; + return value; + } + + public virtual unsafe float Decrypt(float value, int opts, int salt) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + return value; + } + ref int intValue = ref *(int*)&value; + int xorValue = ((1 << 23) - 1) & Decrypt(0xABCD, opts, salt); + intValue ^= xorValue; + return value; + } + + public virtual unsafe double Encrypt(double value, int opts, int salt) + { + if (double.IsNaN(value) || double.IsInfinity(value)) + { + return value; + } + ref long longValue = ref *(long*)&value; + long xorValue = ((1L << 52) - 1) & Decrypt(0xAABBCCDDL, opts, salt); + longValue ^= xorValue; + return value; + } + + public virtual unsafe double Decrypt(double value, int opts, int salt) + { + if (double.IsNaN(value) || double.IsInfinity(value)) + { + return value; + } + ref long longValue = ref *(long*)&value; + long xorValue = ((1L << 52) - 1) & Decrypt(0xAABBCCDDL, opts, salt); + longValue ^= xorValue; + return value; + } + + public virtual unsafe byte[] Encrypt(byte[] value, int offset, int length, int ops, int salt) + { + if (length == 0) + { + return Array.Empty(); + } + + var encryptedBytes = new byte[length]; + int intArrLength = length >> 2; + + // align to 4 + if ((offset & 0x3) != 0) + { + Buffer.BlockCopy(value, offset, encryptedBytes, 0, length); + + // encrypt int + + fixed (byte* dstBytePtr = &encryptedBytes[0]) + { + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + last ^= Encrypt(dstIntPtr[i], ops, salt); + dstIntPtr[i] = last; + } + } + for (int i = intArrLength * 4; i < length; i++) + { + encryptedBytes[i] = (byte)(encryptedBytes[i] ^ salt); + } + } + else + { + // encrypt int + fixed (byte* srcBytePtr = &value[offset]) + { + fixed (byte* dstBytePtr = &encryptedBytes[0]) + { + int* srcIntPtr = (int*)srcBytePtr; + int* dstIntPtr = (int*)dstBytePtr; + + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + last ^= Encrypt(srcIntPtr[i], ops, salt); + dstIntPtr[i] = last; + } + } + } + for (int i = intArrLength * 4; i < length; i++) + { + encryptedBytes[i] = (byte)(value[offset + i] ^ salt); + } + } + return encryptedBytes; + } + + public unsafe virtual byte[] Decrypt(byte[] value, int offset, int length, int ops, int salt) + { + var decryptedBytes = new byte[length]; + int intArrLength = length >> 2; + + // align to 4 + if ((offset & 0x3) != 0) + { + Buffer.BlockCopy(value, offset, decryptedBytes, 0, length); + + // encrypt int + + fixed (byte* dstBytePtr = &decryptedBytes[0]) + { + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + int oldLast = last; + last = dstIntPtr[i]; + dstIntPtr[i] = Decrypt(last ^ oldLast, ops, salt); + } + } + for (int i = intArrLength * 4; i < length; i++) + { + decryptedBytes[i] = (byte)(decryptedBytes[i] ^ salt); + } + } + else + { + // encrypt int + fixed (byte* srcBytePtr = &value[offset]) + { + fixed (byte* dstBytePtr = &decryptedBytes[0]) + { + int* srcIntPtr = (int*)srcBytePtr; + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + int oldLast = last; + last = srcIntPtr[i]; + dstIntPtr[i] = Decrypt(last ^ oldLast, ops, salt); + } + } + } + for (int i = intArrLength * 4; i < length; i++) + { + decryptedBytes[i] = (byte)(value[offset + i] ^ salt); + } + } + return decryptedBytes; + } + + public virtual byte[] Encrypt(string value, int ops, int salt) + { + byte[] bytes = Encoding.UTF8.GetBytes(value); + return Encrypt(bytes, 0, bytes.Length, ops, salt); + } + + public virtual string DecryptString(byte[] value, int offset, int length, int ops, int salt) + { + byte[] bytes = Decrypt(value, offset, length, ops, salt); + return Encoding.UTF8.GetString(bytes); + } + + public virtual unsafe void EncryptBlock(byte[] data, int ops, int salt) + { + int length = data.Length; + int intArrLength = length >> 2; + + fixed (byte* dstBytePtr = &data[0]) + { + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + last ^= Encrypt(dstIntPtr[i], ops, salt); + dstIntPtr[i] = last; + } + } + for (int i = intArrLength * 4; i < length; i++) + { + data[i] = (byte)(data[i] ^ salt); + } + } + + public virtual unsafe void DecryptBlock(byte[] data, int ops, int salt) + { + int length = data.Length; + int intArrLength = length >> 2; + + fixed (byte* dstBytePtr = &data[0]) + { + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + int oldLast = last; + last = dstIntPtr[i]; + dstIntPtr[i] = Decrypt(oldLast ^ last, ops, salt); + } + } + for (int i = intArrLength * 4; i < length; i++) + { + data[i] = (byte)(data[i] ^ salt); + } + } + } +} diff --git a/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs.meta b/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs.meta new file mode 100644 index 0000000..e81d521 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1d4c5725e7ad624ba8e55ecb63bb440 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/IEncryptor.cs b/com.code-philosophy.obfuz/Runtime/IEncryptor.cs new file mode 100644 index 0000000..0d8fdaa --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/IEncryptor.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Obfuz +{ + public interface IEncryptor + { + int OpCodeCount { get; } + + void EncryptBlock(byte[] data, int ops, int salt); + void DecryptBlock(byte[] data, int ops, int salt); + + int Encrypt(int value, int opts, int salt); + int Decrypt(int value, int opts, int salt); + + long Encrypt(long value, int opts, int salt); + long Decrypt(long value, int opts, int salt); + + float Encrypt(float value, int opts, int salt); + float Decrypt(float value, int opts, int salt); + + double Encrypt(double value, int opts, int salt); + double Decrypt(double value, int opts, int salt); + + byte[] Encrypt(byte[] value, int offset, int length, int opts, int salt); + byte[] Decrypt(byte[] value, int offset, int byteLength, int ops, int salt); + + byte[] Encrypt(string value, int ops, int salt); + string DecryptString(byte[] value, int offset, int stringBytesLength, int ops, int salt); + } +} diff --git a/com.code-philosophy.obfuz/Runtime/IEncryptor.cs.meta b/com.code-philosophy.obfuz/Runtime/IEncryptor.cs.meta new file mode 100644 index 0000000..4141821 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/IEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3078fa59ff0af6b4cbbee25e20bc41c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs b/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs new file mode 100644 index 0000000..ec28f36 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz +{ + public class NullEncryptor : EncryptorBase + { + private readonly byte[] _key; + + public override int OpCodeCount => 256; + + public NullEncryptor(byte[] key) + { + _key = key; + } + + public override int Encrypt(int value, int opts, int salt) + { + return value; + } + + public override int Decrypt(int value, int opts, int salt) + { + return value; + } + + public override long Encrypt(long value, int opts, int salt) + { + return value; + } + + public override long Decrypt(long value, int opts, int salt) + { + return value; + } + + public override float Encrypt(float value, int opts, int salt) + { + return value; + } + + public override float Decrypt(float value, int opts, int salt) + { + return value; + } + + public override double Encrypt(double value, int opts, int salt) + { + return value; + } + + public override double Decrypt(double value, int opts, int salt) + { + return value; + } + + public override byte[] Encrypt(byte[] value, int offset, int length, int opts, int salt) + { + if (length == 0) + { + return Array.Empty(); + } + var encryptedBytes = new byte[length]; + Buffer.BlockCopy(value, offset, encryptedBytes, 0, length); + return encryptedBytes; + } + + public override byte[] Decrypt(byte[] value, int offset, int length, int ops, int salt) + { + if (length == 0) + { + return Array.Empty(); + } + byte[] byteArr = new byte[length]; + Buffer.BlockCopy(value, 0, byteArr, 0, length); + return byteArr; + } + + public override byte[] Encrypt(string value, int ops, int salt) + { + return Encoding.UTF8.GetBytes(value); + } + + public override string DecryptString(byte[] value, int offset, int length, int ops, int salt) + { + return Encoding.UTF8.GetString(value, offset, length); + } + } +} diff --git a/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs.meta b/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs.meta new file mode 100644 index 0000000..b490d29 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c53481f2ec513be4783a5ae2f76dc6e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef b/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef new file mode 100644 index 0000000..4ee1d16 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Obfuz.Runtime", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef.meta b/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef.meta new file mode 100644 index 0000000..4868da2 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4140bd2e2764f1f47ab93125ecb61942 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs b/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs new file mode 100644 index 0000000..90d2b17 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz +{ + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)] + public class ObfuzIgnoreAttribute : Attribute + { + } +} diff --git a/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs.meta b/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs.meta new file mode 100644 index 0000000..bc8fb89 --- /dev/null +++ b/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c2b4cf04729157b4dab504167ab5f703 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz/package.json b/com.code-philosophy.obfuz/package.json new file mode 100644 index 0000000..6af9393 --- /dev/null +++ b/com.code-philosophy.obfuz/package.json @@ -0,0 +1,21 @@ +{ + "name": "com.code-philosophy.obfuz", + "version": "1.0.0-alpha", + "displayName": "Obfuz", + "description": "Obfuz is an open-source Unity code obfuscation tool designed to provide Unity developers with a powerful, secure, and user-friendly code protection solution.", + "category": "Editor", + "documentationUrl": "https://www.obfuz.com", + "changelogUrl": "https://github.com/focus-creative-games/obfuz/commits/main/", + "licensesUrl": "https://github.com/focus-creative-games/obfuz/blob/main/LICENSE", + "keywords": [ + "obfuscation", + "obfuscator", + "confuser", + "code-philosophy" + ], + "author": { + "name": "Code Philosophy", + "email": "obfuz@code-philosophy.com", + "url": "https://code-philosophy.com" + } +} \ No newline at end of file diff --git a/com.code-philosophy.obfuz/package.json.meta b/com.code-philosophy.obfuz/package.json.meta new file mode 100644 index 0000000..74168f7 --- /dev/null +++ b/com.code-philosophy.obfuz/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 63433d029d2e08c46abd56175e308a15 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz4hybridclr/Editor.meta b/com.code-philosophy.obfuz4hybridclr/Editor.meta new file mode 100644 index 0000000..ec94239 --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 067341936b8cb2242be3bdc83f3ca3cd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs b/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs new file mode 100644 index 0000000..ea52e32 --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs @@ -0,0 +1,82 @@ +using HybridCLR.Editor; +using Obfuz.Settings; +using Obfuz; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using HybridCLR.Editor.Commands; +using HybridCLR.Editor.Installer; +using System.IO; +using HybridCLR.Editor.ABI; +using UnityEngine; + +namespace Obfuz4HybridCLR +{ + public static class ObfuscateUtil + { + public static bool AreSameDirectory(string path1, string path2) + { + try + { + var dir1 = new DirectoryInfo(path1); + var dir2 = new DirectoryInfo(path2); + + // 比较完整路径(考虑符号链接) + return dir1.FullName.TrimEnd('\\') == dir2.FullName.TrimEnd('\\'); + } + catch + { + return false; + } + } + + public static void CompileAndObfuscateHotUpdateAssemblies(BuildTarget target) + { + string hotUpdateDllPath = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target); + BashUtil.RemoveDir(hotUpdateDllPath); + CompileDllCommand.CompileDll(target); + var assemblySearchPaths = new List + { + SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target), + }; + Obfuscate(target, assemblySearchPaths, hotUpdateDllPath); + } + + public static void Obfuscate(BuildTarget target, List assemblySearchPaths, string outputPath) + { + var obfuzSettings = ObfuzSettings.Instance; + + var assemblySearchDirs = assemblySearchPaths; + ObfuscatorBuilder builder = ObfuscatorBuilder.FromObfuzSettings(obfuzSettings, target, true); + builder.InsertTopPriorityAssemblySearchPaths(assemblySearchDirs); + + string obfuscatedAssemblyOutputPath = obfuzSettings.GetObfuscatedAssemblyOutputPath(target); + if (AreSameDirectory(outputPath, obfuscatedAssemblyOutputPath)) + { + throw new Exception($"outputPath:{outputPath} can't be same to ObfuscatedAssemblyOutputPath:{obfuscatedAssemblyOutputPath}"); + } + foreach (var assemblySearchDir in builder.AssemblySearchPaths) + { + if (AreSameDirectory(assemblySearchDir, obfuscatedAssemblyOutputPath)) + { + throw new Exception($"assemblySearchDir:{assemblySearchDir} can't be same to ObfuscatedAssemblyOutputPath:{obfuscatedAssemblyOutputPath}"); + } + } + + Obfuscator obfuz = builder.Build(); + obfuz.Run(); + + Directory.CreateDirectory(outputPath); + foreach (string srcFile in Directory.GetFiles(obfuscatedAssemblyOutputPath, "*.dll")) + { + string fileName = Path.GetFileName(srcFile); + string destFile = $"{outputPath}/{fileName}"; + File.Copy(srcFile, destFile, true); + Debug.Log($"Copy {srcFile} to {destFile}"); + } + } + } +} diff --git a/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs.meta b/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs.meta new file mode 100644 index 0000000..3702fdd --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7f5fe18513bcdd4c8960d908e88402e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef b/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef new file mode 100644 index 0000000..2af5cd5 --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Obfuz4HybridCLR.Editor", + "rootNamespace": "", + "references": [ + "GUID:2373f786d14518f44b0f475db77ba4de", + "GUID:66e09fc524ec6594b8d6ca1d91aa1a41" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef.meta b/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef.meta new file mode 100644 index 0000000..cc375a6 --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3743e71edcd5bd8499007797ef02cbfb +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs b/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs new file mode 100644 index 0000000..95a115d --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs @@ -0,0 +1,38 @@ +using HybridCLR.Editor.Commands; +using HybridCLR.Editor; +using Obfuz.Settings; +using Obfuz; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using System.Reflection; +using System; +using System.IO; +using HybridCLR.Editor.Link; +using HybridCLR.Editor.Meta; +using UnityEditor.Build; +using HybridCLR.Editor.Installer; + +namespace Obfuz4HybridCLR +{ + public static class PrebuildCommandExt + { + [MenuItem("HybridCLR/ObfuzExtension/GenerateAll")] + public static void GenerateAll() + { + var installer = new InstallerController(); + if (!installer.HasInstalledHybridCLR()) + { + throw new BuildFailedException($"You have not initialized HybridCLR, please install it via menu 'HybridCLR/Installer'"); + } + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + ObfuscateUtil.CompileAndObfuscateHotUpdateAssemblies(target); + Il2CppDefGeneratorCommand.GenerateIl2CppDef(); + LinkGeneratorCommand.GenerateLinkXml(target); + StripAOTDllCommand.GenerateStripedAOTDlls(target); + MethodBridgeGeneratorCommand.GenerateMethodBridgeAndReversePInvokeWrapper(target); + AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target); + } + } +} diff --git a/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs.meta b/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs.meta new file mode 100644 index 0000000..2bcd1c7 --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afc965e1afdfc8e47b8a70be7a93cf25 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz4hybridclr/LICENSE b/com.code-philosophy.obfuz4hybridclr/LICENSE new file mode 100644 index 0000000..093e599 --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Code Philosophy(代码哲学) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/com.code-philosophy.obfuz4hybridclr/LICENSE.meta b/com.code-philosophy.obfuz4hybridclr/LICENSE.meta new file mode 100644 index 0000000..dd09461 --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3036602f815e31341b4445f0e331b58e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.code-philosophy.obfuz4hybridclr/package.json b/com.code-philosophy.obfuz4hybridclr/package.json new file mode 100644 index 0000000..76fed77 --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/package.json @@ -0,0 +1,21 @@ +{ + "name": "com.code-philosophy.obfuz4hybridclr", + "version": "1.0.0-alpha", + "displayName": "Obfuz4HybridCLR", + "description": "Obfuz Extension for HybridCLR", + "category": "Editor", + "documentationUrl": "https://www.obfuz.com", + "changelogUrl": "https://github.com/focus-creative-games/obfuz/commits/main/", + "licensesUrl": "https://github.com/focus-creative-games/obfuz/blob/main/LICENSE", + "keywords": [ + "obfuscation", + "obfuscator", + "confuser", + "code-philosophy" + ], + "author": { + "name": "Code Philosophy", + "email": "obfuz@code-philosophy.com", + "url": "https://code-philosophy.com" + } +} \ No newline at end of file diff --git a/com.code-philosophy.obfuz4hybridclr/package.json.meta b/com.code-philosophy.obfuz4hybridclr/package.json.meta new file mode 100644 index 0000000..5577b3b --- /dev/null +++ b/com.code-philosophy.obfuz4hybridclr/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9ac66e213a764b840b2533ee30123717 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: