obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs

266 lines
11 KiB
C#
Raw Normal View History

2025-07-01 18:46:09 +08:00
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using Obfuz.Data;
using Obfuz.Emit;
using Obfuz.Settings;
2025-07-01 18:46:09 +08:00
using Obfuz.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Obfuz.ObfusPasses.CallObfus
{
struct DelegateProxyMethodData
{
public readonly FieldDef delegateInstanceField;
public readonly MethodDef delegateInvokeMethod;
public DelegateProxyMethodData(FieldDef delegateInstanceField, MethodDef delegateInvokeMethod)
{
this.delegateInstanceField = delegateInstanceField;
this.delegateInvokeMethod = delegateInvokeMethod;
}
}
2025-07-02 18:57:53 +08:00
class DelegateProxyAllocator : GroupByModuleEntityBase
2025-07-01 18:46:09 +08:00
{
private readonly CachedDictionary<MethodSig, TypeDef> _delegateTypes;
private readonly HashSet<string> _allocatedDelegateNames = new HashSet<string>();
private TypeDef _delegateInstanceHolderType;
private bool _done;
class CallInfo
{
public string key1;
public int key2;
public IMethod method;
public bool callVir;
public int index;
public TypeDef delegateType;
public FieldDef delegateInstanceField;
public MethodDef delegateInvokeMethod;
public MethodDef proxyMethod;
}
private readonly Dictionary<MethodKey, CallInfo> _callMethods = new Dictionary<MethodKey, CallInfo>();
private CallObfuscationSettingsFacade _settings;
2025-07-01 18:46:09 +08:00
2025-07-02 18:57:53 +08:00
public DelegateProxyAllocator()
2025-07-01 18:46:09 +08:00
{
_delegateTypes = new CachedDictionary<MethodSig, TypeDef>(SignatureEqualityComparer.Instance, CreateDelegateForSignature);
}
2025-07-02 18:57:53 +08:00
public override void Init()
2025-07-01 18:46:09 +08:00
{
_delegateInstanceHolderType = CreateDelegateInstanceHolderTypeDef();
_settings = CallObfusPass.CurrentSettings;
2025-07-01 18:46:09 +08:00
}
private string AllocateDelegateTypeName(MethodSig delegateInvokeSig)
{
uint hashCode = (uint)SignatureEqualityComparer.Instance.GetHashCode(delegateInvokeSig);
string typeName = $"$Obfuz$Delegate_{hashCode}";
if (_allocatedDelegateNames.Add(typeName))
{
return typeName;
}
2025-08-03 12:37:44 +08:00
for (int i = 0; ; i++)
2025-07-01 18:46:09 +08:00
{
typeName = $"$Obfuz$Delegate_{hashCode}_{i}";
if (_allocatedDelegateNames.Add(typeName))
{
return typeName;
}
}
}
private TypeDef CreateDelegateForSignature(MethodSig delegateInvokeSig)
{
2025-07-02 18:57:53 +08:00
ModuleDef mod = Module;
using (var scope = new DisableTypeDefFindCacheScope(mod))
2025-07-01 18:46:09 +08:00
{
string typeName = AllocateDelegateTypeName(delegateInvokeSig);
2025-07-02 18:57:53 +08:00
mod.Import(typeof(MulticastDelegate));
2025-07-01 18:46:09 +08:00
2025-07-02 18:57:53 +08:00
TypeDef delegateType = new TypeDefUser("", typeName, mod.CorLibTypes.GetTypeRef("System", "MulticastDelegate"));
2025-07-01 18:46:09 +08:00
delegateType.Attributes = TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.Public;
2025-07-02 18:57:53 +08:00
mod.Types.Add(delegateType);
2025-07-01 18:46:09 +08:00
MethodDef ctor = new MethodDefUser(
".ctor",
2025-07-02 18:57:53 +08:00
MethodSig.CreateInstance(mod.CorLibTypes.Void, mod.CorLibTypes.Object, mod.CorLibTypes.IntPtr),
2025-07-01 18:46:09 +08:00
MethodImplAttributes.Runtime,
MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public
);
ctor.DeclaringType = delegateType;
MethodDef invokeMethod = new MethodDefUser(
"Invoke",
MethodSig.CreateInstance(delegateInvokeSig.RetType, delegateInvokeSig.Params.ToArray()),
MethodImplAttributes.Runtime,
MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.Virtual
);
invokeMethod.DeclaringType = delegateType;
return delegateType;
}
}
private TypeDef CreateDelegateInstanceHolderTypeDef()
{
2025-07-02 18:57:53 +08:00
ModuleDef mod = Module;
using (var scope = new DisableTypeDefFindCacheScope(mod))
2025-07-01 18:46:09 +08:00
{
string typeName = "$Obfuz$DelegateInstanceHolder";
2025-07-02 18:57:53 +08:00
TypeDef holderType = new TypeDefUser("", typeName, mod.CorLibTypes.Object.ToTypeDefOrRef());
2025-07-01 18:46:09 +08:00
holderType.Attributes = TypeAttributes.Class | TypeAttributes.Public;
2025-07-02 18:57:53 +08:00
mod.Types.Add(holderType);
2025-07-01 18:46:09 +08:00
return holderType;
}
}
private string AllocateFieldName(IMethod method, bool callVir)
{
uint hashCode = (uint)MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method);
string typeName = $"$Obfuz$Delegate$Field_{hashCode}_{callVir}";
if (_allocatedDelegateNames.Add(typeName))
{
return typeName;
}
for (int i = 0; ; i++)
{
typeName = $"$Obfuz$Delegate$Field_{hashCode}_{callVir}_{i}";
if (_allocatedDelegateNames.Add(typeName))
{
return typeName;
}
}
}
private MethodDef CreateProxyMethod(string name, IMethod calledMethod, bool callVir, MethodSig delegateInvokeSig)
{
var proxyMethod = new MethodDefUser(name, delegateInvokeSig, MethodImplAttributes.Managed, MethodAttributes.Public | MethodAttributes.Static);
var body = new CilBody();
proxyMethod.Body = body;
var ins = body.Instructions;
foreach (Parameter param in proxyMethod.Parameters)
{
ins.Add(Instruction.Create(OpCodes.Ldarg, param));
}
ins.Add(Instruction.Create(callVir ? OpCodes.Callvirt : OpCodes.Call, calledMethod));
ins.Add(Instruction.Create(OpCodes.Ret));
return proxyMethod;
}
public DelegateProxyMethodData Allocate(IMethod method, bool callVir, MethodSig delegateInvokeSig)
{
var key = new MethodKey(method, callVir);
if (!_callMethods.TryGetValue(key, out var callInfo))
{
TypeDef delegateType = _delegateTypes.GetValue(delegateInvokeSig);
MethodDef delegateInvokeMethod = delegateType.FindMethod("Invoke");
string fieldName = AllocateFieldName(method, callVir);
FieldDef delegateInstanceField = new FieldDefUser(fieldName, new FieldSig(delegateType.ToTypeSig()), FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly);
string key1 = $"{method.FullName}_{callVir}";
callInfo = new CallInfo
{
key1 = key1,
key2 = HashUtil.ComputePrimitiveOrStringOrBytesHashCode(key1) * 33445566,
method = method,
callVir = callVir,
delegateType = delegateType,
delegateInstanceField = delegateInstanceField,
delegateInvokeMethod = delegateInvokeMethod,
proxyMethod = CreateProxyMethod($"{fieldName}$Proxy", method, callVir, delegateInvokeSig),
};
_callMethods.Add(key, callInfo);
}
return new DelegateProxyMethodData(callInfo.delegateInstanceField, callInfo.delegateInvokeMethod);
}
2025-07-02 18:57:53 +08:00
public override void Done()
2025-07-01 18:46:09 +08:00
{
if (_done)
{
throw new Exception("Already done");
}
_done = true;
2025-07-02 18:57:53 +08:00
ModuleDef mod = Module;
2025-07-01 18:46:09 +08:00
// for stable order, we sort methods by name
List<CallInfo> callMethodList = _callMethods.Values.ToList();
callMethodList.Sort((a, b) => a.key1.CompareTo(b.key1));
var cctor = new MethodDefUser(".cctor",
2025-07-02 18:57:53 +08:00
MethodSig.CreateStatic(mod.CorLibTypes.Void),
2025-07-01 18:46:09 +08:00
MethodImplAttributes.IL | MethodImplAttributes.Managed,
MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Private);
cctor.DeclaringType = _delegateInstanceHolderType;
//_rvaTypeDef.Methods.Add(cctor);
var body = new CilBody();
cctor.Body = body;
var ins = body.Instructions;
// var arr = new array[];
// var d = new delegate;
// arr[index] = d;
int index = 0;
ins.Add(Instruction.CreateLdcI4(callMethodList.Count));
2025-07-02 18:57:53 +08:00
ins.Add(Instruction.Create(OpCodes.Newarr, mod.CorLibTypes.Object));
2025-07-01 18:46:09 +08:00
foreach (CallInfo ci in callMethodList)
{
ci.index = index;
_delegateInstanceHolderType.Methods.Add(ci.proxyMethod);
ins.Add(Instruction.Create(OpCodes.Dup));
ins.Add(Instruction.CreateLdcI4(index));
ins.Add(Instruction.Create(OpCodes.Ldnull));
ins.Add(Instruction.Create(OpCodes.Ldftn, ci.proxyMethod));
MethodDef ctor = ci.delegateType.FindMethod(".ctor");
UnityEngine.Assertions.Assert.IsNotNull(ctor, $"Delegate type {ci.delegateType.FullName} does not have a constructor.");
2025-07-01 18:46:09 +08:00
ins.Add(Instruction.Create(OpCodes.Newobj, ctor));
ins.Add(Instruction.Create(OpCodes.Stelem_Ref));
++index;
}
List<CallInfo> callMethodList2 = callMethodList.ToList();
callMethodList2.Sort((a, b) => a.key2.CompareTo(b.key2));
2025-07-02 18:57:53 +08:00
EncryptionScopeInfo encryptionScope = EncryptionScope;
DefaultMetadataImporter importer = this.GetDefaultModuleMetadataImporter();
RvaDataAllocator rvaDataAllocator = this.GetEntity<RvaDataAllocator>();
2025-07-01 18:46:09 +08:00
foreach (CallInfo ci in callMethodList2)
{
_delegateInstanceHolderType.Fields.Add(ci.delegateInstanceField);
ins.Add(Instruction.Create(OpCodes.Dup));
IRandom localRandom = encryptionScope.localRandomCreator(HashUtil.ComputePrimitiveOrStringOrBytesHashCode(ci.key1));
int ops = EncryptionUtil.GenerateEncryptionOpCodes(localRandom, encryptionScope.encryptor, _settings.obfuscationLevel);
2025-07-01 18:46:09 +08:00
int salt = localRandom.NextInt();
int encryptedValue = encryptionScope.encryptor.Encrypt(ci.index, ops, salt);
2025-07-02 18:57:53 +08:00
RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue);
2025-07-01 18:46:09 +08:00
ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field));
ins.Add(Instruction.CreateLdcI4(rvaData.offset));
ins.Add(Instruction.CreateLdcI4(ops));
ins.Add(Instruction.CreateLdcI4(salt));
ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaInt));
ins.Add(Instruction.Create(OpCodes.Ldelem_Ref));
ins.Add(Instruction.Create(OpCodes.Stsfld, ci.delegateInstanceField));
}
ins.Add(Instruction.Create(OpCodes.Pop));
ins.Add(Instruction.Create(OpCodes.Ret));
}
}
}