using dnlib.DotNet; using dnlib.DotNet.Emit; using Obfuz.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using MethodImplAttributes = dnlib.DotNet.MethodImplAttributes; using TypeAttributes = dnlib.DotNet.TypeAttributes; namespace Obfuz.Emit { public struct ProxyCallMethodData { public MethodDef proxyMethod; public int secret; } class ModuleDynamicProxyMethodAllocator { private readonly ModuleDef _module; private readonly IRandom _random; class MethodKey : IEquatable { public readonly IMethod _method; public readonly bool _callVir; private readonly int _hashCode; public MethodKey(IMethod method, bool callVir) { _method = method; _callVir = callVir; _hashCode = HashUtil.CombineHash(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method), callVir ? 1 : 0); } public override int GetHashCode() { return _hashCode; } public bool Equals(MethodKey other) { return MethodEqualityComparer.CompareDeclaringTypes.Equals(_method, other._method) && _callVir == other._callVir; } } class MethodProxyInfo { public MethodDef proxyMethod; public int secret; } private readonly Dictionary _methodProxys = new Dictionary(); const int maxProxyMethodPerDispatchMethod = 1000; class DispatchMethodInfo { public MethodDef methodDef; public int secret; public List methods = new List(); } private readonly Dictionary> _dispatchMethods = new Dictionary>(SignatureEqualityComparer.Instance); private TypeDef _proxyTypeDef; public ModuleDynamicProxyMethodAllocator(ModuleDef module, IRandom random) { _module = module; _random = random; } private TypeDef CreateProxyTypeDef() { var typeDef = new TypeDefUser("$Obfuz$ProxyCall", _module.CorLibTypes.Object.ToTypeDefOrRef()); typeDef.Attributes = TypeAttributes.NotPublic | TypeAttributes.Sealed; _module.EnableTypeDefFindCache = false; _module.Types.Add(typeDef); _module.EnableTypeDefFindCache = true; return typeDef; } private MethodDef CreateDispatchMethodInfo(MethodSig methodSig) { if (_proxyTypeDef == null) { _proxyTypeDef = CreateProxyTypeDef(); } MethodDef methodDef = new MethodDefUser($"$Obfuz$ProxyCall$Dispatch${_proxyTypeDef.Methods.Count}", methodSig, MethodImplAttributes.IL | MethodImplAttributes.Managed, MethodAttributes.Static | MethodAttributes.Private); methodDef.DeclaringType = _proxyTypeDef; return methodDef; } private MethodSig CreateDispatchMethodSig(IMethod method) { MethodSig methodSig = MetaUtil.ToSharedMethodSig(_module.CorLibTypes, MetaUtil.GetInflatedMethodSig(method)); // extra param for secret methodSig.Params.Add(_module.CorLibTypes.Int32); return methodSig; } private DispatchMethodInfo GetDispatchMethod(IMethod method) { MethodSig methodSig = CreateDispatchMethodSig(method); if (!_dispatchMethods.TryGetValue(methodSig, out var dispatchMethods)) { dispatchMethods = new List(); _dispatchMethods.Add(methodSig, dispatchMethods); } if (dispatchMethods.Count == 0 || dispatchMethods.Last().methods.Count >= maxProxyMethodPerDispatchMethod) { var newDispatchMethodInfo = new DispatchMethodInfo { methodDef = CreateDispatchMethodInfo(methodSig), secret = 0, }; dispatchMethods.Add(newDispatchMethodInfo); } return dispatchMethods.Last(); } public ProxyCallMethodData Allocate(IMethod method, bool callVir) { var key = new MethodKey(method, callVir); if (!_methodProxys.TryGetValue(key, out var proxyInfo)) { var methodDispatcher = GetDispatchMethod(method); proxyInfo = new MethodProxyInfo() { proxyMethod = methodDispatcher.methodDef, secret = methodDispatcher.methods.Count, }; methodDispatcher.methods.Add(method); _methodProxys.Add(key, proxyInfo); } return new ProxyCallMethodData { proxyMethod = proxyInfo.proxyMethod, secret = proxyInfo.secret }; } public void Done() { foreach (DispatchMethodInfo dispatchMethod in _dispatchMethods.Values.SelectMany(ms => ms)) { var methodDef = dispatchMethod.methodDef; var secret = dispatchMethod.secret; var methodSig = methodDef.MethodSig; var body = new CilBody(); methodDef.Body = body; var ins = body.Instructions; ins.Add(Instruction.Create(OpCodes.Ret)); } } } public class ProxyCallAllocator { private readonly IRandom _random; private readonly Dictionary _moduleAllocators = new Dictionary(); public ProxyCallAllocator(IRandom random) { _random = random; } public ProxyCallMethodData Allocate(ModuleDef mod, IMethod method, bool callVir) { if (!_moduleAllocators.TryGetValue(mod, out var allocator)) { allocator = new ModuleDynamicProxyMethodAllocator(mod, _random); _moduleAllocators.Add(mod, allocator); } return allocator.Allocate(method, callVir); } public void Done() { foreach (var allocator in _moduleAllocators.Values) { allocator.Done(); } _moduleAllocators.Clear(); } } }