move package com.code-philosophy.obfuz and com.code-philosophy.obfuz4hybridclr to root directory
parent
acb35e375e
commit
8f8422864b
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2326b426d539e084dbddf7f7c23ed1bd
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 241df8eaf3a34dc47a0873c37ddb2695
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<T>
|
||||||
|
{
|
||||||
|
void InheritParent(T parentRule);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface IMethodRule<R> where R: IRule<R>
|
||||||
|
{
|
||||||
|
string Name { get; set; }
|
||||||
|
NameMatcher NameMatcher { get; set; }
|
||||||
|
|
||||||
|
R Rule { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class MethodRuleBase<R> : IMethodRule<R> where R : IRule<R>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public NameMatcher NameMatcher { get; set; }
|
||||||
|
|
||||||
|
public R Rule { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITypeRule<T, R> where T: IMethodRule<R> where R : IRule<R>
|
||||||
|
{
|
||||||
|
string Name { get; set; }
|
||||||
|
|
||||||
|
NameMatcher NameMatcher { get; set; }
|
||||||
|
|
||||||
|
R Rule { get; set; }
|
||||||
|
|
||||||
|
List<T> Methods { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class TypeRuleBase<T, R> : ITypeRule<T, R> where T : IMethodRule<R> where R : IRule<R>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public NameMatcher NameMatcher { get; set; }
|
||||||
|
|
||||||
|
public R Rule { get; set; }
|
||||||
|
|
||||||
|
public List<T> Methods { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAssemblyRule<TType, TMethod, TRule> where TType : ITypeRule<TMethod, TRule> where TMethod : IMethodRule<TRule> where TRule : IRule<TRule>
|
||||||
|
{
|
||||||
|
string Name { get; set; }
|
||||||
|
|
||||||
|
TRule Rule { get; set; }
|
||||||
|
|
||||||
|
List<TType> Types { get; set; }
|
||||||
|
}
|
||||||
|
public abstract class AssemblyRuleBase<TType, TMethod, TRule> : IAssemblyRule<TType, TMethod, TRule> where TType : ITypeRule<TMethod, TRule> where TMethod : IMethodRule<TRule> where TRule : IRule<TRule>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public TRule Rule { get; set; }
|
||||||
|
|
||||||
|
public List<TType> Types { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class XmlAssemblyTypeMethodRuleParser<TAssembly, TType, TMethod, TRule>
|
||||||
|
where TMethod : IMethodRule<TRule>, new()
|
||||||
|
where TType : ITypeRule<TMethod, TRule>, new()
|
||||||
|
where TAssembly : IAssemblyRule<TType, TMethod, TRule>, new()
|
||||||
|
where TRule : IRule<TRule>, new()
|
||||||
|
{
|
||||||
|
private readonly HashSet<string> _toObfuscatedAssemblyNames;
|
||||||
|
private readonly Func<string, XmlElement, TRule> _ruleParser;
|
||||||
|
private readonly Action<string, XmlElement> _unknownNodeTypeHandler;
|
||||||
|
private readonly Dictionary<string, TAssembly> _assemblySpecs = new Dictionary<string, TAssembly>();
|
||||||
|
|
||||||
|
public XmlAssemblyTypeMethodRuleParser(IEnumerable<string> toObfuscatedAssemblyNames, Func<string, XmlElement, TRule> ruleParser, Action<string, XmlElement> unknownNodeTypeHandler)
|
||||||
|
{
|
||||||
|
_toObfuscatedAssemblyNames = new HashSet<string>(toObfuscatedAssemblyNames);
|
||||||
|
_ruleParser = ruleParser;
|
||||||
|
_unknownNodeTypeHandler = unknownNodeTypeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, TAssembly> AssemblySpecs => _assemblySpecs;
|
||||||
|
|
||||||
|
public void LoadConfigs(IEnumerable<string> 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<TType>();
|
||||||
|
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<TMethod>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 36a3e142db81f6d4bb54938525e31973
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<R> where R : class, new()
|
||||||
|
{
|
||||||
|
private readonly HashSet<string> _toObfuscatedAssemblyNames;
|
||||||
|
private readonly Func<string, XmlElement, R> _ruleParser;
|
||||||
|
private readonly Action<string, XmlElement> _unknownNodeTypeHandler;
|
||||||
|
private readonly Dictionary<string, AssemblySpec> _assemblySpecs = new Dictionary<string, AssemblySpec>();
|
||||||
|
|
||||||
|
|
||||||
|
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<FieldSpec> Fields { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AssemblySpec
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public List<TypeSpec> Types { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmlFieldRuleParser(IEnumerable<string> toObfuscatedAssemblyNames, Func<string, XmlElement, R> ruleParser, Action<string, XmlElement> unknownNodeTypeHandler)
|
||||||
|
{
|
||||||
|
_toObfuscatedAssemblyNames = new HashSet<string>(toObfuscatedAssemblyNames);
|
||||||
|
_ruleParser = ruleParser;
|
||||||
|
_unknownNodeTypeHandler = unknownNodeTypeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadConfigs(IEnumerable<string> 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<TypeSpec>();
|
||||||
|
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<FieldSpec>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1578270b9b81e1e4dba84d562c91090f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<FieldSpec> fields = new List<FieldSpec>();
|
||||||
|
public List<MethodSpec> methods = new List<MethodSpec>();
|
||||||
|
public List<PropertySpec> properties = new List<PropertySpec>();
|
||||||
|
public List<EventSpec> events = new List<EventSpec>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssemblySpec
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public NameMatcher nameMatcher;
|
||||||
|
public PassRule rule;
|
||||||
|
public List<TypeSpec> types = new List<TypeSpec>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ObfuscationPassType _enabledPasses;
|
||||||
|
private readonly HashSet<string> _toObfuscatedAssemblyNames;
|
||||||
|
private readonly List<AssemblySpec> _assemblySpecs = new List<AssemblySpec>();
|
||||||
|
private readonly PassRule _defaultPassRule;
|
||||||
|
|
||||||
|
private string _curLoadingConfig;
|
||||||
|
|
||||||
|
public ConfigurablePassPolicy(IEnumerable<string> toObfuscatedAssemblyNames, ObfuscationPassType enabledPasses, List<string> configFiles)
|
||||||
|
{
|
||||||
|
_toObfuscatedAssemblyNames = new HashSet<string>(toObfuscatedAssemblyNames);
|
||||||
|
_enabledPasses = enabledPasses;
|
||||||
|
_defaultPassRule = new PassRule { finalPasses = enabledPasses };
|
||||||
|
LoadConfigs(configFiles);
|
||||||
|
InheritParentRules(enabledPasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadConfigs(IEnumerable<string> 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<FieldSpec> fields = typeSpec.fields;
|
||||||
|
List<MethodSpec> methods = typeSpec.methods;
|
||||||
|
List<PropertySpec> properties = typeSpec.properties;
|
||||||
|
List<EventSpec> 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<ModuleDef, (AssemblySpec, PassRule)> _modulePassRuleCaches = new Dictionary<ModuleDef, (AssemblySpec, PassRule)>();
|
||||||
|
private readonly Dictionary<TypeDef, (TypeSpec, PassRule)> _typePassRuleCaches = new Dictionary<TypeDef, (TypeSpec, PassRule)>();
|
||||||
|
private readonly Dictionary<MethodDef, (MethodSpec, PassRule)> _methodPassRuleCaches = new Dictionary<MethodDef, (MethodSpec, PassRule)>();
|
||||||
|
private readonly Dictionary<FieldDef, (FieldSpec, PassRule)> _fieldPassRuleCaches = new Dictionary<FieldDef, (FieldSpec, PassRule)>();
|
||||||
|
private readonly Dictionary<PropertyDef, (PropertySpec, PassRule)> _propertyPassRuleCaches = new Dictionary<PropertyDef, (PropertySpec, PassRule)>();
|
||||||
|
private readonly Dictionary<EventDef, (EventSpec, PassRule)> _eventPassRuleCaches = new Dictionary<EventDef, (EventSpec, PassRule)>();
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 41044699810a34f4780e14de084bf7d7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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$";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aee7817ed523a5e4ea42104013e8a775
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3319ebe75a42f3d4d996846ca09ed099
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<object>
|
||||||
|
{
|
||||||
|
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<object, ConstFieldInfo> _allocatedFields = new Dictionary<object, ConstFieldInfo>(new AnyComparer());
|
||||||
|
private readonly Dictionary<FieldDef, ConstFieldInfo> _field2Fields = new Dictionary<FieldDef, ConstFieldInfo>();
|
||||||
|
|
||||||
|
private readonly List<TypeDef> _holderTypeDefs = new List<TypeDef>();
|
||||||
|
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<ModuleConstFieldAllocator>(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<ModuleConstFieldAllocator>())
|
||||||
|
{
|
||||||
|
moduleAllocator.Done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e75f5cdfd47370d4ea6c4dee7e55a881
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<byte> 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<RvaField> _rvaFields = new List<RvaField>();
|
||||||
|
private RvaField _currentField;
|
||||||
|
|
||||||
|
|
||||||
|
private TypeDef _rvaTypeDef;
|
||||||
|
|
||||||
|
private readonly Dictionary<int, TypeDef> _dataHolderTypeBySizes = new Dictionary<int, TypeDef>();
|
||||||
|
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<byte>((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<ModuleRvaDataAllocator>(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<ModuleRvaDataAllocator>())
|
||||||
|
{
|
||||||
|
allocator.Done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c00ca514f46605645bf40b0135e7e504
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a513a192808ba5f47b1ef8a3ecf02533
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<Instruction> instructions = new List<Instruction>();
|
||||||
|
|
||||||
|
public readonly List<BasicBlock> inBlocks = new List<BasicBlock>();
|
||||||
|
|
||||||
|
public readonly List<BasicBlock> outBlocks = new List<BasicBlock>();
|
||||||
|
|
||||||
|
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<BasicBlock> _blocks = new List<BasicBlock>();
|
||||||
|
private readonly Dictionary<Instruction, BasicBlock> _inst2BlockMap = new Dictionary<Instruction, BasicBlock>();
|
||||||
|
|
||||||
|
public IList<BasicBlock> Blocks => _blocks;
|
||||||
|
|
||||||
|
public BasicBlockCollection(MethodDef method)
|
||||||
|
{
|
||||||
|
_method = method;
|
||||||
|
HashSet<Instruction> 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<Instruction> BuildSplitPoint(MethodDef method)
|
||||||
|
{
|
||||||
|
var insts = method.Body.Instructions;
|
||||||
|
var splitPoints = new HashSet<Instruction>();
|
||||||
|
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<Instruction> 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<BasicBlock> FindLoopBlocks(List<BasicBlock> allBlocks)
|
||||||
|
{
|
||||||
|
// Tarjan算法找强连通分量
|
||||||
|
var sccList = FindStronglyConnectedComponents(allBlocks);
|
||||||
|
|
||||||
|
// 筛选有效循环
|
||||||
|
var loopBlocks = new HashSet<BasicBlock>();
|
||||||
|
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<List<BasicBlock>> FindStronglyConnectedComponents(List<BasicBlock> allBlocks)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
var stack = new Stack<BasicBlock>();
|
||||||
|
var indexes = new Dictionary<BasicBlock, int>();
|
||||||
|
var lowLinks = new Dictionary<BasicBlock, int>();
|
||||||
|
var onStack = new HashSet<BasicBlock>();
|
||||||
|
var sccList = new List<List<BasicBlock>>();
|
||||||
|
|
||||||
|
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>();
|
||||||
|
BasicBlock w;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
w = stack.Pop();
|
||||||
|
onStack.Remove(w);
|
||||||
|
scc.Add(w);
|
||||||
|
} while (!w.Equals(v));
|
||||||
|
sccList.Add(scc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 77c19c023bb7f77489998d994a3be1bd
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<DefaultStaticEncryptionScope>));
|
||||||
|
_dynamicDefaultEncryptionServiceMetadataImporter = new EncryptionServiceMetadataImporter(mod, typeof(EncryptionService<DefaultDynamicEncryptionScope>));
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 76438ce96146edd469872feada7857ba
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<T>(ModuleDef mod, Func<T> 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<T> GetEntities<T>() where T: IGroupByModuleEntity
|
||||||
|
{
|
||||||
|
var managers = new List<T>();
|
||||||
|
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<DefaultMetadataImporter>(module, () => new DefaultMetadataImporter(encryptionScopeProvider));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0bfcb2b5a87851f469d201fc8978c109
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fec4187cc1b96d5439ff908bcecd988f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ca9bd232ed2583f4bb5f330886a329e6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> lines, string indent);
|
||||||
|
|
||||||
|
void GenerateDecryptCode(List<string> 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<string> lines, string indent);
|
||||||
|
public abstract void GenerateDecryptCode(List<string> lines, string indent);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f7b9d087de770a5488a9069ddf697c2f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 981355cf75a9d234883b2a15c446f478
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> lines, string indent)
|
||||||
|
{
|
||||||
|
lines.Add(indent + $"value = ((value + _secretKey[{_opKeyIndex}]) ^ salt) + {_addValue};");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateDecryptCode(List<string> lines, string indent)
|
||||||
|
{
|
||||||
|
lines.Add(indent + $"value = ((value - {_addValue}) ^ salt) - _secretKey[{_opKeyIndex}];");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6bdbdc5fd983f044a87e7b8ab8647aeb
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> 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<string> 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}];");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cda67c0dd0cadd24ea02c2988e34281a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> 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<string> 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}];");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d806305e627be06469fb2d2c2cf98816
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> 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<string> 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);");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bccff31b9f07fcf4f821cee671f82caf
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> lines, string indent)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateDecryptCode(List<string> lines, string indent)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: feafdb30f7b6d5143a89c7659bc16171
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> lines, string indent)
|
||||||
|
{
|
||||||
|
lines.Add(indent + $"value = value * {_multiValue} + _secretKey[{_opKeyIndex}] + salt;");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateDecryptCode(List<string> lines, string indent)
|
||||||
|
{
|
||||||
|
lines.Add(indent + $"value = (value - _secretKey[{_opKeyIndex}] - salt) * {_revertMultiValue};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fd5fdfad694e0ae469bf6ca04c913220
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> 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<string> 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};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e3c8b55b35ff1554489fa657a714f485
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> 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<string> 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};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: adc3dcde66795744fa4bdc753a2c599f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> 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<string> 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;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ad8f4dd724d7ff845b0dd65861054d37
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> lines, string indent)
|
||||||
|
{
|
||||||
|
lines.Add(indent + $"value = ((value ^ _secretKey[{_opKeyIndex}]) + salt) ^ {_xorValue};");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateDecryptCode(List<string> lines, string indent)
|
||||||
|
{
|
||||||
|
lines.Add(indent + $"value = ((value ^ {_xorValue}) - salt) ^ _secretKey[{_opKeyIndex}];");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2f16dd868e4473b45bfa9daaf7fabaf8
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> 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<string> 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;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3eb7e6d475cfc14459d3850c5964ba52
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c6970e037654dcb49912783a40f3e1ba
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string>();
|
||||||
|
AppendHeader(lines);
|
||||||
|
AppendEncryptCodes(lines);
|
||||||
|
AppendDecryptCodes(lines);
|
||||||
|
AppendTailer(lines);
|
||||||
|
return string.Join("\n", lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendEncryptCodes(List<string> 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<string> 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<string> 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<string> lines)
|
||||||
|
{
|
||||||
|
lines.Add(@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendEncryptCode(List<string> lines, IEncryptionInstruction instruction)
|
||||||
|
{
|
||||||
|
instruction.GenerateEncryptCode(lines, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendDecryptCode(List<string> lines, IEncryptionInstruction instruction)
|
||||||
|
{
|
||||||
|
instruction.GenerateDecryptCode(lines, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2246e9d3369eb3c45bc19ae0748d76ba
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<Func<IRandom, int, EncryptionInstructionBase>> _instructionCreators = new List<Func<IRandom, int, EncryptionInstructionBase>>
|
||||||
|
{
|
||||||
|
(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 77d95ff5cf0b3aa4e96a055e37c381ba
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<uint> DecodeOps(uint ops)
|
||||||
|
{
|
||||||
|
var codes = new List<uint>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4f86f4d6faf49764a915d5c675091375
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b7003f9503025794b8aa775d9ade335c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 120b2dcffd582e84dbb92003240824d1
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<Instruction> globalInstructions, List<Instruction> outputInstructions, List<Instruction> totalFinalInstructions);
|
||||||
|
|
||||||
|
private void ObfuscateData(MethodDef method)
|
||||||
|
{
|
||||||
|
BasicBlockCollection bbc = new BasicBlockCollection(method);
|
||||||
|
|
||||||
|
IList<Instruction> instructions = method.Body.Instructions;
|
||||||
|
|
||||||
|
var outputInstructions = new List<Instruction>();
|
||||||
|
var totalFinalInstructions = new List<Instruction>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ae83aaf003665614092aabceabff3cf8
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cf68e45551825c547b137f6e5189937e
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> _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<Instruction> globalInstructions, List<Instruction> outputInstructions, List<Instruction> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 112178b770868274fb8119a4997a3420
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<MethodKey>
|
||||||
|
{
|
||||||
|
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<MethodKey, MethodProxyInfo> _methodProxys = new Dictionary<MethodKey, MethodProxyInfo>();
|
||||||
|
|
||||||
|
|
||||||
|
const int maxProxyMethodPerDispatchMethod = 1000;
|
||||||
|
|
||||||
|
class CallInfo
|
||||||
|
{
|
||||||
|
public IMethod method;
|
||||||
|
public bool callVir;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DispatchMethodInfo
|
||||||
|
{
|
||||||
|
public MethodDef methodDef;
|
||||||
|
public List<CallInfo> methods = new List<CallInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<MethodSig, List<DispatchMethodInfo>> _dispatchMethods = new Dictionary<MethodSig, List<DispatchMethodInfo>>(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<DispatchMethodInfo>();
|
||||||
|
_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<Instruction>();
|
||||||
|
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<ModuleCallProxyAllocator>(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<ModuleCallProxyAllocator>())
|
||||||
|
{
|
||||||
|
allocator.Done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 16b960455f093854d927c2dbd47a4826
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<WhiteListType> types = new List<WhiteListType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhiteListType
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public NameMatcher nameMatcher;
|
||||||
|
public bool? obfuscate;
|
||||||
|
public List<WhiteListMethod> methods = new List<WhiteListMethod>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhiteListMethod
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public NameMatcher nameMatcher;
|
||||||
|
public bool? obfuscate;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObfuscationRule : IRule<ObfuscationRule>
|
||||||
|
{
|
||||||
|
public bool? disableObfuscation;
|
||||||
|
public bool? obfuscateCallInLoop;
|
||||||
|
public bool? cacheCallIndexInLoop;
|
||||||
|
public bool? cacheCallIndexNotLoop;
|
||||||
|
|
||||||
|
public void InheritParent(ObfuscationRule parentRule)
|
||||||
|
{
|
||||||
|
if (disableObfuscation == null)
|
||||||
|
{
|
||||||
|
disableObfuscation = parentRule.disableObfuscation;
|
||||||
|
}
|
||||||
|
if (obfuscateCallInLoop == null)
|
||||||
|
{
|
||||||
|
obfuscateCallInLoop = parentRule.obfuscateCallInLoop;
|
||||||
|
}
|
||||||
|
if (cacheCallIndexInLoop == null)
|
||||||
|
{
|
||||||
|
cacheCallIndexInLoop = parentRule.cacheCallIndexInLoop;
|
||||||
|
}
|
||||||
|
if (cacheCallIndexNotLoop == null)
|
||||||
|
{
|
||||||
|
cacheCallIndexNotLoop = parentRule.cacheCallIndexNotLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssemblySpec : AssemblyRuleBase<TypeSpec, MethodSpec, ObfuscationRule>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeSpec : TypeRuleBase<MethodSpec, ObfuscationRule>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class MethodSpec : MethodRuleBase<ObfuscationRule>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly ObfuscationRule s_default = new ObfuscationRule()
|
||||||
|
{
|
||||||
|
disableObfuscation = false,
|
||||||
|
obfuscateCallInLoop = true,
|
||||||
|
cacheCallIndexInLoop = true,
|
||||||
|
cacheCallIndexNotLoop = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly XmlAssemblyTypeMethodRuleParser<AssemblySpec, TypeSpec, MethodSpec, ObfuscationRule> _configParser;
|
||||||
|
|
||||||
|
private ObfuscationRule _global;
|
||||||
|
private readonly List<WhiteListAssembly> _whiteListAssemblies = new List<WhiteListAssembly>();
|
||||||
|
|
||||||
|
private readonly Dictionary<IMethod, bool> _whiteListMethodCache = new Dictionary<IMethod, bool>(MethodEqualityComparer.CompareDeclaringTypes);
|
||||||
|
private readonly Dictionary<MethodDef, ObfuscationRule> _methodRuleCache = new Dictionary<MethodDef, ObfuscationRule>();
|
||||||
|
|
||||||
|
public ConfigurableObfuscationPolicy(List<string> toObfuscatedAssemblyNames, List<string> xmlConfigFiles)
|
||||||
|
{
|
||||||
|
_configParser = new XmlAssemblyTypeMethodRuleParser<AssemblySpec, TypeSpec, MethodSpec, ObfuscationRule>(toObfuscatedAssemblyNames,
|
||||||
|
ParseObfuscationRule, ParseGlobalElement);
|
||||||
|
LoadConfigs(xmlConfigFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadConfigs(List<string> 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<T>.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d9ea12b16c4b296459db8a60fb1615d6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<Instruction> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e13ba01b03439e049af0e09367825cde
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6af3cd881fdefd14d9a55b77088dd5a4
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<Instruction> obfuscatedInstructions);
|
||||||
|
|
||||||
|
void Done();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ObfuscatorBase : IObfuscator
|
||||||
|
{
|
||||||
|
public abstract void Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, bool needCacheCall, List<Instruction> obfuscatedInstructions);
|
||||||
|
public abstract void Done();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4156317478f8b1d438ef6d5a280d409f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2764442d8fc2b914dbc39dcfa2699698
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 78cc056cd929d70409a0f0737b571a6d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<CustomAttribute>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6b475010a7656a0439ca8664a3d2dbc0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 18104d0c3c665ea489e566eec67f2aea
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<ObfuscationRule>
|
||||||
|
{
|
||||||
|
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<ObfuscationRule>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeSpec : TypeRuleBase<MethodSpec, ObfuscationRule>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssemblySpec : AssemblyRuleBase<TypeSpec, MethodSpec, ObfuscationRule>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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<int> notEncryptInts = new HashSet<int>();
|
||||||
|
public HashSet<long> notEncryptLongs = new HashSet<long>();
|
||||||
|
public HashSet<string> notEncryptStrings = new HashSet<string>();
|
||||||
|
public List<NumberRange<int>> notEncryptIntRanges = new List<NumberRange<int>>();
|
||||||
|
public List<NumberRange<long>> notEncryptLongRanges = new List<NumberRange<long>>();
|
||||||
|
public List<NumberRange<float>> notEncryptFloatRanges = new List<NumberRange<float>>();
|
||||||
|
public List<NumberRange<double>> notEncryptDoubleRanges = new List<NumberRange<double>>();
|
||||||
|
public List<NumberRange<int>> notEncryptArrayLengthRanges = new List<NumberRange<int>>();
|
||||||
|
public List<NumberRange<int>> notEncryptStringLengthRanges = new List<NumberRange<int>>();
|
||||||
|
|
||||||
|
private readonly XmlAssemblyTypeMethodRuleParser<AssemblySpec, TypeSpec, MethodSpec, ObfuscationRule> _xmlParser;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, AssemblySpec> _assemblySpecs = new Dictionary<string, AssemblySpec>();
|
||||||
|
private readonly Dictionary<MethodDef, ObfuscationRule> _methodRuleCache = new Dictionary<MethodDef, ObfuscationRule>();
|
||||||
|
|
||||||
|
public ConfigurableEncryptPolicy(List<string> toObfuscatedAssemblyNames, List<string> xmlConfigFiles)
|
||||||
|
{
|
||||||
|
_xmlParser = new XmlAssemblyTypeMethodRuleParser<AssemblySpec, TypeSpec, MethodSpec, ObfuscationRule>(
|
||||||
|
toObfuscatedAssemblyNames, ParseObfuscationRule, ParseGlobalElement);
|
||||||
|
LoadConfigs(xmlConfigFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadConfigs(List<string> 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<int>(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<long>(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<float>(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<double>(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<int>(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<int>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: da25453bc1fda394097c052af7733260
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<string> _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<Instruction> globalInstructions,
|
||||||
|
List<Instruction> outputInstructions, List<Instruction> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aa0e9191126d4e24c92546b6af2c52cf
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<Instruction> 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<Instruction> 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<Instruction> 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<Instruction> 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<Instruction> 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<Instruction> 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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4c6a0ecde97527e4694731e4d4de129a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<Instruction> obfuscatedInstructions);
|
||||||
|
|
||||||
|
void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List<Instruction> obfuscatedInstructions);
|
||||||
|
|
||||||
|
void ObfuscateFloat(MethodDef method, bool needCacheValue, float value, List<Instruction> obfuscatedInstructions);
|
||||||
|
|
||||||
|
void ObfuscateDouble(MethodDef method, bool needCacheValue, double value, List<Instruction> obfuscatedInstructions);
|
||||||
|
|
||||||
|
void ObfuscateString(MethodDef method, bool needCacheValue, string value, List<Instruction> obfuscatedInstructions);
|
||||||
|
|
||||||
|
void ObfuscateBytes(MethodDef method, bool needCacheValue, byte[] value, List<Instruction> obfuscatedInstructions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ConstEncryptorBase : IConstEncryptor
|
||||||
|
{
|
||||||
|
public abstract void ObfuscateBytes(MethodDef method, bool needCacheValue, byte[] value, List<Instruction> obfuscatedInstructions);
|
||||||
|
public abstract void ObfuscateDouble(MethodDef method, bool needCacheValue, double value, List<Instruction> obfuscatedInstructions);
|
||||||
|
public abstract void ObfuscateFloat(MethodDef method, bool needCacheValue, float value, List<Instruction> obfuscatedInstructions);
|
||||||
|
public abstract void ObfuscateInt(MethodDef method, bool needCacheValue, int value, List<Instruction> obfuscatedInstructions);
|
||||||
|
public abstract void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List<Instruction> obfuscatedInstructions);
|
||||||
|
public abstract void ObfuscateString(MethodDef method, bool needCacheValue, string value, List<Instruction> obfuscatedInstructions);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0ccbcdadf1913b6498eaee53abac5d0b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 18e57864070430a44ac561bdd7d00b2e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9fdb2c243b1ea0f489e67233fda287c9
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<Instruction> instructions, int instructionIndex, List<Instruction> outputInstructions, List<Instruction> totalFinalInstructions)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 477e081ffc0072e4fa1a06100269e4a3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0b789725c4848bd4fb4b3ce1f2e2a9c9
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<ObfuscationRule> _configParser;
|
||||||
|
|
||||||
|
public ConfigurableEncryptPolicy(List<string> toObfuscatedAssemblyNames, List<string> configFiles)
|
||||||
|
{
|
||||||
|
_configParser = new XmlFieldRuleParser<ObfuscationRule>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6b17fa09ce58526459f2b9e375c31cad
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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<FieldDef, FieldEncryptInfo> _fieldEncryptInfoCache = new Dictionary<FieldDef, FieldEncryptInfo>();
|
||||||
|
|
||||||
|
|
||||||
|
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<Instruction> 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<Instruction> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b6707a66ae63e2c498d55088c6e8ef4a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue