/* Copyright (C) 2011-2015 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 dnlib.DotNet; using dnlib.DotNet.Emit; using de4dot.blocks; using de4dot.blocks.cflow; namespace de4dot.code.deobfuscators.DeepSea { class DsMethodCallInliner : MethodCallInlinerBase { InstructionEmulator instructionEmulator = new InstructionEmulator(); IList parameters; Parameter arg1, arg2; Value returnValue; MethodDef methodToInline; CachedCflowDeobfuscator cflowDeobfuscator; public DsMethodCallInliner(CachedCflowDeobfuscator cflowDeobfuscator) { this.cflowDeobfuscator = cflowDeobfuscator; } protected override bool DeobfuscateInternal() { bool modified = false; var instructions = block.Instructions; for (int i = 0; i < instructions.Count; i++) { var instr = instructions[i].Instruction; if (instr.OpCode.Code == Code.Call) modified |= InlineMethod(instr, i); } return modified; } bool InlineMethod(Instruction callInstr, int instrIndex) { var method = callInstr.Operand as MethodDef; if (method == null) { var ms = callInstr.Operand as MethodSpec; if (ms != null) method = ms.Method as MethodDef; if (method == null) return false; } if (!CanInline(method)) return false; if (instrIndex < 2) return false; var ldci4_1st = block.Instructions[instrIndex - 2]; var ldci4_2nd = block.Instructions[instrIndex - 1]; if (!ldci4_1st.IsLdcI4() || !ldci4_2nd.IsLdcI4()) return false; if (!InlineMethod(method, instrIndex, ldci4_1st.GetLdcI4Value(), ldci4_2nd.GetLdcI4Value())) return false; return true; } protected override Instruction OnAfterLoadArg(MethodDef methodToInline, Instruction instr, ref int instrIndex) { if (instr.OpCode.Code != Code.Box) return instr; if (methodToInline.MethodSig.GetGenParamCount() == 0) return instr; return DotNetUtils.GetInstruction(methodToInline.Body.Instructions, ref instrIndex); } bool InlineMethod(MethodDef methodToInline, int instrIndex, int const1, int const2) { this.methodToInline = methodToInline = cflowDeobfuscator.Deobfuscate(methodToInline); parameters = methodToInline.Parameters; arg1 = parameters[parameters.Count - 2]; arg2 = parameters[parameters.Count - 1]; returnValue = null; instructionEmulator.Initialize(methodToInline); foreach (var arg in parameters) { if (!arg.IsNormalMethodParameter) continue; if (arg.Type.ElementType >= ElementType.Boolean && arg.Type.ElementType <= ElementType.U4) instructionEmulator.SetArg(arg, new Int32Value(0)); } instructionEmulator.SetArg(arg1, new Int32Value(const1)); instructionEmulator.SetArg(arg2, new Int32Value(const2)); int index = 0; if (!EmulateInstructions(ref index, false)) return false; var patcher = TryInlineOtherMethod(instrIndex, methodToInline, methodToInline.Body.Instructions[index], index + 1, 2); if (patcher == null) return false; if (!EmulateToReturn(patcher.afterIndex, patcher.lastInstr)) return false; patcher.Patch(block); block.Insert(instrIndex, OpCodes.Pop.ToInstruction()); block.Insert(instrIndex, OpCodes.Pop.ToInstruction()); return true; } bool EmulateInstructions(ref int index, bool allowUnknownArgs) { Instruction instr; var instrs = methodToInline.Body.Instructions; int counter = 0; var foundOpCodes = new Dictionary(); bool checkInstrs = false; while (true) { if (counter++ >= 50) return false; if (index < 0 || index >= instrs.Count) return false; instr = instrs[index]; foundOpCodes[instr.OpCode.Code] = true; switch (instr.OpCode.Code) { case Code.Stloc: case Code.Stloc_S: case Code.Stloc_0: case Code.Stloc_1: case Code.Stloc_2: case Code.Stloc_3: case Code.Ldloc: case Code.Ldloc_S: case Code.Ldloc_0: case Code.Ldloc_1: case Code.Ldloc_2: case Code.Ldloc_3: 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.Add: case Code.Sub: case Code.Xor: case Code.Or: case Code.Nop: case Code.Dup: case Code.Mul: case Code.Rem: case Code.Div: instructionEmulator.Emulate(instr); index++; break; case Code.Ldarg: case Code.Ldarg_S: case Code.Ldarg_0: case Code.Ldarg_1: case Code.Ldarg_2: case Code.Ldarg_3: var arg = instr.GetParameter(parameters); if (arg != arg1 && arg != arg2) { if (!allowUnknownArgs) goto done; checkInstrs = true; } instructionEmulator.Emulate(instr); index++; break; case Code.Call: case Code.Callvirt: case Code.Newobj: goto done; case Code.Switch: var value = instructionEmulator.Pop() as Int32Value; if (value == null || !value.AllBitsValid()) return false; var targets = (Instruction[])instr.Operand; if (value.Value >= 0 && value.Value < targets.Length) index = instrs.IndexOf(targets[value.Value]); else index++; break; case Code.Br: case Code.Br_S: index = instrs.IndexOf((Instruction)instr.Operand); break; case Code.Brtrue: case Code.Brtrue_S: index = EmulateBrtrue(index); break; case Code.Brfalse: case Code.Brfalse_S: index = EmulateBrfalse(index); break; case Code.Isinst: case Code.Castclass: if (returnValue != null && instructionEmulator.Peek() == returnValue) { // Do nothing } else instructionEmulator.Emulate(instr); index++; break; default: if (instr.OpCode.OpCodeType != OpCodeType.Prefix) goto done; index++; break; } } done: if (checkInstrs) { if (!foundOpCodes.ContainsKey(Code.Ldc_I4_1)) return false; if (!foundOpCodes.ContainsKey(Code.Ldc_I4_2)) return false; if (!foundOpCodes.ContainsKey(Code.Add)) return false; if (!foundOpCodes.ContainsKey(Code.Dup)) return false; if (!foundOpCodes.ContainsKey(Code.Mul)) return false; if (!foundOpCodes.ContainsKey(Code.Rem)) return false; if (!foundOpCodes.ContainsKey(Code.Brtrue) && !foundOpCodes.ContainsKey(Code.Brtrue_S) && !foundOpCodes.ContainsKey(Code.Brfalse) && !foundOpCodes.ContainsKey(Code.Brfalse_S)) return false; } return true; } int EmulateBranch(int stackArgs, Bool3 cond, Instruction instrTrue, Instruction instrFalse) { if (cond == Bool3.Unknown) return -1; Instruction instr = cond == Bool3.True ? instrTrue : instrFalse; return methodToInline.Body.Instructions.IndexOf(instr); } int EmulateBrtrue(int instrIndex) { var val1 = instructionEmulator.Pop(); var instr = methodToInline.Body.Instructions[instrIndex]; var instrTrue = (Instruction)instr.Operand; var instrFalse = methodToInline.Body.Instructions[instrIndex + 1]; if (val1.IsInt32()) return EmulateBranch(1, Int32Value.CompareTrue((Int32Value)val1), instrTrue, instrFalse); return -1; } int EmulateBrfalse(int instrIndex) { var val1 = instructionEmulator.Pop(); var instr = methodToInline.Body.Instructions[instrIndex]; var instrTrue = (Instruction)instr.Operand; var instrFalse = methodToInline.Body.Instructions[instrIndex + 1]; if (val1.IsInt32()) return EmulateBranch(1, Int32Value.CompareFalse((Int32Value)val1), instrTrue, instrFalse); return -1; } bool EmulateToReturn(int index, Instruction lastInstr) { int pushes, pops; lastInstr.CalculateStackUsage(false, out pushes, out pops); instructionEmulator.Pop(pops); returnValue = null; if (pushes != 0) { returnValue = new UnknownValue(); instructionEmulator.SetProtected(returnValue); instructionEmulator.Push(returnValue); } if (!EmulateInstructions(ref index, true)) return false; if (index >= methodToInline.Body.Instructions.Count) return false; if (methodToInline.Body.Instructions[index].OpCode.Code != Code.Ret) return false; if (returnValue != null) { if (instructionEmulator.Pop() != returnValue) return false; } return instructionEmulator.StackSize() == 0; } public static bool CanInline(MethodDef method) { if (method == null || method.Body == null) return false; if (method.Attributes != (MethodAttributes.Assembly | MethodAttributes.Static)) return false; if (method.Body.ExceptionHandlers.Count > 0) return false; var parameters = method.MethodSig.GetParams(); int paramCount = parameters.Count; if (paramCount < 2) return false; if (method.GenericParameters.Count > 0) { foreach (var gp in method.GenericParameters) { if (gp.GenericParamConstraints.Count == 0) return false; } } var param1 = parameters[paramCount - 1]; var param2 = parameters[paramCount - 2]; if (!IsIntType(param1.ElementType)) return false; if (!IsIntType(param2.ElementType)) return false; return true; } static bool IsIntType(ElementType etype) { return etype == ElementType.Char || etype == ElementType.I2 || etype == ElementType.I4; } protected override bool IsReturn(MethodDef methodToInline, int instrIndex) { int oldIndex = instrIndex; if (base.IsReturn(methodToInline, oldIndex)) return true; return false; } } }