diff --git a/blocks/DotNetUtils.cs b/blocks/DotNetUtils.cs index 2a322266..03462a8b 100644 --- a/blocks/DotNetUtils.cs +++ b/blocks/DotNetUtils.cs @@ -200,6 +200,21 @@ namespace de4dot.blocks { } } + // Returns true if it's one of the ldarg instructions + public static bool isLdarg(Instruction instr) { + 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: + return true; + default: + return false; + } + } + // Return true if it's one of the stloc instructions public static bool isStloc(Instruction instr) { switch (instr.OpCode.Code) { @@ -577,6 +592,25 @@ namespace de4dot.blocks { return null; } + // Copies most things but not everything + public static MethodDefinition clone(MethodDefinition method) { + var newMethod = new MethodDefinition(method.Name, method.Attributes, method.MethodReturnType.ReturnType); + newMethod.MetadataToken = method.MetadataToken; + newMethod.Attributes = method.Attributes; + newMethod.ImplAttributes = method.ImplAttributes; + newMethod.HasThis = method.HasThis; + newMethod.ExplicitThis = method.ExplicitThis; + newMethod.CallingConvention = method.CallingConvention; + newMethod.SemanticsAttributes = method.SemanticsAttributes; + newMethod.DeclaringType = method.DeclaringType; + foreach (var arg in method.Parameters) + newMethod.Parameters.Add(new ParameterDefinition(arg.Name, arg.Attributes, arg.ParameterType)); + foreach (var gp in method.GenericParameters) + newMethod.GenericParameters.Add(new GenericParameter(gp.Name, newMethod) { Attributes = gp.Attributes }); + copyBodyFromTo(method, newMethod); + return newMethod; + } + public static Instruction clone(Instruction instr) { return new Instruction { Offset = instr.Offset, @@ -1174,5 +1208,50 @@ namespace de4dot.blocks { return false; } + + public static IList getArgPushes(IList instrs, int index) { + return getArgPushes(instrs, ref index); + } + + public static IList getArgPushes(IList instrs, ref int index) { + if (index < 0 || index >= instrs.Count) + return null; + var startInstr = instrs[index]; + int pushes, pops; + calculateStackUsage(startInstr, false, out pushes, out pops); + + index--; + int numArgs = pops; + var args = new List(numArgs); + int stackSize = numArgs; + while (index >= 0 && args.Count != numArgs) { + var instr = instrs[index--]; + calculateStackUsage(instr, false, out pushes, out pops); + if (instr.OpCode.Code == Code.Dup) { + args.Add(instr); + stackSize--; + } + else { + if (pushes == 1) + args.Add(instr); + else if (pushes > 1) + throw new NotImplementedException(); + stackSize -= pushes; + + if (pops != 0) { + index++; + if (getArgPushes(instrs, ref index) == null) + return null; + } + } + + if (stackSize < 0) + return null; + } + if (args.Count != numArgs) + return null; + args.Reverse(); + return args; + } } } diff --git a/blocks/Instr.cs b/blocks/Instr.cs index 23c44bb6..536a9c9c 100644 --- a/blocks/Instr.cs +++ b/blocks/Instr.cs @@ -111,6 +111,10 @@ namespace de4dot.blocks { return DotNetUtils.getLdcI4Value(instruction); } + public bool isLdarg() { + return DotNetUtils.isLdarg(instruction); + } + public bool isStloc() { return DotNetUtils.isStloc(instruction); } diff --git a/blocks/blocks.csproj b/blocks/blocks.csproj index adc6e268..1e3daa86 100644 --- a/blocks/blocks.csproj +++ b/blocks/blocks.csproj @@ -39,19 +39,20 @@ + + + - - diff --git a/blocks/cflow/BlockCflowDeobfuscator.cs b/blocks/cflow/BlockCflowDeobfuscator.cs index 2426d8ec..d0626866 100644 --- a/blocks/cflow/BlockCflowDeobfuscator.cs +++ b/blocks/cflow/BlockCflowDeobfuscator.cs @@ -17,24 +17,22 @@ along with de4dot. If not, see . */ +using System; using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; namespace de4dot.blocks.cflow { - class BlockCflowDeobfuscator { - Blocks blocks; + class BlockCflowDeobfuscator : BlockDeobfuscator { Block block; InstructionEmulator instructionEmulator = new InstructionEmulator(); - public void init(Blocks blocks, Block block) { - this.blocks = blocks; + protected override bool deobfuscate(Block block) { this.block = block; + if (!DotNetUtils.isConditionalBranch(block.LastInstr.OpCode.Code) && block.LastInstr.OpCode.Code != Code.Switch) + return false; instructionEmulator.init(blocks); - } - // Returns true if code was updated, false otherwise - public bool deobfuscate() { var instructions = block.Instructions; if (instructions.Count == 0) return false; @@ -44,7 +42,7 @@ namespace de4dot.blocks.cflow { instructionEmulator.emulate(instr); } } - catch (System.NullReferenceException) { + catch (NullReferenceException) { // Here if eg. invalid metadata token in a call instruction (operand is null) return false; } diff --git a/blocks/cflow/BlockDeobfuscator.cs b/blocks/cflow/BlockDeobfuscator.cs new file mode 100644 index 00000000..d02aee81 --- /dev/null +++ b/blocks/cflow/BlockDeobfuscator.cs @@ -0,0 +1,55 @@ +/* + 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; + +namespace de4dot.blocks.cflow { + public abstract class BlockDeobfuscator : IBlocksDeobfuscator { + protected List allBlocks; + protected Blocks blocks; + + public bool ExecuteOnNoChange { get; set; } + + public virtual void deobfuscateBegin(Blocks blocks) { + this.blocks = blocks; + } + + public bool deobfuscate(List allBlocks) { + init(allBlocks); + + bool changed = false; + foreach (var block in allBlocks) { + try { + changed |= deobfuscate(block); + } + catch (NullReferenceException) { + // Here if eg. invalid metadata token in a call instruction (operand is null) + } + } + return changed; + } + + protected virtual void init(List allBlocks) { + this.allBlocks = allBlocks; + } + + protected abstract bool deobfuscate(Block block); + } +} diff --git a/blocks/cflow/BlocksCflowDeobfuscator.cs b/blocks/cflow/BlocksCflowDeobfuscator.cs index 1bca5490..0c9a2236 100644 --- a/blocks/cflow/BlocksCflowDeobfuscator.cs +++ b/blocks/cflow/BlocksCflowDeobfuscator.cs @@ -23,16 +23,38 @@ using Mono.Cecil.Cil; namespace de4dot.blocks.cflow { public class BlocksCflowDeobfuscator { - BlockCflowDeobfuscator blockCflowDeobfuscator = new BlockCflowDeobfuscator(); Blocks blocks; List allBlocks = new List(); - SwitchCflowDeobfuscator switchCflowDeobfuscator = new SwitchCflowDeobfuscator(); - DeadCodeRemover deadCodeRemover = new DeadCodeRemover(); - DeadStoreRemover deadStoreRemover = new DeadStoreRemover(); - StLdlocFixer stLdlocFixer = new StLdlocFixer(); - ConstantsFolder constantsFolder = new ConstantsFolder(); + List userBlocksDeobfuscators = new List(); + List ourBlocksDeobfuscators = new List(); - public IMethodCallInliner MethodCallInliner { get; set; } + public BlocksCflowDeobfuscator() { + init(); + } + + public BlocksCflowDeobfuscator(IEnumerable blocksDeobfuscator) { + init(); + add(blocksDeobfuscator); + } + + void init() { + ourBlocksDeobfuscators.Add(new BlockCflowDeobfuscator { ExecuteOnNoChange = false }); + ourBlocksDeobfuscators.Add(new SwitchCflowDeobfuscator { ExecuteOnNoChange = false }); + ourBlocksDeobfuscators.Add(new DeadStoreRemover { ExecuteOnNoChange = false }); + ourBlocksDeobfuscators.Add(new DeadCodeRemover { ExecuteOnNoChange = false }); + ourBlocksDeobfuscators.Add(new ConstantsFolder { ExecuteOnNoChange = true }); + ourBlocksDeobfuscators.Add(new StLdlocFixer { ExecuteOnNoChange = true }); + } + + public void add(IEnumerable blocksDeobfuscators) { + foreach (var bd in blocksDeobfuscators) + add(bd); + } + + public void add(IBlocksDeobfuscator blocksDeobfuscator) { + if (blocksDeobfuscator != null) + userBlocksDeobfuscators.Add(blocksDeobfuscator); + } public void init(Blocks blocks) { this.blocks = blocks; @@ -41,6 +63,10 @@ namespace de4dot.blocks.cflow { public void deobfuscate() { bool changed; int iterations = -1; + + deobfuscateBegin(userBlocksDeobfuscators); + deobfuscateBegin(ourBlocksDeobfuscators); + do { iterations++; changed = false; @@ -52,40 +78,39 @@ 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) - continue; - blockCflowDeobfuscator.init(blocks, block); - changed |= blockCflowDeobfuscator.deobfuscate(); - } - - switchCflowDeobfuscator.init(blocks, allBlocks); - changed |= switchCflowDeobfuscator.deobfuscate(); - - deadStoreRemover.init(blocks, allBlocks); - changed |= deadStoreRemover.remove(); - - deadCodeRemover.init(allBlocks); - changed |= deadCodeRemover.remove(); - - if (!changed) { - constantsFolder.init(blocks, allBlocks); - changed |= constantsFolder.deobfuscate(); - } - - if (!changed) { - stLdlocFixer.init(allBlocks, blocks.Locals); - changed |= stLdlocFixer.fix(); - } + changed |= deobfuscate(userBlocksDeobfuscators, allBlocks); + changed |= deobfuscate(ourBlocksDeobfuscators, allBlocks); + changed |= deobfuscateNoChange(changed, userBlocksDeobfuscators, allBlocks); + changed |= deobfuscateNoChange(changed, ourBlocksDeobfuscators, allBlocks); } while (changed); } + void deobfuscateBegin(IEnumerable bds) { + foreach (var bd in bds) + bd.deobfuscateBegin(blocks); + } + + bool deobfuscate(IEnumerable bds, List allBlocks) { + bool changed = false; + foreach (var bd in bds) { + if (bd.ExecuteOnNoChange) + continue; + changed |= bd.deobfuscate(allBlocks); + } + return changed; + } + + bool deobfuscateNoChange(bool changed, IEnumerable bds, List allBlocks) { + foreach (var bd in bds) { + if (changed) + break; + if (!bd.ExecuteOnNoChange) + continue; + changed |= bd.deobfuscate(allBlocks); + } + return changed; + } + // Hack for old Dotfuscator bool fixDotfuscatorLoop() { /* diff --git a/blocks/cflow/CachedCflowDeobfuscator.cs b/blocks/cflow/CachedCflowDeobfuscator.cs new file mode 100644 index 00000000..c3a8bd86 --- /dev/null +++ b/blocks/cflow/CachedCflowDeobfuscator.cs @@ -0,0 +1,74 @@ +/* + 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.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace de4dot.blocks.cflow { + // Only deobfuscates a method once. A copy of the method (now deobfuscated) is returned. + public class CachedCflowDeobfuscator { + BlocksCflowDeobfuscator cflowDeobfuscator = new BlocksCflowDeobfuscator(); + Dictionary deobfuscated = new Dictionary(); + + public CachedCflowDeobfuscator() { + } + + public CachedCflowDeobfuscator(IEnumerable blocksDeobfuscators) { + add(blocksDeobfuscators); + } + + public void add(IEnumerable blocksDeobfuscators) { + foreach (var bd in blocksDeobfuscators) + cflowDeobfuscator.add(bd); + } + + public void add(IBlocksDeobfuscator blocksDeobfuscator) { + cflowDeobfuscator.add(blocksDeobfuscator); + } + + public MethodDefinition deobfuscate(MethodDefinition method) { + MethodDefinition deobfuscatedMethod; + if (deobfuscated.TryGetValue(method, out deobfuscatedMethod)) + return deobfuscatedMethod; + + if (method.Body == null || method.Body.Instructions.Count == 0) { + deobfuscated[method] = method; + return method; + } + + deobfuscatedMethod = DotNetUtils.clone(method); + deobfuscated[method] = deobfuscatedMethod; + + var blocks = new Blocks(deobfuscatedMethod); + deobfuscate(blocks); + IList allInstructions; + IList allExceptionHandlers; + blocks.getCode(out allInstructions, out allExceptionHandlers); + DotNetUtils.restoreBody(deobfuscatedMethod, allInstructions, allExceptionHandlers); + + return deobfuscatedMethod; + } + + void deobfuscate(Blocks blocks) { + cflowDeobfuscator.init(blocks); + cflowDeobfuscator.deobfuscate(); + } + } +} diff --git a/blocks/cflow/CflowDeobfuscator.cs b/blocks/cflow/CflowDeobfuscator.cs index 7eb3862d..3af00398 100644 --- a/blocks/cflow/CflowDeobfuscator.cs +++ b/blocks/cflow/CflowDeobfuscator.cs @@ -25,8 +25,11 @@ namespace de4dot.blocks.cflow { public class CflowDeobfuscator : ICflowDeobfuscator { BlocksCflowDeobfuscator cflowDeobfuscator = new BlocksCflowDeobfuscator(); - public CflowDeobfuscator(IMethodCallInliner methodCallInliner) { - cflowDeobfuscator.MethodCallInliner = methodCallInliner; + public CflowDeobfuscator() { + } + + public CflowDeobfuscator(IBlocksDeobfuscator blocksDeobfuscator) { + cflowDeobfuscator.add(blocksDeobfuscator); } public void deobfuscate(MethodDefinition method) { diff --git a/blocks/cflow/ConstantsFolder.cs b/blocks/cflow/ConstantsFolder.cs index e406a0b6..327a0288 100644 --- a/blocks/cflow/ConstantsFolder.cs +++ b/blocks/cflow/ConstantsFolder.cs @@ -17,6 +17,7 @@ along with de4dot. If not, see . */ +using System; using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; @@ -24,65 +25,62 @@ using de4dot.blocks; namespace de4dot.blocks.cflow { // Very simple constants folder which is all that's needed at the moment - class ConstantsFolder { - List allBlocks; - Blocks blocks; + class ConstantsFolder : BlockDeobfuscator { InstructionEmulator instructionEmulator = new InstructionEmulator(); List args; - public void init(Blocks blocks, List allBlocks) { - this.blocks = blocks; - this.allBlocks = allBlocks; + protected override void init(List allBlocks) { + base.init(allBlocks); args = DotNetUtils.getParameters(blocks.Method); } - public bool deobfuscate() { + protected override bool deobfuscate(Block block) { bool changed = false; - foreach (var block in allBlocks) { - instructionEmulator.init(blocks); - var instrs = block.Instructions; - for (int i = 0; i < instrs.Count; i++) { - var instr = instrs[i]; - switch (instr.OpCode.Code) { - case Code.Ldarg: - case Code.Ldarg_0: - case Code.Ldarg_1: - case Code.Ldarg_2: - case Code.Ldarg_3: - case Code.Ldarg_S: - changed |= fixLoadInstruction(block, i, instructionEmulator.getArg(DotNetUtils.getParameter(args, instr.Instruction))); - break; + instructionEmulator.init(blocks); + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count; i++) { + var instr = instrs[i]; - case Code.Ldloc: - case Code.Ldloc_0: - case Code.Ldloc_1: - case Code.Ldloc_2: - case Code.Ldloc_3: - case Code.Ldloc_S: - changed |= fixLoadInstruction(block, i, instructionEmulator.getLocal(DotNetUtils.getLocalVar(blocks.Locals, instr.Instruction))); - break; + switch (instr.OpCode.Code) { + case Code.Ldarg: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + case Code.Ldarg_S: + changed |= fixLoadInstruction(block, i, instructionEmulator.getArg(DotNetUtils.getParameter(args, instr.Instruction))); + break; - case Code.Ldarga: - case Code.Ldarga_S: - instructionEmulator.makeArgUnknown((ParameterDefinition)instr.Operand); - break; + case Code.Ldloc: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloc_S: + changed |= fixLoadInstruction(block, i, instructionEmulator.getLocal(DotNetUtils.getLocalVar(blocks.Locals, instr.Instruction))); + break; - case Code.Ldloca: - case Code.Ldloca_S: - instructionEmulator.makeLocalUnknown((VariableDefinition)instr.Operand); - break; - } + case Code.Ldarga: + case Code.Ldarga_S: + instructionEmulator.makeArgUnknown((ParameterDefinition)instr.Operand); + break; - try { - instructionEmulator.emulate(instr.Instruction); - } - catch (System.NullReferenceException) { - // Here if eg. invalid metadata token in a call instruction (operand is null) - break; - } + case Code.Ldloca: + case Code.Ldloca_S: + instructionEmulator.makeLocalUnknown((VariableDefinition)instr.Operand); + break; + } + + try { + instructionEmulator.emulate(instr.Instruction); + } + catch (NullReferenceException) { + // Here if eg. invalid metadata token in a call instruction (operand is null) + break; } } + return changed; } diff --git a/blocks/cflow/DeadCodeRemover.cs b/blocks/cflow/DeadCodeRemover.cs index 6af6e4bb..410a62cc 100644 --- a/blocks/cflow/DeadCodeRemover.cs +++ b/blocks/cflow/DeadCodeRemover.cs @@ -23,31 +23,11 @@ using Mono.Cecil.Cil; namespace de4dot.blocks.cflow { // Removes dead code that is the result of one of our optimizations, or created by the // obfuscator. - class DeadCodeRemover { - List allBlocks; + class DeadCodeRemover : BlockDeobfuscator { List allDeadInstructions = new List(); InstructionExpressionFinder instructionExpressionFinder = new InstructionExpressionFinder(); - public void init(List allBlocks) { - this.allBlocks = allBlocks; - } - - public bool remove() { - bool changed = false; - - foreach (var block in allBlocks) { - try { - changed |= remove(block); - } - catch (System.NullReferenceException) { - // Here if eg. invalid metadata token in a call instruction (operand is null) - } - } - - return changed; - } - - bool remove(Block block) { + protected override bool deobfuscate(Block block) { allDeadInstructions.Clear(); bool changed = false; diff --git a/blocks/cflow/DeadStoreRemover.cs b/blocks/cflow/DeadStoreRemover.cs index 9a4347ae..3d7f870c 100644 --- a/blocks/cflow/DeadStoreRemover.cs +++ b/blocks/cflow/DeadStoreRemover.cs @@ -26,17 +26,12 @@ namespace de4dot.blocks.cflow { // dead code and remove it. // I've only seen Xenocode generate this kind of code, so the code below is a special case of // the more general case. - class DeadStoreRemover { + class DeadStoreRemover : IBlocksDeobfuscator { Blocks blocks; - List allBlocks = new List(); + List allBlocks; List localFlags = new List(); List deadLocals = new List(); - public void init(Blocks blocks, List allBlocks) { - this.blocks = blocks; - this.allBlocks = allBlocks; - } - [Flags] enum AccessFlags { None = 0, @@ -44,7 +39,18 @@ namespace de4dot.blocks.cflow { Write = 2, } - public bool remove() { + public bool ExecuteOnNoChange { get; set; } + + public void deobfuscateBegin(Blocks blocks) { + this.blocks = blocks; + } + + public bool deobfuscate(List allBlocks) { + this.allBlocks = allBlocks; + return remove(); + } + + bool remove() { if (blocks.Locals.Count == 0) return false; diff --git a/blocks/cflow/IMethodCallInliner.cs b/blocks/cflow/IBlocksDeobfuscator.cs similarity index 76% rename from blocks/cflow/IMethodCallInliner.cs rename to blocks/cflow/IBlocksDeobfuscator.cs index 49814968..653083e8 100644 --- a/blocks/cflow/IMethodCallInliner.cs +++ b/blocks/cflow/IBlocksDeobfuscator.cs @@ -17,11 +17,15 @@ along with de4dot. If not, see . */ -namespace de4dot.blocks.cflow { - public interface IMethodCallInliner { - void init(Blocks blocks, Block block); +using System.Collections.Generic; - // Returns true if something was inlined - bool deobfuscate(); +namespace de4dot.blocks.cflow { + public interface IBlocksDeobfuscator { + bool ExecuteOnNoChange { get; } + + void deobfuscateBegin(Blocks blocks); + + // Returns true if something was updated + bool deobfuscate(List allBlocks); } } diff --git a/blocks/cflow/InstructionEmulator.cs b/blocks/cflow/InstructionEmulator.cs index 786f5b09..18ce2bae 100644 --- a/blocks/cflow/InstructionEmulator.cs +++ b/blocks/cflow/InstructionEmulator.cs @@ -26,6 +26,7 @@ using Mono.Cecil.Metadata; namespace de4dot.blocks.cflow { public class InstructionEmulator { ValueStack valueStack = new ValueStack(); + Dictionary protectedStackValues = new Dictionary(); IList parameterDefinitions; IList variableDefinitions; List args = new List(); @@ -52,6 +53,7 @@ namespace de4dot.blocks.cflow { this.parameterDefinitions = method.Parameters; this.variableDefinitions = method.Body.Variables; valueStack.init(); + protectedStackValues.Clear(); if (method != prev_method) { prev_method = method; @@ -77,6 +79,10 @@ namespace de4dot.blocks.cflow { locals.AddRange(cached_locals); } + public void setProtected(Value value) { + protectedStackValues[value] = true; + } + static Value getUnknownValue(TypeReference typeReference) { if (typeReference == null) return new UnknownValue(); @@ -94,9 +100,11 @@ namespace de4dot.blocks.cflow { return new UnknownValue(); } - static Value truncateValue(Value value, TypeReference typeReference) { + Value truncateValue(Value value, TypeReference typeReference) { if (typeReference == null) return value; + if (protectedStackValues.ContainsKey(value)) + return value; switch (typeReference.EType) { case ElementType.Boolean: @@ -208,6 +216,10 @@ namespace de4dot.blocks.cflow { return new UnknownValue(); } + public int stackSize() { + return valueStack.Size; + } + public void push(Value value) { valueStack.push(value); } diff --git a/blocks/cflow/MethodCallInlinerBase.cs b/blocks/cflow/MethodCallInlinerBase.cs index 6cb49e3f..17f5de53 100644 --- a/blocks/cflow/MethodCallInlinerBase.cs +++ b/blocks/cflow/MethodCallInlinerBase.cs @@ -17,11 +17,12 @@ along with de4dot. If not, see . */ +using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; namespace de4dot.blocks.cflow { - public abstract class MethodCallInlinerBase : IMethodCallInliner { + public abstract class MethodCallInlinerBase : IBlocksDeobfuscator { // We can't catch all infinite loops, so inline methods at most this many times const int MAX_ITERATIONS = 10; @@ -29,24 +30,46 @@ namespace de4dot.blocks.cflow { protected Block block; int iteration; - public void init(Blocks blocks, Block block) { + public bool ExecuteOnNoChange { get; set; } + + public void deobfuscateBegin(Blocks blocks) { this.blocks = blocks; - this.block = block; - this.iteration = 0; + iteration = 0; } - public bool deobfuscate() { + public bool deobfuscate(List allBlocks) { if (iteration++ >= MAX_ITERATIONS) return false; - return deobfuscateInternal(); + bool changed = false; + foreach (var block in allBlocks) { + this.block = block; + changed |= deobfuscateInternal(); + } + return changed; } protected abstract bool deobfuscateInternal(); + protected class InstructionPatcher { + readonly int patchIndex; + public readonly int afterIndex; + public readonly Instruction lastInstr; + readonly Instr clonedInstr; + public InstructionPatcher(int patchIndex, int afterIndex, Instruction lastInstr) { + this.patchIndex = patchIndex; + this.afterIndex = afterIndex; + this.lastInstr = lastInstr; + this.clonedInstr = new Instr(DotNetUtils.clone(lastInstr)); + } + + public void patch(Block block) { + block.Instructions[patchIndex] = clonedInstr; + } + } + protected bool inlineLoadMethod(int patchIndex, MethodDefinition methodToInline, Instruction loadInstr, int instrIndex) { - var instr = DotNetUtils.getInstruction(methodToInline.Body.Instructions, ref instrIndex); - if (instr == null || instr.OpCode.Code != Code.Ret) + if (!isReturn(methodToInline, instrIndex)) return false; int methodArgsCount = DotNetUtils.getArgsCount(methodToInline); @@ -58,6 +81,21 @@ namespace de4dot.blocks.cflow { } protected bool inlineOtherMethod(int patchIndex, MethodDefinition methodToInline, Instruction instr, int instrIndex, int popLastArgs = 0) { + return patchMethod(methodToInline, tryInlineOtherMethod(patchIndex, methodToInline, instr, instrIndex, popLastArgs)); + } + + protected bool patchMethod(MethodDefinition methodToInline, InstructionPatcher patcher) { + if (patcher == null) + return false; + + if (!isReturn(methodToInline, patcher.afterIndex)) + return false; + + patcher.patch(block); + return true; + } + + protected InstructionPatcher tryInlineOtherMethod(int patchIndex, MethodDefinition methodToInline, Instruction instr, int instrIndex, int popLastArgs = 0) { int loadIndex = 0; int methodArgsCount = DotNetUtils.getArgsCount(methodToInline); bool foundLdarga = false; @@ -83,75 +121,66 @@ namespace de4dot.blocks.cflow { break; if (DotNetUtils.getArgIndex(instr) != loadIndex) - return false; + return null; loadIndex++; instr = DotNetUtils.getInstruction(methodToInline.Body.Instructions, ref instrIndex); } if (instr == null || loadIndex != methodArgsCount - popLastArgs) - return false; + return null; if (instr.OpCode.Code == Code.Call || instr.OpCode.Code == Code.Callvirt) { if (foundLdarga) - return false; + return null; var callInstr = instr; var calledMethod = callInstr.Operand as MethodReference; if (calledMethod == null) - return false; + return null; if (!isCompatibleType(-1, calledMethod.MethodReturnType.ReturnType, methodToInline.MethodReturnType.ReturnType)) - return false; + return null; if (!checkSameMethods(calledMethod, methodToInline, popLastArgs)) - return false; + return null; - instr = DotNetUtils.getInstruction(methodToInline.Body.Instructions, ref instrIndex); - if (instr == null || instr.OpCode.Code != Code.Ret) - return false; - - block.Instructions[patchIndex] = new Instr(DotNetUtils.clone(callInstr)); - return true; + return new InstructionPatcher(patchIndex, instrIndex, callInstr); } else if (instr.OpCode.Code == Code.Newobj) { if (foundLdarga) - return false; + return null; var newobjInstr = instr; var ctor = newobjInstr.Operand as MethodReference; if (ctor == null) - return false; + return null; if (!isCompatibleType(-1, ctor.DeclaringType, methodToInline.MethodReturnType.ReturnType)) - return false; + return null; var methodArgs = DotNetUtils.getArgs(methodToInline); var calledMethodArgs = DotNetUtils.getArgs(ctor); if (methodArgs.Count + 1 - popLastArgs != calledMethodArgs.Count) - return false; + return null; for (int i = 1; i < calledMethodArgs.Count; i++) { if (!isCompatibleType(i, calledMethodArgs[i], methodArgs[i - 1])) - return false; + return null; } - instr = DotNetUtils.getInstruction(methodToInline.Body.Instructions, ref instrIndex); - if (instr == null || instr.OpCode.Code != Code.Ret) - return false; - - block.Instructions[patchIndex] = new Instr(DotNetUtils.clone(newobjInstr)); - return true; + return new InstructionPatcher(patchIndex, instrIndex, newobjInstr); } else if (instr.OpCode.Code == Code.Ldfld || instr.OpCode.Code == Code.Ldflda || instr.OpCode.Code == Code.Ldftn || instr.OpCode.Code == Code.Ldvirtftn) { var ldInstr = instr; - instr = DotNetUtils.getInstruction(methodToInline.Body.Instructions, ref instrIndex); - if (instr == null || instr.OpCode.Code != Code.Ret) - return false; - if (methodArgsCount != 1) - return false; - block.Instructions[patchIndex] = new Instr(DotNetUtils.clone(ldInstr)); - return true; + return null; + + return new InstructionPatcher(patchIndex, instrIndex, ldInstr); } - return false; + return null; + } + + protected virtual bool isReturn(MethodDefinition methodToInline, int instrIndex) { + var instr = DotNetUtils.getInstruction(methodToInline.Body.Instructions, ref instrIndex); + return instr != null && instr.OpCode.Code == Code.Ret; } protected bool checkSameMethods(MethodReference method, MethodDefinition methodToInline, int ignoreLastMethodToInlineArgs = 0) { diff --git a/blocks/cflow/StLdlocFixer.cs b/blocks/cflow/StLdlocFixer.cs index 114fef29..3e6a69c8 100644 --- a/blocks/cflow/StLdlocFixer.cs +++ b/blocks/cflow/StLdlocFixer.cs @@ -22,25 +22,15 @@ using Mono.Cecil.Cil; namespace de4dot.blocks.cflow { // Replace stloc + ldloc with dup + stloc - class StLdlocFixer { + class StLdlocFixer : BlockDeobfuscator { IList locals; - List allBlocks; - public void init(List allBlocks, IList locals) { - this.allBlocks = allBlocks; - this.locals = locals; + protected override void init(List allBlocks) { + base.init(allBlocks); + locals = blocks.Locals; } - public bool fix() { - bool changed = false; - - foreach (var block in allBlocks) - changed |= fix(block); - - return changed; - } - - bool fix(Block block) { + protected override bool deobfuscate(Block block) { bool changed = false; var instructions = block.Instructions; for (int i = 0; i < instructions.Count; i++) { diff --git a/blocks/cflow/SwitchCflowDeobfuscator.cs b/blocks/cflow/SwitchCflowDeobfuscator.cs index 166a4fbb..e6b022f7 100644 --- a/blocks/cflow/SwitchCflowDeobfuscator.cs +++ b/blocks/cflow/SwitchCflowDeobfuscator.cs @@ -17,54 +17,34 @@ along with de4dot. If not, see . */ +using System; using System.Collections.Generic; using Mono.Cecil.Cil; namespace de4dot.blocks.cflow { - class SwitchCflowDeobfuscator { - List allBlocks; - Blocks blocks; + class SwitchCflowDeobfuscator : BlockDeobfuscator { InstructionEmulator instructionEmulator = new InstructionEmulator(); - public void init(Blocks blocks, List allBlocks) { - this.blocks = blocks; - this.allBlocks = allBlocks; - } + protected override bool deobfuscate(Block switchBlock) { + if (switchBlock.LastInstr.OpCode.Code != Code.Switch) + return false; - public bool deobfuscate() { - bool changed = false; + if (isSwitchTopOfStack(switchBlock) && deobfuscateTos(switchBlock)) + return true; - foreach (var switchBlock in allBlocks) { - if (switchBlock.LastInstr.OpCode.Code != Code.Switch) - continue; + if (isLdlocBranch(switchBlock, true) && deobfuscateLdloc(switchBlock)) + return true; - if (isSwitchTopOfStack(switchBlock) && deobfuscateTos(switchBlock)) { - changed = true; - continue; - } + if (isStLdlocBranch(switchBlock, true) && deobfuscateStLdloc(switchBlock)) + return true; - if (isLdlocBranch(switchBlock, true) && deobfuscateLdloc(switchBlock)) { - changed = true; - continue; - } + if (isSwitchType1(switchBlock) && deobfuscateType1(switchBlock)) + return true; - if (isStLdlocBranch(switchBlock, true) && deobfuscateStLdloc(switchBlock)) { - changed = true; - continue; - } + if (switchBlock.FirstInstr.isLdloc() && fixSwitchBranch(switchBlock)) + return true; - if (isSwitchType1(switchBlock) && deobfuscateType1(switchBlock)) { - changed = true; - continue; - } - - if (switchBlock.FirstInstr.isLdloc() && fixSwitchBranch(switchBlock)) { - changed = true; - continue; - } - } - - return changed; + return false; } static bool isSwitchTopOfStack(Block switchBlock) { @@ -268,7 +248,7 @@ namespace de4dot.blocks.cflow { try { instructionEmulator.emulate(switchBlock.Instructions, 0, switchBlock.Instructions.Count - 1); } - catch (System.NullReferenceException) { + catch (NullReferenceException) { // Here if eg. invalid metadata token in a call instruction (operand is null) target = null; return false; @@ -283,7 +263,7 @@ namespace de4dot.blocks.cflow { instructionEmulator.emulate(source.Instructions); instructionEmulator.emulate(switchBlock.Instructions, 0, switchBlock.Instructions.Count - 1); } - catch (System.NullReferenceException) { + catch (NullReferenceException) { // Here if eg. invalid metadata token in a call instruction (operand is null) return false; } diff --git a/blocks/cflow/ValueStack.cs b/blocks/cflow/ValueStack.cs index e9528fd5..7d26d67e 100644 --- a/blocks/cflow/ValueStack.cs +++ b/blocks/cflow/ValueStack.cs @@ -25,6 +25,10 @@ namespace de4dot.blocks.cflow { class ValueStack { List stack = new List(); + public int Size { + get { return stack.Count; } + } + public void init() { stack.Clear(); } diff --git a/de4dot.code/IObfuscatedFile.cs b/de4dot.code/IObfuscatedFile.cs index 2923d683..d02c900b 100644 --- a/de4dot.code/IObfuscatedFile.cs +++ b/de4dot.code/IObfuscatedFile.cs @@ -32,6 +32,7 @@ namespace de4dot.code { INameChecker NameChecker { get; } bool RenameResourcesInCode { get; } bool RemoveNamespaceWithOneType { get; } + bool RenameResourceKeys { get; } void deobfuscateBegin(); void deobfuscate(); diff --git a/de4dot.code/MethodReturnValueInliner.cs b/de4dot.code/MethodReturnValueInliner.cs index 52695e94..ba2480ba 100644 --- a/de4dot.code/MethodReturnValueInliner.cs +++ b/de4dot.code/MethodReturnValueInliner.cs @@ -146,6 +146,12 @@ namespace de4dot.code { Blocks blocks; VariableValues variableValues; int errors = 0; + bool useUnknownArgs = false; + + public bool UseUnknownArgs { + get { return useUnknownArgs; } + set { useUnknownArgs = value; } + } protected class CallResult { public Block block; @@ -200,13 +206,16 @@ namespace de4dot.code { } } - void getLocalVariableValue(VariableDefinition variable, out object value) { + bool getLocalVariableValue(VariableDefinition variable, out object value) { if (variableValues == null) variableValues = new VariableValues(blocks.Locals, allBlocks); var val = variableValues.getValue(variable); - if (!val.isValid()) - throw new ApplicationException("Could not get value of local variable"); + if (!val.isValid()) { + value = null; + return false; + } value = val.Value; + return true; } void findAllCallResults() { @@ -320,17 +329,29 @@ namespace de4dot.code { getLocalVariableValue(Instr.getLocalVar(blocks.Locals, instr), out arg); break; + case Code.Ldfld: case Code.Ldsfld: arg = instr.Operand; break; default: - Log.w("Could not find all arguments to method {0} ({1:X8}), instr: {2}", - Utils.removeNewlines(method), - method.MetadataToken.ToInt32(), - instr); - errors++; - return false; + int pushes, pops; + DotNetUtils.calculateStackUsage(instr.Instruction, false, out pushes, out pops); + if (!useUnknownArgs || pushes != 1) { + Log.w("Could not find all arguments to method {0} ({1:X8}), instr: {2}", + Utils.removeNewlines(method), + method.MetadataToken.ToInt32(), + instr); + errors++; + return false; + } + + for (int i = 0; i < pops; i++) { + if (!getArg(method, block, ref arg, ref instrIndex)) + return false; + } + arg = null; + break; } break; } diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index fe21f169..f5ea464d 100644 --- a/de4dot.code/ObfuscatedFile.cs +++ b/de4dot.code/ObfuscatedFile.cs @@ -120,6 +120,10 @@ namespace de4dot.code { get { return (deob.RenamingOptions & RenamingOptions.RemoveNamespaceIfOneType) != 0; } } + public bool RenameResourceKeys { + get { return (deob.RenamingOptions & RenamingOptions.RenameResourceKeys) != 0; } + } + public IDeobfuscator Deobfuscator { get { return deob; } } @@ -541,7 +545,7 @@ namespace de4dot.code { Log.v("Deobfuscating methods"); var methodPrinter = new MethodPrinter(); - var cflowDeobfuscator = new BlocksCflowDeobfuscator { MethodCallInliner = deob.MethodCallInliner }; + var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators); foreach (var method in allMethods) { Log.v("Deobfuscating {0} ({1:X8})", Utils.removeNewlines(method), method.MetadataToken.ToUInt32()); Log.indent(); @@ -715,7 +719,7 @@ namespace de4dot.code { return; deobfuscate(method, "Deobfuscating control flow", (blocks) => { - var cflowDeobfuscator = new BlocksCflowDeobfuscator { MethodCallInliner = deob.MethodCallInliner }; + var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators); cflowDeobfuscator.init(blocks); cflowDeobfuscator.deobfuscate(); }); diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index 301df29c..bc39df46 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -107,6 +107,7 @@ + @@ -115,6 +116,10 @@ + + + + @@ -125,7 +130,7 @@ - + @@ -152,7 +157,7 @@ - + @@ -253,6 +258,7 @@ + @@ -262,6 +268,7 @@ + diff --git a/de4dot.code/deobfuscators/CliSecure/vm/VmOpCodeHandlerDetector.cs b/de4dot.code/deobfuscators/CliSecure/vm/VmOpCodeHandlerDetector.cs index 6542e2f9..782dd42f 100644 --- a/de4dot.code/deobfuscators/CliSecure/vm/VmOpCodeHandlerDetector.cs +++ b/de4dot.code/deobfuscators/CliSecure/vm/VmOpCodeHandlerDetector.cs @@ -200,7 +200,7 @@ namespace de4dot.code.deobfuscators.CliSecure.vm { "System.Collections.Generic.Dictionary`2", "System.UInt16", }; - var cflowDeobfuscator = new CflowDeobfuscator(new NoMethodInliner()); + var cflowDeobfuscator = new CflowDeobfuscator(); foreach (var type in module.Types) { var cctor = DotNetUtils.getMethod(type, ".cctor"); if (cctor == null) diff --git a/de4dot.code/deobfuscators/ConstantsReader.cs b/de4dot.code/deobfuscators/ConstantsReader.cs new file mode 100644 index 00000000..2a0fcd62 --- /dev/null +++ b/de4dot.code/deobfuscators/ConstantsReader.cs @@ -0,0 +1,316 @@ +/* + 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.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Metadata; +using de4dot.blocks; + +namespace de4dot.code.deobfuscators { + class ConstantsReader { + protected IInstructions instructions; + protected IList locals; + protected Dictionary localsValues = new Dictionary(); + + public interface IInstructions { + int Count { get; } + Instruction this[int index] { get; } + } + + class ListInstructions : IInstructions { + IList instrs; + + public int Count { + get { return instrs.Count; } + } + + public Instruction this[int index] { + get { return instrs[index]; } + } + + public ListInstructions(IList instrs) { + this.instrs = instrs; + } + } + + class ListInstrs : IInstructions { + IList instrs; + + public int Count { + get { return instrs.Count; } + } + + public Instruction this[int index] { + get { return instrs[index].Instruction; } + } + + public ListInstrs(IList instrs) { + this.instrs = instrs; + } + } + + public ConstantsReader(IList instrs) { + this.instructions = new ListInstructions(instrs); + } + + public ConstantsReader(IList instrs) { + this.instructions = new ListInstrs(instrs); + } + + public ConstantsReader(MethodDefinition method) + : this(method.Body.Instructions) { + this.locals = method.Body.Variables; + } + + public ConstantsReader(IList instrs, IList locals) + : this(instrs) { + this.locals = locals; + } + + public bool getNextInt32(ref int index, out int val) { + for (; index < instructions.Count; index++) { + var instr = instructions[index]; + if (!isLoadConstant(instr)) + continue; + + return getInt32(ref index, out val); + } + + val = 0; + return false; + } + + public bool isLoadConstant(Instruction instr) { + if (DotNetUtils.isLdcI4(instr)) + return true; + if (DotNetUtils.isLdloc(instr)) { + int tmp; + return getLocalConstant(instr, out tmp); + } + if (DotNetUtils.isLdarg(instr)) { + int tmp; + return getArgConstant(instr, out tmp); + } + return false; + } + + public bool getInt16(ref int index, out short val) { + int tmp; + if (!getInt32(ref index, out tmp)) { + val = 0; + return false; + } + + val = (short)tmp; + return true; + } + + struct ConstantInfo { + public int index; + public int constant; + public ConstantInfo(int index, int constant) { + this.index = index; + this.constant = constant; + } + } + + public bool getInt32(ref int index, out int val) { + val = 0; + if (index >= instructions.Count) + return false; + + var stack = new Stack(); + + int op1; + ConstantInfo info1, info2; + for (; index < instructions.Count; index++) { + var instr = instructions[index]; + switch (instr.OpCode.Code) { + case Code.Conv_I1: + if (stack.Count < 1) + goto done; + stack.Push(new ConstantInfo(index, (sbyte)stack.Pop().constant)); + break; + + case Code.Conv_U1: + if (stack.Count < 1) + goto done; + stack.Push(new ConstantInfo(index, (byte)stack.Pop().constant)); + break; + + case Code.Conv_I2: + if (stack.Count < 1) + goto done; + stack.Push(new ConstantInfo(index, (short)stack.Pop().constant)); + break; + + case Code.Conv_U2: + if (stack.Count < 1) + goto done; + stack.Push(new ConstantInfo(index, (ushort)stack.Pop().constant)); + break; + + case Code.Conv_I4: + case Code.Conv_U4: + stack.Push(new ConstantInfo(index, stack.Pop().constant)); + break; + + case Code.Not: + stack.Push(new ConstantInfo(index, ~stack.Pop().constant)); + break; + + case Code.Neg: + stack.Push(new ConstantInfo(index, -stack.Pop().constant)); + break; + + case Code.Ldloc: + case Code.Ldloc_S: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + if (!getLocalConstant(instr, out op1)) + goto done; + stack.Push(new ConstantInfo(index, op1)); + break; + + case Code.Ldarg: + case Code.Ldarg_S: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + if (!getArgConstant(instr, out op1)) + goto done; + stack.Push(new ConstantInfo(index, op1)); + break; + + case Code.Ldc_I4: + case Code.Ldc_I4_S: + 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: + stack.Push(new ConstantInfo(index, DotNetUtils.getLdcI4Value(instr))); + break; + + case Code.Add: + if (stack.Count < 2) + goto done; + info2 = stack.Pop(); + info1 = stack.Pop(); + stack.Push(new ConstantInfo(index, info1.constant + info2.constant)); + break; + + case Code.Sub: + if (stack.Count < 2) + goto done; + info2 = stack.Pop(); + info1 = stack.Pop(); + stack.Push(new ConstantInfo(index, info1.constant - info2.constant)); + break; + + case Code.Xor: + if (stack.Count < 2) + goto done; + info2 = stack.Pop(); + info1 = stack.Pop(); + stack.Push(new ConstantInfo(index, info1.constant ^ info2.constant)); + break; + + case Code.Or: + if (stack.Count < 2) + goto done; + info2 = stack.Pop(); + info1 = stack.Pop(); + stack.Push(new ConstantInfo(index, info1.constant | info2.constant)); + break; + + case Code.And: + if (stack.Count < 2) + goto done; + info2 = stack.Pop(); + info1 = stack.Pop(); + stack.Push(new ConstantInfo(index, info1.constant & info2.constant)); + break; + + case Code.Mul: + if (stack.Count < 2) + goto done; + info2 = stack.Pop(); + info1 = stack.Pop(); + stack.Push(new ConstantInfo(index, info1.constant * info2.constant)); + break; + + case Code.Div: + if (stack.Count < 2) + goto done; + info2 = stack.Pop(); + info1 = stack.Pop(); + stack.Push(new ConstantInfo(index, info1.constant / info2.constant)); + break; + + case Code.Div_Un: + if (stack.Count < 2) + goto done; + info2 = stack.Pop(); + info1 = stack.Pop(); + stack.Push(new ConstantInfo(index, (int)((uint)info1.constant / (uint)info2.constant))); + break; + + default: + goto done; + } + } +done: + if (stack.Count == 0) + return false; + while (stack.Count > 1) + stack.Pop(); + info1 = stack.Pop(); + index = info1.index + 1; + val = info1.constant; + return true; + } + + protected virtual bool getLocalConstant(Instruction instr, out int value) { + value = 0; + if (locals == null) + return false; + var local = DotNetUtils.getLocalVar(locals, instr); + if (local == null) + return false; + if (local.VariableType.EType != ElementType.I4) + return false; + return localsValues.TryGetValue(local, out value); + } + + protected virtual bool getArgConstant(Instruction instr, out int value) { + value = 0; + return false; + } + } +} diff --git a/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs new file mode 100644 index 00000000..ac100559 --- /dev/null +++ b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs @@ -0,0 +1,326 @@ +/* + 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.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Metadata; +using de4dot.blocks; +using de4dot.blocks.cflow; + +namespace de4dot.code.deobfuscators.DeepSea { + class ArrayBlockDeobfuscator : BlockDeobfuscator { + ModuleDefinition module; + FieldDefinitionAndDeclaringTypeDict fieldToInfo = new FieldDefinitionAndDeclaringTypeDict(); + Dictionary localToInfo = new Dictionary(); + DsConstantsReader constantsReader; + + class FieldInfo { + public readonly FieldDefinition field; + public readonly FieldDefinition arrayInitField; + public readonly byte[] array; + + public FieldInfo(FieldDefinition field, FieldDefinition arrayInitField) { + this.field = field; + this.arrayInitField = arrayInitField; + this.array = (byte[])arrayInitField.InitialValue.Clone(); + } + } + + public bool Detected { + get { return fieldToInfo.Count != 0; } + } + + public ArrayBlockDeobfuscator(ModuleDefinition module) { + this.module = module; + } + + public void init() { + initializeArrays(DotNetUtils.getModuleTypeCctor(module)); + } + + void initializeArrays(MethodDefinition method) { + if (method == null || method.Body == null) + return; + + var instructions = method.Body.Instructions; + for (int i = 0; i < instructions.Count; i++) { + var ldci4 = instructions[i]; + if (!DotNetUtils.isLdcI4(ldci4)) + continue; + i++; + var instrs = DotNetUtils.getInstructions(instructions, i, OpCodes.Newarr, OpCodes.Dup, OpCodes.Ldtoken, OpCodes.Call, OpCodes.Stsfld); + if (instrs == null) + continue; + + var arrayType = instrs[0].Operand as TypeReference; + if (arrayType == null || arrayType.EType != ElementType.U1) + continue; + + var arrayInitField = instrs[2].Operand as FieldDefinition; + if (arrayInitField == null || arrayInitField.InitialValue == null || arrayInitField.InitialValue.Length == 0) + continue; + + var calledMethod = instrs[3].Operand as MethodReference; + if (calledMethod == null || calledMethod.FullName != "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)") + continue; + + var targetField = instrs[4].Operand as FieldDefinition; + if (targetField == null) + continue; + + fieldToInfo.add(targetField, new FieldInfo(targetField, arrayInitField)); + } + } + + public override void deobfuscateBegin(Blocks blocks) { + base.deobfuscateBegin(blocks); + initLocalToInfo(); + } + + void initLocalToInfo() { + localToInfo.Clear(); + + foreach (var block in blocks.MethodBlocks.getAllBlocks()) { + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count - 1; i++) { + var ldsfld = instrs[i]; + if (ldsfld.OpCode.Code != Code.Ldsfld) + continue; + var stloc = instrs[i + 1]; + if (!stloc.isStloc()) + continue; + + var info = fieldToInfo.find((FieldReference)ldsfld.Operand); + if (info == null) + continue; + var local = DotNetUtils.getLocalVar(blocks.Locals, stloc.Instruction); + if (local == null) + continue; + + localToInfo[local] = info; + } + } + } + + protected override bool deobfuscate(Block block) { + bool changed = false; + + constantsReader = null; + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count; i++) { + bool ch = deobfuscate1(block, i); + if (ch) { + changed = true; + continue; + } + + ch = deobfuscate2(block, i); + if (ch) { + changed = true; + continue; + } + + ch = deobfuscate3(block, i); + if (ch) { + changed = true; + continue; + } + } + + return changed; + } + + bool deobfuscate1(Block block, int i) { + var instrs = block.Instructions; + if (i >= instrs.Count - 2) + return false; + + var ldloc = instrs[i]; + if (!ldloc.isLdloc()) + return false; + var local = DotNetUtils.getLocalVar(blocks.Locals, ldloc.Instruction); + if (local == null) + return false; + FieldInfo info; + if (!localToInfo.TryGetValue(local, out info)) + return false; + + var ldci4 = instrs[i + 1]; + if (!ldci4.isLdcI4()) + return false; + + var ldelem = instrs[i + 2]; + if (ldelem.OpCode.Code != Code.Ldelem_U1) + return false; + + block.remove(i, 3 - 1); + instrs[i] = new Instr(DotNetUtils.createLdci4(info.array[ldci4.getLdcI4Value()])); + return true; + } + + bool deobfuscate2(Block block, int i) { + var instrs = block.Instructions; + if (i >= instrs.Count - 2) + return false; + + var ldsfld = instrs[i]; + if (ldsfld.OpCode.Code != Code.Ldsfld) + return false; + var info = fieldToInfo.find(ldsfld.Operand as FieldReference); + if (info == null) + return false; + + var ldci4 = instrs[i + 1]; + if (!ldci4.isLdcI4()) + return false; + + var ldelem = instrs[i + 2]; + if (ldelem.OpCode.Code != Code.Ldelem_U1) + return false; + + block.remove(i, 3 - 1); + instrs[i] = new Instr(DotNetUtils.createLdci4(info.array[ldci4.getLdcI4Value()])); + return true; + } + + bool deobfuscate3(Block block, int i) { + var instrs = block.Instructions; + if (i + 1 >= instrs.Count) + return false; + + int start = i; + var ldsfld = instrs[i]; + if (ldsfld.OpCode.Code != Code.Ldsfld) + return false; + var info = fieldToInfo.find(ldsfld.Operand as FieldReference); + if (info == null) + return false; + + if (!instrs[i + 1].isLdcI4()) + return false; + + var constants = getConstantsReader(block); + int value; + i += 2; + if (!constants.getInt32(ref i, out value)) + return false; + + if (i >= instrs.Count) + return false; + var stelem = instrs[i]; + if (stelem.OpCode.Code != Code.Stelem_I1) + return false; + + block.remove(start, i - start + 1); + return true; + } + + DsConstantsReader getConstantsReader(Block block) { + if (constantsReader != null) + return constantsReader; + return constantsReader = new DsConstantsReader(block.Instructions); + } + + public IEnumerable cleanUp() { + var removedFields = new List(); + var moduleCctor = DotNetUtils.getModuleTypeCctor(module); + if (moduleCctor == null) + return removedFields; + var moduleCctorBlocks = new Blocks(moduleCctor); + + var keep = findFieldsToKeep(); + foreach (var fieldInfo in fieldToInfo.getValues()) { + if (keep.ContainsKey(fieldInfo)) + continue; + if (removeInitCode(moduleCctorBlocks, fieldInfo)) { + removedFields.Add(fieldInfo.field); + removedFields.Add(fieldInfo.arrayInitField); + } + fieldInfo.arrayInitField.InitialValue = new byte[1]; + fieldInfo.arrayInitField.FieldType = module.TypeSystem.Byte; + } + + IList allInstructions; + IList allExceptionHandlers; + moduleCctorBlocks.getCode(out allInstructions, out allExceptionHandlers); + DotNetUtils.restoreBody(moduleCctorBlocks.Method, allInstructions, allExceptionHandlers); + return removedFields; + } + + bool removeInitCode(Blocks blocks, FieldInfo info) { + bool removedSomething = false; + foreach (var block in blocks.MethodBlocks.getAllBlocks()) { + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count - 5; i++) { + var ldci4 = instrs[i]; + if (!ldci4.isLdcI4()) + continue; + if (instrs[i + 1].OpCode.Code != Code.Newarr) + continue; + if (instrs[i + 2].OpCode.Code != Code.Dup) + continue; + var ldtoken = instrs[i + 3]; + if (ldtoken.OpCode.Code != Code.Ldtoken) + continue; + if (ldtoken.Operand != info.arrayInitField) + continue; + var call = instrs[i + 4]; + if (call.OpCode.Code != Code.Call) + continue; + var calledMethod = call.Operand as MethodReference; + if (calledMethod == null || calledMethod.FullName != "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)") + continue; + var stsfld = instrs[i + 5]; + if (stsfld.OpCode.Code != Code.Stsfld) + continue; + if (stsfld.Operand != info.field) + continue; + block.remove(i, 6); + i--; + removedSomething = true; + } + } + return removedSomething; + } + + Dictionary findFieldsToKeep() { + var keep = new Dictionary(); + foreach (var type in module.GetTypes()) { + foreach (var method in type.Methods) { + if (type == DotNetUtils.getModuleType(module) && method.Name == ".cctor") + continue; + if (method.Body == null) + continue; + + foreach (var instr in method.Body.Instructions) { + var field = instr.Operand as FieldReference; + if (field == null) + continue; + var fieldInfo = fieldToInfo.find(field); + if (fieldInfo == null) + continue; + keep[fieldInfo] = true; + } + } + } + return keep; + } + } +} diff --git a/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs b/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs index 0424c2c6..49e0a041 100644 --- a/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs @@ -29,12 +29,16 @@ namespace de4dot.code.deobfuscators.DeepSea { class AssemblyResolver : ResolverBase { Version version; List fieldInfos; + MethodDefinition decryptMethod; enum Version { Unknown, V3Old, V3, V4, + V404, + V41, + V41SL, } public class AssemblyInfo { @@ -67,6 +71,10 @@ namespace de4dot.code.deobfuscators.DeepSea { } } + public MethodDefinition DecryptMethod { + get { return decryptMethod; } + } + public AssemblyResolver(ModuleDefinition module, ISimpleDeobfuscator simpleDeobfuscator, IDeobfuscator deob) : base(module, simpleDeobfuscator, deob) { } @@ -89,6 +97,7 @@ namespace de4dot.code.deobfuscators.DeepSea { continue; if (!DotNetUtils.isMethod(method, "System.Void", "(System.String)")) continue; + simpleDeobfuscator.deobfuscate(method); if (!new LocalTypes(method).all(requiredLocals_sl)) continue; @@ -102,10 +111,50 @@ namespace de4dot.code.deobfuscators.DeepSea { } void updateVersion(MethodDefinition handler) { - if (isV3Old(handler)) + if (isV3Old(handler)) { version = Version.V3Old; - else - version = Version.V3; + return; + } + if (isV3SL(handler)) { + version = Version.V3; // 3.x-4.0.4 + return; + } + if (isV41SL(handler)) { + version = Version.V41SL; + return; + } + } + + static bool isV3SL(MethodDefinition handler) { + var instrs = handler.Body.Instructions; + for (int i = 0; i < instrs.Count - 3; i++) { + if (!DotNetUtils.isLdloc(instrs[i])) + continue; + if (instrs[i + 1].OpCode.Code != Code.Add) + continue; + if (!DotNetUtils.isLdcI4(instrs[i + 2])) + continue; + if (instrs[i + 3].OpCode.Code != Code.And) + continue; + return true; + } + return false; + } + + static bool isV41SL(MethodDefinition handler) { + var instrs = handler.Body.Instructions; + for (int i = 0; i < instrs.Count; i++) { + if (!DotNetUtils.isLdcI4(instrs[i]) || DotNetUtils.getLdcI4Value(instrs[i]) != 5) + continue; + if (instrs[i + 1].OpCode.Code != Code.And) + continue; + if (!DotNetUtils.isLdcI4(instrs[i + 2]) || DotNetUtils.getLdcI4Value(instrs[i + 2]) != 0x1F) + continue; + if (instrs[i + 3].OpCode.Code != Code.And) + continue; + return true; + } + return false; } static bool isV3Old(MethodDefinition method) { @@ -127,10 +176,19 @@ namespace de4dot.code.deobfuscators.DeepSea { simpleDeobfuscator.deobfuscate(handler); List fieldInfosTmp; - if (checkHandlerV4(handler, out fieldInfosTmp) || - checkHandlerV4_0_4(handler, out fieldInfosTmp)) { + MethodDefinition decryptMethodTmp; + if (checkHandlerV4(handler, out fieldInfosTmp, out decryptMethodTmp)) { version = Version.V4; fieldInfos = fieldInfosTmp; + decryptMethod = decryptMethodTmp; + return true; + } + + Version versionTmp = checkHandlerV404_41(handler, out fieldInfosTmp, out decryptMethodTmp); + if (fieldInfosTmp.Count != 0) { + version = versionTmp; + fieldInfos = fieldInfosTmp; + decryptMethod = decryptMethodTmp; return true; } @@ -163,8 +221,9 @@ namespace de4dot.code.deobfuscators.DeepSea { } // 4.0.1.18 .. 4.0.3 - bool checkHandlerV4(MethodDefinition handler, out List fieldInfos) { + bool checkHandlerV4(MethodDefinition handler, out List fieldInfos, out MethodDefinition decryptMethod) { fieldInfos = new List(); + decryptMethod = null; var instrs = handler.Body.Instructions; for (int i = 0; i < instrs.Count - 3; i++) { @@ -193,21 +252,25 @@ namespace de4dot.code.deobfuscators.DeepSea { call = instrs[index++]; if (call.OpCode.Code != Code.Call) return false; - if (!DotNetUtils.isMethod(call.Operand as MethodReference, "System.Reflection.Assembly", "(System.RuntimeFieldHandle,System.Int32,System.Int32)")) + var decryptMethodTmp = call.Operand as MethodDefinition; + if (!DotNetUtils.isMethod(decryptMethodTmp, "System.Reflection.Assembly", "(System.RuntimeFieldHandle,System.Int32,System.Int32)")) return false; + decryptMethod = decryptMethodTmp; fieldInfos.Add(new FieldInfo(field, magic)); } return fieldInfos.Count != 0; } - // 4.0.4+ - bool checkHandlerV4_0_4(MethodDefinition handler, out List fieldInfos) { + // 4.0.4, 4.1+ + Version checkHandlerV404_41(MethodDefinition handler, out List fieldInfos, out MethodDefinition decryptMethod) { + Version version = Version.Unknown; fieldInfos = new List(); + decryptMethod = null; var instrs = handler.Body.Instructions; - for (int i = 0; i < instrs.Count - 8; i++) { + for (int i = 0; i < instrs.Count - 6; i++) { int index = i; var ldci4_len = instrs[index++]; @@ -233,26 +296,137 @@ namespace de4dot.code.deobfuscators.DeepSea { if (!DotNetUtils.isMethod(call1.Operand as MethodReference, "System.Void", "(System.Array,System.RuntimeFieldHandle)")) continue; - if (!DotNetUtils.isLdloc(instrs[index++])) - continue; - - var ldci4_magic = instrs[index++]; - if (!DotNetUtils.isLdcI4(ldci4_magic)) - continue; - int magic = DotNetUtils.getLdcI4Value(ldci4_magic); - - var call2 = instrs[index++]; - if (call2.OpCode.Code == Code.Tail) - call2 = instrs[index++]; - if (call2.OpCode.Code != Code.Call) - continue; - if (!DotNetUtils.isMethod(call2.Operand as MethodReference, "System.Reflection.Assembly", "(System.Byte[],System.Int32)")) + int callIndex = getCallDecryptMethodIndex(instrs, index); + if (callIndex < 0) continue; + var args = DsUtils.getArgValues(instrs, callIndex); + if (args == null) + continue; + var decryptMethodTmp = instrs[callIndex].Operand as MethodDefinition; + if (decryptMethodTmp == null) + continue; + int magic; + Version versionTmp; + getMagic(decryptMethodTmp, args, out versionTmp, out magic); + version = versionTmp; + decryptMethod = decryptMethodTmp; fieldInfos.Add(new FieldInfo(field, magic)); } - return fieldInfos.Count != 0; + return version; + } + + static bool getMagic(MethodDefinition method, IList args, out Version version, out int magic) { + magic = 0; + int magicIndex = getMagicIndex(method, out version); + if (magicIndex < 0 || magicIndex >= args.Count) + return false; + var val = args[magicIndex]; + if (!(val is int)) + return false; + + magic = (int)val; + return true; + } + + static int getMagicIndex(MethodDefinition method, out Version version) { + int magicIndex = getMagicIndex404(method); + if (magicIndex >= 0) { + version = Version.V404; + return magicIndex; + } + + magicIndex = getMagicIndex41Trial(method); + if (magicIndex >= 0) { + version = Version.V41; + return magicIndex; + } + + version = Version.Unknown; + return -1; + } + + static int getMagicIndex404(MethodDefinition method) { + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 4; i++) { + int index = i; + if (!DotNetUtils.isLdloc(instrs[index++])) + continue; + var ldarg = instrs[index++]; + if (!DotNetUtils.isLdarg(ldarg)) + continue; + if (instrs[index++].OpCode.Code != Code.Add) + continue; + var ldci4 = instrs[index++]; + if (!DotNetUtils.isLdcI4(ldci4)) + continue; + if (DotNetUtils.getLdcI4Value(ldci4) != 0xFF) + continue; + return DotNetUtils.getArgIndex(ldarg); + } + return -1; + } + + static int getMagicIndex41Trial(MethodDefinition method) { + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 4; i++) { + int index = i; + if (instrs[index++].OpCode.Code != Code.Div) + continue; + var ldarg = instrs[index++]; + if (!DotNetUtils.isLdarg(ldarg)) + continue; + if (instrs[index++].OpCode.Code != Code.Add) + continue; + var ldci4 = instrs[index++]; + if (!DotNetUtils.isLdcI4(ldci4)) + continue; + if (DotNetUtils.getLdcI4Value(ldci4) != 0xFF) + continue; + return DotNetUtils.getArgIndex(ldarg); + } + return -1; + } + + static int getCallDecryptMethodIndex(IList instrs, int index) { + index = getRetIndex(instrs, index); + if (index < 0) + return -1; + for (int i = index - 1; i >= 0; i--) { + var instr = instrs[i]; + if (!isCallOrNext(instr)) + break; + if (instr.OpCode.Code != Code.Call) + continue; + var calledMethod = instr.Operand as MethodReference; + if (calledMethod == null || calledMethod.Parameters.Count < 2) + continue; + + return i; + } + return -1; + } + + static int getRetIndex(IList instrs, int index) { + for (int i = index; i < instrs.Count; i++) { + var instr = instrs[i]; + if (instr.OpCode.Code == Code.Ret) + return i; + if (!isCallOrNext(instr)) + break; + } + return -1; + } + + static bool isCallOrNext(Instruction instr) { + switch (instr.OpCode.FlowControl) { + case FlowControl.Call: + case FlowControl.Next: + return true; + default: + return false; + } } public IEnumerable getAssemblyInfos() { @@ -261,16 +435,22 @@ namespace de4dot.code.deobfuscators.DeepSea { switch (version) { case Version.V3Old: + return getAssemblyInfos(resource => decryptResourceV3Old(resource)); case Version.V3: - return getAssemblyInfosV3(); + return getAssemblyInfos(resource => decryptResourceV3(resource)); case Version.V4: + case Version.V404: return getAssemblyInfosV4(); + case Version.V41: + return getAssemblyInfosV41(); + case Version.V41SL: + return getAssemblyInfos(resource => decryptResourceV41SL(resource)); default: throw new ApplicationException("Unknown version"); } } - IEnumerable getAssemblyInfosV3() { + IEnumerable getAssemblyInfos(Func decrypter) { var infos = new List(); foreach (var tmp in module.Resources) { @@ -279,7 +459,7 @@ namespace de4dot.code.deobfuscators.DeepSea { continue; if (!Regex.IsMatch(resource.Name, @"^[0-9A-F]{40}$")) continue; - var info = getAssemblyInfoV3(resource); + var info = getAssemblyInfo(resource, decrypter); if (info == null) continue; infos.Add(info); @@ -288,9 +468,9 @@ namespace de4dot.code.deobfuscators.DeepSea { return infos; } - AssemblyInfo getAssemblyInfoV3(EmbeddedResource resource) { + AssemblyInfo getAssemblyInfo(EmbeddedResource resource, Func decrypter) { try { - var decrypted = version == Version.V3Old ? decryptResourceV3Old(resource) : decryptResourceV3(resource); + var decrypted = decrypter(resource); return getAssemblyInfo(decrypted, resource); } catch (Exception) { @@ -306,14 +486,14 @@ namespace de4dot.code.deobfuscators.DeepSea { return new AssemblyInfo(decryptedData, fullName, simpleName, extension, resource); } - IEnumerable getAssemblyInfosV4() { + IEnumerable getAssemblyInfos(Func decrypter) { var infos = new List(); if (fieldInfos == null) return infos; foreach (var fieldInfo in fieldInfos) { - var decrypted = decryptResourceV4(fieldInfo.field.InitialValue, fieldInfo.magic); + var decrypted = decrypter(fieldInfo.field.InitialValue, fieldInfo.magic); infos.Add(getAssemblyInfo(decrypted, null)); fieldInfo.field.InitialValue = new byte[1]; fieldInfo.field.FieldType = module.TypeSystem.Byte; @@ -321,5 +501,19 @@ namespace de4dot.code.deobfuscators.DeepSea { return infos; } + + IEnumerable getAssemblyInfosV4() { + return getAssemblyInfos((data, magic) => decryptResourceV4(data, magic)); + } + + IEnumerable getAssemblyInfosV41() { + return getAssemblyInfos((data, magic) => inflateIfNeeded(decrypt41Trial(data, magic))); + } + + static byte[] decrypt41Trial(byte[] data, int magic) { + for (int i = 0; i < data.Length; i++) + data[i] ^= (byte)(i / 3 + magic); + return data; + } } } diff --git a/de4dot.code/deobfuscators/DeepSea/CastDeobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/CastDeobfuscator.cs new file mode 100644 index 00000000..1bb9c5f6 --- /dev/null +++ b/de4dot.code/deobfuscators/DeepSea/CastDeobfuscator.cs @@ -0,0 +1,213 @@ +/* + 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 de4dot.blocks; +using de4dot.blocks.cflow; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace de4dot.code.deobfuscators.DeepSea { + class CastDeobfuscator : IBlocksDeobfuscator { + Blocks blocks; + Dictionary localInfos = new Dictionary(); + + class LocalInfo { + public readonly VariableDefinition local; + TypeReference type; + bool isValid; + + public TypeReference CastType { + get { return type; } + set { + if (!isValid) + return; + + if (value == null) { + invalid(); + return; + } + + if (type != null && !MemberReferenceHelper.compareTypes(type, value)) { + invalid(); + return; + } + + type = value; + } + } + + public LocalInfo(VariableDefinition local) { + this.local = local; + this.isValid = true; + } + + public void invalid() { + isValid = false; + type = null; + } + + public override string ToString() { + if (type == null) + return string.Format("{0} - INVALID", local); + return string.Format("{0} - {1:X8} {2}", local, type.MetadataToken.ToInt32(), type.FullName); + } + } + + public bool ExecuteOnNoChange { + get { return true; } + } + + public void deobfuscateBegin(Blocks blocks) { + this.blocks = blocks; + } + + public bool deobfuscate(List allBlocks) { + if (!init(allBlocks)) + return false; + + bool changed = false; + + var indexesToRemove = new List(); + foreach (var block in allBlocks) { + indexesToRemove.Clear(); + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count - 1; i++) { + var instr = instrs[i]; + if (instr.OpCode.Code == Code.Ldloca || instr.OpCode.Code == Code.Ldloca_S) { + var local = instr.Operand as VariableDefinition; + if (local == null) + continue; + localInfos[local].invalid(); + } + else if (instr.isLdloc()) { + var local = DotNetUtils.getLocalVar(blocks.Locals, instr.Instruction); + if (local == null) + continue; + var localInfo = localInfos[local]; + var cast = instrs[i + 1]; + if (localInfo.CastType == null) + continue; + if (!isCast(cast)) + throw new ApplicationException("Not a cast instr"); + + indexesToRemove.Add(i + 1); + } + } + if (indexesToRemove.Count > 0) { + block.remove(indexesToRemove); + changed = true; + } + } + + foreach (var info in localInfos.Values) { + if (info.CastType == null) + continue; + info.local.VariableType = info.CastType; + } + + if (changed) { + foreach (var block in allBlocks) { + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count - 1; i++) { + var instr = instrs[i]; + int castIndex = i + 1; + if (instr.OpCode.Code == Code.Dup) { + if (i == 0) + continue; + castIndex = i; + instr = instrs[i - 1]; + } + + if (instr.isLdarg()) + addCast(block, castIndex, i + 1, DotNetUtils.getArgType(blocks.Method, instr.Instruction)); + else if (instr.OpCode.Code == Code.Ldfld || instr.OpCode.Code == Code.Ldsfld) { + var field = instr.Operand as FieldReference; + if (field == null) + continue; + addCast(block, castIndex, i + 1, field.FieldType); + } + else if (instr.OpCode.Code == Code.Call || instr.OpCode.Code == Code.Callvirt) { + var calledMethod = instr.Operand as MethodReference; + if (calledMethod == null || !DotNetUtils.hasReturnValue(calledMethod)) + continue; + addCast(block, castIndex, i + 1, calledMethod.MethodReturnType.ReturnType); + } + } + } + } + + return changed; + } + + bool addCast(Block block, int castIndex, int index, TypeReference type) { + if (type == null) + return false; + if (castIndex >= block.Instructions.Count || index >= block.Instructions.Count) + return false; + var stloc = block.Instructions[index]; + if (!stloc.isStloc()) + return false; + var local = DotNetUtils.getLocalVar(blocks.Locals, stloc.Instruction); + if (local == null) + return false; + var localInfo = localInfos[local]; + if (localInfo.CastType == null) + return false; + + if (!MemberReferenceHelper.compareTypes(localInfo.CastType, type)) + block.insert(castIndex, new Instruction(OpCodes.Castclass, localInfo.CastType)); + return true; + } + + bool init(List allBlocks) { + localInfos.Clear(); + foreach (var local in blocks.Locals) + localInfos[local] = new LocalInfo(local); + if (localInfos.Count == 0) + return false; + + foreach (var block in allBlocks) { + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count - 1; i++) { + var ldloc = instrs[i]; + if (!ldloc.isLdloc()) + continue; + var local = DotNetUtils.getLocalVar(blocks.Locals, ldloc.Instruction); + if (local == null) + continue; + var localInfo = localInfos[local]; + localInfo.CastType = getCastType(instrs[i + 1]); + } + } + return true; + } + + static bool isCast(Instr instr) { + return instr.OpCode.Code == Code.Castclass || instr.OpCode.Code == Code.Isinst; + } + + static TypeReference getCastType(Instr instr) { + if (!isCast(instr)) + return null; + return instr.Operand as TypeReference; + } + } +} diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 97ed2461..7eac25c9 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -31,6 +31,8 @@ namespace de4dot.code.deobfuscators.DeepSea { BoolOption decryptResources; BoolOption dumpEmbeddedAssemblies; BoolOption restoreFields; + BoolOption renameResourceKeys; + BoolOption castDeobfuscation; public DeobfuscatorInfo() : base() { @@ -39,6 +41,8 @@ namespace de4dot.code.deobfuscators.DeepSea { decryptResources = new BoolOption(null, makeArgName("rsrc"), "Decrypt resources", true); dumpEmbeddedAssemblies = new BoolOption(null, makeArgName("embedded"), "Dump embedded assemblies", true); restoreFields = new BoolOption(null, makeArgName("fields"), "Restore fields", true); + renameResourceKeys = new BoolOption(null, makeArgName("keys"), "Rename resource keys", true); + castDeobfuscation = new BoolOption(null, makeArgName("casts"), "Deobfuscate casts", true); } public override string Name { @@ -57,6 +61,8 @@ namespace de4dot.code.deobfuscators.DeepSea { DecryptResources = decryptResources.get(), DumpEmbeddedAssemblies = dumpEmbeddedAssemblies.get(), RestoreFields = restoreFields.get(), + RenameResourceKeys = renameResourceKeys.get(), + CastDeobfuscation = castDeobfuscation.get(), }); } @@ -67,6 +73,8 @@ namespace de4dot.code.deobfuscators.DeepSea { decryptResources, dumpEmbeddedAssemblies, restoreFields, + renameResourceKeys, + castDeobfuscation, }; } } @@ -80,6 +88,7 @@ namespace de4dot.code.deobfuscators.DeepSea { ResourceResolver resourceResolver; AssemblyResolver assemblyResolver; FieldsRestorer fieldsRestorer; + ArrayBlockDeobfuscator arrayBlockDeobfuscator; internal class Options : OptionsBase { public bool InlineMethods { get; set; } @@ -87,6 +96,8 @@ namespace de4dot.code.deobfuscators.DeepSea { public bool DecryptResources { get; set; } public bool DumpEmbeddedAssemblies { get; set; } public bool RestoreFields { get; set; } + public bool RenameResourceKeys { get; set; } + public bool CastDeobfuscation { get; set; } } public override string Type { @@ -105,17 +116,32 @@ namespace de4dot.code.deobfuscators.DeepSea { get { return startedDeobfuscating ? options.InlineMethods : true; } } - public override IMethodCallInliner MethodCallInliner { + public override IEnumerable BlocksDeobfuscators { get { + var list = new List(getBlocksDeobfuscators()); if (CanInlineMethods) - return new MethodCallInliner(); - return new NoMethodInliner(); + list.Add(new DsMethodCallInliner(new CachedCflowDeobfuscator(getBlocksDeobfuscators()))); + return list; } } + List getBlocksDeobfuscators() { + var list = new List(); + if (arrayBlockDeobfuscator.Detected) + list.Add(arrayBlockDeobfuscator); + if (!startedDeobfuscating || options.CastDeobfuscation) + list.Add(new CastDeobfuscator()); + return list; + } + public Deobfuscator(Options options) : base(options) { this.options = options; + + if (options.RenameResourceKeys) + this.RenamingOptions |= RenamingOptions.RenameResourceKeys; + else + this.RenamingOptions &= ~RenamingOptions.RenameResourceKeys; } protected override int detectInternal() { @@ -131,6 +157,9 @@ namespace de4dot.code.deobfuscators.DeepSea { } protected override void scanForObfuscator() { + staticStringInliner.UseUnknownArgs = true; + arrayBlockDeobfuscator = new ArrayBlockDeobfuscator(module); + arrayBlockDeobfuscator.init(); stringDecrypter = new StringDecrypter(module); stringDecrypter.find(DeobfuscatedFile); resourceResolver = new ResourceResolver(module, DeobfuscatedFile, this); @@ -146,8 +175,10 @@ namespace de4dot.code.deobfuscators.DeepSea { if (detectMethodProxyObfuscation()) return DeobfuscatorInfo.THE_NAME + " 3.5"; return DeobfuscatorInfo.THE_NAME + " 1.x-3.x"; - case StringDecrypter.DecrypterVersion.V4: - return DeobfuscatorInfo.THE_NAME + " 4.x"; + case StringDecrypter.DecrypterVersion.V4_0: + return DeobfuscatorInfo.THE_NAME + " 4.0"; + case StringDecrypter.DecrypterVersion.V4_1: + return DeobfuscatorInfo.THE_NAME + " 4.1"; } return DeobfuscatorInfo.THE_NAME; @@ -165,7 +196,7 @@ namespace de4dot.code.deobfuscators.DeepSea { continue; if (checkedMethods++ >= 1000) goto done; - if (!DeepSea.MethodCallInliner.canInline(method)) + if (!DsMethodCallInliner.canInline(method)) continue; foundProxies++; } @@ -207,6 +238,7 @@ done: addCctorInitCallToBeRemoved(resourceResolver.InitMethod); addCallToBeRemoved(module.EntryPoint, resourceResolver.InitMethod); addMethodToBeRemoved(resourceResolver.InitMethod, "Resource resolver init method"); + addMethodToBeRemoved(resourceResolver.InitMethod2, "Resource resolver init method #2"); addMethodToBeRemoved(resourceResolver.HandlerMethod, "Resource resolver handler method"); addMethodToBeRemoved(resourceResolver.GetDataMethod, "Resource resolver 'get resource data' method"); } @@ -224,6 +256,7 @@ done: addCallToBeRemoved(module.EntryPoint, assemblyResolver.InitMethod); addMethodToBeRemoved(assemblyResolver.InitMethod, "Assembly resolver init method"); addMethodToBeRemoved(assemblyResolver.HandlerMethod, "Assembly resolver handler method"); + addMethodToBeRemoved(assemblyResolver.DecryptMethod, "Assembly resolver decrypt method"); } public override void deobfuscateMethodEnd(Blocks blocks) { @@ -245,13 +278,15 @@ done: stringDecrypter.cleanup(); } + addFieldsToBeRemoved(arrayBlockDeobfuscator.cleanUp(), "Control flow obfuscation array"); + base.deobfuscateEnd(); } void removeInlinedMethods() { if (!options.InlineMethods || !options.RemoveInlinedMethods) return; - removeInlinedMethods(DsInlinedMethodsFinder.find(module)); + removeInlinedMethods(DsInlinedMethodsFinder.find(module, staticStringInliner.Methods)); } public override IEnumerable getStringDecrypterMethods() { diff --git a/blocks/cflow/NoMethodInliner.cs b/de4dot.code/deobfuscators/DeepSea/DsConstantsReader.cs similarity index 61% rename from blocks/cflow/NoMethodInliner.cs rename to de4dot.code/deobfuscators/DeepSea/DsConstantsReader.cs index 0c52667e..b4cb27f5 100644 --- a/blocks/cflow/NoMethodInliner.cs +++ b/de4dot.code/deobfuscators/DeepSea/DsConstantsReader.cs @@ -17,13 +17,24 @@ along with de4dot. If not, see . */ -namespace de4dot.blocks.cflow { - public class NoMethodInliner : IMethodCallInliner { - public void init(Blocks blocks, Block block) { +using System.Collections.Generic; +using Mono.Cecil.Cil; +using de4dot.blocks; + +namespace de4dot.code.deobfuscators.DeepSea { + class DsConstantsReader : ConstantsReader { + public DsConstantsReader(List instrs) + : base(instrs) { } - public bool deobfuscate() { - return false; + protected override bool getLocalConstant(Instruction instr, out int value) { + value = 0; + return true; + } + + protected override bool getArgConstant(Instruction instr, out int value) { + value = 0; + return true; } } } diff --git a/de4dot.code/deobfuscators/DeepSea/DsInlinedMethodsFinder.cs b/de4dot.code/deobfuscators/DeepSea/DsInlinedMethodsFinder.cs index 277f9b28..57e92ae5 100644 --- a/de4dot.code/deobfuscators/DeepSea/DsInlinedMethodsFinder.cs +++ b/de4dot.code/deobfuscators/DeepSea/DsInlinedMethodsFinder.cs @@ -22,12 +22,16 @@ using Mono.Cecil; namespace de4dot.code.deobfuscators.DeepSea { static class DsInlinedMethodsFinder { - public static List find(ModuleDefinition module) { + public static List find(ModuleDefinition module, IEnumerable notInlinedMethods) { + var notInlinedMethodsDict = new Dictionary(); + foreach (var method in notInlinedMethods) + notInlinedMethodsDict[method] = true; + var inlinedMethods = new List(); foreach (var type in module.GetTypes()) { foreach (var method in type.Methods) { - if (MethodCallInliner.canInline(method)) + if (!notInlinedMethodsDict.ContainsKey(method) && DsMethodCallInliner.canInline(method)) inlinedMethods.Add(method); } } diff --git a/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs b/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs new file mode 100644 index 00000000..8d3deefe --- /dev/null +++ b/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs @@ -0,0 +1,333 @@ +/* + 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.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Metadata; +using de4dot.blocks; +using de4dot.blocks.cflow; + +namespace de4dot.code.deobfuscators.DeepSea { + class DsMethodCallInliner : MethodCallInlinerBase { + InstructionEmulator instructionEmulator = new InstructionEmulator(); + List parameters; + ParameterDefinition arg1, arg2; + Value returnValue; + MethodDefinition methodToInline; + CachedCflowDeobfuscator cflowDeobfuscator; + + public DsMethodCallInliner(CachedCflowDeobfuscator cflowDeobfuscator) { + this.cflowDeobfuscator = cflowDeobfuscator; + } + + protected override bool deobfuscateInternal() { + 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 (!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; + } + + bool inlineMethod(MethodDefinition methodToInline, int instrIndex, int const1, int const2) { + this.methodToInline = methodToInline = cflowDeobfuscator.deobfuscate(methodToInline); + + parameters = DotNetUtils.getParameters(methodToInline); + arg1 = parameters[parameters.Count - 2]; + arg2 = parameters[parameters.Count - 1]; + returnValue = null; + + instructionEmulator.init(methodToInline); + foreach (var arg in parameters) { + if (arg.ParameterType.EType >= ElementType.Boolean && arg.ParameterType.EType <= 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, Instruction.Create(OpCodes.Pop)); + block.insert(instrIndex, Instruction.Create(OpCodes.Pop)); + 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 = DotNetUtils.getParameter(parameters, instr); + 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; + DotNetUtils.calculateStackUsage(lastInstr, false, out pushes, out pops); + for (int i = 0; i < pops; i++) + instructionEmulator.pop(); + + 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(MethodDefinition method) { + if (method == null || method.Body == null) + return false; + if (method.Attributes != (MethodAttributes.Assembly | MethodAttributes.Static)) + return false; + if (method.GenericParameters.Count > 0) + return false; + if (method.Body.ExceptionHandlers.Count > 0) + return false; + + var parameters = method.Parameters; + int paramCount = parameters.Count; + if (paramCount < 2) + return false; + var param1 = parameters[paramCount - 1]; + var param2 = parameters[paramCount - 2]; + if (!isIntType(param1.ParameterType.EType)) + return false; + if (!isIntType(param2.ParameterType.EType)) + return false; + + return true; + } + + static bool isIntType(ElementType etype) { + return etype == ElementType.Char || etype == ElementType.I2 || etype == ElementType.I4; + } + + protected override bool isReturn(MethodDefinition methodToInline, int instrIndex) { + int oldIndex = instrIndex; + if (base.isReturn(methodToInline, oldIndex)) + return true; + + return false; + } + } +} diff --git a/de4dot.code/deobfuscators/DeepSea/DsUtils.cs b/de4dot.code/deobfuscators/DeepSea/DsUtils.cs new file mode 100644 index 00000000..3366a669 --- /dev/null +++ b/de4dot.code/deobfuscators/DeepSea/DsUtils.cs @@ -0,0 +1,91 @@ +/* + 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.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using de4dot.blocks; + +namespace de4dot.code.deobfuscators.DeepSea { + static class DsUtils { + public static IList getArgValues(IList instrs, int index) { + return getArgValues(DotNetUtils.getArgPushes(instrs, index)); + } + + public static IList getArgValues(IList argInstrs) { + if (argInstrs == null) + return null; + var args = new List(argInstrs.Count); + foreach (var argInstr in argInstrs) { + object arg; + getArgValue(argInstr, out arg); + args.Add(arg); + } + return args; + } + + public static bool getArgValue(MethodDefinition method, int index, out object arg) { + return getArgValue(method.Body.Instructions[index], out arg); + } + + public static bool getArgValue(Instruction instr, out object arg) { + switch (instr.OpCode.Code) { + case Code.Ldc_I4_S: arg = (int)(sbyte)instr.Operand; return true; + case Code.Ldc_I4_M1: arg = -1; return true; + case Code.Ldc_I4_0: arg = 0; return true; + case Code.Ldc_I4_1: arg = 1; return true; + case Code.Ldc_I4_2: arg = 2; return true; + case Code.Ldc_I4_3: arg = 3; return true; + case Code.Ldc_I4_4: arg = 4; return true; + case Code.Ldc_I4_5: arg = 5; return true; + case Code.Ldc_I4_6: arg = 6; return true; + case Code.Ldc_I4_7: arg = 7; return true; + case Code.Ldc_I4_8: arg = 8; return true; + case Code.Ldnull: arg = null; return true; + + case Code.Ldstr: + case Code.Ldc_I4: + case Code.Ldc_I8: + case Code.Ldc_R4: + case Code.Ldc_R8: + arg = instr.Operand; + return true; + + 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.Ldloc: + case Code.Ldloc_S: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + arg = null; + return true; + + default: + arg = null; + return false; + } + } + } +} diff --git a/de4dot.code/deobfuscators/DeepSea/FieldsRestorer.cs b/de4dot.code/deobfuscators/DeepSea/FieldsRestorer.cs index 78b1c0c8..dfb5b30f 100644 --- a/de4dot.code/deobfuscators/DeepSea/FieldsRestorer.cs +++ b/de4dot.code/deobfuscators/DeepSea/FieldsRestorer.cs @@ -21,6 +21,7 @@ using System; using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; +using Mono.Cecil.Metadata; using de4dot.blocks; namespace de4dot.code.deobfuscators.DeepSea { @@ -35,7 +36,7 @@ namespace de4dot.code.deobfuscators.DeepSea { get { var list = new List(structToOwners.Count); foreach (var structType in structToOwners.getKeys()) { - if (structType.Methods.Count != 0) + if (!hasNoMethods(structType)) continue; list.Add(structType); @@ -44,6 +45,21 @@ namespace de4dot.code.deobfuscators.DeepSea { } } + static bool hasNoMethods(TypeDefinition type) { + if (type.Methods.Count == 0) + return true; + if (type.BaseType == null) + return false; + if (type.BaseType.EType != ElementType.Object) + return false; + if (type.Methods.Count != 1) + return false; + var ctor = type.Methods[0]; + if (ctor.Name != ".ctor" || ctor.Parameters.Count != 0) + return false; + return true; + } + public FieldsRestorer(ModuleDefinition module) { this.module = module; } @@ -78,7 +94,9 @@ namespace de4dot.code.deobfuscators.DeepSea { foreach (var type in module.GetTypes()) { foreach (var field in getPossibleFields(type)) { var fieldType = DotNetUtils.getType(module, field.FieldType); - if (fieldType == null || !fieldType.IsValueType) + if (fieldType == null) + continue; + if (!checkBaseType(fieldType)) continue; if ((fieldType.Attributes & ~TypeAttributes.Sealed) != TypeAttributes.NestedAssembly) continue; @@ -90,9 +108,9 @@ namespace de4dot.code.deobfuscators.DeepSea { continue; if (fieldType.HasEvents || fieldType.HasProperties || fieldType.HasInterfaces) continue; - if (hasNonStaticMethods(fieldType)) + if (checkMethods(fieldType)) continue; - if (hasStaticFields(fieldType)) + if (!checkFields(fieldType)) continue; List list; @@ -132,7 +150,9 @@ namespace de4dot.code.deobfuscators.DeepSea { if (field.Attributes != FieldAttributes.Private) continue; var fieldType = DotNetUtils.getType(module, field.FieldType); - if (fieldType == null || !fieldType.IsValueType) + if (fieldType == null) + continue; + if (!checkBaseType(fieldType)) continue; var list = typeToFields.find(fieldType); if (list == null) @@ -146,6 +166,12 @@ namespace de4dot.code.deobfuscators.DeepSea { } } + static bool checkBaseType(TypeDefinition type) { + if (type == null || type.BaseType == null) + return false; + return type.BaseType.FullName == "System.ValueType" || type.BaseType.EType == ElementType.Object; + } + void removeType(Dictionary> candidates, TypeReference type) { var typeDef = DotNetUtils.getType(module, type); if (typeDef == null) @@ -153,10 +179,12 @@ namespace de4dot.code.deobfuscators.DeepSea { candidates.Remove(typeDef); } - static bool hasNonStaticMethods(TypeDefinition type) { + static bool checkMethods(TypeDefinition type) { foreach (var method in type.Methods) { if (method.Name == ".cctor") continue; + if (type.BaseType != null && type.BaseType.EType == ElementType.Object && method.Name == ".ctor" && method.Parameters.Count == 0) + continue; if (!method.IsStatic) return true; if (method.GenericParameters.Count > 0) @@ -169,22 +197,31 @@ namespace de4dot.code.deobfuscators.DeepSea { return false; } - static bool hasStaticFields(TypeDefinition type) { + static bool checkFields(TypeDefinition type) { + if (type.Fields.Count == 0) + return false; foreach (var field in type.Fields) { if (field.IsStatic) - return true; + return false; + if (!field.IsAssembly) + return false; } - return false; + return true; } public void deobfuscate(Blocks blocks) { + deobfuscateNormal(blocks); + fixFieldCtorCalls(blocks); + } + + void deobfuscateNormal(Blocks blocks) { var instrsToRemove = new List(); foreach (var block in blocks.MethodBlocks.getAllBlocks()) { instrsToRemove.Clear(); var instrs = block.Instructions; for (int i = instrs.Count - 1; i >= 0; i--) { var instr = instrs[i]; - if (instr.OpCode.Code != Code.Ldflda) + if (instr.OpCode.Code != Code.Ldflda && instr.OpCode.Code != Code.Ldfld) continue; var structField = instr.Operand as FieldReference; if (structField == null || !structFieldsToFix.find(structField)) @@ -199,6 +236,40 @@ namespace de4dot.code.deobfuscators.DeepSea { } } + void fixFieldCtorCalls(Blocks blocks) { + if (blocks.Method.Name != ".ctor") + return; + var instrsToRemove = new List(); + foreach (var block in blocks.MethodBlocks.getAllBlocks()) { + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count; i++) { + var stfld = instrs[i]; + if (stfld.OpCode.Code != Code.Stfld) + continue; + var field = stfld.Operand as FieldReference; + if (field == null) + continue; + if (!structFieldsToFix.find(field)) + continue; + var instrs2 = toInstructionList(instrs); + var instrPushes = DotNetUtils.getArgPushes(instrs2, i); + if (instrPushes == null || instrPushes.Count != 2) + continue; + block.remove(i, 1); + block.remove(instrs2.IndexOf(instrPushes[1]), 1); + block.remove(instrs2.IndexOf(instrPushes[0]), 1); + i -= 3; + } + } + } + + static IList toInstructionList(IEnumerable instrs) { + var newInstrs = new List(); + foreach (var instr in instrs) + newInstrs.Add(instr.Instruction); + return newInstrs; + } + FieldDefinition getNewField(FieldReference structField, FieldReference oldFieldRef) { var fieldsDict = typeToFieldsDict.find(structField.DeclaringType); if (fieldsDict == null) diff --git a/de4dot.code/deobfuscators/DeepSea/MethodCallInliner.cs b/de4dot.code/deobfuscators/DeepSea/MethodCallInliner.cs deleted file mode 100644 index 2590f2f9..00000000 --- a/de4dot.code/deobfuscators/DeepSea/MethodCallInliner.cs +++ /dev/null @@ -1,208 +0,0 @@ -/* - 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.Collections.Generic; -using Mono.Cecil; -using Mono.Cecil.Cil; -using de4dot.blocks; -using de4dot.blocks.cflow; - -namespace de4dot.code.deobfuscators.DeepSea { - class MethodCallInliner : MethodCallInlinerBase { - InstructionEmulator instructionEmulator = new InstructionEmulator(); - - protected override bool deobfuscateInternal() { - 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 (!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; - } - - bool inlineMethod(MethodDefinition methodToInline, int instrIndex, int const1, int const2) { - var parameters = DotNetUtils.getParameters(methodToInline); - var arg1 = parameters[parameters.Count - 2]; - var arg2 = parameters[parameters.Count - 1]; - - instructionEmulator.init(methodToInline); - instructionEmulator.setArg(arg1, new Int32Value(const1)); - instructionEmulator.setArg(arg2, new Int32Value(const2)); - - Instruction instr; - var instrs = methodToInline.Body.Instructions; - int index = 0; - int counter = 0; - while (true) { - if (counter++ >= 50) - return false; - if (index >= instrs.Count) - return false; - instr = instrs[index]; - 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: - 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 = DotNetUtils.getParameter(parameters, instr); - if (arg != arg1 && arg != arg2) - goto checkInline; - instructionEmulator.emulate(instr); - index++; - break; - - case Code.Call: - case Code.Callvirt: - case Code.Newobj: - goto checkInline; - - 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; - - default: - if (instr.OpCode.OpCodeType != OpCodeType.Prefix) - return false; - index++; - break; - } - } -checkInline: - if (!inlineOtherMethod(instrIndex, methodToInline, instr, index + 1, 2)) - return false; - - block.insert(instrIndex, Instruction.Create(OpCodes.Pop)); - block.insert(instrIndex, Instruction.Create(OpCodes.Pop)); - return true; - } - - public static bool canInline(MethodDefinition method) { - if (method == null || method.Body == null) - return false; - if (method.GenericParameters.Count > 0) - return false; - if (method.Body.ExceptionHandlers.Count > 0) - return false; - var parameters = method.Parameters; - int paramCount = parameters.Count; - if (paramCount < 2) - return false; - if (parameters[paramCount - 1].ParameterType.FullName != "System.Int32") - return false; - if (parameters[paramCount - 2].ParameterType.FullName != "System.Int32") - return false; - - if (method.Attributes != (MethodAttributes.Assembly | MethodAttributes.Static)) - return false; - - if (!checkInstrs(method)) - return false; - - return true; - } - - static bool checkInstrs(MethodDefinition method) { - bool foundSwitch = false, foundXor = false; - foreach (var instr in method.Body.Instructions) { - if (foundSwitch && foundXor) - break; - switch (instr.OpCode.Code) { - case Code.Switch: - foundSwitch = true; - break; - case Code.Xor: - foundXor = true; - break; - } - } - return foundSwitch && foundXor; - } - } -} diff --git a/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs b/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs index 74b0a2b1..df8dd39c 100644 --- a/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs +++ b/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs @@ -143,6 +143,14 @@ namespace de4dot.code.deobfuscators.DeepSea { return decryptResource(data, 0, data.Length, 0); } + protected static byte[] decryptResourceV41SL(EmbeddedResource resource) { + var data = resource.GetResourceData(); + byte k = data[0]; + for (int i = 0; i < data.Length - 1; i++) + data[i + 1] ^= (byte)((k << (i & 5)) + i); + return inflateIfNeeded(data, 1, data.Length - 1); + } + protected static byte[] decryptResourceV3(EmbeddedResource resource) { return decryptResourceV3(resource.GetResourceData()); } @@ -158,7 +166,14 @@ namespace de4dot.code.deobfuscators.DeepSea { protected static byte[] decryptResource(byte[] data, int start, int len, int magic) { for (int i = start; i < start + len; i++) data[i] ^= (byte)(i - start + magic); + return inflateIfNeeded(data, start, len); + } + protected static byte[] inflateIfNeeded(byte[] data) { + return inflateIfNeeded(data, 0, data.Length); + } + + protected static byte[] inflateIfNeeded(byte[] data, int start, int len) { if (BitConverter.ToInt16(data, start) != 0x5A4D) return DeobUtils.inflate(data, start, len, true); diff --git a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs index c5ab071c..f8c623fa 100644 --- a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs @@ -17,24 +17,70 @@ 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.DeepSea { class ResourceResolver : ResolverBase { - EmbeddedResource resource; - FieldDefinition resourceField; - MethodDefinition getDataMethod; - int magicV4; - bool isV3; + Data30 data30; + Data40 data40; + Data41 data41; + ResourceVersion version = ResourceVersion.Unknown; + + enum ResourceVersion { + Unknown, + V3, + V40, + V41, + } + + class Data30 { + public EmbeddedResource resource; + } + + class Data40 { + public FieldDefinition resourceField; + public MethodDefinition resolveHandler2; + public MethodDefinition getDataMethod; + public int magic; + } + + class Data41 { + public FieldDefinition resourceField; + public MethodDefinition resolveHandler2; + public int magic; + public bool isTrial; + } + + class HandlerInfo { + public MethodDefinition handler; + public IList args; + + public HandlerInfo(MethodDefinition handler, IList args) { + this.handler = handler; + this.args = args; + } + } + + public MethodDefinition InitMethod2 { + get { + if (data40 != null) + return data40.resolveHandler2; + if (data41 != null) + return data41.resolveHandler2; + return null; + } + } public MethodDefinition GetDataMethod { - get { return getDataMethod; } + get { return data40 != null ? data40.getDataMethod : null; } } public EmbeddedResource Resource { - get { return resource; } + get { return data30 != null ? data30.resource : null; } } public ResourceResolver(ModuleDefinition module, ISimpleDeobfuscator simpleDeobfuscator, IDeobfuscator deob) @@ -47,23 +93,119 @@ namespace de4dot.code.deobfuscators.DeepSea { protected override bool checkHandlerMethodDesktopInternal(MethodDefinition handler) { if (checkHandlerV3(handler)) { - isV3 = true; + version = ResourceVersion.V3; return true; } - FieldDefinition resourceFieldTmp; - MethodDefinition getDataMethodTmp; simpleDeobfuscator.deobfuscate(handler); - if (checkHandlerV4(handler, out resourceFieldTmp, out getDataMethodTmp, out magicV4)) { - isV3 = false; - resourceField = resourceFieldTmp; - getDataMethod = getDataMethodTmp; + if ((data40 = checkHandlerV40(handler)) != null) { + version = ResourceVersion.V40; + return true; + } + + var info = getHandlerArgs41(handler); + Data41 data41Tmp; + if (info != null && checkHandlerV41(info, out data41Tmp)) { + version = ResourceVersion.V41; + data41 = data41Tmp; return true; } return false; } + HandlerInfo getHandlerArgs41(MethodDefinition handler) { + var instrs = handler.Body.Instructions; + for (int i = 0; i < instrs.Count; i++) { + var instr = instrs[i]; + if (instr.OpCode.Code != Code.Call) + continue; + var calledMethod = instr.Operand as MethodDefinition; + if (calledMethod == null) + continue; + var args = DsUtils.getArgValues(instrs, i); + if (args == null) + continue; + + return new HandlerInfo(calledMethod, args); + } + return null; + } + + bool checkHandlerV41(HandlerInfo info, out Data41 data41) { + data41 = new Data41(); + data41.resolveHandler2 = info.handler; + data41.resourceField = getLdtokenField(info.handler); + if (data41.resourceField == null) + return false; + int magicArgIndex = getMagicArgIndex41Retail(info.handler); + if (magicArgIndex < 0) { + magicArgIndex = getMagicArgIndex41Trial(info.handler); + data41.isTrial = true; + } + var asmVer = module.Assembly.Name.Version; + if (magicArgIndex < 0 || magicArgIndex >= info.args.Count) + return false; + var val = info.args[magicArgIndex]; + if (!(val is int)) + return false; + if (data41.isTrial) + data41.magic = (int)val >> 3; + else + data41.magic = ((asmVer.Major << 3) | (asmVer.Minor << 2) | asmVer.Revision) - (int)val; + return true; + } + + static int getMagicArgIndex41Retail(MethodDefinition method) { + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 3; i++) { + var add = instrs[i]; + if (add.OpCode.Code != Code.Add) + continue; + var ldarg = instrs[i + 1]; + if (!DotNetUtils.isLdarg(ldarg)) + continue; + var sub = instrs[i + 2]; + if (sub.OpCode.Code != Code.Sub) + continue; + var ldci4 = instrs[i + 3]; + if (!DotNetUtils.isLdcI4(ldci4) || DotNetUtils.getLdcI4Value(ldci4) != 0xFF) + continue; + + return DotNetUtils.getArgIndex(ldarg); + } + return -1; + } + + static int getMagicArgIndex41Trial(MethodDefinition method) { + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 2; i++) { + var ldarg = instrs[i]; + if (!DotNetUtils.isLdarg(ldarg)) + continue; + if (!DotNetUtils.isLdcI4(instrs[i + 1])) + continue; + if (instrs[i + 2].OpCode.Code != Code.Shr) + continue; + + return DotNetUtils.getArgIndex(ldarg); + } + return -1; + } + + static FieldDefinition getLdtokenField(MethodDefinition method) { + foreach (var instr in method.Body.Instructions) { + if (instr.OpCode.Code != Code.Ldtoken) + continue; + var field = instr.Operand as FieldDefinition; + if (field == null || field.InitialValue == null || field.InitialValue.Length == 0) + continue; + + return field; + } + return null; + } + static string[] handlerLocalTypes_V3 = new string[] { "System.AppDomain", "System.Byte[]", @@ -79,7 +221,9 @@ namespace de4dot.code.deobfuscators.DeepSea { return new LocalTypes(handler).all(handlerLocalTypes_V3); } - static bool checkHandlerV4(MethodDefinition handler, out FieldDefinition resourceField, out MethodDefinition getDataMethod, out int magic) { + static Data40 checkHandlerV40(MethodDefinition handler) { + var data40 = new Data40(); + var instrs = handler.Body.Instructions; for (int i = 0; i < instrs.Count; i++) { int index = i; @@ -105,7 +249,7 @@ namespace de4dot.code.deobfuscators.DeepSea { // 4.0.1.18 .. 4.0.3 } - if (field.InitialValue == null || field.InitialValue.Length == 0) + if (field == null || field.InitialValue == null || field.InitialValue.Length == 0) continue; var ldci4_len = instrs[index++]; @@ -120,25 +264,24 @@ namespace de4dot.code.deobfuscators.DeepSea { var ldci4_magic = instrs[index++]; if (!DotNetUtils.isLdcI4(ldci4_magic)) continue; - magic = DotNetUtils.getLdcI4Value(ldci4_magic); + data40.magic = DotNetUtils.getLdcI4Value(ldci4_magic); var call = instrs[index++]; if (call.OpCode.Code == Code.Tail) call = instrs[index++]; if (call.OpCode.Code != Code.Call) continue; - if (!DotNetUtils.isMethod(call.Operand as MethodReference, "System.Reflection.Assembly", methodSig)) + var resolveHandler2 = call.Operand as MethodDefinition; + if (!DotNetUtils.isMethod(resolveHandler2, "System.Reflection.Assembly", methodSig)) continue; - resourceField = field; - getDataMethod = method; - return true; + data40.resourceField = field; + data40.getDataMethod = method; + data40.resolveHandler2 = resolveHandler2; + return data40; } - magic = 0; - resourceField = null; - getDataMethod = null; - return false; + return null; } static FieldDefinition getResourceField(MethodDefinition method) { @@ -157,11 +300,12 @@ namespace de4dot.code.deobfuscators.DeepSea { if (resolveHandler == null) return; - if (isV3) { + if (version == ResourceVersion.V3) { simpleDeobfuscator.deobfuscate(resolveHandler); simpleDeobfuscator.decryptStrings(resolveHandler, deob); - resource = DeobUtils.getEmbeddedResourceFromCodeStrings(module, resolveHandler); - if (resource == null) { + data30 = new Data30(); + data30.resource = DeobUtils.getEmbeddedResourceFromCodeStrings(module, resolveHandler); + if (data30.resource == null) { Log.w("Could not find resource of encrypted resources"); return; } @@ -171,22 +315,34 @@ namespace de4dot.code.deobfuscators.DeepSea { public bool mergeResources(out EmbeddedResource rsrc) { rsrc = null; - if (isV3) { - if (resource == null) + switch (version) { + case ResourceVersion.V3: + if (data30.resource == null) return false; - DeobUtils.decryptAndAddResources(module, resource.Name, () => decryptResourceV3(resource)); - rsrc = resource; - } - else { - if (resourceField == null) - return false; + DeobUtils.decryptAndAddResources(module, data30.resource.Name, () => decryptResourceV3(data30.resource)); + rsrc = data30.resource; + return true; - string name = string.Format("Embedded data field {0:X8} RVA {1:X8}", resourceField.MetadataToken.ToInt32(), resourceField.RVA); - DeobUtils.decryptAndAddResources(module, name, () => decryptResourceV4(resourceField.InitialValue, magicV4)); - resourceField.InitialValue = new byte[1]; - resourceField.FieldType = module.TypeSystem.Byte; + case ResourceVersion.V40: + return decryptResource(data40.resourceField, data40.magic); + + case ResourceVersion.V41: + return decryptResource(data41.resourceField, data41.magic); + + default: + return true; } + } + + bool decryptResource(FieldDefinition resourceField, int magic) { + if (resourceField == null) + return false; + + string name = string.Format("Embedded data field {0:X8} RVA {1:X8}", resourceField.MetadataToken.ToInt32(), resourceField.RVA); + DeobUtils.decryptAndAddResources(module, name, () => decryptResourceV4(resourceField.InitialValue, magic)); + resourceField.InitialValue = new byte[1]; + resourceField.FieldType = module.TypeSystem.Byte; return true; } } diff --git a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs index ad0993a7..3eb6a3f7 100644 --- a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs +++ b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.Text; using Mono.Cecil; using Mono.Cecil.Cil; +using Mono.Cecil.Metadata; using de4dot.blocks; namespace de4dot.code.deobfuscators.DeepSea { @@ -33,7 +34,8 @@ namespace de4dot.code.deobfuscators.DeepSea { public enum DecrypterVersion { Unknown, V1_3, - V4, + V4_0, + V4_1, } interface IDecrypterInfo { @@ -44,6 +46,12 @@ namespace de4dot.code.deobfuscators.DeepSea { } static short[] findKey(MethodDefinition initMethod, FieldDefinition keyField) { + var fields = new FieldDefinitionAndDeclaringTypeDict(); + fields.add(keyField, true); + return findKey(initMethod, fields); + } + + static short[] findKey(MethodDefinition initMethod, FieldDefinitionAndDeclaringTypeDict fields) { var instrs = initMethod.Body.Instructions; for (int i = 0; i < instrs.Count - 2; i++) { var ldci4 = instrs[i]; @@ -69,7 +77,7 @@ namespace de4dot.code.deobfuscators.DeepSea { var field = getStoreField(initMethod, startInitIndex, local); if (field == null) continue; - if (keyField == field) + if (fields.find(field)) return array; } @@ -94,9 +102,267 @@ namespace de4dot.code.deobfuscators.DeepSea { return null; } - class DecrypterInfo4 : IDecrypterInfo { + static bool findMagic(MethodDefinition method, out int magic) { + int arg1, arg2; + return findMagic(method, out arg1, out arg2, out magic); + } + + static bool findMagic(MethodDefinition method, out int arg1, out int arg2, out int magic) { + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 3; i++) { + if ((arg1 = DotNetUtils.getArgIndex(instrs[i])) < 0) + continue; + var ldci4 = instrs[i + 1]; + if (!DotNetUtils.isLdcI4(ldci4)) + continue; + if (instrs[i + 2].OpCode.Code != Code.Xor) + continue; + if ((arg2 = DotNetUtils.getArgIndex(instrs[i + 3])) < 0) + continue; + magic = DotNetUtils.getLdcI4Value(ldci4); + return true; + } + arg1 = arg2 = 0; + magic = 0; + return false; + } + + class DecrypterInfo41 : IDecrypterInfo { + MethodDefinition cctor; + int magic; + int arg1, arg2; + FieldDefinitionAndDeclaringTypeDict fields; + ArrayInfo arrayInfo; + ushort[] encryptedData; + short[] key; + int keyShift; + bool isTrial; + + class ArrayInfo { + public int sizeInElems; + public TypeReference elementType; + public FieldDefinition initField; + public FieldDefinition field; + + public ArrayInfo(int sizeInElems, TypeReference elementType, FieldDefinition initField, FieldDefinition field) { + this.sizeInElems = sizeInElems; + this.elementType = elementType; + this.initField = initField; + this.field = field; + } + } + + public DecrypterVersion Version { + get { return DecrypterVersion.V4_1; } + } + + public MethodDefinition Method { get; private set; } + + public DecrypterInfo41(MethodDefinition cctor, MethodDefinition method) { + this.cctor = cctor; + Method = method; + } + + public static bool isPossibleDecrypterMethod(MethodDefinition method) { + if (!checkMethodSignature(method)) + return false; + var fields = getFields(method); + if (fields == null || fields.Count != 3) + return false; + + return true; + } + + static bool checkMethodSignature(MethodDefinition method) { + if (method.MethodReturnType.ReturnType.EType != ElementType.String) + return false; + int count = 0; + foreach (var arg in method.Parameters) { + if (arg.ParameterType.EType == ElementType.I4) + count++; + } + return count >= 2; + } + + static FieldDefinitionAndDeclaringTypeDict getFields(MethodDefinition method) { + var fields = new FieldDefinitionAndDeclaringTypeDict(); + foreach (var instr in method.Body.Instructions) { + if (instr.OpCode.Code != Code.Ldsfld && instr.OpCode.Code != Code.Stsfld) + continue; + var field = instr.Operand as FieldDefinition; + if (field == null) + continue; + if (field.DeclaringType != method.DeclaringType) + continue; + fields.add(field, true); + } + return fields; + } + + public bool initialize() { + if (!findMagic(Method, out arg1, out arg2, out magic)) + return false; + + fields = getFields(Method); + if (fields == null) + return false; + + arrayInfo = getArrayInfo(cctor); + if (arrayInfo == null) + return false; + + if (arrayInfo.initField.InitialValue.Length % 2 == 1) + return false; + encryptedData = new ushort[arrayInfo.initField.InitialValue.Length / 2]; + Buffer.BlockCopy(arrayInfo.initField.InitialValue, 0, encryptedData, 0, arrayInfo.initField.InitialValue.Length); + + isTrial = !DeobUtils.hasInteger(Method, 0xFFF0); + keyShift = findKeyShift(cctor); + key = findKey(); + if (key == null || key.Length == 0) + return false; + + return true; + } + + int findKeyShift(MethodDefinition method) { + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 3; i++) { + int index = i; + + var ldci4 = instrs[index++]; + if (!DotNetUtils.isLdcI4(ldci4)) + continue; + if (DotNetUtils.getLdcI4Value(ldci4) != 0xFF) + continue; + + if (instrs[index++].OpCode.Code != Code.And) + continue; + if (instrs[index++].OpCode.Code != Code.Dup) + continue; + + var ldci4_2 = instrs[index++]; + if (!DotNetUtils.isLdcI4(ldci4_2)) + continue; + + if (findNextFieldUse(method, index) < 0) + continue; + + return DotNetUtils.getLdcI4Value(ldci4_2); + } + return -1; + } + + int findNextFieldUse(MethodDefinition method, int index) { + var instrs = method.Body.Instructions; + for (int i = index; i < instrs.Count; i++) { + var instr = instrs[i]; + if (instr.OpCode.Code != Code.Ldsfld && instr.OpCode.Code != Code.Stsfld) + continue; + var field = instr.Operand as FieldReference; + if (!fields.find(field)) + return -1; + + return i; + } + return -1; + } + + ArrayInfo getArrayInfo(MethodDefinition method) { + var instructions = method.Body.Instructions; + for (int i = 0; i < instructions.Count; i++) { + var ldci4_arraySizeInBytes = instructions[i]; + if (!DotNetUtils.isLdcI4(ldci4_arraySizeInBytes)) + continue; + i++; + var instrs = DotNetUtils.getInstructions(instructions, i, OpCodes.Newarr, OpCodes.Dup, OpCodes.Ldtoken, OpCodes.Call, OpCodes.Stsfld); + if (instrs == null) + continue; + + int sizeInBytes = DotNetUtils.getLdcI4Value(ldci4_arraySizeInBytes); + var elementType = instrs[0].Operand as TypeReference; + var initField = instrs[2].Operand as FieldDefinition; + var field = instrs[4].Operand as FieldDefinition; + if (elementType == null) + continue; + if (initField == null || initField.InitialValue == null || initField.InitialValue.Length == 0) + continue; + if (!fields.find(field)) + continue; + + return new ArrayInfo(sizeInBytes, elementType, initField, field); + } + return null; + } + + short[] findKey() { + if (cctor.Module.Assembly == null) + return null; + var pkt = cctor.Module.Assembly.Name.PublicKeyToken; + if (pkt != null && pkt.Length > 0) + return getPublicKeyTokenKey(pkt); + return findKey(cctor); + } + + short[] findKey(MethodDefinition initMethod) { + return StringDecrypter.findKey(initMethod, fields); + } + + short[] getPublicKeyTokenKey(byte[] publicKeyToken) { + if (keyShift < 0) + throw new ApplicationException("Could not find shift value"); + var key = new short[publicKeyToken.Length]; + for (int i = 0; i < publicKeyToken.Length; i++) { + int b = publicKeyToken[i]; + key[i] = (short)((b << keyShift) ^ b); + } + return key; + } + + public string decrypt(object[] args) { + if (isTrial) + return decryptTrial((int)args[arg1], (int)args[arg2]); + return decryptRetail((int)args[arg1], (int)args[arg2]); + } + + string decryptTrial(int magic2, int magic3) { + int offset = magic ^ magic2 ^ magic3; + var keyChar = encryptedData[offset + 1]; + int cachedIndex = encryptedData[offset] ^ keyChar; + int numChars = ((keyChar ^ encryptedData[offset + 2]) << 16) + (keyChar ^ encryptedData[offset + 3]); + offset += 4; + var sb = new StringBuilder(numChars); + for (int i = 0; i < numChars; i++) + sb.Append((char)(keyChar ^ encryptedData[offset + i] ^ key[(offset + i) % key.Length])); + return sb.ToString(); + } + + string decryptRetail(int magic2, int magic3) { + int offset = magic ^ magic2 ^ magic3; + var keyChar = encryptedData[offset + 2]; + int cachedIndex = encryptedData[offset + 1] ^ keyChar; + int flags = encryptedData[offset] ^ keyChar; + int numChars = (flags >> 1) & ~7 | (flags & 7); + if ((flags & 8) != 0) { + numChars <<= 15; + numChars |= encryptedData[offset + 3] ^ keyChar; + offset++; + } + offset += 3; + var sb = new StringBuilder(numChars); + for (int i = 0; i < numChars; i++) + sb.Append((char)(keyChar ^ encryptedData[offset + numChars - i - 1] ^ key[(i + 1 + offset) % key.Length])); + return sb.ToString(); + } + + public void cleanup() { + arrayInfo.initField.InitialValue = new byte[1]; + arrayInfo.initField.FieldType = arrayInfo.initField.Module.TypeSystem.Byte; + } + } + + class DecrypterInfo40 : IDecrypterInfo { MethodDefinition cctor; - public MethodDefinition Method { get; set; } int magic; FieldDefinition cachedStringsField; FieldDefinition keyField; @@ -105,15 +371,23 @@ namespace de4dot.code.deobfuscators.DeepSea { short[] key; ushort[] encryptedData; + public MethodDefinition Method { get; private set; } + public DecrypterVersion Version { - get { return DecrypterVersion.V4; } + get { return DecrypterVersion.V4_0; } } - public DecrypterInfo4(MethodDefinition cctor, MethodDefinition method) { + public DecrypterInfo40(MethodDefinition cctor, MethodDefinition method) { this.cctor = cctor; this.Method = method; } + public static bool isPossibleDecrypterMethod(MethodDefinition method) { + if (!checkFields(method.DeclaringType.Fields)) + return false; + return DotNetUtils.isMethod(method, "System.String", "(System.Int32,System.Int32)"); + } + public bool initialize() { if (!findMagic(Method, out magic)) return false; @@ -140,24 +414,6 @@ namespace de4dot.code.deobfuscators.DeepSea { return true; } - static bool findMagic(MethodDefinition method, out int magic) { - var instrs = method.Body.Instructions; - for (int i = 0; i < instrs.Count - 2; i++) { - var ldarg = instrs[i]; - if (DotNetUtils.getArgIndex(ldarg) < 0) - continue; - var ldci4 = instrs[i + 1]; - if (!DotNetUtils.isLdcI4(ldci4)) - continue; - if (instrs[i + 2].OpCode.Code != Code.Xor) - continue; - magic = DotNetUtils.getLdcI4Value(ldci4); - return true; - } - magic = 0; - return false; - } - List findFields() { var charArrayFields = new List(); @@ -263,20 +519,27 @@ namespace de4dot.code.deobfuscators.DeepSea { } } - class DecrypterInfo3 : IDecrypterInfo { + class DecrypterInfo13 : IDecrypterInfo { MethodDefinition cctor; - public MethodDefinition Method { get; set; } FieldDefinition cachedStringsField; FieldDefinition keyField; int magic; string[] encryptedStrings; short[] key; + public MethodDefinition Method { get; private set; } + public DecrypterVersion Version { get { return DecrypterVersion.V1_3; } } - public DecrypterInfo3(MethodDefinition cctor, MethodDefinition method) { + public static bool isPossibleDecrypterMethod(MethodDefinition method) { + if (!checkFields(method.DeclaringType.Fields)) + return false; + return DotNetUtils.isMethod(method, "System.String", "(System.Int32)"); + } + + public DecrypterInfo13(MethodDefinition cctor, MethodDefinition method) { this.cctor = cctor; this.Method = method; } @@ -442,27 +705,31 @@ namespace de4dot.code.deobfuscators.DeepSea { bool hasPublicKeyToken = module.Assembly.Name.PublicKeyToken != null && module.Assembly.Name.PublicKeyToken.Length != 0; foreach (var type in module.GetTypes()) { - if (!checkFields(type.Fields)) - continue; var cctor = DotNetUtils.getMethod(type, ".cctor"); if (cctor == null) continue; - if (!hasPublicKeyToken) - simpleDeobfuscator.deobfuscate(cctor); + bool deobfuscatedCctor = false; foreach (var method in type.Methods) { - if (method.Body == null) + if (!method.IsStatic || method.Body == null) continue; IDecrypterInfo info = null; - if (DotNetUtils.isMethod(method, "System.String", "(System.Int32)")) { + if (DecrypterInfo13.isPossibleDecrypterMethod(method)) { + deobfuscateCctor(simpleDeobfuscator, cctor, ref deobfuscatedCctor, hasPublicKeyToken); simpleDeobfuscator.deobfuscate(method); - info = getInfoV3(cctor, method); + info = getInfoV13(cctor, method); } - else if (DotNetUtils.isMethod(method, "System.String", "(System.Int32,System.Int32)")) { + else if (DecrypterInfo40.isPossibleDecrypterMethod(method)) { + deobfuscateCctor(simpleDeobfuscator, cctor, ref deobfuscatedCctor, hasPublicKeyToken); simpleDeobfuscator.deobfuscate(method); - info = getInfoV4(cctor, method); + info = getInfoV40(cctor, method); + } + else if (DecrypterInfo41.isPossibleDecrypterMethod(method)) { + deobfuscateCctor(simpleDeobfuscator, cctor, ref deobfuscatedCctor, hasPublicKeyToken); + simpleDeobfuscator.deobfuscate(method); + info = getInfoV41(cctor, method); } if (info == null) @@ -473,6 +740,13 @@ namespace de4dot.code.deobfuscators.DeepSea { } } + static void deobfuscateCctor(ISimpleDeobfuscator simpleDeobfuscator, MethodDefinition cctor, ref bool deobfuscatedCctor, bool hasPublicKeyToken) { + if (deobfuscatedCctor || hasPublicKeyToken) + return; + simpleDeobfuscator.deobfuscate(cctor); + deobfuscatedCctor = true; + } + static bool checkFields(IEnumerable fields) { bool foundCharAry = false, foundStringAry = false; foreach (var field in fields) { @@ -490,15 +764,22 @@ namespace de4dot.code.deobfuscators.DeepSea { return foundCharAry && foundStringAry; } - DecrypterInfo3 getInfoV3(MethodDefinition cctor, MethodDefinition method) { - var info = new DecrypterInfo3(cctor, method); + DecrypterInfo13 getInfoV13(MethodDefinition cctor, MethodDefinition method) { + var info = new DecrypterInfo13(cctor, method); if (!info.initialize()) return null; return info; } - DecrypterInfo4 getInfoV4(MethodDefinition cctor, MethodDefinition method) { - var info = new DecrypterInfo4(cctor, method); + DecrypterInfo40 getInfoV40(MethodDefinition cctor, MethodDefinition method) { + var info = new DecrypterInfo40(cctor, method); + if (!info.initialize()) + return null; + return info; + } + + DecrypterInfo41 getInfoV41(MethodDefinition cctor, MethodDefinition method) { + var info = new DecrypterInfo41(cctor, method); if (!info.initialize()) return null; return info; diff --git a/de4dot.code/deobfuscators/DeobfuscatorBase.cs b/de4dot.code/deobfuscators/DeobfuscatorBase.cs index 5ad2bb25..3c209ded 100644 --- a/de4dot.code/deobfuscators/DeobfuscatorBase.cs +++ b/de4dot.code/deobfuscators/DeobfuscatorBase.cs @@ -95,11 +95,12 @@ namespace de4dot.code.deobfuscators { get { return Operations.DecryptStrings != OpDecryptString.None && staticStringInliner.InlinedAllCalls; } } - public virtual IMethodCallInliner MethodCallInliner { + public virtual IEnumerable BlocksDeobfuscators { get { + var list = new List(); if (CanInlineMethods) - return new MethodCallInliner(false); - return new NoMethodInliner(); + list.Add(new MethodCallInliner(false)); + return list; } } @@ -744,6 +745,10 @@ namespace de4dot.code.deobfuscators { return name != null && checkValidName(name); } + public virtual bool isValidResourceKeyName(string name) { + return name != null && checkValidName(name); + } + public virtual void OnBeforeAddingResources(MetadataBuilder builder) { } diff --git a/de4dot.code/deobfuscators/Eazfuscator_NET/ConstantsReader.cs b/de4dot.code/deobfuscators/Eazfuscator_NET/ConstantsReader.cs deleted file mode 100644 index 7bffdad8..00000000 --- a/de4dot.code/deobfuscators/Eazfuscator_NET/ConstantsReader.cs +++ /dev/null @@ -1,220 +0,0 @@ -/* - 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.Collections.Generic; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Cecil.Metadata; -using de4dot.blocks; - -namespace de4dot.code.deobfuscators.Eazfuscator_NET { - class ConstantsReader { - IList instructions; - IList locals; - Dictionary localsValues = new Dictionary(); - - public ConstantsReader(MethodDefinition method) { - instructions = method.Body.Instructions; - locals = method.Body.Variables; - initialize(); - } - - void initialize() { - findConstants(); - } - - void findConstants() { - for (int index = 0; index < instructions.Count; ) { - int value; - if (!getInt32(ref index, out value)) - break; - var stloc = instructions[index]; - if (!DotNetUtils.isStloc(stloc)) - break; - var local = DotNetUtils.getLocalVar(locals, stloc); - if (local == null || local.VariableType.EType != ElementType.I4) - break; - localsValues[local] = value; - index++; - } - - if (localsValues.Count != 2) - localsValues.Clear(); - } - - public bool getNextInt32(ref int index, out int val) { - for (; index < instructions.Count; index++) { - var instr = instructions[index]; - if (!isLoadConstant(instr)) - continue; - - return getInt32(ref index, out val); - } - - val = 0; - return false; - } - - public bool isLoadConstant(Instruction instr) { - if (DotNetUtils.isLdcI4(instr)) - return true; - if (!DotNetUtils.isLdloc(instr)) - return false; - int tmp; - return getLocalConstant(instr, out tmp); - } - - public bool getInt16(ref int index, out short val) { - int tmp; - if (!getInt32(ref index, out tmp)) { - val = 0; - return false; - } - - val = (short)tmp; - return true; - } - - public bool getInt32(ref int index, out int val) { - val = 0; - if (index >= instructions.Count) - return false; - - var stack = new Stack(); - - int op1; - for (; index < instructions.Count; index++) { - var instr = instructions[index]; - switch (instr.OpCode.Code) { - case Code.Conv_I1: - if (stack.Count < 1) - goto done; - stack.Push((sbyte)stack.Pop()); - break; - - case Code.Conv_U1: - if (stack.Count < 1) - goto done; - stack.Push((byte)stack.Pop()); - break; - - case Code.Conv_I2: - if (stack.Count < 1) - goto done; - stack.Push((short)stack.Pop()); - break; - - case Code.Conv_U2: - if (stack.Count < 1) - goto done; - stack.Push((ushort)stack.Pop()); - break; - - case Code.Conv_I4: - case Code.Conv_U4: - break; - - case Code.Not: - stack.Push(~stack.Pop()); - break; - - case Code.Neg: - stack.Push(-stack.Pop()); - break; - - case Code.Ldloc: - case Code.Ldloc_S: - case Code.Ldloc_0: - case Code.Ldloc_1: - case Code.Ldloc_2: - case Code.Ldloc_3: - if (!getLocalConstant(instr, out op1)) - goto done; - stack.Push(op1); - break; - - case Code.Ldc_I4: - case Code.Ldc_I4_S: - 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: - stack.Push(DotNetUtils.getLdcI4Value(instr)); - break; - - case Code.Add: - if (stack.Count < 2) - goto done; - stack.Push(stack.Pop() + stack.Pop()); - break; - - case Code.Sub: - if (stack.Count < 2) - goto done; - stack.Push(-(stack.Pop() - stack.Pop())); - break; - - case Code.Xor: - if (stack.Count < 2) - goto done; - stack.Push(stack.Pop() ^ stack.Pop()); - break; - - case Code.Or: - if (stack.Count < 2) - goto done; - stack.Push(stack.Pop() | stack.Pop()); - break; - - case Code.And: - if (stack.Count < 2) - goto done; - stack.Push(stack.Pop() & stack.Pop()); - break; - - default: - goto done; - } - } -done: - if (stack.Count == 0) - return false; - while (stack.Count > 1) - stack.Pop(); - val = stack.Pop(); - return true; - } - - bool getLocalConstant(Instruction instr, out int value) { - value = 0; - var local = DotNetUtils.getLocalVar(locals, instr); - if (local == null) - return false; - if (local.VariableType.EType != ElementType.I4) - return false; - return localsValues.TryGetValue(local, out value); - } - } -} diff --git a/de4dot.code/deobfuscators/Eazfuscator_NET/DecrypterType.cs b/de4dot.code/deobfuscators/Eazfuscator_NET/DecrypterType.cs index 88f27868..f113dfa0 100644 --- a/de4dot.code/deobfuscators/Eazfuscator_NET/DecrypterType.cs +++ b/de4dot.code/deobfuscators/Eazfuscator_NET/DecrypterType.cs @@ -283,7 +283,7 @@ namespace de4dot.code.deobfuscators.Eazfuscator_NET { int index = 0; var instrs = method.Body.Instructions; - var constantsReader = new ConstantsReader(method); + var constantsReader = new EfConstantsReader(method); while (true) { int val; if (!constantsReader.getNextInt32(ref index, out val)) diff --git a/de4dot.code/deobfuscators/Eazfuscator_NET/EfConstantsReader.cs b/de4dot.code/deobfuscators/Eazfuscator_NET/EfConstantsReader.cs new file mode 100644 index 00000000..fc2b7702 --- /dev/null +++ b/de4dot.code/deobfuscators/Eazfuscator_NET/EfConstantsReader.cs @@ -0,0 +1,54 @@ +/* + 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 Mono.Cecil; +using Mono.Cecil.Metadata; +using de4dot.blocks; + +namespace de4dot.code.deobfuscators.Eazfuscator_NET { + class EfConstantsReader : ConstantsReader { + public EfConstantsReader(MethodDefinition method) + : base(method) { + initialize(); + } + + void initialize() { + findConstants(); + } + + void findConstants() { + for (int index = 0; index < instructions.Count; ) { + int value; + if (!getInt32(ref index, out value)) + break; + var stloc = instructions[index]; + if (!DotNetUtils.isStloc(stloc)) + break; + var local = DotNetUtils.getLocalVar(locals, stloc); + if (local == null || local.VariableType.EType != ElementType.I4) + break; + localsValues[local] = value; + index++; + } + + if (localsValues.Count != 2) + localsValues.Clear(); + } + } +} diff --git a/de4dot.code/deobfuscators/Eazfuscator_NET/StringDecrypter.cs b/de4dot.code/deobfuscators/Eazfuscator_NET/StringDecrypter.cs index 3205592f..98dcfc4b 100644 --- a/de4dot.code/deobfuscators/Eazfuscator_NET/StringDecrypter.cs +++ b/de4dot.code/deobfuscators/Eazfuscator_NET/StringDecrypter.cs @@ -44,7 +44,7 @@ namespace de4dot.code.deobfuscators.Eazfuscator_NET { BinaryReader reader; DecrypterType decrypterType; StreamHelperType streamHelperType; - ConstantsReader stringMethodConsts; + EfConstantsReader stringMethodConsts; bool isV32OrLater; class StreamHelperType { @@ -219,7 +219,7 @@ namespace de4dot.code.deobfuscators.Eazfuscator_NET { bool findConstants(ISimpleDeobfuscator simpleDeobfuscator) { simpleDeobfuscator.deobfuscate(stringMethod); - stringMethodConsts = new ConstantsReader(stringMethod); + stringMethodConsts = new EfConstantsReader(stringMethod); if (!findResource(stringMethod)) return false; @@ -658,7 +658,7 @@ namespace de4dot.code.deobfuscators.Eazfuscator_NET { return findIntsCctor2(cctor); int tmp1, tmp2, tmp3 = 0; - var constantsReader = new ConstantsReader(cctor); + var constantsReader = new EfConstantsReader(cctor); if (!constantsReader.getNextInt32(ref index, out tmp1)) return false; if (tmp1 == 0 && !constantsReader.getNextInt32(ref index, out tmp1)) @@ -684,7 +684,7 @@ namespace de4dot.code.deobfuscators.Eazfuscator_NET { bool findIntsCctor2(MethodDefinition cctor) { int index = 0; var instrs = cctor.Body.Instructions; - var constantsReader = new ConstantsReader(cctor); + var constantsReader = new EfConstantsReader(cctor); while (index >= 0) { int val; if (!constantsReader.getNextInt32(ref index, out val)) diff --git a/de4dot.code/deobfuscators/IDeobfuscator.cs b/de4dot.code/deobfuscators/IDeobfuscator.cs index 848f4913..76858479 100644 --- a/de4dot.code/deobfuscators/IDeobfuscator.cs +++ b/de4dot.code/deobfuscators/IDeobfuscator.cs @@ -50,6 +50,7 @@ namespace de4dot.code.deobfuscators { [Flags] public enum RenamingOptions { RemoveNamespaceIfOneType = 1, + RenameResourceKeys = 2, } public interface IDeobfuscator : INameChecker { @@ -61,7 +62,7 @@ namespace de4dot.code.deobfuscators { StringFeatures StringFeatures { get; } RenamingOptions RenamingOptions { get; } DecrypterType DefaultDecrypterType { get; } - IMethodCallInliner MethodCallInliner { get; } + IEnumerable BlocksDeobfuscators { get; } // This is non-null only in detect() and deobfuscateBegin(). IDeobfuscatedFile DeobfuscatedFile { get; set; } diff --git a/de4dot.code/deobfuscators/Spices_Net/Deobfuscator.cs b/de4dot.code/deobfuscators/Spices_Net/Deobfuscator.cs index 8cafb0e4..23bde7cf 100644 --- a/de4dot.code/deobfuscators/Spices_Net/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/Spices_Net/Deobfuscator.cs @@ -100,11 +100,12 @@ namespace de4dot.code.deobfuscators.Spices_Net { get { return startedDeobfuscating ? options.InlineMethods : true; } } - public override IMethodCallInliner MethodCallInliner { + public override IEnumerable BlocksDeobfuscators { get { + var list = new List(); if (CanInlineMethods) - return methodCallInliner; - return new NoMethodInliner(); + list.Add(methodCallInliner); + return list; } } diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v3/Deobfuscator.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v3/Deobfuscator.cs index cca460a4..150c96be 100644 --- a/de4dot.code/deobfuscators/dotNET_Reactor/v3/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/dotNET_Reactor/v3/Deobfuscator.cs @@ -218,6 +218,10 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v3 { return name != null && checkValidName(name, isRandomNameMembers); } + public override bool isValidResourceKeyName(string name) { + return name != null && checkValidName(name, isRandomNameMembers); + } + protected override int detectInternal() { int val = 0; diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs index a72ee466..0be1f216 100644 --- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs @@ -222,6 +222,10 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { return name != null && checkValidName(name, isRandomNameMembers); } + public override bool isValidResourceKeyName(string name) { + return name != null && checkValidName(name, isRandomNameMembers); + } + protected override int detectInternal() { int val = 0; diff --git a/de4dot.code/renamer/INameChecker.cs b/de4dot.code/renamer/INameChecker.cs index 4334dd16..2edd05f0 100644 --- a/de4dot.code/renamer/INameChecker.cs +++ b/de4dot.code/renamer/INameChecker.cs @@ -27,5 +27,6 @@ namespace de4dot.code.renamer { bool isValidFieldName(string name); bool isValidGenericParamName(string name); bool isValidMethodArgName(string name); + bool isValidResourceKeyName(string name); } } diff --git a/de4dot.code/renamer/Renamer.cs b/de4dot.code/renamer/Renamer.cs index 2b29baa8..b4fc276b 100644 --- a/de4dot.code/renamer/Renamer.cs +++ b/de4dot.code/renamer/Renamer.cs @@ -78,6 +78,7 @@ namespace de4dot.code.renamer { Log.n("Renaming all obfuscated symbols"); modules.initialize(); + renameResourceKeys(); var groups = modules.initializeVirtualMembers(); memberInfos.initialize(modules); renameTypeDefinitions(); @@ -92,6 +93,14 @@ namespace de4dot.code.renamer { modules.cleanUp(); } + void renameResourceKeys() { + foreach (var module in modules.TheModules) { + if (!module.ObfuscatedFile.RenameResourceKeys) + continue; + new ResourceKeysRenamer(module.ModuleDefinition, module.ObfuscatedFile.NameChecker).rename(); + } + } + void removeUselessOverrides(MethodNameGroups groups) { foreach (var group in groups.getAllGroups()) { foreach (var method in group.Methods) { @@ -396,8 +405,60 @@ namespace de4dot.code.renamer { var allGroups = groups.getAllGroups(); restoreVirtualProperties(allGroups); restorePropertiesFromNames(allGroups); + resetVirtualPropertyNames(allGroups); restoreVirtualEvents(allGroups); restoreEventsFromNames(allGroups); + resetVirtualEventNames(allGroups); + } + + void resetVirtualPropertyNames(IEnumerable allGroups) { + if (!this.RenameProperties) + return; + foreach (var group in allGroups) { + PropertyDef prop = null; + foreach (var method in group.Methods) { + if (method.Property == null) + continue; + if (method.Owner.HasModule) + continue; + prop = method.Property; + break; + } + if (prop == null) + continue; + foreach (var method in group.Methods) { + if (!method.Owner.HasModule) + continue; + if (method.Property == null) + continue; + memberInfos.prop(method.Property).rename(prop.PropertyDefinition.Name); + } + } + } + + void resetVirtualEventNames(IEnumerable allGroups) { + if (!this.RenameEvents) + return; + foreach (var group in allGroups) { + EventDef evt = null; + foreach (var method in group.Methods) { + if (method.Event == null) + continue; + if (method.Owner.HasModule) + continue; + evt = method.Event; + break; + } + if (evt == null) + continue; + foreach (var method in group.Methods) { + if (!method.Owner.HasModule) + continue; + if (method.Event == null) + continue; + memberInfos.evt(method.Event).rename(evt.EventDefinition.Name); + } + } } void restoreVirtualProperties(IEnumerable allGroups) { diff --git a/de4dot.code/renamer/ResourceKeysRenamer.cs b/de4dot.code/renamer/ResourceKeysRenamer.cs new file mode 100644 index 00000000..6ad77116 --- /dev/null +++ b/de4dot.code/renamer/ResourceKeysRenamer.cs @@ -0,0 +1,257 @@ +/* + 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 System.IO; +using System.Text; +using System.Text.RegularExpressions; +using Mono.Cecil; +using Mono.Cecil.Cil; +using de4dot.blocks; +using de4dot.code.resources; + +namespace de4dot.code.renamer { + class ResourceKeysRenamer { + const int RESOURCE_KEY_MAX_LEN = 50; + const string DEFAULT_KEY_NAME = "Key"; + + ModuleDefinition module; + INameChecker nameChecker; + Dictionary newNames = new Dictionary(); + + public ResourceKeysRenamer(ModuleDefinition module, INameChecker nameChecker) { + this.module = module; + this.nameChecker = nameChecker; + } + + public void rename() { + Log.v("Renaming resource keys ({0})", module); + Log.indent(); + foreach (var type in module.GetTypes()) { + string resourceName = getResourceName(type); + if (resourceName == null) + continue; + var resource = getResource(resourceName); + if (resource == null) { + Log.w("Could not find resource {0}", Utils.removeNewlines(resourceName)); + continue; + } + Log.v("Resource: {0}", Utils.toCsharpString(resource.Name)); + Log.indent(); + rename(type, resource); + Log.deIndent(); + } + Log.deIndent(); + } + + EmbeddedResource getResource(string resourceName) { + var resource = DotNetUtils.getResource(module, resourceName + ".resources") as EmbeddedResource; + if (resource != null) + return resource; + + string name = ""; + var pieces = resourceName.Split('.'); + Array.Reverse(pieces); + foreach (var piece in pieces) { + name = piece + name; + resource = DotNetUtils.getResource(module, name + ".resources") as EmbeddedResource; + if (resource != null) + return resource; + } + return null; + } + + static string getResourceName(TypeDefinition type) { + foreach (var method in type.Methods) { + if (method.Body == null) + continue; + var instrs = method.Body.Instructions; + string resourceName = null; + for (int i = 0; i < instrs.Count; i++) { + var instr = instrs[i]; + if (instr.OpCode.Code == Code.Ldstr) { + resourceName = instr.Operand as string; + continue; + } + + if (instr.OpCode.Code == Code.Newobj) { + var ctor = instr.Operand as MethodReference; + if (ctor.FullName != "System.Void System.Resources.ResourceManager::.ctor(System.String,System.Reflection.Assembly)") + continue; + if (resourceName == null) { + Log.w("Could not find resource name"); + continue; + } + + return resourceName; + } + } + } + return null; + } + + class RenameInfo { + public readonly ResourceElement element; + public string newName; + public bool foundInCode; + public RenameInfo(ResourceElement element, string newName) { + this.element = element; + this.newName = newName; + this.foundInCode = false; + } + public override string ToString() { + return string.Format("{0} => {1}", element, newName); + } + } + + void rename(TypeDefinition type, EmbeddedResource resource) { + newNames.Clear(); + var resourceSet = ResourceReader.read(module, resource.GetResourceStream()); + var renamed = new List(); + foreach (var elem in resourceSet.ResourceElements) { + if (nameChecker.isValidResourceKeyName(elem.Name)) { + newNames.Add(elem.Name, true); + continue; + } + + renamed.Add(new RenameInfo(elem, getNewName(elem))); + } + + if (renamed.Count == 0) + return; + + rename(type, renamed); + + var outStream = new MemoryStream(); + ResourceWriter.write(module, outStream, resourceSet); + outStream.Position = 0; + var newResource = new EmbeddedResource(resource.Name, resource.Attributes, outStream); + int resourceIndex = module.Resources.IndexOf(resource); + if (resourceIndex < 0) + throw new ApplicationException("Could not find index of resource"); + module.Resources[resourceIndex] = newResource; + } + + void rename(TypeDefinition type, List renamed) { + var nameToInfo = new Dictionary(StringComparer.Ordinal); + foreach (var info in renamed) + nameToInfo[info.element.Name] = info; + + foreach (var method in type.Methods) { + if (method.Body == null) + continue; + + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count; i++) { + var call = instrs[i]; + if (call.OpCode.Code != Code.Call && call.OpCode.Code != Code.Callvirt) + continue; + var calledMethod = call.Operand as MethodReference; + if (calledMethod == null) + continue; + + int ldstrIndex; + switch (calledMethod.FullName) { + case "System.String System.Resources.ResourceManager::GetString(System.String,System.Globalization.CultureInfo)": + case "System.IO.UnmanagedMemoryStream System.Resources.ResourceManager::GetStream(System.String,System.Globalization.CultureInfo)": + case "System.Object System.Resources.ResourceManager::GetObject(System.String,System.Globalization.CultureInfo)": + ldstrIndex = i - 2; + break; + + case "System.String System.Resources.ResourceManager::GetString(System.String)": + case "System.IO.UnmanagedMemoryStream System.Resources.ResourceManager::GetStream(System.String)": + case "System.Object System.Resources.ResourceManager::GetObject(System.String)": + ldstrIndex = i - 1; + break; + + default: + continue; + } + + Instruction ldstr = null; + string name = null; + if (ldstrIndex >= 0) + ldstr = instrs[ldstrIndex]; + if (ldstr == null || (name = ldstr.Operand as string) == null) { + Log.w("Could not find string argument to method {0}", calledMethod); + continue; + } + + RenameInfo info; + if (!nameToInfo.TryGetValue(name, out info)) + continue; // should not be renamed + + ldstr.Operand = info.newName; + Log.v("Renamed resource key {0} => {1}", Utils.toCsharpString(info.element.Name), Utils.toCsharpString(info.newName)); + info.element.Name = info.newName; + info.foundInCode = true; + } + } + + foreach (var info in renamed) { + if (!info.foundInCode) + Log.w("Could not find resource key {0} in code", Utils.removeNewlines(info.element.Name)); + } + } + + string getNewName(ResourceElement elem) { + if (elem.ResourceData.Code != ResourceTypeCode.String) + return createDefaultName(); + var stringData = (BuiltInResourceData)elem.ResourceData; + var name = createPrefixFromStringData((string)stringData.Data); + return createName(counter => counter == 0 ? name : string.Format("{0}_{1}", name, counter)); + } + + string createPrefixFromStringData(string data) { + var sb = new StringBuilder(); + data = data.Substring(0, Math.Min(data.Length, 100)); + data = Regex.Replace(data, "[`'\"]", ""); + data = Regex.Replace(data, @"[^\w]+", " "); + foreach (var piece in data.Split(' ')) { + if (piece.Length == 0) + continue; + var piece2 = piece.Substring(0, 1).ToUpperInvariant() + piece.Substring(1).ToLowerInvariant(); + int maxLen = RESOURCE_KEY_MAX_LEN - sb.Length; + if (maxLen <= 0) + break; + if (piece2.Length > maxLen) + piece2 = piece2.Substring(0, maxLen); + sb.Append(piece2); + } + if (sb.Length <= 3) + return createDefaultName(); + return sb.ToString(); + } + + string createDefaultName() { + return createName(counter => string.Format("{0}{1}", DEFAULT_KEY_NAME, counter)); + } + + string createName(Func create) { + for (int counter = 0; ; counter++) { + string newName = create(counter); + if (!newNames.ContainsKey(newName)) { + newNames[newName] = true; + return newName; + } + } + } + } +} diff --git a/de4dot.code/renamer/VariableNameState.cs b/de4dot.code/renamer/VariableNameState.cs index 67dcd0d4..a877a2fa 100644 --- a/de4dot.code/renamer/VariableNameState.cs +++ b/de4dot.code/renamer/VariableNameState.cs @@ -89,7 +89,7 @@ namespace de4dot.code.renamer { public string getNewPropertyName(PropertyDefinition propertyDefinition) { var propType = propertyDefinition.PropertyType; string newName; - if (propType is GenericParameter) + if (isGeneric(propType)) newName = existingPropertyNames.getName(propertyDefinition.Name, genericPropertyNameCreator); else newName = existingPropertyNames.getName(propertyDefinition.Name, () => propertyNameCreator.create(propType)); @@ -97,6 +97,17 @@ namespace de4dot.code.renamer { return newName; } + static bool isGeneric(TypeReference type) { + while (true) { + if (type is GenericParameter) + return true; + var ts = type as TypeSpecification; + if (ts == null) + return false; + type = ts.ElementType; + } + } + public string getNewEventName(EventDefinition eventDefinition) { string newName = eventNameCreator.create(); addEventName(newName); diff --git a/de4dot.code/resources/BuiltInResourceData.cs b/de4dot.code/resources/BuiltInResourceData.cs index d13489fc..a6a52f80 100644 --- a/de4dot.code/resources/BuiltInResourceData.cs +++ b/de4dot.code/resources/BuiltInResourceData.cs @@ -26,6 +26,10 @@ namespace de4dot.code.resources { readonly ResourceTypeCode code; readonly object data; + public object Data { + get { return data; } + } + public ResourceTypeCode Code { get { return code; } } diff --git a/de4dot.code/resources/ResourceReader.cs b/de4dot.code/resources/ResourceReader.cs new file mode 100644 index 00000000..26b979ab --- /dev/null +++ b/de4dot.code/resources/ResourceReader.cs @@ -0,0 +1,187 @@ +/* + 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 System.IO; +using System.Text; +using System.Text.RegularExpressions; +using Mono.Cecil; + +namespace de4dot.code.resources { + [Serializable] + class ResourceReaderException : Exception { + public ResourceReaderException(string msg) + : base(msg) { + } + } + + class ResourceReader { + ModuleDefinition module; + BinaryReader reader; + ResourceDataCreator resourceDataCreator; + + ResourceReader(ModuleDefinition module, Stream stream) { + this.module = module; + this.reader = new BinaryReader(stream); + this.resourceDataCreator = new ResourceDataCreator(module); + } + + public static ResourceElementSet read(ModuleDefinition module, Stream stream) { + return new ResourceReader(module, stream).read(); + } + + ResourceElementSet read() { + ResourceElementSet resources = new ResourceElementSet(); + + uint sig = reader.ReadUInt32(); + if (sig != 0xBEEFCACE) + throw new ResourceReaderException(string.Format("Invalid resource sig: {0:X8}", sig)); + if (!checkReaders()) + throw new ResourceReaderException("Invalid resource reader"); + int version = reader.ReadInt32(); + if (version != 2) + throw new ResourceReaderException(string.Format("Invalid resource version: {0}", version)); + int numResources = reader.ReadInt32(); + if (numResources < 0) + throw new ResourceReaderException(string.Format("Invalid number of resources: {0}", numResources)); + int numUserTypes = reader.ReadInt32(); + if (numUserTypes < 0) + throw new ResourceReaderException(string.Format("Invalid number of user types: {0}", numUserTypes)); + + var userTypes = new List(); + for (int i = 0; i < numUserTypes; i++) + userTypes.Add(new UserResourceType(reader.ReadString(), ResourceTypeCode.UserTypes + i)); + reader.BaseStream.Position = (reader.BaseStream.Position + 7) & ~7; + + var hashes = new int[numResources]; + for (int i = 0; i < numResources; i++) + hashes[i] = reader.ReadInt32(); + var offsets = new int[numResources]; + for (int i = 0; i < numResources; i++) + offsets[i] = reader.ReadInt32(); + + long baseOffset = reader.BaseStream.Position; + long dataBaseOffset = reader.ReadInt32(); + long nameBaseOffset = reader.BaseStream.Position; + long end = reader.BaseStream.Length; + + var infos = new List(numResources); + + var nameReader = new BinaryReader(reader.BaseStream, Encoding.Unicode); + for (int i = 0; i < numResources; i++) { + nameReader.BaseStream.Position = nameBaseOffset + offsets[i]; + var name = nameReader.ReadString(); + long offset = dataBaseOffset + nameReader.ReadInt32(); + infos.Add(new ResourceInfo(name, offset)); + } + + infos.Sort(sortResourceInfo); + for (int i = 0; i < infos.Count; i++) { + var info = infos[i]; + var element = new ResourceElement(); + element.Name = info.name; + reader.BaseStream.Position = info.offset; + long nextDataOffset = i == infos.Count - 1 ? end : infos[i + 1].offset; + int size = (int)(nextDataOffset - info.offset); + element.ResourceData = readResourceData(userTypes, size); + + resources.add(element); + } + + return resources; + } + + static int sortResourceInfo(ResourceInfo a, ResourceInfo b) { + return Utils.compareInt32((int)a.offset, (int)b.offset); + } + + class ResourceInfo { + public string name; + public long offset; + public ResourceInfo(string name, long offset) { + this.name = name; + this.offset = offset; + } + public override string ToString() { + return string.Format("{0:X8} - {1}", offset, name); + } + } + + IResourceData readResourceData(List userTypes, int size) { + uint code = readUInt32(reader); + switch ((ResourceTypeCode)code) { + case ResourceTypeCode.Null: return resourceDataCreator.createNull(); + case ResourceTypeCode.String: return resourceDataCreator.create(reader.ReadString()); + case ResourceTypeCode.Boolean: return resourceDataCreator.create(reader.ReadBoolean()); + case ResourceTypeCode.Char: return resourceDataCreator.create((char)reader.ReadUInt16()); + case ResourceTypeCode.Byte: return resourceDataCreator.create(reader.ReadByte()); + case ResourceTypeCode.SByte: return resourceDataCreator.create(reader.ReadSByte()); + case ResourceTypeCode.Int16: return resourceDataCreator.create(reader.ReadInt16()); + case ResourceTypeCode.UInt16: return resourceDataCreator.create(reader.ReadUInt16()); + case ResourceTypeCode.Int32: return resourceDataCreator.create(reader.ReadInt32()); + case ResourceTypeCode.UInt32: return resourceDataCreator.create(reader.ReadUInt32()); + case ResourceTypeCode.Int64: return resourceDataCreator.create(reader.ReadInt64()); + case ResourceTypeCode.UInt64: return resourceDataCreator.create(reader.ReadUInt64()); + case ResourceTypeCode.Single: return resourceDataCreator.create(reader.ReadSingle()); + case ResourceTypeCode.Double: return resourceDataCreator.create(reader.ReadDouble()); + case ResourceTypeCode.Decimal: return resourceDataCreator.create(reader.ReadDecimal()); + case ResourceTypeCode.DateTime: return resourceDataCreator.create(new DateTime(reader.ReadInt64())); + case ResourceTypeCode.TimeSpan: return resourceDataCreator.create(new TimeSpan(reader.ReadInt64())); + case ResourceTypeCode.ByteArray: return resourceDataCreator.create(reader.ReadBytes(reader.ReadInt32())); + default: + int userTypeIndex = (int)(code - (uint)ResourceTypeCode.UserTypes); + if (userTypeIndex < 0 || userTypeIndex >= userTypes.Count) + throw new ResourceReaderException(string.Format("Invalid resource data code: {0}", code)); + return resourceDataCreator.createSerialized(reader.ReadBytes(size)); + } + } + + static uint readUInt32(BinaryReader reader) { + uint val = 0; + for (int i = 0; i < 5; i++) { + byte b = reader.ReadByte(); + val |= b; + if ((b & 0x80) == 0) + return val; + } + throw new ResourceReaderException("Invalid encoded int32"); + } + + bool checkReaders() { + bool validReader = false; + + int numReaders = reader.ReadInt32(); + if (numReaders < 0) + throw new ResourceReaderException(string.Format("Invalid number of readers: {0}", numReaders)); + int readersSize = reader.ReadInt32(); + if (readersSize < 0) + throw new ResourceReaderException(string.Format("Invalid readers size: {0:X8}", readersSize)); + + for (int i = 0; i < numReaders; i++) { + var resourceReaderFullName = reader.ReadString(); + var resourceSetFullName = reader.ReadString(); + if (Regex.IsMatch(resourceReaderFullName, @"^System\.Resources\.ResourceReader,\s*mscorlib,")) + validReader = true; + } + + return validReader; + } + } +} diff --git a/de4dot.cui/Program.cs b/de4dot.cui/Program.cs index 9a772e25..b5ff9e53 100644 --- a/de4dot.cui/Program.cs +++ b/de4dot.cui/Program.cs @@ -79,9 +79,18 @@ namespace de4dot.cui { exitCode = 1; } catch (Exception ex) { - printStackTrace(ex); - Log.e("\nTry the latest version before reporting this problem!"); - Log.e("Send bug reports to de4dot@gmail.com or go to https://github.com/0xd4d/de4dot/issues"); + if (printFullStackTrace()) { + printStackTrace(ex); + Log.e("\nTry the latest version before reporting this problem!"); + Log.e("Send bug reports to de4dot@gmail.com or go to https://github.com/0xd4d/de4dot/issues"); + } + else { + Log.e("\n\n"); + Log.e("Hmmmm... something didn't work. Try the latest version. ({0})", ex.GetType()); + Log.e("If it's a supported obfuscator, it could be a bug or a new obfuscator version."); + Log.e("If it's an unsupported obfuscator, make sure the methods are decrypted!"); + Log.e("Send bug reports to de4dot@gmail.com or go to https://github.com/0xd4d/de4dot/issues"); + } exitCode = 1; } @@ -97,11 +106,30 @@ namespace de4dot.cui { return exitCode; } + static bool printFullStackTrace() { + if (Log.isAtLeast(Log.LogLevel.verbose)) + return true; + if (hasEnv("STACKTRACE")) + return true; + + return false; + } + + static bool hasEnv(string name) { + foreach (var tmp in Environment.GetEnvironmentVariables().Keys) { + var env = tmp as string; + if (env == null) + continue; + if (string.Equals(env, name, StringComparison.OrdinalIgnoreCase)) + return true; + } + return false; + } + static bool isN00bUser() { - var env = Environment.GetEnvironmentVariables(); - if (env["VisualStudioDir"] != null) + if (hasEnv("VisualStudioDir")) return false; - return env["windir"] != null && env["PROMPT"] == null; + return hasEnv("windir") && !hasEnv("PROMPT"); } public static void printStackTrace(Exception ex, Log.LogLevel logLevel = Log.LogLevel.error) {