支持从rva data中读取常量数据

backup
walon 2025-04-22 19:34:46 +08:00
parent b19959488f
commit f1b3bd3329
11 changed files with 398 additions and 11 deletions

View File

@ -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<byte> bytes;
public void FillPadding()
{
// 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<(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<byte>((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();
}
}
}
}

View File

@ -1,7 +1,9 @@
{ {
"name": "Obfuz", "name": "Obfuz",
"rootNamespace": "", "rootNamespace": "",
"references": [], "references": [
"GUID:7e4de3067c2ab5c43a03ac49273dfb68"
],
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"
], ],

View File

@ -1,6 +1,7 @@
using dnlib.DotNet; using dnlib.DotNet;
using dnlib.DotNet.Emit; using dnlib.DotNet.Emit;
using NUnit.Framework; using NUnit.Framework;
using Obfuz.Emit;
using System.Collections.Generic; using System.Collections.Generic;
namespace Obfuz.Virtualization namespace Obfuz.Virtualization
@ -9,5 +10,6 @@ namespace Obfuz.Virtualization
{ {
public MethodDef method; public MethodDef method;
public List<Instruction> output; public List<Instruction> output;
public RvaDataAllocator rvaDataAllocator;
} }
} }

View File

@ -1,4 +1,5 @@
using Obfuz.Utils; using Obfuz.Emit;
using Obfuz.Utils;
namespace Obfuz.Virtualization namespace Obfuz.Virtualization
{ {

View File

@ -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}.");
}
}
}
}

View File

@ -1,4 +1,6 @@
namespace Obfuz.Virtualization using dnlib.DotNet;
namespace Obfuz.Virtualization
{ {
public abstract class DataNodeBase : IDataNode public abstract class DataNodeBase : IDataNode
{ {
@ -18,6 +20,11 @@
public byte[] BytesValue => (byte[])Value; public byte[] BytesValue => (byte[])Value;
public virtual void Init(CreateExpressionOptions options)
{
}
public abstract void Compile(CompileContext ctx); public abstract void Compile(CompileContext ctx);
} }

View File

@ -23,18 +23,24 @@ namespace Obfuz.Virtualization
public override void Stop(ObfuscatorContext ctx) public override void Stop(ObfuscatorContext ctx)
{ {
_dataObfuscator.Stop();
} }
public override void Process(ObfuscatorContext ctx) public override void Process(ObfuscatorContext ctx)
{ {
foreach (var ass in ctx.assemblies) 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; continue;
} }

View File

@ -1,5 +1,7 @@
using dnlib.DotNet; using dnlib.DotNet;
using dnlib.DotNet.Emit; using dnlib.DotNet.Emit;
using Obfuz.Emit;
using Obfuz.Utils;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -7,7 +9,16 @@ namespace Obfuz.Virtualization
{ {
public class DefaultDataObfuscator : IDataObfuscator 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<Instruction> obfuscatedInstructions) public void ObfuscateInt(MethodDef method, int value, List<Instruction> obfuscatedInstructions)
{ {
@ -16,6 +27,7 @@ namespace Obfuz.Virtualization
{ {
method = method, method = method,
output = obfuscatedInstructions, output = obfuscatedInstructions,
rvaDataAllocator = _rvaDataAllocator,
}; };
node.Compile(ctx); node.Compile(ctx);
//obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldc_I4, value)); //obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldc_I4, value));
@ -46,5 +58,10 @@ namespace Obfuz.Virtualization
{ {
obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldstr, value)); obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldstr, value));
} }
public void Stop()
{
_rvaDataAllocator.SetFieldsRVA();
}
} }
} }

View File

@ -13,6 +13,8 @@ namespace Obfuz.Virtualization
object Value { get; } object Value { get; }
void Init(CreateExpressionOptions options);
void Compile(CompileContext ctx); void Compile(CompileContext ctx);
} }
} }

View File

@ -19,5 +19,6 @@ namespace Obfuz.Virtualization
void ObfuscateString(MethodDef method, string value, List<Instruction> obfuscatedInstructions); void ObfuscateString(MethodDef method, string value, List<Instruction> obfuscatedInstructions);
void ObfuscateBytes(MethodDef method, Array value, List<Instruction> obfuscatedInstructions); void ObfuscateBytes(MethodDef method, Array value, List<Instruction> obfuscatedInstructions);
void Stop();
} }
} }

View File

@ -9,10 +9,11 @@ namespace Obfuz.Virtualization
{ {
private readonly Dictionary<DataNodeType, List<IFunction>> _functions = new Dictionary<DataNodeType, List<IFunction>>(); private readonly Dictionary<DataNodeType, List<IFunction>> _functions = new Dictionary<DataNodeType, List<IFunction>>();
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<IFunction>() var int32Funcs = new List<IFunction>()
{ {
new Int32FunctionAdd(), new Int32FunctionAdd(),
@ -29,7 +30,8 @@ namespace Obfuz.Virtualization
} }
if (options.depth >= 2) 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)]; var func = funcs[options.random.NextInt(funcs.Count)];
++options.depth; ++options.depth;