diff --git a/blocks/DotNetUtils.cs b/blocks/DotNetUtils.cs index 2134718d..785be832 100644 --- a/blocks/DotNetUtils.cs +++ b/blocks/DotNetUtils.cs @@ -357,6 +357,15 @@ namespace de4dot.blocks { return null; } + public static Instruction clone(Instruction instr) { + return new Instruction { + Offset = instr.Offset, + OpCode = instr.OpCode, + Operand = instr.Operand, + SequencePoint = instr.SequencePoint, + }; + } + public static void copyBody(MethodDefinition method, out IList instructions, out IList exceptionHandlers) { if (method == null || !method.HasBody) { instructions = new List(); @@ -370,15 +379,8 @@ namespace de4dot.blocks { exceptionHandlers = new List(oldExHandlers.Count); var oldToIndex = Utils.createObjectToIndexDictionary(oldInstrs); - foreach (var oldInstr in oldInstrs) { - var newInstr = new Instruction { - Offset = oldInstr.Offset, - OpCode = oldInstr.OpCode, - Operand = oldInstr.Operand, - SequencePoint = oldInstr.SequencePoint, - }; - instructions.Add(newInstr); - } + foreach (var oldInstr in oldInstrs) + instructions.Add(clone(oldInstr)); foreach (var newInstr in instructions) { var operand = newInstr.Operand; @@ -614,5 +616,46 @@ namespace de4dot.blocks { return scope.Name == assemblySimpleName || scope.Name.StartsWith(assemblySimpleName + ",", StringComparison.Ordinal); } + + public static int getArgIndex(MethodReference method, Instruction instr) { + switch (instr.OpCode.Code) { + case Code.Ldarg_0: return 0; + case Code.Ldarg_1: return 1; + case Code.Ldarg_2: return 2; + case Code.Ldarg_3: return 3; + + case Code.Ldarga: + case Code.Ldarga_S: + case Code.Ldarg: + case Code.Ldarg_S: + return getArgIndex(method, instr.Operand as ParameterDefinition); + } + + return -1; + } + + public static int getArgIndex(MethodReference method, ParameterDefinition arg) { + if (arg == null) + return -1; + if (method.HasThis) + return arg.Index + 1; + return arg.Index; + } + + public static List getArgs(MethodReference method) { + var args = new List(method.Parameters.Count + 1); + if (method.HasThis) + args.Add(method.DeclaringType); + foreach (var arg in method.Parameters) + args.Add(arg.ParameterType); + return args; + } + + public static int getArgsCount(MethodReference method) { + int count = method.Parameters.Count; + if (method.HasThis) + count++; + return count; + } } } diff --git a/blocks/blocks.csproj b/blocks/blocks.csproj index dcce1e7e..7f7c4165 100644 --- a/blocks/blocks.csproj +++ b/blocks/blocks.csproj @@ -42,6 +42,7 @@ + diff --git a/blocks/cflow/BlockCflowDeobfuscator.cs b/blocks/cflow/BlockCflowDeobfuscator.cs index 799b14ce..f4811671 100644 --- a/blocks/cflow/BlockCflowDeobfuscator.cs +++ b/blocks/cflow/BlockCflowDeobfuscator.cs @@ -40,8 +40,6 @@ namespace de4dot.blocks.cflow { return false; for (int i = 0; i < instructions.Count - 1; i++) { var instr = instructions[i].Instruction; - if (patchBoolCallMethod(instr, i)) - instr = instructions[i].Instruction; instructionEmulator.emulate(instr); } @@ -77,40 +75,6 @@ namespace de4dot.blocks.cflow { } } - // This is a hack for .NET Reactor - bool patchBoolCallMethod(Instruction instr, int instrIndex) { - if (instr.OpCode.Code != Code.Call) - return false; - var method = instr.Operand as MethodDefinition; - if (method == null) - return false; - if (method.Parameters.Count > 0) - return false; - if (!method.IsStatic) - return false; - if (!MemberReferenceHelper.verifyType(method.MethodReturnType.ReturnType, "mscorlib", "System.Boolean")) - return false; - var body = method.Body; - if (body == null) - return false; - var instrs = body.Instructions; - if (instrs.Count > 10) - return false; - - var ldci4 = instrs[0]; - if (ldci4.OpCode.Code == Code.Br || ldci4.OpCode.Code == Code.Br_S) - ldci4 = (Instruction)ldci4.Operand; - if (ldci4 == null || !DotNetUtils.isLdcI4(ldci4) || ldci4.Next == null || ldci4.Next.OpCode.Code != Code.Ret) - return false; - - if (!MemberReferenceHelper.compareTypes(method.DeclaringType, blocks.Method.DeclaringType)) - return false; - - int val = DotNetUtils.getLdcI4Value(ldci4); - block.Instructions[instrIndex] = new Instr(Instruction.Create(OpCodes.Ldc_I4, val)); - return true; - } - bool emulateBranch(int stackArgs, Bool3 cond) { if (cond == Bool3.Unknown) return false; diff --git a/blocks/cflow/BlocksCflowDeobfuscator.cs b/blocks/cflow/BlocksCflowDeobfuscator.cs index 963b8d3a..6e6cf9d5 100644 --- a/blocks/cflow/BlocksCflowDeobfuscator.cs +++ b/blocks/cflow/BlocksCflowDeobfuscator.cs @@ -29,6 +29,7 @@ namespace de4dot.blocks.cflow { DeadCodeRemover deadCodeRemover = new DeadCodeRemover(); DeadStoreRemover deadStoreRemover = new DeadStoreRemover(); StLdlocFixer stLdlocFixer = new StLdlocFixer(); + MethodCallInliner methodCallInliner = new MethodCallInliner(); public void init(Blocks blocks) { this.blocks = blocks; @@ -48,6 +49,11 @@ namespace de4dot.blocks.cflow { if (iterations == 0) changed |= fixDotfuscatorLoop(); + foreach (var block in allBlocks) { + methodCallInliner.init(blocks, block); + changed |= methodCallInliner.deobfuscate(); + } + foreach (var block in allBlocks) { var lastInstr = block.LastInstr; if (!DotNetUtils.isConditionalBranch(lastInstr.OpCode.Code) && lastInstr.OpCode.Code != Code.Switch) diff --git a/blocks/cflow/MethodCallInliner.cs b/blocks/cflow/MethodCallInliner.cs new file mode 100644 index 00000000..1b8d1f3b --- /dev/null +++ b/blocks/cflow/MethodCallInliner.cs @@ -0,0 +1,189 @@ +/* + Copyright (C) 2011 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.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace de4dot.blocks.cflow { + class MethodCallInliner { + Blocks blocks; + Block block; + + public void init(Blocks blocks, Block block) { + this.blocks = blocks; + this.block = block; + } + + public bool deobfuscate() { + bool changed = false; + var instructions = block.Instructions; + for (int i = 0; i < instructions.Count; i++) { + var instr = instructions[i].Instruction; + if (instr.OpCode.Code == Code.Call) + changed |= inlineMethod(instr, i); + } + return changed; + } + + bool inlineMethod(Instruction callInstr, int instrIndex) { + var method = callInstr.Operand as MethodDefinition; + if (method == null) + return false; + + if (!method.IsStatic) + return false; + var body = method.Body; + if (body == null) + return false; + if (!MemberReferenceHelper.compareTypes(method.DeclaringType, blocks.Method.DeclaringType)) + return false; + + int index = 0; + var instr = getInstruction(body.Instructions, ref index); + if (instr == null) + return false; + + switch (instr.OpCode.Code) { + case Code.Ldarg: + case Code.Ldarg_S: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + case Code.Call: + return inlineOtherMethod(instrIndex, method, instr, index); + + case Code.Ldc_I4: + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4_M1: + case Code.Ldc_I4_S: + case Code.Ldc_I8: + case Code.Ldc_R4: + case Code.Ldc_R8: + case Code.Ldftn: + case Code.Ldnull: + case Code.Ldstr: + case Code.Ldtoken: + case Code.Ldsfld: + case Code.Ldsflda: + return inlineLoadMethod(instrIndex, method, instr, index); + + default: + return false; + } + } + + bool inlineLoadMethod(int patchIndex, MethodDefinition method, Instruction loadInstr, int instrIndex) { + var instr = getInstruction(method.Body.Instructions, ref instrIndex); + if (instr == null || instr.OpCode.Code != Code.Ret) + return false; + + int methodArgsCount = DotNetUtils.getArgsCount(method); + for (int i = 0; i < methodArgsCount; i++) + block.insert(patchIndex++, Instruction.Create(OpCodes.Pop)); + + block.Instructions[patchIndex] = new Instr(DotNetUtils.clone(loadInstr)); + return true; + } + + bool inlineOtherMethod(int patchIndex, MethodDefinition method, Instruction instr, int instrIndex) { + int loadIndex = 0; + int methodArgsCount = (method.HasThis ? 1 : 0) + method.Parameters.Count; + while (instr != null && loadIndex < methodArgsCount) { + switch (instr.OpCode.Code) { + case Code.Ldarg: + case Code.Ldarg_S: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + if (DotNetUtils.getArgIndex(method, instr) != loadIndex) + return false; + loadIndex++; + instr = getInstruction(method.Body.Instructions, ref instrIndex); + continue; + } + break; + } + if (instr == null || loadIndex != methodArgsCount) + return false; + + if (instr.OpCode.Code != Code.Call && instr.OpCode.Code != Code.Callvirt) + return false; + var callInstr = instr; + var calledMethod = callInstr.Operand as MethodReference; + if (calledMethod == null) + return false; + + if (!isCompatibleType(calledMethod.MethodReturnType.ReturnType, method.MethodReturnType.ReturnType)) + return false; + var methodArgs = DotNetUtils.getArgs(method); + var calledMethodArgs = DotNetUtils.getArgs(calledMethod); + if (methodArgs.Count != calledMethodArgs.Count) + return false; + for (int i = 0; i < methodArgs.Count; i++) { + if (!isCompatibleType(calledMethodArgs[i], methodArgs[i])) + return false; + } + + instr = getInstruction(method.Body.Instructions, ref instrIndex); + if (instr == null || instr.OpCode.Code != Code.Ret) + return false; + + block.Instructions[patchIndex] = new Instr(DotNetUtils.clone(callInstr)); + return true; + } + + static bool isCompatibleType(TypeReference origType, TypeReference newType) { + if (MemberReferenceHelper.compareTypes(origType, newType)) + return true; + if (newType.IsValueType || origType.IsValueType) + return false; + return newType.FullName == "System.Object"; + } + + static Instruction getInstruction(IList instructions, ref int index) { + for (int i = 0; i < 10; i++) { + if (index < 0 || index >= instructions.Count) + return null; + var instr = instructions[index++]; + if (instr.OpCode.Code == Code.Nop) + continue; + if (instr == null || (instr.OpCode.Code != Code.Br && instr.OpCode.Code != Code.Br_S)) + return instr; + instr = instr.Operand as Instruction; + if (instr == null) + return null; + index = instructions.IndexOf(instr); + if (index < 0) + return null; + } + return null; + } + } +}