diff --git a/Editor/EncryptionVM/IEncryptionInstruction.cs b/Editor/EncryptionVM/IEncryptionInstruction.cs index c1765ca..49808c4 100644 --- a/Editor/EncryptionVM/IEncryptionInstruction.cs +++ b/Editor/EncryptionVM/IEncryptionInstruction.cs @@ -1,15 +1,24 @@ -namespace Obfuz.EncryptionVM +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM { public interface IEncryptionInstruction { int Encrypt(int value, int[] secretKey, int salt); int Decrypt(int value, int[] secretKey, int salt); + + void GenerateEncryptCode(List lines, string indent); + + void GenerateDecryptCode(List lines, string indent); } public abstract class EncryptionInstructionBase : IEncryptionInstruction { public abstract int Encrypt(int value, int[] secretKey, int salt); public abstract int Decrypt(int value, int[] secretKey, int salt); + + public abstract void GenerateEncryptCode(List lines, string indent); + public abstract void GenerateDecryptCode(List lines, string indent); } } diff --git a/Editor/EncryptionVM/Instructions/AddInstruction.cs b/Editor/EncryptionVM/Instructions/AddInstruction.cs index e8af5b9..f6c31f3 100644 --- a/Editor/EncryptionVM/Instructions/AddInstruction.cs +++ b/Editor/EncryptionVM/Instructions/AddInstruction.cs @@ -1,4 +1,6 @@ -namespace Obfuz.EncryptionVM.Instructions +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions { public class AddInstruction : EncryptionInstructionBase { @@ -19,5 +21,15 @@ { return value - secretKey[_opKeyIndex] - salt - _addValue; } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value += _secretKey[{_opKeyIndex}] + salt + {_addValue};"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value -= _secretKey[{_opKeyIndex}] + salt + {_addValue};"); + } } } diff --git a/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs b/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs index c3271ed..2d9dcd1 100644 --- a/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs +++ b/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs @@ -1,4 +1,6 @@ -namespace Obfuz.EncryptionVM.Instructions +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions { public class BitRotateInstruction : EncryptionInstructionBase { @@ -25,5 +27,20 @@ uint part2 = value2 << (32 - _rotateBitNum); return (int)(part1 | part2); } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = ((int)(part1 | part2) ^ _secretKey[{_opKeyIndex}]) + salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)((value - salt) ^ _secretKey[{_opKeyIndex}]);"); + lines.Add(indent + $"uint part1 = value2 >> {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = value2 << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } } } diff --git a/Editor/EncryptionVM/Instructions/EncryptFunction.cs b/Editor/EncryptionVM/Instructions/EncryptFunction.cs index 3fc2a35..ed59848 100644 --- a/Editor/EncryptionVM/Instructions/EncryptFunction.cs +++ b/Editor/EncryptionVM/Instructions/EncryptFunction.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -32,5 +33,15 @@ namespace Obfuz.EncryptionVM.Instructions } return value; } + + public override void GenerateEncryptCode(List lines, string indent) + { + throw new NotImplementedException(); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + throw new NotImplementedException(); + } } } diff --git a/Editor/EncryptionVM/Instructions/XorInstruction.cs b/Editor/EncryptionVM/Instructions/XorInstruction.cs index 60b8c9a..ed26756 100644 --- a/Editor/EncryptionVM/Instructions/XorInstruction.cs +++ b/Editor/EncryptionVM/Instructions/XorInstruction.cs @@ -1,4 +1,6 @@ -namespace Obfuz.EncryptionVM.Instructions +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions { public class XorInstruction : EncryptionInstructionBase { @@ -20,5 +22,15 @@ { return value ^ secretKey[_opKeyIndex] ^ salt ^ _xorValue; } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= _secretKey[{_opKeyIndex}] ^ salt ^ {_xorValue};"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= _secretKey[{_opKeyIndex}] ^ salt ^ {_xorValue};"); + } } } diff --git a/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs b/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs index e16e0ce..da08a3d 100644 --- a/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs +++ b/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs @@ -1,27 +1,204 @@ 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 string _vmCodeGenerateSecretKey; - private readonly IRandom _random; - private VirtualMachine _vm; + private readonly int _opCodeCount; + private readonly int _opCodeBits; + private readonly VirtualMachine _vm; - public VirtualMachineCodeGenerator(string vmCodeGenerateSecretKey, int opCount) + public VirtualMachineCodeGenerator(string vmCodeGenerateSecretKey, int opCodeCount) { - _vmCodeGenerateSecretKey = vmCodeGenerateSecretKey; - _vm = new VirtualMachineCreator(_vmCodeGenerateSecretKey).CreateVirtualMachine(opCount); + _opCodeCount = opCodeCount; + _opCodeBits = GetBitCount(opCodeCount - 1); + _vm = new VirtualMachineCreator(vmCodeGenerateSecretKey).CreateVirtualMachine(opCodeCount); + } + + public VirtualMachineCodeGenerator(VirtualMachine vm) + { + _opCodeCount = vm.opCodes.Length; + _opCodeBits = GetBitCount(_opCodeCount - 1); + _vm = vm; + } + + private static int GetBitCount(int value) + { + int count = 0; + while (value > 0) + { + count++; + value >>= 1; + } + return count; + } + + public bool ValidateMatch(string outputFile) + { + if (!File.Exists(outputFile)) + { + return false; + } + string oldCode = NormalizeText(File.ReadAllText(outputFile, Encoding.UTF8)); + string newCode = NormalizeText(GenerateCode()); + return oldCode == newCode; + } + + private static string NormalizeText(string input) + { + return Regex.Replace(input, @"\s+", string.Empty); } public void Generate(string outputFile) { + FileUtil.CreateParentDir(outputFile); + string code = GenerateCode(); + + File.WriteAllText(outputFile, code, Encoding.UTF8); + Debug.Log($"Generate EncryptionVM code to {outputFile}"); + UnityEditor.AssetDatabase.Refresh(); + } + + private string GenerateCode() + { + var lines = new List(); + AppendHeader(lines); + AppendEncryptCodes(lines); + AppendDecryptCodes(lines); + AppendTailer(lines); + return string.Join("\n", lines); + } + + private void AppendEncryptCodes(List lines) + { + lines.Add(@" + private int ExecuteEncrypt(int value, int opCode, int salt) + { + switch (opCode) + {"); + foreach (var opCode in _vm.opCodes) + { + lines.Add(@$" case {opCode.code}: + {{"); + AppendEncryptCode(lines, opCode.function); + lines.Add(@" + return value; + }"); + } + + lines.Add(@" + default: + throw new System.Exception($""Invalid opCode:{opCode}""); + } + }"); + } + + private void AppendDecryptCodes(List lines) + { + lines.Add(@" + private int ExecuteDecrypt(int value, int opCode, int salt) + { + switch (opCode) + {"); + foreach (var opCode in _vm.opCodes) + { + lines.Add(@$" case {opCode.code}: + {{"); + AppendDecryptCode(lines, opCode.function); + lines.Add(@" + return value; + }"); + } + + lines.Add(@" + default: + throw new System.Exception($""Invalid opCode:{opCode}""); + } + }"); + } + + private void AppendHeader(List lines) + { + + lines.Add($"/// This file is auto-generated by Obfuz. Do not modify it."); + lines.Add($"///"); + //lines.Add($"/// Created Time: {DateTime.Now}"); + + lines.Add($"/// Version: {_vm.version}"); + lines.Add($"/// SecretKey: {_vm.codeGenerationSecretKey}"); + lines.Add($"/// OpCodeCount: {_vm.opCodes.Length}"); + + lines.Add(@" +namespace Obfuz.EncryptionVM +{ + public class GeneratedEncryptionVirtualMachine : Obfuz.EncryptorBase + {"); + lines.Add(@$" + private const int OpCodeBits = {_opCodeBits}; + + private const int OpCodeCount = {_opCodeCount}; + + private const int OpCodeMask = {_opCodeCount - 1}; +"); + lines.Add(@" + + private readonly int[] _secretKey; + + public GeneratedEncryptionVirtualMachine(byte[] secretKey) + { + this._secretKey = ConvertToIntKey(secretKey); + } + + public override int Encrypt(int value, int opts, int salt) + { + while (opts > 0) + { + int opCode = opts & OpCodeMask; + value = ExecuteEncrypt(value, opCode, salt); + opts >>= OpCodeBits; + } + return value; + } + + public override int Decrypt(int value, int opts, int salt) + { + while (opts > 0) + { + int opCode = opts & OpCodeMask; + value = ExecuteDecrypt(value, opCode, salt); + opts >>= OpCodeBits; + } + return value; + } +"); + } + + private void AppendTailer(List lines) + { + lines.Add(@" + } +} + +"); + } + + private void AppendEncryptCode(List lines, IEncryptionInstruction instruction) + { + instruction.GenerateEncryptCode(lines, " "); + } + + private void AppendDecryptCode(List lines, IEncryptionInstruction instruction) + { + instruction.GenerateDecryptCode(lines, " "); } } } diff --git a/Editor/Obfuscator.cs b/Editor/Obfuscator.cs index c903a42..91574ea 100644 --- a/Editor/Obfuscator.cs +++ b/Editor/Obfuscator.cs @@ -33,6 +33,7 @@ namespace Obfuz private readonly int _globalRandomSeed; private readonly string _encryptionVmGenerationSecretKey; private readonly int _encryptionVmOpCodeCount; + private readonly string _encryptionVmCodeFile; private ObfuscationPassContext _ctx; @@ -42,6 +43,7 @@ namespace Obfuz _globalRandomSeed = builder.GlobalRandomSeed; _encryptionVmGenerationSecretKey = builder.EncryptionVmGenerationSecretKey; _encryptionVmOpCodeCount = builder.EncryptionVmOpCodeCount; + _encryptionVmCodeFile = builder.EncryptionVmCodeFile; _toObfuscatedAssemblyNames = builder.ToObfuscatedAssemblyNames; _notObfuscatedAssemblyNamesReferencingObfuscated = builder.NotObfuscatedAssemblyNamesReferencingObfuscated; @@ -67,7 +69,55 @@ namespace Obfuz { var vmCreator = new VirtualMachineCreator(_encryptionVmGenerationSecretKey); var vm = vmCreator.CreateVirtualMachine(_encryptionVmOpCodeCount); - return new VirtualMachineSimulator(vm, _secretKey); + var vmGenerator = new VirtualMachineCodeGenerator(vm); + + if (!File.Exists(_encryptionVmCodeFile)) + { + throw new Exception($"EncryptionVm CodeFile:`{_encryptionVmCodeFile}` not exists! Please run `Obfuz/GenerateVm` to generate it!"); + } + if (!vmGenerator.ValidateMatch(_encryptionVmCodeFile)) + { + throw new Exception($"EncryptionVm CodeFile:`{_encryptionVmCodeFile}` not match with encryptionVM settings! Please run `Obfuz/GenerateVm` to update it!"); + } + var vms = new VirtualMachineSimulator(vm, _secretKey); + + var generatedVmTypes = AppDomain.CurrentDomain.GetAssemblies() + .Select(assembly => assembly.GetType("Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine")) + .Where(type => type != null) + .ToList(); + if (generatedVmTypes.Count == 0) + { + throw new Exception($"class Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine not found in any assembly! Please run `Obfuz/GenerateVm` to generate it!"); + } + if (generatedVmTypes.Count > 1) + { + throw new Exception($"class Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine found in multiple assemblies! Please retain only one!"); + } + + var gvmInstance = (IEncryptor)Activator.CreateInstance(generatedVmTypes[0], new object[] { _secretKey } ); + + int testValue = 11223344; + for (int i = 0; i < vm.opCodes.Length; i++) + { + int encryptedValueOfVms = vms.Encrypt(testValue, i, i); + int decryptedValueOfVms = vms.Decrypt(encryptedValueOfVms, i, i); + if (decryptedValueOfVms != testValue) + { + throw new Exception($"VirtualMachineSimulator decrypt failed! opCode:{i}, originalValue:{testValue} decryptedValue:{decryptedValueOfVms}"); + } + int encryptedValueOfGvm = gvmInstance.Encrypt(testValue, i, i); + int decryptedValueOfGvm = gvmInstance.Decrypt(encryptedValueOfGvm, i, i); + if (encryptedValueOfGvm != encryptedValueOfVms) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testValue} encryptedValue VirtualMachineSimulator:{encryptedValueOfVms} GeneratedEncryptionVirtualMachine:{encryptedValueOfGvm}"); + } + if (decryptedValueOfGvm != testValue) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt failed! opCode:{i}, originalValue:{testValue} decryptedValue:{decryptedValueOfGvm}"); + } + } + + return vms; } private void OnPreObfuscation() diff --git a/Editor/ObfuscatorBuilder.cs b/Editor/ObfuscatorBuilder.cs index 9d0287f..dcdfb1f 100644 --- a/Editor/ObfuscatorBuilder.cs +++ b/Editor/ObfuscatorBuilder.cs @@ -1,4 +1,5 @@ -using Obfuz.ObfusPasses; +using Obfuz.EncryptionVM; +using Obfuz.ObfusPasses; using Obfuz.ObfusPasses.CallObfus; using Obfuz.ObfusPasses.ConstEncrypt; using Obfuz.ObfusPasses.ExprObfus; @@ -6,6 +7,7 @@ using Obfuz.ObfusPasses.FieldEncrypt; using Obfuz.ObfusPasses.SymbolObfus; using Obfuz.Settings; using System.Collections.Generic; +using System.IO; using System.Linq; using UnityEditor; @@ -17,6 +19,8 @@ namespace Obfuz private int _globalRandomSeed; private string _encryptionVmGenerationSecretKey; private int _encryptionVmOpCodeCount; + public string _encryptionVmCodeFile; + private List _toObfuscatedAssemblyNames = new List(); private List _notObfuscatedAssemblyNamesReferencingObfuscated = new List(); private List _assemblySearchDirs = new List(); @@ -48,6 +52,12 @@ namespace Obfuz set => _encryptionVmOpCodeCount = value; } + public string EncryptionVmCodeFile + { + get => _encryptionVmCodeFile; + set => _encryptionVmCodeFile = value; + } + public List ToObfuscatedAssemblyNames { get => _toObfuscatedAssemblyNames; @@ -98,6 +108,7 @@ namespace Obfuz _globalRandomSeed = settings.globalRandomSeed, _encryptionVmGenerationSecretKey = settings.encryptionVMSettings.codeGenerationSecretKey, _encryptionVmOpCodeCount = settings.encryptionVMSettings.encryptionOpCodeCount, + _encryptionVmCodeFile = settings.encryptionVMSettings.CodeOutputPath, _toObfuscatedAssemblyNames = settings.toObfuscatedAssemblyNames.ToList(), _notObfuscatedAssemblyNamesReferencingObfuscated = settings.notObfuscatedAssemblyNamesReferencingObfuscated.ToList(), _assemblySearchDirs = settings.extraAssemblySearchDirs.ToList(), diff --git a/Editor/Settings/EncryptionVMSettings.cs b/Editor/Settings/EncryptionVMSettings.cs index d600ba2..c9fc363 100644 --- a/Editor/Settings/EncryptionVMSettings.cs +++ b/Editor/Settings/EncryptionVMSettings.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -18,5 +19,7 @@ namespace Obfuz.Settings [Tooltip("encryption virtual machine source code output dir")] public string codeOutputDir = "Assets/Obfuz"; + + public string CodeOutputPath { get => Path.Combine(codeOutputDir, "GeneratedEncryptionVirtualMachine.cs"); } } } diff --git a/Editor/Unity/ObfuzMenu.cs b/Editor/Unity/ObfuzMenu.cs index 4bf51a8..2a80be1 100644 --- a/Editor/Unity/ObfuzMenu.cs +++ b/Editor/Unity/ObfuzMenu.cs @@ -1,3 +1,5 @@ +using Obfuz.EncryptionVM; +using Obfuz.Settings; using UnityEditor; using UnityEngine; @@ -6,9 +8,17 @@ namespace Obfuz.Unity public static class ObfuzMenu { - [MenuItem("Obfuz/Settings...", priority = 61)] + [MenuItem("Obfuz/Settings...", priority = 1)] public static void OpenSettings() => SettingsService.OpenProjectSettings("Project/Obfuz"); + [MenuItem("Obfuz/GenerateVM", priority = 62)] + public static void GenerateEncryptionVM() + { + EncryptionVMSettings settings = ObfuzSettings.Instance.encryptionVMSettings; + var generator = new VirtualMachineCodeGenerator(settings.codeGenerationSecretKey, settings.encryptionOpCodeCount); + generator.Generate(settings.CodeOutputPath); + } + [MenuItem("Obfuz/Documents/Quick Start")] public static void OpenQuickStart() => Application.OpenURL("https://obfuz.doc.code-philosophy.com/docs/beginner/quickstart"); diff --git a/Editor/Utils/FileUtil.cs b/Editor/Utils/FileUtil.cs index b400d20..ccd210d 100644 --- a/Editor/Utils/FileUtil.cs +++ b/Editor/Utils/FileUtil.cs @@ -10,6 +10,10 @@ namespace Obfuz.Utils { public static class FileUtil { + public static void CreateParentDir(string path) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } public static void RemoveDir(string dir, bool log = false) { diff --git a/Runtime/EncryptorBase.cs b/Runtime/EncryptorBase.cs index 360b27f..efa93d9 100644 --- a/Runtime/EncryptorBase.cs +++ b/Runtime/EncryptorBase.cs @@ -1,11 +1,21 @@ using System; using System.Text; using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.Assertions; namespace Obfuz { public abstract class EncryptorBase : IEncryptor { + public static int[] ConvertToIntKey(byte[] key) + { + Assert.AreEqual(0, key.Length % 4); + int align4Length = key.Length / 4; + int[] intKey = new int[align4Length]; + Buffer.BlockCopy(key, 0, intKey, 0, key.Length); + return intKey; + } + public abstract int Encrypt(int value, int opts, int salt); public abstract int Decrypt(int value, int opts, int salt);