From f1b3bd3329e4811261e03806cbdae85576e90cd0 Mon Sep 17 00:00:00 2001 From: walon Date: Tue, 22 Apr 2025 19:34:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BB=8Erva=20data=E4=B8=AD?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E5=B8=B8=E9=87=8F=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Editor/Emit/RvaDataAllocator.cs | 217 ++++++++++++++++++ Editor/Obfuz.Editor.asmdef | 4 +- Editor/Virtualization/CompileContext.cs | 2 + .../Virtualization/CreateExpressionOptions.cs | 3 +- .../DataNodes/ConstFromFieldRvaDataNode.cs | 130 +++++++++++ .../Virtualization/DataNodes/DataNodeBase.cs | 9 +- .../Virtualization/DataVirtualizationPass.cs | 14 +- .../Virtualization/DefaultDataObfuscator.cs | 19 +- Editor/Virtualization/IDataNode.cs | 2 + Editor/Virtualization/IDataObfuscator.cs | 1 + .../Virtualization/RandomDataNodeCreator.cs | 8 +- 11 files changed, 398 insertions(+), 11 deletions(-) create mode 100644 Editor/Emit/RvaDataAllocator.cs create mode 100644 Editor/Virtualization/DataNodes/ConstFromFieldRvaDataNode.cs diff --git a/Editor/Emit/RvaDataAllocator.cs b/Editor/Emit/RvaDataAllocator.cs new file mode 100644 index 0000000..7ee2924 --- /dev/null +++ b/Editor/Emit/RvaDataAllocator.cs @@ -0,0 +1,217 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine.Assertions; + +namespace Obfuz.Emit +{ + 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 RvaDataAllocator + { + // randomized + const int maxRvaDataSize = 0x100; + + private readonly IRandom _random; + + class RvaField + { + public FieldDef holderDataField; + public FieldDef runtimeValueField; + public uint size; + public List bytes; + + public void FillPadding() + { + // fill with random value + for (int i = bytes.Count; i < size; i++) + { + bytes.Add(0xAB); + } + } + } + + private readonly List _rvaFields = new List(); + private RvaField _currentField; + + + private TypeDef _rvaTypeDef; + + private readonly Dictionary<(ModuleDef, int), TypeDef> _dataHolderTypeBySizes = new Dictionary<(ModuleDef, int), TypeDef>(); + + public RvaDataAllocator(IRandom random) + { + _random = random; + } + + private (FieldDef, FieldDef) CreateDataHolderRvaField(ModuleDef mod, TypeDef dataHolderType) + { + if (_rvaTypeDef == null) + { + mod.EnableTypeDefFindCache = false; + //_rvaTypeDef = mod.Find("$ObfuzRVA$", false); + //if (_rvaTypeDef != null) + //{ + // throw new Exception($"can't obfuscate a obfuscated assembly"); + //} + ITypeDefOrRef objectTypeRef = mod.Import(typeof(object)); + _rvaTypeDef = new TypeDefUser("$Obfuz$RVA$",objectTypeRef); + mod.Types.Add(_rvaTypeDef); + mod.EnableTypeDefFindCache = true; + } + + + var holderField = new FieldDefUser($"$RVA_Data{_rvaTypeDef.Fields.Count}", new FieldSig(dataHolderType.ToTypeSig()), FieldAttributes.InitOnly | FieldAttributes.Static | FieldAttributes.HasFieldRVA); + holderField.DeclaringType = _rvaTypeDef; + + var runtimeValueField = new FieldDefUser($"$RVA_Value{_rvaTypeDef.Fields.Count}", new FieldSig(new SZArraySig(mod.CorLibTypes.Byte)), FieldAttributes.Static); + runtimeValueField.DeclaringType = _rvaTypeDef; + return (holderField, runtimeValueField); + } + + private TypeDef GetDataHolderType(ModuleDef mod, int size) + { + size = (size + 15) & ~15; // align to 6 bytes + if (_dataHolderTypeBySizes.TryGetValue((mod, size), out var type)) + return type; + var dataHolderType = new TypeDefUser($"$ObfuzRVA$DataHolder{size}", mod.Import(typeof(ValueType))); + dataHolderType.Layout = TypeAttributes.ExplicitLayout; + dataHolderType.PackingSize = 1; + dataHolderType.ClassSize = (uint)size; + _dataHolderTypeBySizes.Add((mod, size), dataHolderType); + mod.Types.Add(dataHolderType); + return dataHolderType; + } + + private static int AlignTo(int size, int alignment) + { + return (size + alignment - 1) & ~(alignment - 1); + } + + private RvaField CreateRvaField(ModuleDef mod, int size) + { + TypeDef dataHolderType = GetDataHolderType(mod, size); + var (holderDataField, runtimeValueField) = CreateDataHolderRvaField(mod, dataHolderType); + var newRvaField = new RvaField + { + holderDataField = holderDataField, + runtimeValueField = runtimeValueField, + size = dataHolderType.ClassSize, + bytes = new List((int)dataHolderType.ClassSize), + }; + _rvaFields.Add(newRvaField); + return newRvaField; + } + + private RvaField GetRvaField(ModuleDef mod, int preservedSize, int alignment) + { + Assert.IsTrue(preservedSize % alignment == 0); + // for big size, create a new field + if (preservedSize >= maxRvaDataSize) + { + return CreateRvaField(mod, preservedSize); + } + + if (_currentField != null) + { + int offset = AlignTo(_currentField.bytes.Count, alignment); + + int expectedSize = offset + preservedSize; + if (expectedSize <= _currentField.size) + { + // insert random padding + for (int i = _currentField.bytes.Count; i < offset; i++) + { + //_currentField.bytes.Add((byte)_random.NextInt(0, 256)); + // TODO replace with random value + _currentField.bytes.Add(0xAB); + } + return _currentField; + } + + _currentField.FillPadding(); + } + _currentField = CreateRvaField(mod, maxRvaDataSize); + return _currentField; + } + + public RvaData Allocate(ModuleDef mod, int value) + { + RvaField field = GetRvaField(mod, 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(ModuleDef mod, long value) + { + RvaField field = GetRvaField(mod, 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(ModuleDef mod, float value) + { + RvaField field = GetRvaField(mod, 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(ModuleDef mod, double value) + { + RvaField field = GetRvaField(mod, 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(ModuleDef mod, string value) + { + byte[] bytes = Encoding.UTF8.GetBytes(value); + return Allocate(mod, bytes); + } + + public RvaData Allocate(ModuleDef mod, byte[] value) + { + RvaField field = GetRvaField(mod, value.Length, 1); + int offset = field.bytes.Count; + field.bytes.AddRange(value); + return new RvaData(field.runtimeValueField, offset, value.Length); + } + + public void SetFieldsRVA() + { + foreach (var field in _rvaFields) + { + Assert.IsTrue(field.bytes.Count <= field.size); + if (field.bytes.Count < field.size) + { + field.FillPadding(); + } + field.holderDataField.InitialValue = field.bytes.ToArray(); + } + } + } +} diff --git a/Editor/Obfuz.Editor.asmdef b/Editor/Obfuz.Editor.asmdef index 9357018..8c0e02a 100644 --- a/Editor/Obfuz.Editor.asmdef +++ b/Editor/Obfuz.Editor.asmdef @@ -1,7 +1,9 @@ { "name": "Obfuz", "rootNamespace": "", - "references": [], + "references": [ + "GUID:7e4de3067c2ab5c43a03ac49273dfb68" + ], "includePlatforms": [ "Editor" ], diff --git a/Editor/Virtualization/CompileContext.cs b/Editor/Virtualization/CompileContext.cs index d23aacc..6a30ad4 100644 --- a/Editor/Virtualization/CompileContext.cs +++ b/Editor/Virtualization/CompileContext.cs @@ -1,6 +1,7 @@ using dnlib.DotNet; using dnlib.DotNet.Emit; using NUnit.Framework; +using Obfuz.Emit; using System.Collections.Generic; namespace Obfuz.Virtualization @@ -9,5 +10,6 @@ namespace Obfuz.Virtualization { public MethodDef method; public List output; + public RvaDataAllocator rvaDataAllocator; } } diff --git a/Editor/Virtualization/CreateExpressionOptions.cs b/Editor/Virtualization/CreateExpressionOptions.cs index 117d710..d4ca437 100644 --- a/Editor/Virtualization/CreateExpressionOptions.cs +++ b/Editor/Virtualization/CreateExpressionOptions.cs @@ -1,4 +1,5 @@ -using Obfuz.Utils; +using Obfuz.Emit; +using Obfuz.Utils; namespace Obfuz.Virtualization { diff --git a/Editor/Virtualization/DataNodes/ConstFromFieldRvaDataNode.cs b/Editor/Virtualization/DataNodes/ConstFromFieldRvaDataNode.cs new file mode 100644 index 0000000..80ffda0 --- /dev/null +++ b/Editor/Virtualization/DataNodes/ConstFromFieldRvaDataNode.cs @@ -0,0 +1,130 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static UnityEngine.Networking.UnityWebRequest; +using UnityEngine.UIElements; + +namespace Obfuz.Virtualization +{ + [NodeOutput(DataNodeType.Int32)] + [NodeOutput(DataNodeType.Int64)] + [NodeOutput(DataNodeType.Float32)] + [NodeOutput(DataNodeType.Float64)] + [NodeOutput(DataNodeType.Bytes)] + [NodeOutput(DataNodeType.String)] + public class ConstFromFieldRvaDataNode : DataNodeAny + { + + private RvaData AllocateRvaData(CompileContext ctx) + { + ModuleDef mod = ctx.method.Module; + RvaDataAllocator allocator = ctx.rvaDataAllocator; + switch (Type) + { + case DataNodeType.Int32: return allocator.Allocate(mod, IntValue); + case DataNodeType.Int64: return allocator.Allocate(mod, LongValue); + case DataNodeType.Float32: return allocator.Allocate(mod, FloatValue); + case DataNodeType.Float64: return allocator.Allocate(mod, DoubleValue); + case DataNodeType.Bytes: return allocator.Allocate(mod, BytesValue); + case DataNodeType.String: return allocator.Allocate(mod, StringValue); + default: + throw new NotSupportedException($"Unsupported type: {Type}."); + } + } + + private static IMethod s_convertInt; + private static IMethod s_convertLong; + private static IMethod s_convertFloat; + private static IMethod s_convertDouble; + + private static IMethod s_convertString; + private static IField s_Encoding_Utf8; + private static IMethod s_convertBytes; + + private void InitImportMetadatas(ModuleDef mod) + { + if (s_convertInt != null) + { + return; + } + s_convertInt = mod.Import(typeof(BitConverter).GetMethod("ToInt32", new[] { typeof(byte[]), typeof(int) })); + s_convertLong = mod.Import(typeof(BitConverter).GetMethod("ToInt64", new[] { typeof(byte[]), typeof(int) })); + s_convertFloat = mod.Import(typeof(BitConverter).GetMethod("ToSingle", new[] { typeof(byte[]), typeof(int) })); + s_convertDouble = mod.Import(typeof(BitConverter).GetMethod("ToDouble", new[] { typeof(byte[]), typeof(int) })); + s_convertString = mod.Import(typeof(Encoding).GetMethod("GetString", new[] { typeof(byte[]), typeof(int), typeof(int) })); + s_Encoding_Utf8 = mod.Import(typeof(Encoding).GetField("UTF8")); + s_convertBytes = mod.Import(typeof(Array).GetMethod("Copy", new[] { typeof(Array), typeof(int), typeof(Array), typeof(int), typeof(int) })); + } + + IMethod GetConvertMethod(ModuleDef mod) + { + InitImportMetadatas(mod); + switch (Type) + { + case DataNodeType.Int32: return s_convertInt; + case DataNodeType.Int64: return s_convertLong; + case DataNodeType.Float32: return s_convertFloat; + case DataNodeType.Float64: return s_convertDouble; + case DataNodeType.String: return s_convertString; + case DataNodeType.Bytes: return s_convertBytes; + default: throw new NotSupportedException($"Unsupported type: {Type}."); + } + } + + public override void Compile(CompileContext ctx) + { + // only support Int32, int64, bytes. + // string can only create from StringFromBytesNode + // x = memcpy array.GetRange(index, length); + var output = ctx.output; + RvaData rvaData = AllocateRvaData(ctx); + ModuleDef mod = ctx.method.Module; + IMethod convertMethod = GetConvertMethod(mod); + switch (Type) + { + case DataNodeType.Int32: + case DataNodeType.Int64: + case DataNodeType.Float32: + case DataNodeType.Float64: + { + ctx.output.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + output.Add(Instruction.Create(OpCodes.Ldc_I4, rvaData.offset)); + output.Add(Instruction.Create(OpCodes.Call, convertMethod)); + break; + } + case DataNodeType.String: + { + + // Encoding.UTF8.GetString(data, offset, length); + ctx.output.Add(Instruction.Create(OpCodes.Ldsfld, s_Encoding_Utf8)); + ctx.output.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + output.Add(Instruction.Create(OpCodes.Ldc_I4, rvaData.offset)); + output.Add(Instruction.Create(OpCodes.Ldc_I4, rvaData.size)); + output.Add(Instruction.Create(OpCodes.Call, convertMethod)); + break; + } + case DataNodeType.Bytes: + { + // byte[] result = new byte[length]; + // Array.Copy(data, offset, result, 0, length); + ctx.output.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + output.Add(Instruction.Create(OpCodes.Ldc_I4, rvaData.offset)); + output.Add(Instruction.Create(OpCodes.Ldc_I4, rvaData.size)); + output.Add(Instruction.Create(OpCodes.Newarr, new SZArraySig(mod.CorLibTypes.Byte).ToTypeDefOrRef())); + output.Add(Instruction.Create(OpCodes.Ldc_I4, 0)); + output.Add(Instruction.Create(OpCodes.Ldc_I4, rvaData.size)); + output.Add(Instruction.Create(OpCodes.Call, convertMethod)); + break; + } + default: + throw new NotSupportedException($"Unsupported type: {Type}."); + } + } + } +} diff --git a/Editor/Virtualization/DataNodes/DataNodeBase.cs b/Editor/Virtualization/DataNodes/DataNodeBase.cs index 06699b3..d8d7010 100644 --- a/Editor/Virtualization/DataNodes/DataNodeBase.cs +++ b/Editor/Virtualization/DataNodes/DataNodeBase.cs @@ -1,4 +1,6 @@ -namespace Obfuz.Virtualization +using dnlib.DotNet; + +namespace Obfuz.Virtualization { public abstract class DataNodeBase : IDataNode { @@ -18,6 +20,11 @@ public byte[] BytesValue => (byte[])Value; + public virtual void Init(CreateExpressionOptions options) + { + + } + public abstract void Compile(CompileContext ctx); } diff --git a/Editor/Virtualization/DataVirtualizationPass.cs b/Editor/Virtualization/DataVirtualizationPass.cs index ae91a27..c9acc49 100644 --- a/Editor/Virtualization/DataVirtualizationPass.cs +++ b/Editor/Virtualization/DataVirtualizationPass.cs @@ -23,18 +23,24 @@ namespace Obfuz.Virtualization public override void Stop(ObfuscatorContext ctx) { - + _dataObfuscator.Stop(); } public override void Process(ObfuscatorContext ctx) { foreach (var ass in ctx.assemblies) { - foreach (TypeDef type in ass.module.GetTypes()) + // ToArray to avoid modify list exception + foreach (TypeDef type in ass.module.GetTypes().ToArray()) { - foreach (MethodDef method in type.Methods) + if (type.Name.StartsWith("$Obfuz$")) { - if (!method.HasBody || !_dataObfuscatorPolicy.NeedObfuscateMethod(method)) + continue; + } + // ToArray to avoid modify list exception + foreach (MethodDef method in type.Methods.ToArray()) + { + if (!method.HasBody || method.Name.StartsWith("$Obfuz$") || !_dataObfuscatorPolicy.NeedObfuscateMethod(method)) { continue; } diff --git a/Editor/Virtualization/DefaultDataObfuscator.cs b/Editor/Virtualization/DefaultDataObfuscator.cs index 54e2b02..e5788c3 100644 --- a/Editor/Virtualization/DefaultDataObfuscator.cs +++ b/Editor/Virtualization/DefaultDataObfuscator.cs @@ -1,5 +1,7 @@ using dnlib.DotNet; using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Utils; using System; using System.Collections.Generic; @@ -7,7 +9,16 @@ namespace Obfuz.Virtualization { public class DefaultDataObfuscator : IDataObfuscator { - private readonly RandomDataNodeCreator _nodeCreator = new RandomDataNodeCreator(); + private readonly IRandom _random; + private readonly RandomDataNodeCreator _nodeCreator; + private readonly RvaDataAllocator _rvaDataAllocator; + + public DefaultDataObfuscator() + { + _random = new RandomWithKey(new byte[] { 0x1, 0x2, 0x3, 0x4 }, 0x5); + _nodeCreator = new RandomDataNodeCreator(_random); + _rvaDataAllocator = new RvaDataAllocator(_random); + } public void ObfuscateInt(MethodDef method, int value, List obfuscatedInstructions) { @@ -16,6 +27,7 @@ namespace Obfuz.Virtualization { method = method, output = obfuscatedInstructions, + rvaDataAllocator = _rvaDataAllocator, }; node.Compile(ctx); //obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldc_I4, value)); @@ -46,5 +58,10 @@ namespace Obfuz.Virtualization { obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldstr, value)); } + + public void Stop() + { + _rvaDataAllocator.SetFieldsRVA(); + } } } diff --git a/Editor/Virtualization/IDataNode.cs b/Editor/Virtualization/IDataNode.cs index 386d386..88a4137 100644 --- a/Editor/Virtualization/IDataNode.cs +++ b/Editor/Virtualization/IDataNode.cs @@ -13,6 +13,8 @@ namespace Obfuz.Virtualization object Value { get; } + void Init(CreateExpressionOptions options); + void Compile(CompileContext ctx); } } diff --git a/Editor/Virtualization/IDataObfuscator.cs b/Editor/Virtualization/IDataObfuscator.cs index d2fc8f0..84e1821 100644 --- a/Editor/Virtualization/IDataObfuscator.cs +++ b/Editor/Virtualization/IDataObfuscator.cs @@ -19,5 +19,6 @@ namespace Obfuz.Virtualization void ObfuscateString(MethodDef method, string value, List obfuscatedInstructions); void ObfuscateBytes(MethodDef method, Array value, List obfuscatedInstructions); + void Stop(); } } diff --git a/Editor/Virtualization/RandomDataNodeCreator.cs b/Editor/Virtualization/RandomDataNodeCreator.cs index 39c106c..b5bfdd4 100644 --- a/Editor/Virtualization/RandomDataNodeCreator.cs +++ b/Editor/Virtualization/RandomDataNodeCreator.cs @@ -9,10 +9,11 @@ namespace Obfuz.Virtualization { private readonly Dictionary> _functions = new Dictionary>(); - private readonly IRandom _random = new RandomWithKey(new byte[] { 0x1, 0x2, 0x3, 0x4 }, 0x5); + private readonly IRandom _random; - public RandomDataNodeCreator() + public RandomDataNodeCreator(IRandom random) { + _random = random; var int32Funcs = new List() { new Int32FunctionAdd(), @@ -29,7 +30,8 @@ namespace Obfuz.Virtualization } if (options.depth >= 2) { - return new ConstDataNode() { Type = type, Value = value }; + //return new ConstDataNode() { Type = type, Value = value }; + return new ConstFromFieldRvaDataNode() { Type = type, Value = value }; } var func = funcs[options.random.NextInt(funcs.Count)]; ++options.depth;