/* Copyright (C) 2011-2012 de4dot@gmail.com This file is part of de4dot. de4dot is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. de4dot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with de4dot. If not, see . */ using System; using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; using de4dot.blocks; namespace de4dot.code.deobfuscators { abstract class ProxyDelegateFinderBase { protected ModuleDefinition module; protected List delegateCreatorMethods = new List(); protected Dictionary delegateTypesDict = new Dictionary(); FieldDefinitionAndDeclaringTypeDict fieldToDelegateInfo = new FieldDefinitionAndDeclaringTypeDict(); Dictionary proxyMethodToField = new Dictionary(); int errors = 0; public int Errors { get { return errors; } } protected class DelegateInfo { public MethodReference methodRef; // Method we should call public FieldDefinition field; // Field holding the Delegate instance public OpCode callOpcode; public DelegateInfo(FieldDefinition field, MethodReference methodRef, OpCode callOpcode) { this.field = field; this.methodRef = methodRef; this.callOpcode = callOpcode; } } public int RemovedDelegateCreatorCalls { get; set; } public IEnumerable DelegateTypes { get { return delegateTypesDict.Keys; } } public IEnumerable DelegateCreatorTypes { get { foreach (var method in delegateCreatorMethods) yield return method.DeclaringType; } } public bool Detected { get { return delegateCreatorMethods.Count != 0; } } public ProxyDelegateFinderBase(ModuleDefinition module) { this.module = module; } public ProxyDelegateFinderBase(ModuleDefinition module, ProxyDelegateFinderBase oldOne) { this.module = module; foreach (var method in oldOne.delegateCreatorMethods) delegateCreatorMethods.Add(lookup(method, "Could not find delegate creator method")); foreach (var kv in oldOne.delegateTypesDict) delegateTypesDict[lookup(kv.Key, "Could not find delegate type")] = kv.Value; foreach (var key in oldOne.fieldToDelegateInfo.getKeys()) fieldToDelegateInfo.add(lookup(key, "Could not find field"), copy(oldOne.fieldToDelegateInfo.find(key))); foreach (var kv in oldOne.proxyMethodToField) { var key = lookup(kv.Key, "Could not find proxy method"); var value = lookup(kv.Value, "Could not find proxy field"); proxyMethodToField[key] = value; } } DelegateInfo copy(DelegateInfo di) { var method = lookup(di.methodRef, "Could not find method ref"); var field = lookup(di.field, "Could not find delegate field"); return new DelegateInfo(field, method, di.callOpcode); } protected T lookup(T def, string errorMessage) where T : MemberReference { return DeobUtils.lookup(module, def, errorMessage); } public void setDelegateCreatorMethod(MethodDefinition delegateCreatorMethod) { if (delegateCreatorMethod == null) return; delegateCreatorMethods.Add(delegateCreatorMethod); } protected bool isDelegateCreatorMethod(MethodDefinition method) { foreach (var m in delegateCreatorMethods) { if (m == method) return true; } return false; } protected virtual IEnumerable getDelegateTypes() { foreach (var type in module.Types) { if (type.BaseType == null || type.BaseType.FullName != "System.MulticastDelegate") continue; yield return type; } } public void find() { if (delegateCreatorMethods.Count == 0) return; Log.v("Finding all proxy delegates"); foreach (var type in getDelegateTypes()) { var cctor = DotNetUtils.getMethod(type, ".cctor"); if (cctor == null || !cctor.HasBody) continue; if (!type.HasFields) continue; object context = checkCctor(type, cctor); if (context == null) continue; Log.v("Found proxy delegate: {0} ({1:X8})", Utils.removeNewlines(type), type.MetadataToken.ToUInt32()); RemovedDelegateCreatorCalls++; onFoundProxyDelegate(type); Log.indent(); foreach (var field in type.Fields) { if (!field.IsStatic) continue; MethodReference calledMethod; OpCode callOpcode; getCallInfo(context, field, out calledMethod, out callOpcode); if (calledMethod == null) continue; addDelegateInfo(new DelegateInfo(field, calledMethod, callOpcode)); Log.v("Field: {0}, Opcode: {1}, Method: {2} ({3:X8})", Utils.removeNewlines(field.Name), callOpcode, Utils.removeNewlines(calledMethod), calledMethod.MetadataToken.ToUInt32()); } Log.deIndent(); delegateTypesDict[type] = true; } } protected void addDelegateInfo(DelegateInfo di) { fieldToDelegateInfo.add(di.field, di); } protected virtual void onFoundProxyDelegate(TypeDefinition type) { } protected abstract object checkCctor(TypeDefinition type, MethodDefinition cctor); protected abstract void getCallInfo(object context, FieldDefinition field, out MethodReference calledMethod, out OpCode callOpcode); protected void add(MethodDefinition proxyMethod, FieldDefinition proxyField) { if (proxyMethod == null || proxyField == null) return; proxyMethodToField[proxyMethod] = proxyField; } DelegateInfo getDelegateInfo(FieldReference field) { if (field == null) return null; return fieldToDelegateInfo.find(field); } class BlockInstr { public Block Block { get; set; } public int Index { get; set; } } class RemoveInfo { public int Index { get; set; } public DelegateInfo DelegateInfo { get; set; } public bool IsCall { get { return DelegateInfo != null; } } } protected virtual bool ProxyCallIsObfuscated { get { return false; } } public void deobfuscate(Blocks blocks) { if (blocks.Method.DeclaringType != null && delegateTypesDict.ContainsKey(blocks.Method.DeclaringType)) return; var allBlocks = blocks.MethodBlocks.getAllBlocks(); int loops = ProxyCallIsObfuscated ? 50 : 1; for (int i = 0; i < loops; i++) { if (!deobfuscate(blocks, allBlocks)) break; } fixBrokenCalls(blocks.Method, allBlocks); } bool deobfuscate(Blocks blocks, IList allBlocks) { var removeInfos = new Dictionary>(); foreach (var block in allBlocks) { var instrs = block.Instructions; for (int i = 0; i < instrs.Count; i++) { var instr = instrs[i]; if (instr.OpCode == OpCodes.Ldsfld) { var di = getDelegateInfo(instr.Operand as FieldReference); if (di == null) continue; var visited = new Dictionary(); var callInfo = findProxyCall(di, block, i, visited, 1); if (callInfo != null) { add(removeInfos, block, i, null); add(removeInfos, callInfo.Block, callInfo.Index, di); } else { errors++; Log.w("Could not fix proxy call. Method: {0} ({1:X8}), Proxy type: {2} ({3:X8})", Utils.removeNewlines(blocks.Method), blocks.Method.MetadataToken.ToInt32(), Utils.removeNewlines(di.field.DeclaringType), di.field.DeclaringType.MetadataToken.ToInt32()); } } else if (instr.OpCode == OpCodes.Call) { var method = instr.Operand as MethodDefinition; if (method == null) continue; FieldDefinition field; if (!proxyMethodToField.TryGetValue(method, out field)) continue; var di = getDelegateInfo(field); if (di == null) continue; add(removeInfos, block, i, di); } } } foreach (var block in removeInfos.Keys) { var list = removeInfos[block]; var removeIndexes = new List(list.Count); foreach (var info in list) { if (info.IsCall) { var opcode = info.DelegateInfo.callOpcode; var newInstr = Instruction.Create(opcode, info.DelegateInfo.methodRef); block.replace(info.Index, 1, newInstr); } else removeIndexes.Add(info.Index); } block.remove(removeIndexes); } return removeInfos.Count > 0; } void add(Dictionary> removeInfos, Block block, int index, DelegateInfo di) { List list; if (!removeInfos.TryGetValue(block, out list)) removeInfos[block] = list = new List(); list.Add(new RemoveInfo { Index = index, DelegateInfo = di, }); } BlockInstr findProxyCall(DelegateInfo di, Block block, int index, Dictionary visited, int stack) { if (visited.ContainsKey(block)) return null; if (index <= 0) visited[block] = true; var instrs = block.Instructions; for (int i = index + 1; i < instrs.Count; i++) { if (stack <= 0) return null; var instr = instrs[i]; DotNetUtils.updateStack(instr.Instruction, ref stack, false); if (stack < 0) return null; if (instr.OpCode != OpCodes.Call && instr.OpCode != OpCodes.Callvirt) { if (stack <= 0) return null; continue; } var calledMethod = instr.Operand as MethodReference; if (calledMethod == null) return null; if (stack != (DotNetUtils.hasReturnValue(calledMethod) ? 1 : 0)) continue; if (calledMethod.Name != "Invoke") return null; return new BlockInstr { Block = block, Index = i, }; } if (stack <= 0) return null; foreach (var target in block.getTargets()) { var info = findProxyCall(di, target, -1, visited, stack); if (info != null) return info; } return null; } // The obfuscator could be buggy and call a proxy delegate without pushing the // instance field. SA has done it, so let's fix it. void fixBrokenCalls(MethodDefinition obfuscatedMethod, IList allBlocks) { foreach (var block in allBlocks) { var instrs = block.Instructions; for (int i = 0; i < instrs.Count; i++) { var call = instrs[i]; if (call.OpCode != OpCodes.Call && call.OpCode != OpCodes.Callvirt) continue; var methodRef = call.Operand as MethodReference; if (methodRef.Name != "Invoke") continue; var method = DotNetUtils.getMethod(module, methodRef); if (method == null) continue; var declaringType = DotNetUtils.getType(module, method.DeclaringType); if (declaringType == null) continue; if (!delegateTypesDict.ContainsKey(declaringType)) continue; // Oooops!!! The obfuscator is buggy. Well, let's hope it is, or it's my code. ;) Log.w("Holy obfuscator bugs, Batman! Found a proxy delegate call with no instance push in {0:X8}. Replacing it with a throw...", obfuscatedMethod.MetadataToken.ToInt32()); block.insert(i, Instruction.Create(OpCodes.Ldnull)); block.replace(i + 1, 1, Instruction.Create(OpCodes.Throw)); i++; } } } } }