diff --git a/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs b/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs new file mode 100644 index 0000000..8ae8961 --- /dev/null +++ b/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs @@ -0,0 +1,19 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public class ConfigurableEncryptPolicy : EncryptPolicyBase + { + + public override bool NeedEncrypt(FieldDef field) + { + TypeDef type = field.DeclaringType; + // TODO + if (type.Name == "EncryptField" || type.Name == "EncryptProperty") + { + return true; + } + return false; + } + } +} diff --git a/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs b/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs new file mode 100644 index 0000000..64c0fd1 --- /dev/null +++ b/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs @@ -0,0 +1,151 @@ +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 IRandom _random; + private readonly IEncryptor _encryptor; + + public DefaultFieldEncryptor(IRandom random, IEncryptor encryptor) + { + _random = random; + _encryptor = encryptor; + } + + private DefaultModuleMetadataImporter GetMetadataImporter(MethodDef method) + { + return MetadataImporter.Instance.GetDefaultModuleMetadataImporter(method.Module); + } + + class FieldEncryptInfo + { + public int encryptOps; + public int salt; + public ElementType fieldType; + public long xorValueForZero; + } + + private readonly Dictionary _fieldEncryptInfoCache = new Dictionary(); + + + private long CalcXorValueForZero(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 FieldEncryptInfo GetFieldEncryptInfo(FieldDef field) + { + if (_fieldEncryptInfoCache.TryGetValue(field, out var info)) + { + return info; + } + + int encryptOps = _random.NextInt(); + int salt = _random.NextInt(); + ElementType fieldType = field.FieldSig.Type.RemovePinnedAndModifiers().ElementType; + long xorValueForZero = CalcXorValueForZero(fieldType, encryptOps, salt); + + info = new FieldEncryptInfo + { + encryptOps = encryptOps, + salt = salt, + fieldType = fieldType, + xorValueForZero = xorValueForZero, + }; + _fieldEncryptInfoCache[field] = info; + return info; + } + + public override void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction) + { + DefaultModuleMetadataImporter importer = GetMetadataImporter(method); + 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, importer.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, importer.EncryptLong)); + // xor + outputInstructions.Add(Instruction.Create(OpCodes.Ldc_I8, fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + } + else + { + Assert.IsTrue(false, $"Unsupported field type: {fei.fieldType} for encryption"); + } + + outputInstructions.Add(currentInstruction.Clone()); + } + + public override void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction) + { + outputInstructions.Add(currentInstruction.Clone()); + DefaultModuleMetadataImporter importer = GetMetadataImporter(method); + FieldEncryptInfo fei = GetFieldEncryptInfo(field); + if (fei.fieldType == ElementType.I4 || fei.fieldType == ElementType.U4 || fei.fieldType == ElementType.R4) + { + // value has been put on stack + // xor + 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, importer.DecryptInt)); + } + else if (fei.fieldType == ElementType.I8 || fei.fieldType == ElementType.U8 || fei.fieldType == ElementType.R8) + { + // value has been put on stack + // xor + 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, importer.DecryptLong)); + } + else + { + Assert.IsTrue(false, $"Unsupported field type: {fei.fieldType} for decryption"); + } + } + } +} diff --git a/Editor/ObfusPasses/MemEncrypt/MemEncryptPass.cs b/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs similarity index 59% rename from Editor/ObfusPasses/MemEncrypt/MemEncryptPass.cs rename to Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs index 80e56b7..b779f83 100644 --- a/Editor/ObfusPasses/MemEncrypt/MemEncryptPass.cs +++ b/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs @@ -1,21 +1,21 @@ using dnlib.DotNet; using dnlib.DotNet.Emit; using Obfuz; -using Obfuz.ObfusPasses.MemEncrypt; using System.Collections; using System.Collections.Generic; using UnityEngine; -namespace Obfuz.ObfusPasses.MemEncrypt +namespace Obfuz.ObfusPasses.FieldEncrypt { - public class MemEncryptPass : InstructionObfuscationPassBase + public class FieldEncryptPass : InstructionObfuscationPassBase { - private readonly IEncryptPolicy _encryptionPolicy = new ConfigEncryptionPolicy(); - private readonly IMemEncryptor _memoryEncryptor = new DefaultMemoryEncryptor(); + private readonly IEncryptPolicy _encryptionPolicy = new ConfigurableEncryptPolicy(); + private IFieldEncryptor _memoryEncryptor; public override void Start(ObfuscationPassContext ctx) { + _memoryEncryptor = new DefaultFieldEncryptor(ctx.random, ctx.encryptor); } @@ -29,32 +29,31 @@ namespace Obfuz.ObfusPasses.MemEncrypt return true; } - private FieldDef TryResolveFieldDef(IField field) + private bool IsSupportedFieldType(TypeSig type) { - if (field is FieldDef fieldDef) + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) { - return fieldDef; + case ElementType.I4: + case ElementType.I8: + case ElementType.U4: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + return true; + default: return false; } - if (field is MemberRef memberRef) - { - return memberRef.ResolveFieldDef(); - } - throw new System.Exception($"Cannot resolve field: {field}"); } protected override bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, List outputInstructions, List totalFinalInstructions) { Code code = inst.OpCode.Code; - if (!(inst.Operand is IField field)) + if (!(inst.Operand is IField field) || !field.IsField) { return false; } - FieldDef fieldDef = TryResolveFieldDef(field); - if (fieldDef == null) - { - return false; - } - if (!_encryptionPolicy.NeedEncrypt(fieldDef)) + FieldDef fieldDef = field.ResolveFieldDefThrow(); + if (!IsSupportedFieldType(fieldDef.FieldSig.Type) || !_encryptionPolicy.NeedEncrypt(fieldDef)) { return false; } @@ -67,22 +66,22 @@ namespace Obfuz.ObfusPasses.MemEncrypt { case Code.Ldfld: { - _memoryEncryptor.Decrypt(fieldDef, outputInstructions, ctx); + _memoryEncryptor.Decrypt(callingMethod, fieldDef, outputInstructions, inst); break; } case Code.Stfld: { - _memoryEncryptor.Encrypt(fieldDef, outputInstructions, ctx); + _memoryEncryptor.Encrypt(callingMethod, fieldDef, outputInstructions, inst); break; } case Code.Ldsfld: { - _memoryEncryptor.Decrypt(fieldDef, outputInstructions, ctx); + _memoryEncryptor.Decrypt(callingMethod, fieldDef, outputInstructions, inst); break; } case Code.Stsfld: { - _memoryEncryptor.Encrypt(fieldDef, outputInstructions, ctx); + _memoryEncryptor.Encrypt(callingMethod, fieldDef, outputInstructions, inst); break; } case Code.Ldflda: diff --git a/Editor/ObfusPasses/MemEncrypt/IEncryptPolicy.cs b/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs similarity index 89% rename from Editor/ObfusPasses/MemEncrypt/IEncryptPolicy.cs rename to Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs index d47b342..a1a7705 100644 --- a/Editor/ObfusPasses/MemEncrypt/IEncryptPolicy.cs +++ b/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Obfuz.ObfusPasses.MemEncrypt +namespace Obfuz.ObfusPasses.FieldEncrypt { public interface IEncryptPolicy { diff --git a/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs b/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs new file mode 100644 index 0000000..7c9f7c3 --- /dev/null +++ b/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs @@ -0,0 +1,29 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public class MemoryEncryptionContext + { + public ModuleDef module; + + public Instruction currentInstruction; + } + + public interface IFieldEncryptor + { + void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + + void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + } + + public abstract class FieldEncryptorBase : IFieldEncryptor + { + public abstract void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + public abstract void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + } +} diff --git a/Editor/ObfusPasses/MemEncrypt/ConfigEncryptionPolicy.cs b/Editor/ObfusPasses/MemEncrypt/ConfigEncryptionPolicy.cs deleted file mode 100644 index 0d4d55e..0000000 --- a/Editor/ObfusPasses/MemEncrypt/ConfigEncryptionPolicy.cs +++ /dev/null @@ -1,39 +0,0 @@ -using dnlib.DotNet; - -namespace Obfuz.ObfusPasses.MemEncrypt -{ - public class ConfigEncryptionPolicy : EncryptPolicyBase - { - - private bool IsSupportedFieldType(TypeSig type) - { - type = type.RemovePinnedAndModifiers(); - switch (type.ElementType) - { - case ElementType.I4: - case ElementType.I8: - case ElementType.U4: - case ElementType.U8: - case ElementType.R4: - case ElementType.R8: - return true; - default: return false; - } - } - - public override bool NeedEncrypt(FieldDef field) - { - TypeDef type = field.DeclaringType; - if (!IsSupportedFieldType(field.FieldType)) - { - return false; - } - // TODO - if (type.Name == "EncryptField" || type.Name == "EncryptProperty") - { - return true; - } - return false; - } - } -} diff --git a/Editor/ObfusPasses/MemEncrypt/DefaultMemoryEncryptor.cs b/Editor/ObfusPasses/MemEncrypt/DefaultMemoryEncryptor.cs deleted file mode 100644 index ae3f1ca..0000000 --- a/Editor/ObfusPasses/MemEncrypt/DefaultMemoryEncryptor.cs +++ /dev/null @@ -1,108 +0,0 @@ -using dnlib.DotNet; -using dnlib.DotNet.Emit; -using Obfuz.Emit; -using Obfuz.Utils; -using System; -using System.Collections.Generic; -using UnityEngine.Assertions; - -namespace Obfuz.ObfusPasses.MemEncrypt -{ - public class DefaultMemoryEncryptor : MemEncryptorBase - { - - private class ModuleDefaultMemoryEncryptor - { - private readonly ModuleDef _module; - - public ModuleDefaultMemoryEncryptor(ModuleDef module) - { - _module = module; - InitMetadatas(module); - } - - private static IMethod s_castIntAsFloat; - private static IMethod s_castLongAsDouble; - private static IMethod s_castFloatAsInt; - private static IMethod s_castDoubleAsLong; - - private void InitMetadatas(ModuleDef mod) - { - if (s_castFloatAsInt != null) - { - return; - } - var constUtilityType = typeof(ConstUtility); - - s_castIntAsFloat = mod.Import(constUtilityType.GetMethod("CastIntAsFloat")); - Assert.IsNotNull(s_castIntAsFloat, "CastIntAsFloat not found"); - s_castLongAsDouble = mod.Import(constUtilityType.GetMethod("CastLongAsDouble")); - Assert.IsNotNull(s_castLongAsDouble, "CastLongAsDouble not found"); - s_castFloatAsInt = mod.Import(constUtilityType.GetMethod("CastFloatAsInt")); - Assert.IsNotNull(s_castFloatAsInt, "CastFloatAsInt not found"); - s_castDoubleAsLong = mod.Import(constUtilityType.GetMethod("CastDoubleAsLong")); - Assert.IsNotNull(s_castDoubleAsLong, "CastDoubleAsLong not found"); - } - - - private VariableEncryption CreateEncryption(ElementType type) - { - IRandom random = new RandomWithKey(new byte[16], 1234); - switch (type) - { - case ElementType.I4: - return new VariableEncryption(DataNodeType.Int32, random); - case ElementType.I8: - return new VariableEncryption(DataNodeType.Int64, random); - case ElementType.R4: - return new VariableEncryption(DataNodeType.Float32, random); - case ElementType.R8: - return new VariableEncryption(DataNodeType.Float64, random); - default: throw new Exception($"Unsupported type {type} for MemoryEncryptor"); - } - } - - public void Encrypt(FieldDef field, List outputInstructions, MemoryEncryptionContext ctx) - { - - ElementType type = field.FieldType.RemovePinnedAndModifiers().ElementType; - var encryption = CreateEncryption(type); - - encryption.EmitTransform(outputInstructions, new EncryptionCompileContext { module = _module }); - - outputInstructions.Add(ctx.currentInstruction.Clone()); - } - - public void Decrypt(FieldDef field, List outputInstructions, MemoryEncryptionContext ctx) - { - outputInstructions.Add(ctx.currentInstruction.Clone()); - ElementType type = field.FieldType.RemovePinnedAndModifiers().ElementType; - var encryption = CreateEncryption(type); - - encryption.EmitRevertTransform(outputInstructions, new EncryptionCompileContext { module = _module }); - } - } - - private readonly Dictionary _moduleEncryptors = new Dictionary(); - - private ModuleDefaultMemoryEncryptor GetModuleEncryptor(ModuleDef module) - { - if (!_moduleEncryptors.TryGetValue(module, out var encryptor)) - { - encryptor = new ModuleDefaultMemoryEncryptor(module); - _moduleEncryptors.Add(module, encryptor); - } - return encryptor; - } - - public override void Encrypt(FieldDef field, List outputInstructions, MemoryEncryptionContext ctx) - { - GetModuleEncryptor(ctx.module).Encrypt(field, outputInstructions, ctx); - } - - public override void Decrypt(FieldDef field, List outputInstructions, MemoryEncryptionContext ctx) - { - GetModuleEncryptor(ctx.module).Decrypt(field, outputInstructions, ctx); - } - } -} diff --git a/Editor/ObfusPasses/MemEncrypt/IMemEncryptor.cs b/Editor/ObfusPasses/MemEncrypt/IMemEncryptor.cs deleted file mode 100644 index 258be56..0000000 --- a/Editor/ObfusPasses/MemEncrypt/IMemEncryptor.cs +++ /dev/null @@ -1,29 +0,0 @@ -using dnlib.DotNet; -using dnlib.DotNet.Emit; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Obfuz.ObfusPasses.MemEncrypt -{ - public class MemoryEncryptionContext - { - public ModuleDef module; - - public Instruction currentInstruction; - } - - public interface IMemEncryptor - { - void Encrypt(FieldDef field, List outputInstructions, MemoryEncryptionContext ctx); - - void Decrypt(FieldDef field, List outputInstructions, MemoryEncryptionContext ctx); - } - - public abstract class MemEncryptorBase : IMemEncryptor - { - public abstract void Encrypt(FieldDef field, List outputInstructions, MemoryEncryptionContext ctx); - public abstract void Decrypt(FieldDef field, List outputInstructions, MemoryEncryptionContext ctx); - } -} diff --git a/Editor/ObfuscatorBuilder.cs b/Editor/ObfuscatorBuilder.cs index 5b5dc4c..d6f092d 100644 --- a/Editor/ObfuscatorBuilder.cs +++ b/Editor/ObfuscatorBuilder.cs @@ -1,9 +1,8 @@ using Obfuz.ObfusPasses; using Obfuz.ObfusPasses.CallObfus; -using Obfuz.ObfusPasses.CleanUp; using Obfuz.ObfusPasses.ConstEncrypt; using Obfuz.ObfusPasses.ExprObfus; -using Obfuz.ObfusPasses.MemEncrypt; +using Obfuz.ObfusPasses.FieldEncrypt; using Obfuz.ObfusPasses.SymbolObfus; using Obfuz.Settings; using System.Collections.Generic; @@ -77,7 +76,7 @@ namespace Obfuz ObfuscationPassType obfuscationPasses = settings.enabledObfuscationPasses; if (obfuscationPasses.HasFlag(ObfuscationPassType.MemoryEncryption)) { - builder.AddPass(new MemEncryptPass()); + builder.AddPass(new FieldEncryptPass()); } if (obfuscationPasses.HasFlag(ObfuscationPassType.CallProxy)) { diff --git a/Editor/Settings/FieldEncryptSettings.cs b/Editor/Settings/FieldEncryptSettings.cs new file mode 100644 index 0000000..93b4532 --- /dev/null +++ b/Editor/Settings/FieldEncryptSettings.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class FieldEncryptSettings + { + [Tooltip("config xml files")] + public string[] configFiles; + } +}