From a6863b508992d1fe23582ef5b452eed88aceb98c Mon Sep 17 00:00:00 2001 From: walon Date: Wed, 28 May 2025 17:23:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B8=B8=E9=87=8F=E5=8A=A0=E5=AF=86=E6=94=AF?= =?UTF-8?q?=E6=8C=81array=EF=BC=88byte[]=EF=BC=8Cint[]=EF=BC=8Cfloat[]?= =?UTF-8?q?=EF=BC=8Cint[,]=E7=AD=89=E7=AD=89=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Editor/Emit/DefaultMetadataImporter.cs | 7 ++ .../ConstEncrypt/ConstEncryptPass.cs | 51 ++++---- .../ConstEncrypt/DefaultConstEncryptor.cs | 73 +++++++---- .../ConstEncrypt/IConstEncryptor.cs | 3 +- .../Settings/SymbolObfuscationSettings.cs | 4 +- .../Editor/Unity/LinkXmlProcess.cs | 13 ++ .../Runtime/EncryptionService.cs | 5 + .../Runtime/EncryptorBase.cs | 114 ++++++++++++++++-- .../Runtime/IEncryptor.cs | 2 + 9 files changed, 210 insertions(+), 62 deletions(-) diff --git a/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs b/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs index b614e21..639d47f 100644 --- a/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs +++ b/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs @@ -31,6 +31,8 @@ namespace Obfuz.Emit private IMethod _decryptFromRvaString; private IMethod _decryptFromRvaBytes; + private IMethod _decryptInitializeArray; + public IMethod EncryptBlock => _encryptBlock; public IMethod DecryptBlock => _decryptBlock; @@ -54,6 +56,8 @@ namespace Obfuz.Emit public IMethod DecryptFromRvaBytes => _decryptFromRvaBytes; public IMethod DecryptFromRvaString => _decryptFromRvaString; + public IMethod DecryptInitializeArray => _decryptInitializeArray; + public EncryptionServiceMetadataImporter(ModuleDef mod, Type encryptionServiceType) { _module = mod; @@ -99,6 +103,8 @@ namespace Obfuz.Emit Assert.IsNotNull(_decryptFromRvaBytes); _decryptFromRvaString = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaString", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); Assert.IsNotNull(_decryptFromRvaString); + _decryptInitializeArray = mod.Import(encryptionServiceType.GetMethod("DecryptInitializeArray", new[] { typeof(System.Array), typeof(System.RuntimeFieldHandle), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptInitializeArray); } } @@ -194,5 +200,6 @@ namespace Obfuz.Emit public IMethod DecryptFromRvaBytes => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaBytes; public IMethod DecryptFromRvaString => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaString; + public IMethod DecryptInitializeArray => _defaultEncryptionServiceMetadataImporter.DecryptInitializeArray; } } diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs index 6b37b1e..634561a 100644 --- a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs @@ -2,12 +2,14 @@ using dnlib.DotNet.Emit; using Obfuz.Emit; using Obfuz.Settings; +using Obfuz.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using UnityEngine; using UnityEngine.Assertions; namespace Obfuz.ObfusPasses.ConstEncrypt @@ -113,34 +115,27 @@ namespace Obfuz.ObfusPasses.ConstEncrypt } 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; - // } - // } - //} + 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(); + if (ravFieldDef.Module != method.Module) + { + return false; + } + byte[] data = ravFieldDef.InitialValue; + if (data != null && data.Length > 0 && _dataObfuscatorPolicy.NeedObfuscateArray(method, currentInLoop, data)) + { + // don't need cache for byte array obfuscation + needCache = false; + _dataObfuscator.ObfuscateBytes(method, needCache, ravFieldDef, data, outputInstructions); + return true; + } + } + } return false; } default: return false; diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs index 9807026..b0b90a5 100644 --- a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using NUnit.Framework; using System.Text; using Obfuz.Settings; +using UnityEngine.Assertions.Must; namespace Obfuz.ObfusPasses.ConstEncrypt { @@ -144,30 +145,60 @@ namespace Obfuz.ObfusPasses.ConstEncrypt obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaDouble)); } - public void ObfuscateBytes(MethodDef method, bool needCacheValue, byte[] value, List obfuscatedInstructions) + + class EncryptedRvaDataInfo { - 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; - //} + public readonly FieldDef fieldDef; + public readonly byte[] originalBytes; + public readonly byte[] encryptedBytes; + public readonly int opts; + public readonly int salt; - //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); + public EncryptedRvaDataInfo(FieldDef fieldDef, byte[] originalBytes, byte[] encryptedBytes, int opts, int salt) + { + this.fieldDef = fieldDef; + this.originalBytes = originalBytes; + this.encryptedBytes = encryptedBytes; + this.opts = opts; + this.salt = salt; + } + } - //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)); + private readonly Dictionary _encryptedRvaFields = new Dictionary(); + + private EncryptedRvaDataInfo GetEncryptedRvaData(FieldDef fieldDef) + { + if (!_encryptedRvaFields.TryGetValue(fieldDef, out var encryptedRvaData)) + { + EncryptionScopeInfo encryptionScope = _encryptionScopeProvider.GetScope(fieldDef.Module); + IRandom random = CreateRandomForValue(encryptionScope, FieldEqualityComparer.CompareDeclaringTypes.GetHashCode(fieldDef)); + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + byte[] originalBytes = fieldDef.InitialValue; + byte[] encryptedBytes = (byte[])originalBytes.Clone(); + encryptionScope.encryptor.EncryptBlock(encryptedBytes, ops, salt); + Assert.AreNotEqual(originalBytes, encryptedBytes, "Original bytes should not be the same as encrypted bytes."); + encryptedRvaData = new EncryptedRvaDataInfo(fieldDef, originalBytes, encryptedBytes, ops, salt); + _encryptedRvaFields.Add(fieldDef, encryptedRvaData); + fieldDef.InitialValue = encryptedBytes; + byte[] decryptedBytes = (byte[])encryptedBytes.Clone(); + encryptionScope.encryptor.DecryptBlock(decryptedBytes, ops, salt); + Assert.AreEqual(originalBytes, decryptedBytes, "Decrypted bytes should match the original bytes after encryption and decryption."); + } + return encryptedRvaData; + } + + + public void ObfuscateBytes(MethodDef method, bool needCacheValue, FieldDef field, byte[] value, List obfuscatedInstructions) + { + EncryptedRvaDataInfo encryptedData = GetEncryptedRvaData(field); + Assert.AreEqual(value.Length, encryptedData.encryptedBytes.Length); + + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(encryptedData.encryptedBytes.Length)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(encryptedData.opts)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(encryptedData.salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptInitializeArray)); } public void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions) diff --git a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs index 7043aec..26cf9aa 100644 --- a/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs +++ b/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs @@ -17,7 +17,7 @@ namespace Obfuz.ObfusPasses.ConstEncrypt void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions); - void ObfuscateBytes(MethodDef method, bool needCacheValue, byte[] value, List obfuscatedInstructions); + void ObfuscateBytes(MethodDef method, bool needCacheValue, FieldDef field, byte[] value, List obfuscatedInstructions); } public abstract class ConstEncryptorBase : IConstEncryptor @@ -28,5 +28,6 @@ namespace Obfuz.ObfusPasses.ConstEncrypt public abstract void ObfuscateInt(MethodDef method, bool needCacheValue, int value, List obfuscatedInstructions); public abstract void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List obfuscatedInstructions); public abstract void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions); + public abstract void ObfuscateBytes(MethodDef method, bool needCacheValue, FieldDef field, byte[] value, List obfuscatedInstructions); } } diff --git a/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs b/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs index e7ca08f..ae7f274 100644 --- a/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs +++ b/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs @@ -47,8 +47,8 @@ namespace Obfuz.Settings obfuscatedNamePrefix = obfuscatedNamePrefix, useConsistentNamespaceObfuscation = useConsistentNamespaceObfuscation, symbolMappingFile = symbolMappingFile, - ruleFiles = ruleFiles.ToList(), - customRenamePolicyTypes = customRenamePolicyTypes.Select(typeName => ReflectionUtil.FindUniqueTypeInCurrentAppDomain(typeName)).ToList(), + ruleFiles = ruleFiles?.ToList() ?? new List(), + customRenamePolicyTypes = customRenamePolicyTypes?.Select(typeName => ReflectionUtil.FindUniqueTypeInCurrentAppDomain(typeName)).ToList() ?? new List(), }; } } diff --git a/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs b/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs index f307da7..8ad335d 100644 --- a/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs +++ b/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs @@ -26,6 +26,19 @@ namespace Obfuz.Unity return GenerateAdditionalLinkXmlFile(data.target); } +#if !UNITY_2021_2_OR_NEWER + + public void OnBeforeRun(BuildReport report, UnityLinkerBuildPipelineData data) + { + throw new NotImplementedException(); + } + + public void OnAfterRun(BuildReport report, UnityLinkerBuildPipelineData data) + { + throw new NotImplementedException(); + } +#endif + public static string GenerateAdditionalLinkXmlFile(BuildTarget target) { ObfuzSettings settings = ObfuzSettings.Instance; diff --git a/com.code-philosophy.obfuz/Runtime/EncryptionService.cs b/com.code-philosophy.obfuz/Runtime/EncryptionService.cs index ccd0cea..c1428eb 100644 --- a/com.code-philosophy.obfuz/Runtime/EncryptionService.cs +++ b/com.code-philosophy.obfuz/Runtime/EncryptionService.cs @@ -122,5 +122,10 @@ namespace Obfuz { return Decrypt(data, offset, bytesLength, ops, salt); } + + public static void DecryptInitializeArray(System.Array arr, System.RuntimeFieldHandle field, int length, int ops, int salt) + { + _encryptor.DecryptInitializeArray(arr, field, length, ops, salt); + } } } diff --git a/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs b/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs index 15416ad..76327cf 100644 --- a/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs +++ b/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs @@ -1,5 +1,7 @@ using JetBrains.Annotations; using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.Assertions; @@ -247,24 +249,116 @@ namespace Obfuz public virtual unsafe void DecryptBlock(byte[] data, int ops, int salt) { - int length = data.Length; + fixed(byte* dataPtr = &data[0]) + { + DecryptBlock(dataPtr, data.Length, ops, salt); + } + } + + private unsafe void DecryptBlock(byte* data, int length, int ops, int salt) + { int intArrLength = length >> 2; - fixed (byte* dstBytePtr = &data[0]) + int* dstIntPtr = (int*)data; + int last = 0; + for (int i = 0; i < intArrLength; i++) { - int* dstIntPtr = (int*)dstBytePtr; - int last = 0; - for (int i = 0; i < intArrLength; i++) - { - int oldLast = last; - last = dstIntPtr[i]; - dstIntPtr[i] = Decrypt(oldLast ^ last, ops, salt); - } + int oldLast = last; + last = dstIntPtr[i]; + dstIntPtr[i] = Decrypt(oldLast ^ last, ops, salt); } for (int i = intArrLength * 4; i < length; i++) { data[i] = (byte)(data[i] ^ salt); } } + + public virtual unsafe void DecryptInitializeArray(System.Array arr, System.RuntimeFieldHandle field, int length, int ops, int salt) + { + //Assert.AreEqual(Marshal.SizeOf(arr.GetType().GetElementType()), arr.Length); + RuntimeHelpers.InitializeArray(arr, field); + if (arr is byte[] byteArr) + { + fixed (byte* dataPtr = &byteArr[0]) + { + DecryptBlock(dataPtr, length, ops, salt); + } + } + else if (arr is int[] intArr) + { + fixed (int* dataPtr = &intArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is long[] longArr) + { + fixed (long* dataPtr = &longArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is sbyte[] sbyteArr) + { + fixed (sbyte* dataPtr = &sbyteArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is short[] shortArr) + { + fixed (short* dataPtr = &shortArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is ushort[] ushortArr) + { + fixed (ushort* dataPtr = &ushortArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is uint[] uintArr) + { + fixed (uint* dataPtr = &uintArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is ulong[] ulongArr) + { + fixed (ulong* dataPtr = &ulongArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is float[] floatArr) + { + fixed (float* dataPtr = &floatArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is double[] doubleArr) + { + fixed (double* dataPtr = &doubleArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else + { + void* dataPtr = UnsafeUtility.PinGCArrayAndGetDataAddress(arr, out ulong handle); + try + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + finally + { + UnsafeUtility.ReleaseGCObject(handle); + } + } + } } } diff --git a/com.code-philosophy.obfuz/Runtime/IEncryptor.cs b/com.code-philosophy.obfuz/Runtime/IEncryptor.cs index 0d8fdaa..a098ba7 100644 --- a/com.code-philosophy.obfuz/Runtime/IEncryptor.cs +++ b/com.code-philosophy.obfuz/Runtime/IEncryptor.cs @@ -28,5 +28,7 @@ namespace Obfuz byte[] Encrypt(string value, int ops, int salt); string DecryptString(byte[] value, int offset, int stringBytesLength, int ops, int salt); + + void DecryptInitializeArray(System.Array arr, System.RuntimeFieldHandle field, int length, int ops, int salt); } }