2025-07-01 18:46:09 +08:00
|
|
|
|
using dnlib.DotNet;
|
|
|
|
|
using dnlib.DotNet.Emit;
|
|
|
|
|
using Obfuz.Data;
|
|
|
|
|
using Obfuz.Emit;
|
|
|
|
|
using Obfuz.Settings;
|
|
|
|
|
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>();
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string AllocateDelegateTypeName(MethodSig delegateInvokeSig)
|
|
|
|
|
{
|
|
|
|
|
uint hashCode = (uint)SignatureEqualityComparer.Instance.GetHashCode(delegateInvokeSig);
|
|
|
|
|
string typeName = $"$Obfuz$Delegate_{hashCode}";
|
|
|
|
|
if (_allocatedDelegateNames.Add(typeName))
|
|
|
|
|
{
|
|
|
|
|
return typeName;
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; ;i++)
|
|
|
|
|
{
|
|
|
|
|
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");
|
2025-07-23 19:34:42 +08:00
|
|
|
|
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, 4);
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|