From 2bba9e688a5cec436d3433f9a8b5b87a53b90c22 Mon Sep 17 00:00:00 2001 From: de4dot Date: Fri, 21 Oct 2011 18:02:58 +0200 Subject: [PATCH] Remove more dead code and useless stores --- blocks/ScopeBlock.cs | 15 ++- blocks/blocks.csproj | 2 + blocks/cflow/BlocksCflowDeobfuscator.cs | 13 +- blocks/cflow/DeadCodeRemover.cs | 13 +- blocks/cflow/DeadStoreRemover.cs | 152 ++++++++++++++++++++++++ blocks/cflow/StLdlocFixer.cs | 76 ++++++++++++ 6 files changed, 260 insertions(+), 11 deletions(-) create mode 100644 blocks/cflow/DeadStoreRemover.cs create mode 100644 blocks/cflow/StLdlocFixer.cs diff --git a/blocks/ScopeBlock.cs b/blocks/ScopeBlock.cs index 0a2a2b65..edf7b6d9 100644 --- a/blocks/ScopeBlock.cs +++ b/blocks/ScopeBlock.cs @@ -40,18 +40,23 @@ namespace de4dot.blocks { } public IList getAllBaseBlocks() { - return getAllBlocks(new List()); + return getTheBlocks(new List()); } public IList getAllBlocks() { - return getAllBlocks(new List()); + return getTheBlocks(new List()); + } + + public IList getAllBlocks(IList allBlocks) { + allBlocks.Clear(); + return getTheBlocks(allBlocks); } public IList getAllScopeBlocks() { - return getAllBlocks(new List()); + return getTheBlocks(new List()); } - IList getAllBlocks(IList list) where T : BaseBlock { + public IList getTheBlocks(IList list) where T : BaseBlock { addBlocks(list, this); return list; } @@ -319,7 +324,7 @@ namespace de4dot.blocks { // Get removed blocks and make sure they're not referenced by remaining code var removedBlocks = new List(); foreach (var handler in tryBlock.TryHandlerBlocks) - handler.getAllBlocks(removedBlocks); + handler.getTheBlocks(removedBlocks); if (!verifyNoExternalRefs(removedBlocks)) throw new ApplicationException("Removed blocks are referenced by remaining code"); diff --git a/blocks/blocks.csproj b/blocks/blocks.csproj index fb0ad203..48087935 100644 --- a/blocks/blocks.csproj +++ b/blocks/blocks.csproj @@ -38,10 +38,12 @@ + + diff --git a/blocks/cflow/BlocksCflowDeobfuscator.cs b/blocks/cflow/BlocksCflowDeobfuscator.cs index 0c1937a3..94836ecb 100644 --- a/blocks/cflow/BlocksCflowDeobfuscator.cs +++ b/blocks/cflow/BlocksCflowDeobfuscator.cs @@ -39,14 +39,15 @@ namespace de4dot.blocks.cflow { var allBlocks = new List(); var switchCflowDeobfuscator = new SwitchCflowDeobfuscator(); var deadCodeRemover = new DeadCodeRemover(); + var deadStoreRemover = new DeadStoreRemover(); + var stLdlocFixer = new StLdlocFixer(); bool changed; do { changed = false; removeDeadBlocks(); mergeBlocks(); - allBlocks.Clear(); - allBlocks.AddRange(blocks.MethodBlocks.getAllBlocks()); + blocks.MethodBlocks.getAllBlocks(allBlocks); foreach (var block in allBlocks) { var lastInstr = block.LastInstr; @@ -59,8 +60,16 @@ namespace de4dot.blocks.cflow { switchCflowDeobfuscator.init(blocks, allBlocks); changed |= switchCflowDeobfuscator.deobfuscate(); + deadStoreRemover.init(blocks, allBlocks); + changed |= deadStoreRemover.remove(); + deadCodeRemover.init(allBlocks); changed |= deadCodeRemover.remove(); + + if (!changed) { + stLdlocFixer.init(allBlocks, blocks.Locals); + changed |= stLdlocFixer.fix(); + } } while (changed); } diff --git a/blocks/cflow/DeadCodeRemover.cs b/blocks/cflow/DeadCodeRemover.cs index 24ddac1f..0387c939 100644 --- a/blocks/cflow/DeadCodeRemover.cs +++ b/blocks/cflow/DeadCodeRemover.cs @@ -21,6 +21,8 @@ using System.Collections.Generic; 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; List allDeadInstructions = new List(); @@ -42,6 +44,7 @@ namespace de4dot.blocks.cflow { bool remove(Block block) { allDeadInstructions.Clear(); + bool changed = false; var instructions = block.Instructions; for (int i = 0; i < instructions.Count; i++) { var instr = instructions[i]; @@ -79,11 +82,13 @@ namespace de4dot.blocks.cflow { break; } } - if (allDeadInstructions.Count == 0) - return false; - block.remove(allDeadInstructions); - return true; + if (allDeadInstructions.Count > 0) { + block.remove(allDeadInstructions); + changed = true; + } + + return changed; } bool okInstructions(Block block, IEnumerable indexes) { diff --git a/blocks/cflow/DeadStoreRemover.cs b/blocks/cflow/DeadStoreRemover.cs new file mode 100644 index 00000000..68bfcdfc --- /dev/null +++ b/blocks/cflow/DeadStoreRemover.cs @@ -0,0 +1,152 @@ +/* + Copyright (C) 2011 de4dot@gmail.com + + This file is part of de4dot. + + de4dot is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + de4dot is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with de4dot. If not, see . +*/ + +using System; +using System.Collections.Generic; +using Mono.Cecil.Cil; + +namespace de4dot.blocks.cflow { + // Removes dead stores by replacing the stloc with a pop. Other optimizations will notice it's + // 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 { + Blocks blocks; + List allBlocks = new List(); + 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, + Read = 1, + Write = 2, + } + + public bool remove() { + if (blocks.Locals.Count == 0) + return false; + + localFlags.Clear(); + deadLocals.Clear(); + for (int i = 0; i < blocks.Locals.Count; i++) { + localFlags.Add(AccessFlags.None); + deadLocals.Add(false); + } + + findLoadStores(); + + bool deadStores = false; + for (int i = 0; i < blocks.Locals.Count; i++) { + var flags = localFlags[i]; + if ((flags & AccessFlags.Read) == AccessFlags.None) { + deadLocals[i] = true; + deadStores = true; + } + } + if (!deadStores) + return false; + + return removeDeadStores(); + } + + void findLoadStores() { + foreach (var block in allBlocks) { + foreach (var instr in block.Instructions) { + VariableDefinition local; + AccessFlags flags; + switch (instr.OpCode.Code) { + case Code.Ldloc: + case Code.Ldloc_S: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + local = Instr.getLocalVar(blocks.Locals, instr); + flags = AccessFlags.Read; + break; + + case Code.Stloc: + case Code.Stloc_S: + case Code.Stloc_0: + case Code.Stloc_1: + case Code.Stloc_2: + case Code.Stloc_3: + local = Instr.getLocalVar(blocks.Locals, instr); + flags = AccessFlags.Write; + break; + + case Code.Ldloca_S: + case Code.Ldloca: + local = instr.Operand as VariableDefinition; + flags = AccessFlags.Read | AccessFlags.Write; + break; + + default: + local = null; + flags = AccessFlags.None; + break; + } + + if (local == null) + continue; + localFlags[local.Index] |= flags; + } + } + } + + bool removeDeadStores() { + bool changed = false; + foreach (var block in allBlocks) { + var instructions = block.Instructions; + for (int i = 0; i < instructions.Count; i++) { + var instr = instructions[i]; + VariableDefinition local; + 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: + local = Instr.getLocalVar(blocks.Locals, instr); + break; + + default: + continue; + } + + if (local == null) + continue; + if (!deadLocals[local.Index]) + continue; + instructions[i] = new Instr(Instruction.Create(OpCodes.Pop)); + changed = true; + } + } + + return changed; + } + } +} diff --git a/blocks/cflow/StLdlocFixer.cs b/blocks/cflow/StLdlocFixer.cs new file mode 100644 index 00000000..b240a505 --- /dev/null +++ b/blocks/cflow/StLdlocFixer.cs @@ -0,0 +1,76 @@ +/* + Copyright (C) 2011 de4dot@gmail.com + + This file is part of de4dot. + + de4dot is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + de4dot is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with de4dot. If not, see . +*/ + +using System.Collections.Generic; +using Mono.Cecil.Cil; + +namespace de4dot.blocks.cflow { + // Replace stloc + ldloc with dup + stloc + class StLdlocFixer { + IList locals; + List allBlocks; + + public void init(List allBlocks, IList locals) { + this.allBlocks = allBlocks; + this.locals = locals; + } + + public bool fix() { + bool changed = false; + + foreach (var block in allBlocks) + changed |= fix(block); + + return changed; + } + + bool fix(Block block) { + bool changed = false; + var instructions = block.Instructions; + for (int i = 0; i < instructions.Count; i++) { + var instr = instructions[i]; + switch (instr.OpCode.Code) { + // Xenocode generates stloc + ldloc. Replace it with dup + stloc. It will eventually + // become dup + pop and be removed. + case Code.Stloc: + case Code.Stloc_S: + case Code.Stloc_0: + case Code.Stloc_1: + case Code.Stloc_2: + case Code.Stloc_3: + if (i + 1 >= instructions.Count) + break; + if (!instructions[i + 1].isLdloc()) + break; + if (Instr.getLocalVar(locals, instr) != Instr.getLocalVar(locals, instructions[i + 1])) + break; + instructions[i] = new Instr(Instruction.Create(OpCodes.Dup)); + instructions[i + 1] = instr; + changed = true; + break; + + default: + break; + } + } + + return changed; + } + } +}