From 83b14da5c8ccaf187368c36ab2ef9bc5af80418d Mon Sep 17 00:00:00 2001 From: de4dot Date: Sun, 29 Apr 2012 22:22:43 +0200 Subject: [PATCH 01/46] Refactor: create common cflow deob iface --- blocks/blocks.csproj | 4 +- blocks/cflow/BlockCflowDeobfuscator.cs | 14 ++- ...oMethodInliner.cs => BlockDeobfuscator.cs} | 32 ++++++- blocks/cflow/BlocksCflowDeobfuscator.cs | 89 +++++++++++-------- blocks/cflow/CflowDeobfuscator.cs | 7 +- blocks/cflow/ConstantsFolder.cs | 88 +++++++++--------- blocks/cflow/DeadCodeRemover.cs | 24 +---- blocks/cflow/DeadStoreRemover.cs | 20 +++-- ...dCallInliner.cs => IBlocksDeobfuscator.cs} | 12 +-- blocks/cflow/MethodCallInlinerBase.cs | 17 ++-- blocks/cflow/StLdlocFixer.cs | 20 ++--- blocks/cflow/SwitchCflowDeobfuscator.cs | 56 ++++-------- de4dot.code/ObfuscatedFile.cs | 4 +- .../CliSecure/vm/VmOpCodeHandlerDetector.cs | 2 +- .../deobfuscators/DeepSea/Deobfuscator.cs | 7 +- de4dot.code/deobfuscators/DeobfuscatorBase.cs | 7 +- de4dot.code/deobfuscators/IDeobfuscator.cs | 2 +- .../deobfuscators/Spices_Net/Deobfuscator.cs | 7 +- 18 files changed, 208 insertions(+), 204 deletions(-) rename blocks/cflow/{NoMethodInliner.cs => BlockDeobfuscator.cs} (51%) rename blocks/cflow/{IMethodCallInliner.cs => IBlocksDeobfuscator.cs} (79%) diff --git a/blocks/blocks.csproj b/blocks/blocks.csproj index adc6e268..22bf7642 100644 --- a/blocks/blocks.csproj +++ b/blocks/blocks.csproj @@ -39,19 +39,19 @@ + + - - 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/NoMethodInliner.cs b/blocks/cflow/BlockDeobfuscator.cs similarity index 51% rename from blocks/cflow/NoMethodInliner.cs rename to blocks/cflow/BlockDeobfuscator.cs index 0c52667e..c5a33a09 100644 --- a/blocks/cflow/NoMethodInliner.cs +++ b/blocks/cflow/BlockDeobfuscator.cs @@ -17,13 +17,37 @@ along with de4dot. If not, see . */ +using System; +using System.Collections.Generic; + namespace de4dot.blocks.cflow { - public class NoMethodInliner : IMethodCallInliner { - public void init(Blocks blocks, Block block) { + public abstract class BlockDeobfuscator : IBlocksDeobfuscator { + protected List allBlocks; + protected Blocks blocks; + + public virtual void deobfuscateBegin(Blocks blocks) { + this.blocks = blocks; } - public bool deobfuscate() { - return false; + 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..5f5e564a 100644 --- a/blocks/cflow/BlocksCflowDeobfuscator.cs +++ b/blocks/cflow/BlocksCflowDeobfuscator.cs @@ -23,16 +23,39 @@ 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 callAlways = new List(); + List callNoChange = new List(); - public IMethodCallInliner MethodCallInliner { get; set; } + public BlocksCflowDeobfuscator() { + init(); + } + + public BlocksCflowDeobfuscator(IEnumerable blocksDeobfuscator) { + init(); + add(blocksDeobfuscator); + } + + void init() { + callAlways.Add(new BlockCflowDeobfuscator()); + callAlways.Add(new SwitchCflowDeobfuscator()); + callAlways.Add(new DeadStoreRemover()); + callAlways.Add(new DeadCodeRemover()); + callNoChange.Add(new ConstantsFolder()); + callNoChange.Add(new StLdlocFixer()); + } + + 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 +64,11 @@ namespace de4dot.blocks.cflow { public void deobfuscate() { bool changed; int iterations = -1; + + deobfuscateBegin(userBlocksDeobfuscators); + deobfuscateBegin(callAlways); + deobfuscateBegin(callNoChange); + do { iterations++; changed = false; @@ -52,40 +80,29 @@ namespace de4dot.blocks.cflow { if (iterations == 0) changed |= fixDotfuscatorLoop(); - foreach (var block in allBlocks) { - MethodCallInliner.init(blocks, block); - changed |= MethodCallInliner.deobfuscate(); - } + changed |= deobfuscate(userBlocksDeobfuscators, allBlocks); + changed |= deobfuscate(callAlways, allBlocks); - 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(); + foreach (var bd in callNoChange) { + if (changed) + break; + changed |= bd.deobfuscate(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) + changed |= bd.deobfuscate(allBlocks); + return changed; + } + // Hack for old Dotfuscator bool fixDotfuscatorLoop() { /* 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..806655dd 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,16 @@ namespace de4dot.blocks.cflow { Write = 2, } - public bool remove() { + 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 79% rename from blocks/cflow/IMethodCallInliner.cs rename to blocks/cflow/IBlocksDeobfuscator.cs index 49814968..2e5cb49a 100644 --- a/blocks/cflow/IMethodCallInliner.cs +++ b/blocks/cflow/IBlocksDeobfuscator.cs @@ -17,11 +17,13 @@ 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 { + void deobfuscateBegin(Blocks blocks); + + // Returns true if something was updated + bool deobfuscate(List allBlocks); } } diff --git a/blocks/cflow/MethodCallInlinerBase.cs b/blocks/cflow/MethodCallInlinerBase.cs index 6cb49e3f..3744c1da 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,17 +30,21 @@ namespace de4dot.blocks.cflow { protected Block block; int iteration; - public void init(Blocks blocks, Block block) { + 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(); 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/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index fe21f169..f00ecdea 100644 --- a/de4dot.code/ObfuscatedFile.cs +++ b/de4dot.code/ObfuscatedFile.cs @@ -541,7 +541,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 +715,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/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/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 97ed2461..e7b11a2b 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -105,11 +105,12 @@ namespace de4dot.code.deobfuscators.DeepSea { get { return startedDeobfuscating ? options.InlineMethods : true; } } - public override IMethodCallInliner MethodCallInliner { + public override IEnumerable BlocksDeobfuscators { get { + var list = new List(); if (CanInlineMethods) - return new MethodCallInliner(); - return new NoMethodInliner(); + list.Add(new MethodCallInliner()); + return list; } } diff --git a/de4dot.code/deobfuscators/DeobfuscatorBase.cs b/de4dot.code/deobfuscators/DeobfuscatorBase.cs index 5ad2bb25..1b4faf5c 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; } } diff --git a/de4dot.code/deobfuscators/IDeobfuscator.cs b/de4dot.code/deobfuscators/IDeobfuscator.cs index 848f4913..93a05537 100644 --- a/de4dot.code/deobfuscators/IDeobfuscator.cs +++ b/de4dot.code/deobfuscators/IDeobfuscator.cs @@ -61,7 +61,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; } } From 6b18d70e7710c41620a60b48f847cd86174831c4 Mon Sep 17 00:00:00 2001 From: de4dot Date: Mon, 30 Apr 2012 01:26:10 +0200 Subject: [PATCH 02/46] Move common code to another class --- de4dot.code/de4dot.code.csproj | 3 +- .../{Eazfuscator_NET => }/ConstantsReader.cs | 70 ++++++++++++------- .../Eazfuscator_NET/DecrypterType.cs | 2 +- .../Eazfuscator_NET/EfConstantsReader.cs | 54 ++++++++++++++ .../Eazfuscator_NET/StringDecrypter.cs | 8 +-- 5 files changed, 105 insertions(+), 32 deletions(-) rename de4dot.code/deobfuscators/{Eazfuscator_NET => }/ConstantsReader.cs (78%) create mode 100644 de4dot.code/deobfuscators/Eazfuscator_NET/EfConstantsReader.cs diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index 301df29c..68fe955d 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -107,6 +107,7 @@ + @@ -152,7 +153,7 @@ - + diff --git a/de4dot.code/deobfuscators/Eazfuscator_NET/ConstantsReader.cs b/de4dot.code/deobfuscators/ConstantsReader.cs similarity index 78% rename from de4dot.code/deobfuscators/Eazfuscator_NET/ConstantsReader.cs rename to de4dot.code/deobfuscators/ConstantsReader.cs index 7bffdad8..a3b3d1e7 100644 --- a/de4dot.code/deobfuscators/Eazfuscator_NET/ConstantsReader.cs +++ b/de4dot.code/deobfuscators/ConstantsReader.cs @@ -23,39 +23,57 @@ using Mono.Cecil.Cil; using Mono.Cecil.Metadata; using de4dot.blocks; -namespace de4dot.code.deobfuscators.Eazfuscator_NET { +namespace de4dot.code.deobfuscators { class ConstantsReader { - IList instructions; - IList locals; - Dictionary localsValues = new Dictionary(); + protected IInstructions instructions; + protected IList locals; + protected Dictionary localsValues = new Dictionary(); - public ConstantsReader(MethodDefinition method) { - instructions = method.Body.Instructions; - locals = method.Body.Variables; - initialize(); + public interface IInstructions { + int Count { get; } + Instruction this[int index] { get; } } - void initialize() { - findConstants(); - } + class ListInstructions : IInstructions { + IList instrs; - 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++; + public int Count { + get { return instrs.Count; } } - if (localsValues.Count != 2) - localsValues.Clear(); + 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(MethodDefinition method) { + this.locals = method.Body.Variables; + this.instructions = new ListInstructions(method.Body.Instructions); + } + + public ConstantsReader(IList locals, IList instrs) { + this.locals = locals; + this.instructions = new ListInstrs(instrs); } public bool getNextInt32(ref int index, out int val) { 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)) From e29a8ea6928db1569834eb5ffe239a45abc48e68 Mon Sep 17 00:00:00 2001 From: de4dot Date: Mon, 30 Apr 2012 01:29:05 +0200 Subject: [PATCH 03/46] Update cflow deobfuscator --- de4dot.code/de4dot.code.csproj | 1 + .../DeepSea/ArrayBlockDeobfuscator.cs | 236 ++++++++++++++++++ .../deobfuscators/DeepSea/Deobfuscator.cs | 8 +- 3 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index 68fe955d..bdb4687c 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -116,6 +116,7 @@ + diff --git a/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs new file mode 100644 index 00000000..51729cc5 --- /dev/null +++ b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs @@ -0,0 +1,236 @@ +/* + 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(); + ConstantsReader constantsReader; + + class FieldInfo { + public FieldDefinition field; + public byte[] array; + + public FieldInfo(FieldDefinition field, byte[] array) { + this.field = field; + this.array = array; + } + } + + 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, (byte[])arrayInitField.InitialValue.Clone())); + } + } + + 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 >= 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; + + var constants = getConstantsReader(block); + int value; + i++; + 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; + } + + ConstantsReader getConstantsReader(Block block) { + if (constantsReader != null) + return constantsReader; + return constantsReader = new ConstantsReader(blocks.Locals, block.Instructions); + } + } +} diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index e7b11a2b..85fc66c2 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -80,6 +80,7 @@ namespace de4dot.code.deobfuscators.DeepSea { ResourceResolver resourceResolver; AssemblyResolver assemblyResolver; FieldsRestorer fieldsRestorer; + ArrayBlockDeobfuscator arrayBlockDeobfuscator; internal class Options : OptionsBase { public bool InlineMethods { get; set; } @@ -108,8 +109,11 @@ namespace de4dot.code.deobfuscators.DeepSea { public override IEnumerable BlocksDeobfuscators { get { var list = new List(); - if (CanInlineMethods) + if (CanInlineMethods) { list.Add(new MethodCallInliner()); + if (arrayBlockDeobfuscator.Detected) + list.Add(arrayBlockDeobfuscator); + } return list; } } @@ -132,6 +136,8 @@ namespace de4dot.code.deobfuscators.DeepSea { } protected override void scanForObfuscator() { + arrayBlockDeobfuscator = new ArrayBlockDeobfuscator(module); + arrayBlockDeobfuscator.init(); stringDecrypter = new StringDecrypter(module); stringDecrypter.find(DeobfuscatedFile); resourceResolver = new ResourceResolver(module, DeobfuscatedFile, this); From a1daee56f8d66c55112d279bb3cfa57f3629afda Mon Sep 17 00:00:00 2001 From: de4dot Date: Mon, 30 Apr 2012 08:31:09 +0200 Subject: [PATCH 04/46] Support more types of args --- de4dot.code/MethodReturnValueInliner.cs | 33 ++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/de4dot.code/MethodReturnValueInliner.cs b/de4dot.code/MethodReturnValueInliner.cs index 52695e94..9516915a 100644 --- a/de4dot.code/MethodReturnValueInliner.cs +++ b/de4dot.code/MethodReturnValueInliner.cs @@ -200,13 +200,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 +323,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 (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; } From f307520e62700c221976b459e294008f4deebb45 Mon Sep 17 00:00:00 2001 From: de4dot Date: Mon, 30 Apr 2012 08:33:01 +0200 Subject: [PATCH 05/46] Decrypt DS 4.1 strings --- .../deobfuscators/DeepSea/Deobfuscator.cs | 6 +- .../deobfuscators/DeepSea/StringDecrypter.cs | 290 +++++++++++++++--- 2 files changed, 255 insertions(+), 41 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 85fc66c2..6fff5237 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -153,8 +153,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; diff --git a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs index ad0993a7..97d05a6c 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 { @@ -94,9 +96,204 @@ 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; + ushort[] key; + + 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); + + key = findKey(); + if (key == null || key.Length == 0) + return false; + + return true; + } + + 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; + } + + ushort[] 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); + } + + ushort[] findKey(MethodDefinition initMethod) { + throw new NotImplementedException(); //TODO: + } + + static ushort[] getPublicKeyTokenKey(byte[] publicKeyToken) { + var key = new ushort[publicKeyToken.Length]; + for (int i = 0; i < publicKeyToken.Length; i++) { + int b = publicKeyToken[i]; + key[i] = (ushort)((b << 7) ^ b); + } + return key; + } + + public string decrypt(object[] args) { + return decrypt((int)args[arg1], (int)args[arg2]); + } + + string decrypt(int magic2, int magic3) { + int offset = magic ^ magic2 ^ magic3; + var keyChar = encryptedData[offset + 2]; + int cachedIndex = encryptedData[offset + 1] ^ keyChar; + var sb = new StringBuilder(); + 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; + 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 +302,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 +345,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 +450,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 +636,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 +671,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 +695,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; From 1805e352c468c33d34610939fe04976f4abc7e22 Mon Sep 17 00:00:00 2001 From: de4dot Date: Mon, 30 Apr 2012 12:18:47 +0200 Subject: [PATCH 06/46] Disable using unknown args by default --- de4dot.code/MethodReturnValueInliner.cs | 8 +++++++- de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/de4dot.code/MethodReturnValueInliner.cs b/de4dot.code/MethodReturnValueInliner.cs index 9516915a..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; @@ -331,7 +337,7 @@ namespace de4dot.code { default: int pushes, pops; DotNetUtils.calculateStackUsage(instr.Instruction, false, out pushes, out pops); - if (pushes != 1) { + if (!useUnknownArgs || pushes != 1) { Log.w("Could not find all arguments to method {0} ({1:X8}), instr: {2}", Utils.removeNewlines(method), method.MetadataToken.ToInt32(), diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 6fff5237..280bcfc0 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -136,6 +136,7 @@ namespace de4dot.code.deobfuscators.DeepSea { } protected override void scanForObfuscator() { + staticStringInliner.UseUnknownArgs = true; arrayBlockDeobfuscator = new ArrayBlockDeobfuscator(module); arrayBlockDeobfuscator.init(); stringDecrypter = new StringDecrypter(module); From 2594317b18bcbb32ec499122ac40ed03d9f8bc57 Mon Sep 17 00:00:00 2001 From: de4dot Date: Mon, 30 Apr 2012 12:49:43 +0200 Subject: [PATCH 07/46] Use other sb ctor --- de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs index 97d05a6c..b5768058 100644 --- a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs +++ b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs @@ -272,7 +272,6 @@ namespace de4dot.code.deobfuscators.DeepSea { int offset = magic ^ magic2 ^ magic3; var keyChar = encryptedData[offset + 2]; int cachedIndex = encryptedData[offset + 1] ^ keyChar; - var sb = new StringBuilder(); int flags = encryptedData[offset] ^ keyChar; int numChars = (flags >> 1) & ~7 | (flags & 7); if ((flags & 8) != 0) { @@ -281,6 +280,7 @@ namespace de4dot.code.deobfuscators.DeepSea { 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(); From c1abe2965bd349f5ddf633605a54b2895489f842 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 10:43:33 +0200 Subject: [PATCH 08/46] Add isLdarg() --- blocks/DotNetUtils.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/blocks/DotNetUtils.cs b/blocks/DotNetUtils.cs index 2a322266..94a5670f 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) { From 61bb3abdee9e78fcde8fc579dd1f6dcbd37b40ad Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 10:44:34 +0200 Subject: [PATCH 09/46] Add method to return stack size and not truncate locals/args --- blocks/cflow/InstructionEmulator.cs | 14 +++++++++++++- blocks/cflow/ValueStack.cs | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) 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/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(); } From 6f807f2484fd74329f004049afa2e639834836a1 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 10:45:18 +0200 Subject: [PATCH 10/46] Refactor --- blocks/cflow/MethodCallInlinerBase.cs | 88 +++++++++++++++++---------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/blocks/cflow/MethodCallInlinerBase.cs b/blocks/cflow/MethodCallInlinerBase.cs index 3744c1da..0df68b0d 100644 --- a/blocks/cflow/MethodCallInlinerBase.cs +++ b/blocks/cflow/MethodCallInlinerBase.cs @@ -49,9 +49,25 @@ namespace de4dot.blocks.cflow { 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); @@ -63,6 +79,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; @@ -88,75 +119,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) { From db14e73369016d6ef93b5c32f35298c97a4305e7 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 10:47:21 +0200 Subject: [PATCH 11/46] Make sure index is correct, and add method to read arg constants --- de4dot.code/deobfuscators/ConstantsReader.cs | 128 +++++++++++++++---- 1 file changed, 103 insertions(+), 25 deletions(-) diff --git a/de4dot.code/deobfuscators/ConstantsReader.cs b/de4dot.code/deobfuscators/ConstantsReader.cs index a3b3d1e7..2a0fcd62 100644 --- a/de4dot.code/deobfuscators/ConstantsReader.cs +++ b/de4dot.code/deobfuscators/ConstantsReader.cs @@ -66,16 +66,24 @@ namespace de4dot.code.deobfuscators { } } - public ConstantsReader(MethodDefinition method) { - this.locals = method.Body.Variables; - this.instructions = new ListInstructions(method.Body.Instructions); + public ConstantsReader(IList instrs) { + this.instructions = new ListInstructions(instrs); } - public ConstantsReader(IList locals, IList instrs) { - this.locals = locals; + 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]; @@ -92,10 +100,15 @@ namespace de4dot.code.deobfuscators { public bool isLoadConstant(Instruction instr) { if (DotNetUtils.isLdcI4(instr)) return true; - if (!DotNetUtils.isLdloc(instr)) - return false; - int tmp; - return getLocalConstant(instr, out tmp); + 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) { @@ -109,51 +122,62 @@ namespace de4dot.code.deobfuscators { 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(); + 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((sbyte)stack.Pop()); + stack.Push(new ConstantInfo(index, (sbyte)stack.Pop().constant)); break; case Code.Conv_U1: if (stack.Count < 1) goto done; - stack.Push((byte)stack.Pop()); + stack.Push(new ConstantInfo(index, (byte)stack.Pop().constant)); break; case Code.Conv_I2: if (stack.Count < 1) goto done; - stack.Push((short)stack.Pop()); + stack.Push(new ConstantInfo(index, (short)stack.Pop().constant)); break; case Code.Conv_U2: if (stack.Count < 1) goto done; - stack.Push((ushort)stack.Pop()); + 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(~stack.Pop()); + stack.Push(new ConstantInfo(index, ~stack.Pop().constant)); break; case Code.Neg: - stack.Push(-stack.Pop()); + stack.Push(new ConstantInfo(index, -stack.Pop().constant)); break; case Code.Ldloc: @@ -164,7 +188,18 @@ namespace de4dot.code.deobfuscators { case Code.Ldloc_3: if (!getLocalConstant(instr, out op1)) goto done; - stack.Push(op1); + 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: @@ -179,37 +214,71 @@ namespace de4dot.code.deobfuscators { case Code.Ldc_I4_7: case Code.Ldc_I4_8: case Code.Ldc_I4_M1: - stack.Push(DotNetUtils.getLdcI4Value(instr)); + stack.Push(new ConstantInfo(index, DotNetUtils.getLdcI4Value(instr))); break; case Code.Add: if (stack.Count < 2) goto done; - stack.Push(stack.Pop() + stack.Pop()); + 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; - stack.Push(-(stack.Pop() - stack.Pop())); + 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; - stack.Push(stack.Pop() ^ stack.Pop()); + 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; - stack.Push(stack.Pop() | stack.Pop()); + 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; - stack.Push(stack.Pop() & stack.Pop()); + 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: @@ -221,12 +290,16 @@ done: return false; while (stack.Count > 1) stack.Pop(); - val = stack.Pop(); + info1 = stack.Pop(); + index = info1.index + 1; + val = info1.constant; return true; } - bool getLocalConstant(Instruction instr, out int value) { + 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; @@ -234,5 +307,10 @@ done: return false; return localsValues.TryGetValue(local, out value); } + + protected virtual bool getArgConstant(Instruction instr, out int value) { + value = 0; + return false; + } } } From e8049c6a057c4dffd493ce7ad608da57051a3897 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 10:48:44 +0200 Subject: [PATCH 12/46] Inline some obfuscated methods --- de4dot.code/de4dot.code.csproj | 3 +- .../DeepSea/ArrayBlockDeobfuscator.cs | 13 +- .../deobfuscators/DeepSea/Deobfuscator.cs | 4 +- .../DeepSea/DsConstantsReader.cs | 40 ++ .../DeepSea/DsInlinedMethodsFinder.cs | 2 +- .../DeepSea/DsMethodCallInliner.cs | 366 ++++++++++++++++++ .../DeepSea/MethodCallInliner.cs | 208 ---------- 7 files changed, 419 insertions(+), 217 deletions(-) create mode 100644 de4dot.code/deobfuscators/DeepSea/DsConstantsReader.cs create mode 100644 de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs delete mode 100644 de4dot.code/deobfuscators/DeepSea/MethodCallInliner.cs diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index bdb4687c..9fa20405 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -117,6 +117,7 @@ + @@ -127,7 +128,7 @@ - + diff --git a/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs index 51729cc5..790929ba 100644 --- a/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs @@ -29,7 +29,7 @@ namespace de4dot.code.deobfuscators.DeepSea { ModuleDefinition module; FieldDefinitionAndDeclaringTypeDict fieldToInfo = new FieldDefinitionAndDeclaringTypeDict(); Dictionary localToInfo = new Dictionary(); - ConstantsReader constantsReader; + DsConstantsReader constantsReader; class FieldInfo { public FieldDefinition field; @@ -200,7 +200,7 @@ namespace de4dot.code.deobfuscators.DeepSea { bool deobfuscate3(Block block, int i) { var instrs = block.Instructions; - if (i >= instrs.Count) + if (i + 1 >= instrs.Count) return false; int start = i; @@ -211,9 +211,12 @@ namespace de4dot.code.deobfuscators.DeepSea { if (info == null) return false; + if (!instrs[i + 1].isLdcI4()) + return false; + var constants = getConstantsReader(block); int value; - i++; + i += 2; if (!constants.getInt32(ref i, out value)) return false; @@ -227,10 +230,10 @@ namespace de4dot.code.deobfuscators.DeepSea { return true; } - ConstantsReader getConstantsReader(Block block) { + DsConstantsReader getConstantsReader(Block block) { if (constantsReader != null) return constantsReader; - return constantsReader = new ConstantsReader(blocks.Locals, block.Instructions); + return constantsReader = new DsConstantsReader(block.Instructions); } } } diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 280bcfc0..719dae75 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -110,7 +110,7 @@ namespace de4dot.code.deobfuscators.DeepSea { get { var list = new List(); if (CanInlineMethods) { - list.Add(new MethodCallInliner()); + list.Add(new DsMethodCallInliner()); if (arrayBlockDeobfuscator.Detected) list.Add(arrayBlockDeobfuscator); } @@ -175,7 +175,7 @@ namespace de4dot.code.deobfuscators.DeepSea { continue; if (checkedMethods++ >= 1000) goto done; - if (!DeepSea.MethodCallInliner.canInline(method)) + if (!DeepSea.DsMethodCallInliner.canInline(method)) continue; foundProxies++; } diff --git a/de4dot.code/deobfuscators/DeepSea/DsConstantsReader.cs b/de4dot.code/deobfuscators/DeepSea/DsConstantsReader.cs new file mode 100644 index 00000000..b4cb27f5 --- /dev/null +++ b/de4dot.code/deobfuscators/DeepSea/DsConstantsReader.cs @@ -0,0 +1,40 @@ +/* + 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.Cil; +using de4dot.blocks; + +namespace de4dot.code.deobfuscators.DeepSea { + class DsConstantsReader : ConstantsReader { + public DsConstantsReader(List instrs) + : base(instrs) { + } + + 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..b0b28600 100644 --- a/de4dot.code/deobfuscators/DeepSea/DsInlinedMethodsFinder.cs +++ b/de4dot.code/deobfuscators/DeepSea/DsInlinedMethodsFinder.cs @@ -27,7 +27,7 @@ namespace de4dot.code.deobfuscators.DeepSea { foreach (var type in module.GetTypes()) { foreach (var method in type.Methods) { - if (MethodCallInliner.canInline(method)) + if (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..7aed7ebb --- /dev/null +++ b/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs @@ -0,0 +1,366 @@ +/* + 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; + + 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; + + 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)) + 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 (hasSecuritySafeCriticalAttribute(method)) { + if (!isIntType(param1.ParameterType.EType)) + return false; + if (!isIntType(param2.ParameterType.EType)) + return false; + } + else { + if (param1.ParameterType.EType != ElementType.I4) + return false; + if (param2.ParameterType.EType != ElementType.I4) + return false; + if (!checkInstrs(method)) + return false; + } + + return true; + } + + static bool isIntType(ElementType etype) { + return etype == ElementType.Char || etype == ElementType.I2 || etype == ElementType.I4; + } + + static bool hasSecuritySafeCriticalAttribute(MethodDefinition method) { + return hasAttribute(method, "System.Security.SecuritySafeCriticalAttribute"); + } + + static bool hasAttribute(MethodDefinition method, string attrName) { + foreach (var attr in method.CustomAttributes) { + if (attr.Constructor.DeclaringType.FullName == attrName) + return true; + } + return false; + } + + 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; + } + + 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/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; - } - } -} From b404620683e6779aa0f137a7e50fbff205851166 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 13:48:44 +0200 Subject: [PATCH 13/46] Add MethodDefinition clone() method --- blocks/DotNetUtils.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/blocks/DotNetUtils.cs b/blocks/DotNetUtils.cs index 94a5670f..97355e48 100644 --- a/blocks/DotNetUtils.cs +++ b/blocks/DotNetUtils.cs @@ -592,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 }); + DotNetUtils.copyBodyFromTo(method, newMethod); + return newMethod; + } + public static Instruction clone(Instruction instr) { return new Instruction { Offset = instr.Offset, From 83e8fac8eafb540405a04890ee34567ce83ae262 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 13:49:12 +0200 Subject: [PATCH 14/46] Add CachedCflowDeobfuscator class --- blocks/blocks.csproj | 1 + blocks/cflow/CachedCflowDeobfuscator.cs | 74 +++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 blocks/cflow/CachedCflowDeobfuscator.cs diff --git a/blocks/blocks.csproj b/blocks/blocks.csproj index 22bf7642..1e3daa86 100644 --- a/blocks/blocks.csproj +++ b/blocks/blocks.csproj @@ -40,6 +40,7 @@ + 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(); + } + } +} From 597fcb021082c7d0ee5ec801499532d8c161588d Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 13:51:07 +0200 Subject: [PATCH 15/46] Cflow deob methods --- de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs | 3 ++- .../deobfuscators/DeepSea/DsMethodCallInliner.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 719dae75..dcd99f77 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -110,9 +110,10 @@ namespace de4dot.code.deobfuscators.DeepSea { get { var list = new List(); if (CanInlineMethods) { - list.Add(new DsMethodCallInliner()); if (arrayBlockDeobfuscator.Detected) list.Add(arrayBlockDeobfuscator); + + list.Add(new DsMethodCallInliner(new CachedCflowDeobfuscator(list))); } return list; } diff --git a/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs b/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs index 7aed7ebb..4e626b92 100644 --- a/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs +++ b/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs @@ -31,6 +31,11 @@ namespace de4dot.code.deobfuscators.DeepSea { ParameterDefinition arg1, arg2; Value returnValue; MethodDefinition methodToInline; + CachedCflowDeobfuscator cflowDeobfuscator; + + public DsMethodCallInliner(CachedCflowDeobfuscator cflowDeobfuscator) { + this.cflowDeobfuscator = cflowDeobfuscator; + } protected override bool deobfuscateInternal() { bool changed = false; @@ -66,6 +71,8 @@ namespace de4dot.code.deobfuscators.DeepSea { } bool inlineMethod(MethodDefinition methodToInline, int instrIndex, int const1, int const2) { + if (hasSecuritySafeCriticalAttribute(methodToInline)) + methodToInline = cflowDeobfuscator.deobfuscate(methodToInline); this.methodToInline = methodToInline; parameters = DotNetUtils.getParameters(methodToInline); @@ -224,7 +231,8 @@ done: return false; if (!foundOpCodes.ContainsKey(Code.Rem)) return false; - if (!foundOpCodes.ContainsKey(Code.Brtrue) && !foundOpCodes.ContainsKey(Code.Brtrue_S)) + if (!foundOpCodes.ContainsKey(Code.Brtrue) && !foundOpCodes.ContainsKey(Code.Brtrue_S) && + !foundOpCodes.ContainsKey(Code.Brfalse) && !foundOpCodes.ContainsKey(Code.Brfalse_S)) return false; } return true; From c61161be1d0c47f1e340d5af463bfa79d9cb2174 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 2 May 2012 18:43:04 +0200 Subject: [PATCH 16/46] Ignore method attributes --- .../DeepSea/DsMethodCallInliner.cs | 51 ++----------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs b/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs index 4e626b92..8d3deefe 100644 --- a/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs +++ b/de4dot.code/deobfuscators/DeepSea/DsMethodCallInliner.cs @@ -71,9 +71,7 @@ namespace de4dot.code.deobfuscators.DeepSea { } bool inlineMethod(MethodDefinition methodToInline, int instrIndex, int const1, int const2) { - if (hasSecuritySafeCriticalAttribute(methodToInline)) - methodToInline = cflowDeobfuscator.deobfuscate(methodToInline); - this.methodToInline = methodToInline; + this.methodToInline = methodToInline = cflowDeobfuscator.deobfuscate(methodToInline); parameters = DotNetUtils.getParameters(methodToInline); arg1 = parameters[parameters.Count - 2]; @@ -312,20 +310,10 @@ done: return false; var param1 = parameters[paramCount - 1]; var param2 = parameters[paramCount - 2]; - if (hasSecuritySafeCriticalAttribute(method)) { - if (!isIntType(param1.ParameterType.EType)) - return false; - if (!isIntType(param2.ParameterType.EType)) - return false; - } - else { - if (param1.ParameterType.EType != ElementType.I4) - return false; - if (param2.ParameterType.EType != ElementType.I4) - return false; - if (!checkInstrs(method)) - return false; - } + if (!isIntType(param1.ParameterType.EType)) + return false; + if (!isIntType(param2.ParameterType.EType)) + return false; return true; } @@ -334,35 +322,6 @@ done: return etype == ElementType.Char || etype == ElementType.I2 || etype == ElementType.I4; } - static bool hasSecuritySafeCriticalAttribute(MethodDefinition method) { - return hasAttribute(method, "System.Security.SecuritySafeCriticalAttribute"); - } - - static bool hasAttribute(MethodDefinition method, string attrName) { - foreach (var attr in method.CustomAttributes) { - if (attr.Constructor.DeclaringType.FullName == attrName) - return true; - } - return false; - } - - 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; - } - protected override bool isReturn(MethodDefinition methodToInline, int instrIndex) { int oldIndex = instrIndex; if (base.isReturn(methodToInline, oldIndex)) From e221fc7f523ef32796566677fcd7592a36d02294 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 05:34:12 +0200 Subject: [PATCH 17/46] Add property to tell whether to execute it or not --- blocks/cflow/BlockDeobfuscator.cs | 2 ++ blocks/cflow/BlocksCflowDeobfuscator.cs | 44 +++++++++++++++---------- blocks/cflow/DeadStoreRemover.cs | 2 ++ blocks/cflow/IBlocksDeobfuscator.cs | 2 ++ blocks/cflow/MethodCallInlinerBase.cs | 2 ++ 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/blocks/cflow/BlockDeobfuscator.cs b/blocks/cflow/BlockDeobfuscator.cs index c5a33a09..d02aee81 100644 --- a/blocks/cflow/BlockDeobfuscator.cs +++ b/blocks/cflow/BlockDeobfuscator.cs @@ -25,6 +25,8 @@ namespace de4dot.blocks.cflow { protected List allBlocks; protected Blocks blocks; + public bool ExecuteOnNoChange { get; set; } + public virtual void deobfuscateBegin(Blocks blocks) { this.blocks = blocks; } diff --git a/blocks/cflow/BlocksCflowDeobfuscator.cs b/blocks/cflow/BlocksCflowDeobfuscator.cs index 5f5e564a..0c9a2236 100644 --- a/blocks/cflow/BlocksCflowDeobfuscator.cs +++ b/blocks/cflow/BlocksCflowDeobfuscator.cs @@ -26,8 +26,7 @@ namespace de4dot.blocks.cflow { Blocks blocks; List allBlocks = new List(); List userBlocksDeobfuscators = new List(); - List callAlways = new List(); - List callNoChange = new List(); + List ourBlocksDeobfuscators = new List(); public BlocksCflowDeobfuscator() { init(); @@ -39,12 +38,12 @@ namespace de4dot.blocks.cflow { } void init() { - callAlways.Add(new BlockCflowDeobfuscator()); - callAlways.Add(new SwitchCflowDeobfuscator()); - callAlways.Add(new DeadStoreRemover()); - callAlways.Add(new DeadCodeRemover()); - callNoChange.Add(new ConstantsFolder()); - callNoChange.Add(new StLdlocFixer()); + 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) { @@ -66,8 +65,7 @@ namespace de4dot.blocks.cflow { int iterations = -1; deobfuscateBegin(userBlocksDeobfuscators); - deobfuscateBegin(callAlways); - deobfuscateBegin(callNoChange); + deobfuscateBegin(ourBlocksDeobfuscators); do { iterations++; @@ -81,13 +79,9 @@ namespace de4dot.blocks.cflow { changed |= fixDotfuscatorLoop(); changed |= deobfuscate(userBlocksDeobfuscators, allBlocks); - changed |= deobfuscate(callAlways, allBlocks); - - foreach (var bd in callNoChange) { - if (changed) - break; - changed |= bd.deobfuscate(allBlocks); - } + changed |= deobfuscate(ourBlocksDeobfuscators, allBlocks); + changed |= deobfuscateNoChange(changed, userBlocksDeobfuscators, allBlocks); + changed |= deobfuscateNoChange(changed, ourBlocksDeobfuscators, allBlocks); } while (changed); } @@ -98,8 +92,22 @@ namespace de4dot.blocks.cflow { bool deobfuscate(IEnumerable bds, List allBlocks) { bool changed = false; - foreach (var bd in bds) + 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; } diff --git a/blocks/cflow/DeadStoreRemover.cs b/blocks/cflow/DeadStoreRemover.cs index 806655dd..3d7f870c 100644 --- a/blocks/cflow/DeadStoreRemover.cs +++ b/blocks/cflow/DeadStoreRemover.cs @@ -39,6 +39,8 @@ namespace de4dot.blocks.cflow { Write = 2, } + public bool ExecuteOnNoChange { get; set; } + public void deobfuscateBegin(Blocks blocks) { this.blocks = blocks; } diff --git a/blocks/cflow/IBlocksDeobfuscator.cs b/blocks/cflow/IBlocksDeobfuscator.cs index 2e5cb49a..653083e8 100644 --- a/blocks/cflow/IBlocksDeobfuscator.cs +++ b/blocks/cflow/IBlocksDeobfuscator.cs @@ -21,6 +21,8 @@ using System.Collections.Generic; namespace de4dot.blocks.cflow { public interface IBlocksDeobfuscator { + bool ExecuteOnNoChange { get; } + void deobfuscateBegin(Blocks blocks); // Returns true if something was updated diff --git a/blocks/cflow/MethodCallInlinerBase.cs b/blocks/cflow/MethodCallInlinerBase.cs index 0df68b0d..17f5de53 100644 --- a/blocks/cflow/MethodCallInlinerBase.cs +++ b/blocks/cflow/MethodCallInlinerBase.cs @@ -30,6 +30,8 @@ namespace de4dot.blocks.cflow { protected Block block; int iteration; + public bool ExecuteOnNoChange { get; set; } + public void deobfuscateBegin(Blocks blocks) { this.blocks = blocks; iteration = 0; From 05ec2794eb101b903041f12aa6d78e6cb52fda01 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 08:00:50 +0200 Subject: [PATCH 18/46] Add isLadrg() --- blocks/Instr.cs | 4 ++++ 1 file changed, 4 insertions(+) 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); } From fb9e217dac47feb8d30ea173a6943c781aa4fc26 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 08:01:35 +0200 Subject: [PATCH 19/46] Add a cast deobfuscator --- de4dot.code/de4dot.code.csproj | 1 + .../deobfuscators/DeepSea/CastDeobfuscator.cs | 213 ++++++++++++++++++ .../deobfuscators/DeepSea/Deobfuscator.cs | 20 +- 3 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 de4dot.code/deobfuscators/DeepSea/CastDeobfuscator.cs diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index 9fa20405..7ce1fef3 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -117,6 +117,7 @@ + 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 dcd99f77..fc3ba29a 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -108,17 +108,23 @@ namespace de4dot.code.deobfuscators.DeepSea { public override IEnumerable BlocksDeobfuscators { get { - var list = new List(); - if (CanInlineMethods) { - if (arrayBlockDeobfuscator.Detected) - list.Add(arrayBlockDeobfuscator); - - list.Add(new DsMethodCallInliner(new CachedCflowDeobfuscator(list))); - } + var list = new List(getBlocksDeobfuscators()); + if (CanInlineMethods) + list.Add(new DsMethodCallInliner(new CachedCflowDeobfuscator(getBlocksDeobfuscators()))); return list; } } + List getBlocksDeobfuscators() { + var list = new List(); + if (CanInlineMethods) { + if (arrayBlockDeobfuscator.Detected) + list.Add(arrayBlockDeobfuscator); + list.Add(new CastDeobfuscator()); + } + return list; + } + public Deobfuscator(Options options) : base(options) { this.options = options; From 870dab5b900822944e15dc1adb3d5d2b38490bcc Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 09:05:05 +0200 Subject: [PATCH 20/46] Fix renaming events/properties --- de4dot.code/renamer/Renamer.cs | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/de4dot.code/renamer/Renamer.cs b/de4dot.code/renamer/Renamer.cs index 2b29baa8..11775a65 100644 --- a/de4dot.code/renamer/Renamer.cs +++ b/de4dot.code/renamer/Renamer.cs @@ -396,8 +396,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) { From 83dc4226c1198803e1a7020eee3dc36b4c3890a9 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 09:51:26 +0200 Subject: [PATCH 21/46] Make sure string decrypter methods aren't detected as inlined methods --- de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs | 2 +- .../deobfuscators/DeepSea/DsInlinedMethodsFinder.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index fc3ba29a..f1741aa9 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -268,7 +268,7 @@ done: 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/de4dot.code/deobfuscators/DeepSea/DsInlinedMethodsFinder.cs b/de4dot.code/deobfuscators/DeepSea/DsInlinedMethodsFinder.cs index b0b28600..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 (DsMethodCallInliner.canInline(method)) + if (!notInlinedMethodsDict.ContainsKey(method) && DsMethodCallInliner.canInline(method)) inlinedMethods.Add(method); } } From 2761216e39868ba680352b5855ab3eecb599002f Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 14:34:58 +0200 Subject: [PATCH 22/46] Add a resource reader --- de4dot.code/de4dot.code.csproj | 1 + de4dot.code/resources/ResourceReader.cs | 187 ++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 de4dot.code/resources/ResourceReader.cs diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index 7ce1fef3..a5454dd8 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -266,6 +266,7 @@ + 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; + } + } +} From 83725200c129f45f7a5c3d986254afa445a074ca Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 14:53:01 +0200 Subject: [PATCH 23/46] Add isValidResourceKeyName() --- de4dot.code/deobfuscators/DeobfuscatorBase.cs | 4 ++++ de4dot.code/deobfuscators/dotNET_Reactor/v3/Deobfuscator.cs | 4 ++++ de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs | 4 ++++ de4dot.code/renamer/INameChecker.cs | 1 + 4 files changed, 13 insertions(+) diff --git a/de4dot.code/deobfuscators/DeobfuscatorBase.cs b/de4dot.code/deobfuscators/DeobfuscatorBase.cs index 1b4faf5c..3c209ded 100644 --- a/de4dot.code/deobfuscators/DeobfuscatorBase.cs +++ b/de4dot.code/deobfuscators/DeobfuscatorBase.cs @@ -745,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/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); } } From 76a10b1f343d553502b479f2369078bbba0aa95b Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 16:40:17 +0200 Subject: [PATCH 24/46] Add Data property --- de4dot.code/resources/BuiltInResourceData.cs | 4 ++++ 1 file changed, 4 insertions(+) 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; } } From 955c1f10bd8a04cb50cebbff0a0aba80214d295c Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 16:47:34 +0200 Subject: [PATCH 25/46] Rename resource keys --- de4dot.code/ObfuscatedFile.cs | 4 + de4dot.code/de4dot.code.csproj | 1 + de4dot.code/deobfuscators/IDeobfuscator.cs | 1 + de4dot.code/renamer/Renamer.cs | 9 + de4dot.code/renamer/ResourceKeysRenamer.cs | 244 +++++++++++++++++++++ 5 files changed, 259 insertions(+) create mode 100644 de4dot.code/renamer/ResourceKeysRenamer.cs diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index f00ecdea..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; } } diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index a5454dd8..c6badf71 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -257,6 +257,7 @@ + diff --git a/de4dot.code/deobfuscators/IDeobfuscator.cs b/de4dot.code/deobfuscators/IDeobfuscator.cs index 93a05537..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 { diff --git a/de4dot.code/renamer/Renamer.cs b/de4dot.code/renamer/Renamer.cs index 11775a65..563938a7 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.RenameResourcesInCode) + 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) { diff --git a/de4dot.code/renamer/ResourceKeysRenamer.cs b/de4dot.code/renamer/ResourceKeysRenamer.cs new file mode 100644 index 00000000..d725c304 --- /dev/null +++ b/de4dot.code/renamer/ResourceKeysRenamer.cs @@ -0,0 +1,244 @@ +/* + 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 { + ModuleDefinition module; + INameChecker nameChecker; + Dictionary newNames = new Dictionary(); + const string DEFAULT_KEY_NAME = "Key"; + + public ResourceKeysRenamer(ModuleDefinition module, INameChecker nameChecker) { + this.module = module; + this.nameChecker = nameChecker; + } + + public void rename() { + Log.v("Renaming resource keys..."); + Log.indent(); + foreach (var type in module.GetTypes()) { + string resourceName = getResourceName(type); + if (resourceName == null) + continue; + var resource = DotNetUtils.getResource(module, resourceName) as EmbeddedResource; + if (resource == null) { + Log.w("Could not find resource {0}", Utils.removeNewlines(resource)); + continue; + } + Log.v("Resource: {0}", Utils.toCsharpString(resource.Name)); + Log.indent(); + rename(type, resource); + Log.deIndent(); + } + Log.deIndent(); + } + + 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 + ".resources"; + } + } + } + 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)) + 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::GetString(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::GetString(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)) { + Log.w("Could not find resource key '{0}'", Utils.removeNewlines(name)); + continue; + } + + 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; + return createName(createPrefixFromStringData((string)stringData.Data), false); + } + + string createPrefixFromStringData(string data) { + const int MAX_LEN = 30; + + var sb = new StringBuilder(); + data = data.Substring(0, Math.Min(data.Length, 100)); + data = Regex.Replace(data, "[`'\"]", ""); + data = Regex.Replace(data, @"[^\w]", " "); + data = Regex.Replace(data, @"[\s]", " "); + foreach (var piece in data.Split(' ')) { + if (piece.Length == 0) + continue; + var piece2 = piece.Substring(0, 1).ToUpperInvariant() + piece.Substring(1).ToLowerInvariant(); + int maxLen = 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(DEFAULT_KEY_NAME, true); + } + + string createName(string prefix, bool useZeroPostfix) { + for (int counter = 0; ; counter++) { + string newName; + if (useZeroPostfix || counter != 0) + newName = prefix + counter; + else + newName = prefix; + if (!newNames.ContainsKey(newName)) { + newNames[newName] = true; + return newName; + } + } + } + } +} From ea205dcae8119d2a254ca37c62735056fe4378d4 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 16:48:03 +0200 Subject: [PATCH 26/46] Add option to disable renaming resource keys --- de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index f1741aa9..62411a00 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -31,6 +31,7 @@ namespace de4dot.code.deobfuscators.DeepSea { BoolOption decryptResources; BoolOption dumpEmbeddedAssemblies; BoolOption restoreFields; + BoolOption renameResourceKeys; public DeobfuscatorInfo() : base() { @@ -39,6 +40,7 @@ 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); } public override string Name { @@ -57,6 +59,7 @@ namespace de4dot.code.deobfuscators.DeepSea { DecryptResources = decryptResources.get(), DumpEmbeddedAssemblies = dumpEmbeddedAssemblies.get(), RestoreFields = restoreFields.get(), + RenameResourceKeys = renameResourceKeys.get(), }); } @@ -67,6 +70,7 @@ namespace de4dot.code.deobfuscators.DeepSea { decryptResources, dumpEmbeddedAssemblies, restoreFields, + renameResourceKeys, }; } } @@ -88,6 +92,7 @@ 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 override string Type { @@ -128,6 +133,11 @@ namespace de4dot.code.deobfuscators.DeepSea { 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() { From b27e1b36aff1236eab600f0ba276ff49eccce030 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 16:51:36 +0200 Subject: [PATCH 27/46] Add option to disable cast deobfuscation --- de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 62411a00..d08e8c96 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -32,6 +32,7 @@ namespace de4dot.code.deobfuscators.DeepSea { BoolOption dumpEmbeddedAssemblies; BoolOption restoreFields; BoolOption renameResourceKeys; + BoolOption castDeobfuscation; public DeobfuscatorInfo() : base() { @@ -41,6 +42,7 @@ namespace de4dot.code.deobfuscators.DeepSea { 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("cast"), "Deobfuscate casts", true); } public override string Name { @@ -60,6 +62,7 @@ namespace de4dot.code.deobfuscators.DeepSea { DumpEmbeddedAssemblies = dumpEmbeddedAssemblies.get(), RestoreFields = restoreFields.get(), RenameResourceKeys = renameResourceKeys.get(), + CastDeobfuscation = castDeobfuscation.get(), }); } @@ -71,6 +74,7 @@ namespace de4dot.code.deobfuscators.DeepSea { dumpEmbeddedAssemblies, restoreFields, renameResourceKeys, + castDeobfuscation, }; } } @@ -93,6 +97,7 @@ namespace de4dot.code.deobfuscators.DeepSea { public bool DumpEmbeddedAssemblies { get; set; } public bool RestoreFields { get; set; } public bool RenameResourceKeys { get; set; } + public bool CastDeobfuscation { get; set; } } public override string Type { @@ -122,11 +127,10 @@ namespace de4dot.code.deobfuscators.DeepSea { List getBlocksDeobfuscators() { var list = new List(); - if (CanInlineMethods) { - if (arrayBlockDeobfuscator.Detected) - list.Add(arrayBlockDeobfuscator); + if (arrayBlockDeobfuscator.Detected) + list.Add(arrayBlockDeobfuscator); + if (!startedDeobfuscating || options.CastDeobfuscation) list.Add(new CastDeobfuscator()); - } return list; } From 44fea8f185dc9c51918073e8c9b0aaed467e69f6 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 3 May 2012 17:24:59 +0200 Subject: [PATCH 28/46] Fix problems found while testing --- de4dot.code/IObfuscatedFile.cs | 1 + de4dot.code/renamer/Renamer.cs | 2 +- de4dot.code/renamer/ResourceKeysRenamer.cs | 33 ++++++++++++++++------ 3 files changed, 26 insertions(+), 10 deletions(-) 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/renamer/Renamer.cs b/de4dot.code/renamer/Renamer.cs index 563938a7..b4fc276b 100644 --- a/de4dot.code/renamer/Renamer.cs +++ b/de4dot.code/renamer/Renamer.cs @@ -95,7 +95,7 @@ namespace de4dot.code.renamer { void renameResourceKeys() { foreach (var module in modules.TheModules) { - if (!module.ObfuscatedFile.RenameResourcesInCode) + if (!module.ObfuscatedFile.RenameResourceKeys) continue; new ResourceKeysRenamer(module.ModuleDefinition, module.ObfuscatedFile.NameChecker).rename(); } diff --git a/de4dot.code/renamer/ResourceKeysRenamer.cs b/de4dot.code/renamer/ResourceKeysRenamer.cs index d725c304..fcf42007 100644 --- a/de4dot.code/renamer/ResourceKeysRenamer.cs +++ b/de4dot.code/renamer/ResourceKeysRenamer.cs @@ -40,15 +40,15 @@ namespace de4dot.code.renamer { } public void rename() { - Log.v("Renaming resource keys..."); + 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 = DotNetUtils.getResource(module, resourceName) as EmbeddedResource; + var resource = getResource(resourceName); if (resource == null) { - Log.w("Could not find resource {0}", Utils.removeNewlines(resource)); + Log.w("Could not find resource {0}", Utils.removeNewlines(resourceName)); continue; } Log.v("Resource: {0}", Utils.toCsharpString(resource.Name)); @@ -59,6 +59,23 @@ namespace de4dot.code.renamer { 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) @@ -81,7 +98,7 @@ namespace de4dot.code.renamer { continue; } - return resourceName + ".resources"; + return resourceName; } } } @@ -174,10 +191,8 @@ namespace de4dot.code.renamer { } RenameInfo info; - if (!nameToInfo.TryGetValue(name, out info)) { - Log.w("Could not find resource key '{0}'", Utils.removeNewlines(name)); - continue; - } + 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)); @@ -196,7 +211,7 @@ namespace de4dot.code.renamer { if (elem.ResourceData.Code != ResourceTypeCode.String) return createDefaultName(); var stringData = (BuiltInResourceData)elem.ResourceData; - return createName(createPrefixFromStringData((string)stringData.Data), false); + return createPrefixFromStringData((string)stringData.Data); } string createPrefixFromStringData(string data) { From 3572bdfdccb664fc71f4a576f62ff285f70f96b8 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 17:28:52 +0200 Subject: [PATCH 29/46] Set maxlen to 50. Fix incorrect method sig. Make sure there are no dupes. --- de4dot.code/renamer/ResourceKeysRenamer.cs | 28 ++++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/de4dot.code/renamer/ResourceKeysRenamer.cs b/de4dot.code/renamer/ResourceKeysRenamer.cs index fcf42007..c305d433 100644 --- a/de4dot.code/renamer/ResourceKeysRenamer.cs +++ b/de4dot.code/renamer/ResourceKeysRenamer.cs @@ -29,10 +29,12 @@ 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(); - const string DEFAULT_KEY_NAME = "Key"; public ResourceKeysRenamer(ModuleDefinition module, INameChecker nameChecker) { this.module = module; @@ -166,13 +168,13 @@ namespace de4dot.code.renamer { int ldstrIndex; switch (calledMethod.FullName) { case "System.String System.Resources.ResourceManager::GetString(System.String,System.Globalization.CultureInfo)": - case "System.IO.UnmanagedMemoryStream 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::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; @@ -211,22 +213,20 @@ namespace de4dot.code.renamer { if (elem.ResourceData.Code != ResourceTypeCode.String) return createDefaultName(); var stringData = (BuiltInResourceData)elem.ResourceData; - return createPrefixFromStringData((string)stringData.Data); + var name = createPrefixFromStringData((string)stringData.Data); + return createName(counter => counter == 0 ? name : string.Format("{0}_{1}", name, counter)); } string createPrefixFromStringData(string data) { - const int MAX_LEN = 30; - var sb = new StringBuilder(); data = data.Substring(0, Math.Min(data.Length, 100)); data = Regex.Replace(data, "[`'\"]", ""); - data = Regex.Replace(data, @"[^\w]", " "); - data = Regex.Replace(data, @"[\s]", " "); + 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 = MAX_LEN - sb.Length; + int maxLen = RESOURCE_KEY_MAX_LEN - sb.Length; if (maxLen <= 0) break; if (piece2.Length > maxLen) @@ -239,16 +239,12 @@ namespace de4dot.code.renamer { } string createDefaultName() { - return createName(DEFAULT_KEY_NAME, true); + return createName(counter => string.Format("{0}{1}", DEFAULT_KEY_NAME, counter)); } - string createName(string prefix, bool useZeroPostfix) { + string createName(Func create) { for (int counter = 0; ; counter++) { - string newName; - if (useZeroPostfix || counter != 0) - newName = prefix + counter; - else - newName = prefix; + string newName = create(counter); if (!newNames.ContainsKey(newName)) { newNames[newName] = true; return newName; From 1aaa5df9ce391c37de5822dd084a55eeb280f5b9 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 17:30:31 +0200 Subject: [PATCH 30/46] Support trial string encrypter --- .../deobfuscators/DeepSea/StringDecrypter.cs | 89 ++++++++++++++++--- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs index b5768058..3eb6a3f7 100644 --- a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs +++ b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs @@ -46,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]; @@ -71,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; } @@ -128,7 +134,9 @@ namespace de4dot.code.deobfuscators.DeepSea { FieldDefinitionAndDeclaringTypeDict fields; ArrayInfo arrayInfo; ushort[] encryptedData; - ushort[] key; + short[] key; + int keyShift; + bool isTrial; class ArrayInfo { public int sizeInElems; @@ -208,6 +216,8 @@ namespace de4dot.code.deobfuscators.DeepSea { 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; @@ -215,6 +225,49 @@ namespace de4dot.code.deobfuscators.DeepSea { 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++) { @@ -242,7 +295,7 @@ namespace de4dot.code.deobfuscators.DeepSea { return null; } - ushort[] findKey() { + short[] findKey() { if (cctor.Module.Assembly == null) return null; var pkt = cctor.Module.Assembly.Name.PublicKeyToken; @@ -251,24 +304,40 @@ namespace de4dot.code.deobfuscators.DeepSea { return findKey(cctor); } - ushort[] findKey(MethodDefinition initMethod) { - throw new NotImplementedException(); //TODO: + short[] findKey(MethodDefinition initMethod) { + return StringDecrypter.findKey(initMethod, fields); } - static ushort[] getPublicKeyTokenKey(byte[] publicKeyToken) { - var key = new ushort[publicKeyToken.Length]; + 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] = (ushort)((b << 7) ^ b); + key[i] = (short)((b << keyShift) ^ b); } return key; } public string decrypt(object[] args) { - return decrypt((int)args[arg1], (int)args[arg2]); + if (isTrial) + return decryptTrial((int)args[arg1], (int)args[arg2]); + return decryptRetail((int)args[arg1], (int)args[arg2]); } - string decrypt(int magic2, int magic3) { + 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; From 3d1e3e6ca9815986b70bfc2588c36f3473b83a93 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 18:25:43 +0200 Subject: [PATCH 31/46] Add method to get instrs pushing args --- blocks/DotNetUtils.cs | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/blocks/DotNetUtils.cs b/blocks/DotNetUtils.cs index 97355e48..4eadfe64 100644 --- a/blocks/DotNetUtils.cs +++ b/blocks/DotNetUtils.cs @@ -1208,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; + } } } From c78e77c1ec191f6b8643d2b49baecfca5095139f Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 18:26:19 +0200 Subject: [PATCH 32/46] Remove useless class name prefix --- blocks/DotNetUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/DotNetUtils.cs b/blocks/DotNetUtils.cs index 4eadfe64..03462a8b 100644 --- a/blocks/DotNetUtils.cs +++ b/blocks/DotNetUtils.cs @@ -607,7 +607,7 @@ namespace de4dot.blocks { 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 }); - DotNetUtils.copyBodyFromTo(method, newMethod); + copyBodyFromTo(method, newMethod); return newMethod; } From dadc064b559ace433df40831e7f7a7e29c881e49 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 19:00:21 +0200 Subject: [PATCH 33/46] Decrypt V4.1 resources --- .../deobfuscators/DeepSea/ResourceResolver.cs | 237 ++++++++++++++++-- 1 file changed, 221 insertions(+), 16 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs index c5ab071c..a1f3dc0e 100644 --- a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs @@ -17,17 +17,50 @@ 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 { + // V3 EmbeddedResource resource; + + // V40 FieldDefinition resourceField; MethodDefinition getDataMethod; int magicV4; - bool isV3; + + // V41 + Data41 data41; + + ResourceVersion version = ResourceVersion.Unknown; + + enum ResourceVersion { + Unknown, + V3, + V40, + V41, + } + + 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 GetDataMethod { get { return getDataMethod; } @@ -47,23 +80,183 @@ 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; + if (checkHandlerV40(handler, out resourceFieldTmp, out getDataMethodTmp, out magicV4)) { + version = ResourceVersion.V40; resourceField = resourceFieldTmp; getDataMethod = getDataMethodTmp; 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 = getArgValues(DotNetUtils.getArgPushes(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 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; + } + + static bool getArgValue(MethodDefinition method, int index, out object arg) { + return getArgValue(method.Body.Instructions[index], out arg); + } + + 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; + } + } + static string[] handlerLocalTypes_V3 = new string[] { "System.AppDomain", "System.Byte[]", @@ -79,7 +272,7 @@ 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 bool checkHandlerV40(MethodDefinition handler, out FieldDefinition resourceField, out MethodDefinition getDataMethod, out int magic) { var instrs = handler.Body.Instructions; for (int i = 0; i < instrs.Count; i++) { int index = i; @@ -105,7 +298,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++]; @@ -157,7 +350,7 @@ 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); @@ -171,22 +364,34 @@ namespace de4dot.code.deobfuscators.DeepSea { public bool mergeResources(out EmbeddedResource rsrc) { rsrc = null; - if (isV3) { + switch (version) { + case ResourceVersion.V3: if (resource == null) return false; DeobUtils.decryptAndAddResources(module, resource.Name, () => decryptResourceV3(resource)); rsrc = resource; - } - else { - if (resourceField == null) - return false; + 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(resourceField, magicV4); + + 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; } } From 9b9e692947ca78963479a7ae50c839a8ecf82af4 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 19:10:20 +0200 Subject: [PATCH 34/46] Move version specific data to their own class --- .../deobfuscators/DeepSea/ResourceResolver.cs | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs index a1f3dc0e..43a7c627 100644 --- a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs @@ -25,17 +25,9 @@ using de4dot.blocks; namespace de4dot.code.deobfuscators.DeepSea { class ResourceResolver : ResolverBase { - // V3 - EmbeddedResource resource; - - // V40 - FieldDefinition resourceField; - MethodDefinition getDataMethod; - int magicV4; - - // V41 + Data30 data30; + Data40 data40; Data41 data41; - ResourceVersion version = ResourceVersion.Unknown; enum ResourceVersion { @@ -45,6 +37,16 @@ namespace de4dot.code.deobfuscators.DeepSea { V41, } + class Data30 { + public EmbeddedResource resource; + } + + class Data40 { + public FieldDefinition resourceField; + public MethodDefinition getDataMethod; + public int magic; + } + class Data41 { public FieldDefinition resourceField; public MethodDefinition resolveHandler2; @@ -63,11 +65,11 @@ namespace de4dot.code.deobfuscators.DeepSea { } 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) @@ -84,13 +86,9 @@ namespace de4dot.code.deobfuscators.DeepSea { return true; } - FieldDefinition resourceFieldTmp; - MethodDefinition getDataMethodTmp; simpleDeobfuscator.deobfuscate(handler); - if (checkHandlerV40(handler, out resourceFieldTmp, out getDataMethodTmp, out magicV4)) { + if ((data40 = checkHandlerV40(handler)) != null) { version = ResourceVersion.V40; - resourceField = resourceFieldTmp; - getDataMethod = getDataMethodTmp; return true; } @@ -272,7 +270,9 @@ namespace de4dot.code.deobfuscators.DeepSea { return new LocalTypes(handler).all(handlerLocalTypes_V3); } - static bool checkHandlerV40(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; @@ -313,7 +313,7 @@ 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) @@ -323,15 +323,12 @@ namespace de4dot.code.deobfuscators.DeepSea { if (!DotNetUtils.isMethod(call.Operand as MethodReference, "System.Reflection.Assembly", methodSig)) continue; - resourceField = field; - getDataMethod = method; - return true; + data40.resourceField = field; + data40.getDataMethod = method; + return data40; } - magic = 0; - resourceField = null; - getDataMethod = null; - return false; + return null; } static FieldDefinition getResourceField(MethodDefinition method) { @@ -353,8 +350,9 @@ namespace de4dot.code.deobfuscators.DeepSea { 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; } @@ -366,15 +364,15 @@ namespace de4dot.code.deobfuscators.DeepSea { switch (version) { case ResourceVersion.V3: - if (resource == null) + if (data30.resource == null) return false; - DeobUtils.decryptAndAddResources(module, resource.Name, () => decryptResourceV3(resource)); - rsrc = resource; + DeobUtils.decryptAndAddResources(module, data30.resource.Name, () => decryptResourceV3(data30.resource)); + rsrc = data30.resource; return true; case ResourceVersion.V40: - return decryptResource(resourceField, magicV4); + return decryptResource(data40.resourceField, data40.magic); case ResourceVersion.V41: return decryptResource(data41.resourceField, data41.magic); From f6db11ac0967ed5ffcc94f06174216b98f199df5 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 19:32:52 +0200 Subject: [PATCH 35/46] Don't print the full stack trace. Too confusing for most people. --- de4dot.cui/Program.cs | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) 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) { From ee32b84283efb3ceb56172f88a557b5544732446 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 22:20:17 +0200 Subject: [PATCH 36/46] Move code to DsUtils --- de4dot.code/de4dot.code.csproj | 1 + de4dot.code/deobfuscators/DeepSea/DsUtils.cs | 91 +++++++++++++++++++ .../deobfuscators/DeepSea/ResourceResolver.cs | 62 +------------ 3 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 de4dot.code/deobfuscators/DeepSea/DsUtils.cs diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index c6badf71..bc39df46 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -119,6 +119,7 @@ + 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/ResourceResolver.cs b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs index 43a7c627..ad326433 100644 --- a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs @@ -112,7 +112,7 @@ namespace de4dot.code.deobfuscators.DeepSea { var calledMethod = instr.Operand as MethodDefinition; if (calledMethod == null) continue; - var args = getArgValues(DotNetUtils.getArgPushes(instrs, i)); + var args = DsUtils.getArgValues(instrs, i); if (args == null) continue; @@ -195,66 +195,6 @@ namespace de4dot.code.deobfuscators.DeepSea { return null; } - 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; - } - - static bool getArgValue(MethodDefinition method, int index, out object arg) { - return getArgValue(method.Body.Instructions[index], out arg); - } - - 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; - } - } - static string[] handlerLocalTypes_V3 = new string[] { "System.AppDomain", "System.Byte[]", From c5f8aaeb1ac5e519fdb8d0ce02494583fde12ff4 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 9 May 2012 22:24:39 +0200 Subject: [PATCH 37/46] Dump 4.1 embedded assemblies --- .../deobfuscators/DeepSea/AssemblyResolver.cs | 180 +++++++++++++++--- .../deobfuscators/DeepSea/ResolverBase.cs | 7 + 2 files changed, 165 insertions(+), 22 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs b/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs index 0424c2c6..146380d1 100644 --- a/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs @@ -35,6 +35,8 @@ namespace de4dot.code.deobfuscators.DeepSea { V3Old, V3, V4, + V404, + V41, } public class AssemblyInfo { @@ -127,13 +129,19 @@ namespace de4dot.code.deobfuscators.DeepSea { simpleDeobfuscator.deobfuscate(handler); List fieldInfosTmp; - if (checkHandlerV4(handler, out fieldInfosTmp) || - checkHandlerV4_0_4(handler, out fieldInfosTmp)) { + if (checkHandlerV4(handler, out fieldInfosTmp)) { version = Version.V4; fieldInfos = fieldInfosTmp; return true; } + Version versionTmp = checkHandlerV404_41(handler, out fieldInfosTmp); + if (fieldInfosTmp.Count != 0) { + version = versionTmp; + fieldInfos = fieldInfosTmp; + return true; + } + return false; } @@ -202,12 +210,13 @@ namespace de4dot.code.deobfuscators.DeepSea { 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) { + Version version = Version.Unknown; fieldInfos = new List(); 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 +242,136 @@ 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 decryptMethod = instrs[callIndex].Operand as MethodDefinition; + if (decryptMethod == null) + continue; + int magic; + Version versionTmp; + getMagic(decryptMethod, args, out versionTmp, out magic); + version = versionTmp; 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() { @@ -264,7 +383,10 @@ namespace de4dot.code.deobfuscators.DeepSea { case Version.V3: return getAssemblyInfosV3(); case Version.V4: + case Version.V404: return getAssemblyInfosV4(); + case Version.V41: + return getAssemblyInfosV41(); default: throw new ApplicationException("Unknown version"); } @@ -306,14 +428,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 +443,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/ResolverBase.cs b/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs index 74b0a2b1..a6890000 100644 --- a/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs +++ b/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs @@ -158,7 +158,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); From ae7e32ae5b67c7c6d36e8f7553678944f9cf1997 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 10 May 2012 13:39:14 +0200 Subject: [PATCH 38/46] Remove decrypt method and other init method --- .../deobfuscators/DeepSea/AssemblyResolver.cs | 29 ++++++++++++++----- .../deobfuscators/DeepSea/Deobfuscator.cs | 2 ++ .../deobfuscators/DeepSea/ResourceResolver.cs | 15 +++++++++- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs b/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs index 146380d1..1d3e9cba 100644 --- a/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs @@ -29,6 +29,7 @@ namespace de4dot.code.deobfuscators.DeepSea { class AssemblyResolver : ResolverBase { Version version; List fieldInfos; + MethodDefinition decryptMethod; enum Version { Unknown, @@ -69,6 +70,10 @@ namespace de4dot.code.deobfuscators.DeepSea { } } + public MethodDefinition DecryptMethod { + get { return decryptMethod; } + } + public AssemblyResolver(ModuleDefinition module, ISimpleDeobfuscator simpleDeobfuscator, IDeobfuscator deob) : base(module, simpleDeobfuscator, deob) { } @@ -129,16 +134,19 @@ namespace de4dot.code.deobfuscators.DeepSea { simpleDeobfuscator.deobfuscate(handler); List fieldInfosTmp; - if (checkHandlerV4(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); + Version versionTmp = checkHandlerV404_41(handler, out fieldInfosTmp, out decryptMethodTmp); if (fieldInfosTmp.Count != 0) { version = versionTmp; fieldInfos = fieldInfosTmp; + decryptMethod = decryptMethodTmp; return true; } @@ -171,8 +179,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++) { @@ -201,9 +210,11 @@ 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)); } @@ -211,9 +222,10 @@ namespace de4dot.code.deobfuscators.DeepSea { } // 4.0.4, 4.1+ - Version checkHandlerV404_41(MethodDefinition handler, out List fieldInfos) { + 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 - 6; i++) { @@ -248,14 +260,15 @@ namespace de4dot.code.deobfuscators.DeepSea { var args = DsUtils.getArgValues(instrs, callIndex); if (args == null) continue; - var decryptMethod = instrs[callIndex].Operand as MethodDefinition; - if (decryptMethod == null) + var decryptMethodTmp = instrs[callIndex].Operand as MethodDefinition; + if (decryptMethodTmp == null) continue; int magic; Version versionTmp; - getMagic(decryptMethod, args, out versionTmp, out magic); + getMagic(decryptMethodTmp, args, out versionTmp, out magic); version = versionTmp; + decryptMethod = decryptMethodTmp; fieldInfos.Add(new FieldInfo(field, magic)); } diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index d08e8c96..77eb1e35 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -238,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"); } @@ -255,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) { diff --git a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs index ad326433..f8c623fa 100644 --- a/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/ResourceResolver.cs @@ -43,6 +43,7 @@ namespace de4dot.code.deobfuscators.DeepSea { class Data40 { public FieldDefinition resourceField; + public MethodDefinition resolveHandler2; public MethodDefinition getDataMethod; public int magic; } @@ -64,6 +65,16 @@ namespace de4dot.code.deobfuscators.DeepSea { } } + public MethodDefinition InitMethod2 { + get { + if (data40 != null) + return data40.resolveHandler2; + if (data41 != null) + return data41.resolveHandler2; + return null; + } + } + public MethodDefinition GetDataMethod { get { return data40 != null ? data40.getDataMethod : null; } } @@ -260,11 +271,13 @@ namespace de4dot.code.deobfuscators.DeepSea { 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; data40.resourceField = field; data40.getDataMethod = method; + data40.resolveHandler2 = resolveHandler2; return data40; } From 0b47ccf070ff38e0f63987d5c9bae335641a4b5c Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 10 May 2012 18:38:27 +0200 Subject: [PATCH 39/46] Remove cflow obfuscation arrays --- .../DeepSea/ArrayBlockDeobfuscator.cs | 97 ++++++++++++++++++- .../deobfuscators/DeepSea/Deobfuscator.cs | 2 + 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs index 790929ba..a10c1984 100644 --- a/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs @@ -32,12 +32,14 @@ namespace de4dot.code.deobfuscators.DeepSea { DsConstantsReader constantsReader; class FieldInfo { - public FieldDefinition field; - public byte[] array; + public readonly FieldDefinition field; + public readonly FieldDefinition arrayInitField; + public readonly byte[] array; - public FieldInfo(FieldDefinition field, byte[] array) { + public FieldInfo(FieldDefinition field, FieldDefinition arrayInitField) { this.field = field; - this.array = array; + this.arrayInitField = arrayInitField; + this.array = (byte[])arrayInitField.InitialValue.Clone(); } } @@ -83,7 +85,7 @@ namespace de4dot.code.deobfuscators.DeepSea { if (targetField == null) continue; - fieldToInfo.add(targetField, new FieldInfo(targetField, (byte[])arrayInitField.InitialValue.Clone())); + fieldToInfo.add(targetField, new FieldInfo(targetField, arrayInitField)); } } @@ -235,5 +237,90 @@ namespace de4dot.code.deobfuscators.DeepSea { 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; + blocks.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/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 77eb1e35..577bbd89 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -278,6 +278,8 @@ done: stringDecrypter.cleanup(); } + addFieldsToBeRemoved(arrayBlockDeobfuscator.cleanUp(), "Control flow obfuscation array"); + base.deobfuscateEnd(); } From f05a334c11dba38420f0f136bbdf8e7d620bae77 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 10 May 2012 18:41:21 +0200 Subject: [PATCH 40/46] Make sure we don't rename a key to an already existing non-renamed key --- de4dot.code/renamer/ResourceKeysRenamer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/de4dot.code/renamer/ResourceKeysRenamer.cs b/de4dot.code/renamer/ResourceKeysRenamer.cs index c305d433..6ad77116 100644 --- a/de4dot.code/renamer/ResourceKeysRenamer.cs +++ b/de4dot.code/renamer/ResourceKeysRenamer.cs @@ -126,8 +126,10 @@ namespace de4dot.code.renamer { var resourceSet = ResourceReader.read(module, resource.GetResourceStream()); var renamed = new List(); foreach (var elem in resourceSet.ResourceElements) { - if (nameChecker.isValidResourceKeyName(elem.Name)) + if (nameChecker.isValidResourceKeyName(elem.Name)) { + newNames.Add(elem.Name, true); continue; + } renamed.Add(new RenameInfo(elem, getNewName(elem))); } From 1d2a78979f5ee231be69518d698e97c886ec5ce9 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 10 May 2012 19:00:12 +0200 Subject: [PATCH 41/46] Use generic prop creator if the type has a generic parameter --- de4dot.code/renamer/VariableNameState.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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); From cd014f1d7250e83e2d0ecdf6cb03a83d191565d9 Mon Sep 17 00:00:00 2001 From: de4dot Date: Thu, 10 May 2012 20:20:29 +0200 Subject: [PATCH 42/46] Update fields restorer --- .../deobfuscators/DeepSea/FieldsRestorer.cs | 91 +++++++++++++++++-- 1 file changed, 81 insertions(+), 10 deletions(-) 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) From 94ee4064ed6e8e14ef136aa2b28773b76c39f450 Mon Sep 17 00:00:00 2001 From: de4dot Date: Fri, 11 May 2012 18:17:51 +0200 Subject: [PATCH 43/46] Remove namespace prefix --- de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 577bbd89..bad1957f 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -196,7 +196,7 @@ namespace de4dot.code.deobfuscators.DeepSea { continue; if (checkedMethods++ >= 1000) goto done; - if (!DeepSea.DsMethodCallInliner.canInline(method)) + if (!DsMethodCallInliner.canInline(method)) continue; foundProxies++; } From ce3622f6e8ea52fe4b0c43fa20219c878774400d Mon Sep 17 00:00:00 2001 From: de4dot Date: Fri, 11 May 2012 18:18:19 +0200 Subject: [PATCH 44/46] Use the correct variable --- de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs index a10c1984..ac100559 100644 --- a/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/ArrayBlockDeobfuscator.cs @@ -259,7 +259,7 @@ namespace de4dot.code.deobfuscators.DeepSea { IList allInstructions; IList allExceptionHandlers; - blocks.getCode(out allInstructions, out allExceptionHandlers); + moduleCctorBlocks.getCode(out allInstructions, out allExceptionHandlers); DotNetUtils.restoreBody(moduleCctorBlocks.Method, allInstructions, allExceptionHandlers); return removedFields; } From 40898cf23811bc6e4f1dc1b68ad86bdc9288a8c8 Mon Sep 17 00:00:00 2001 From: de4dot Date: Fri, 11 May 2012 19:38:31 +0200 Subject: [PATCH 45/46] Decrypt embedded assemblies (SL) --- .../deobfuscators/DeepSea/AssemblyResolver.cs | 61 ++++++++++++++++--- .../deobfuscators/DeepSea/ResolverBase.cs | 8 +++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs b/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs index 1d3e9cba..49e0a041 100644 --- a/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs +++ b/de4dot.code/deobfuscators/DeepSea/AssemblyResolver.cs @@ -38,6 +38,7 @@ namespace de4dot.code.deobfuscators.DeepSea { V4, V404, V41, + V41SL, } public class AssemblyInfo { @@ -96,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; @@ -109,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) { @@ -393,19 +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) { @@ -414,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); @@ -423,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) { diff --git a/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs b/de4dot.code/deobfuscators/DeepSea/ResolverBase.cs index a6890000..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()); } From bec6725aa79d532b3fa94bef09a689689def6596 Mon Sep 17 00:00:00 2001 From: de4dot Date: Sat, 12 May 2012 21:39:49 +0200 Subject: [PATCH 46/46] Rename option --- de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index bad1957f..7eac25c9 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -42,7 +42,7 @@ namespace de4dot.code.deobfuscators.DeepSea { 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("cast"), "Deobfuscate casts", true); + castDeobfuscation = new BoolOption(null, makeArgName("casts"), "Deobfuscate casts", true); } public override string Name {