diff --git a/blocks/Block.cs b/blocks/Block.cs index bf280366..efc30ccc 100644 --- a/blocks/Block.cs +++ b/blocks/Block.cs @@ -168,6 +168,11 @@ namespace de4dot.blocks { replaceLastInstrsWithBranch(numInstrs, target); } + public void replaceBccWithBranch(bool isTaken) { + Block target = isTaken ? targets[0] : fallThrough; + replaceLastInstrsWithBranch(1, target); + } + public void removeDeadBlock() { if (sources.Count != 0) throw new ApplicationException("Trying to remove a non-dead block"); diff --git a/blocks/Blocks.cs b/blocks/Blocks.cs index 5bb2c857..9f226211 100644 --- a/blocks/Blocks.cs +++ b/blocks/Blocks.cs @@ -74,131 +74,10 @@ namespace de4dot.blocks { return list; } - int removeDeadBlocks() { + public int removeDeadBlocks() { return new DeadBlocksRemover(methodBlocks).remove(); } - class DeadBlocksRemover { - MethodBlocks methodBlocks; - Dictionary checkedBaseBlocks = new Dictionary(); - Dictionary checkedScopeBlocks = new Dictionary(); - Stack baseBlocksToCheck = new Stack(); - Stack scopeBlocksToCheck = new Stack(); - - public DeadBlocksRemover(MethodBlocks methodBlocks) { - this.methodBlocks = methodBlocks; - } - - public int remove() { - addScopeBlock(methodBlocks); - processAll(); - return removeDeadBlocks(); - } - - class ScopeBlockInfo { - public ScopeBlock scopeBlock; - public IList deadBlocks = new List(); - public ScopeBlockInfo(ScopeBlock scopeBlock) { - this.scopeBlock = scopeBlock; - } - } - - int removeDeadBlocks() { - int numDeadBlocks = 0; - - var infos = new Dictionary(); - var deadBlocksDict = new Dictionary(); - foreach (var baseBlock in findDeadBlocks()) { - deadBlocksDict[baseBlock] = true; - ScopeBlock parent = (ScopeBlock)baseBlock.Parent; - ScopeBlockInfo info; - if (!infos.TryGetValue(parent, out info)) - infos[parent] = info = new ScopeBlockInfo(parent); - info.deadBlocks.Add(baseBlock); - numDeadBlocks++; - } - - foreach (var info in infos.Values) - info.scopeBlock.removeAllDeadBlocks(info.deadBlocks, deadBlocksDict); - - return numDeadBlocks; - } - - IList findDeadBlocks() { - var deadBlocks = new List(); - - foreach (var bb in methodBlocks.getAllBaseBlocks()) { - if (!checkedBaseBlocks.ContainsKey(bb)) - deadBlocks.Add(bb); - } - - return deadBlocks; - } - - void addScopeBlock(ScopeBlock scopeBlock) { - scopeBlocksToCheck.Push(scopeBlock); - } - - void processAll() { - bool didSomething; - do { - didSomething = false; - while (baseBlocksToCheck.Count > 0) { - processBaseBlock(baseBlocksToCheck.Pop()); - didSomething = true; - } - while (scopeBlocksToCheck.Count > 0) { - processScopeBlock(scopeBlocksToCheck.Pop()); - didSomething = true; - } - } while (didSomething); - } - - void processBaseBlock(BaseBlock baseBlock) { - if (baseBlock == null || checkedBaseBlocks.ContainsKey(baseBlock)) - return; - checkedBaseBlocks[baseBlock] = true; - - if (baseBlock is Block) { - var block = (Block)baseBlock; - foreach (var block2 in block.getTargets()) - addBaseBlock(block2); - } - else if (baseBlock is ScopeBlock) { - var scopeBlock = (ScopeBlock)baseBlock; - addScopeBlock(scopeBlock); - if (scopeBlock.BaseBlocks != null && scopeBlock.BaseBlocks.Count > 0) - addBaseBlock(scopeBlock.BaseBlocks[0]); - } - else - throw new ApplicationException(string.Format("Unknown BaseBlock type {0}", baseBlock.GetType())); - } - - // Add a block to be processed later, including all its enclosing ScopeBlocks. - void addBaseBlock(BaseBlock baseBlock) { - for (BaseBlock bb = baseBlock; bb != null; bb = bb.Parent) - baseBlocksToCheck.Push(bb); - } - - void processScopeBlock(ScopeBlock scopeBlock) { - if (scopeBlock == null || checkedScopeBlocks.ContainsKey(scopeBlock)) - return; - checkedScopeBlocks[scopeBlock] = true; - addBaseBlock(scopeBlock); - - if (scopeBlock is TryBlock) { - var tryBlock = (TryBlock)scopeBlock; - foreach (var handler in tryBlock.TryHandlerBlocks) - addScopeBlock(handler); - } - else if (scopeBlock is TryHandlerBlock) { - var tryHandlerBlock = (TryHandlerBlock)scopeBlock; - addScopeBlock(tryHandlerBlock.FilterHandlerBlock); - addScopeBlock(tryHandlerBlock.HandlerBlock); - } - } - } - public void getCode(out IList allInstructions, out IList allExceptionHandlers) { new CodeGenerator(methodBlocks).getCode(out allInstructions, out allExceptionHandlers); } diff --git a/blocks/DeadBlocksRemover.cs b/blocks/DeadBlocksRemover.cs new file mode 100644 index 00000000..1fc8e743 --- /dev/null +++ b/blocks/DeadBlocksRemover.cs @@ -0,0 +1,144 @@ +/* + 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; + +namespace de4dot.blocks { + class DeadBlocksRemover { + MethodBlocks methodBlocks; + Dictionary checkedBaseBlocks = new Dictionary(); + Dictionary checkedScopeBlocks = new Dictionary(); + Stack baseBlocksToCheck = new Stack(); + Stack scopeBlocksToCheck = new Stack(); + + public DeadBlocksRemover(MethodBlocks methodBlocks) { + this.methodBlocks = methodBlocks; + } + + public int remove() { + addScopeBlock(methodBlocks); + processAll(); + return removeDeadBlocks(); + } + + class ScopeBlockInfo { + public ScopeBlock scopeBlock; + public IList deadBlocks = new List(); + public ScopeBlockInfo(ScopeBlock scopeBlock) { + this.scopeBlock = scopeBlock; + } + } + + int removeDeadBlocks() { + int numDeadBlocks = 0; + + var infos = new Dictionary(); + var deadBlocksDict = new Dictionary(); + foreach (var baseBlock in findDeadBlocks()) { + deadBlocksDict[baseBlock] = true; + ScopeBlock parent = (ScopeBlock)baseBlock.Parent; + ScopeBlockInfo info; + if (!infos.TryGetValue(parent, out info)) + infos[parent] = info = new ScopeBlockInfo(parent); + info.deadBlocks.Add(baseBlock); + numDeadBlocks++; + } + + foreach (var info in infos.Values) + info.scopeBlock.removeAllDeadBlocks(info.deadBlocks, deadBlocksDict); + + return numDeadBlocks; + } + + IList findDeadBlocks() { + var deadBlocks = new List(); + + foreach (var bb in methodBlocks.getAllBaseBlocks()) { + if (!checkedBaseBlocks.ContainsKey(bb)) + deadBlocks.Add(bb); + } + + return deadBlocks; + } + + void addScopeBlock(ScopeBlock scopeBlock) { + scopeBlocksToCheck.Push(scopeBlock); + } + + void processAll() { + bool didSomething; + do { + didSomething = false; + while (baseBlocksToCheck.Count > 0) { + processBaseBlock(baseBlocksToCheck.Pop()); + didSomething = true; + } + while (scopeBlocksToCheck.Count > 0) { + processScopeBlock(scopeBlocksToCheck.Pop()); + didSomething = true; + } + } while (didSomething); + } + + void processBaseBlock(BaseBlock baseBlock) { + if (baseBlock == null || checkedBaseBlocks.ContainsKey(baseBlock)) + return; + checkedBaseBlocks[baseBlock] = true; + + if (baseBlock is Block) { + var block = (Block)baseBlock; + foreach (var block2 in block.getTargets()) + addBaseBlock(block2); + } + else if (baseBlock is ScopeBlock) { + var scopeBlock = (ScopeBlock)baseBlock; + addScopeBlock(scopeBlock); + if (scopeBlock.BaseBlocks != null && scopeBlock.BaseBlocks.Count > 0) + addBaseBlock(scopeBlock.BaseBlocks[0]); + } + else + throw new ApplicationException(string.Format("Unknown BaseBlock type {0}", baseBlock.GetType())); + } + + // Add a block to be processed later, including all its enclosing ScopeBlocks. + void addBaseBlock(BaseBlock baseBlock) { + for (BaseBlock bb = baseBlock; bb != null; bb = bb.Parent) + baseBlocksToCheck.Push(bb); + } + + void processScopeBlock(ScopeBlock scopeBlock) { + if (scopeBlock == null || checkedScopeBlocks.ContainsKey(scopeBlock)) + return; + checkedScopeBlocks[scopeBlock] = true; + addBaseBlock(scopeBlock); + + if (scopeBlock is TryBlock) { + var tryBlock = (TryBlock)scopeBlock; + foreach (var handler in tryBlock.TryHandlerBlocks) + addScopeBlock(handler); + } + else if (scopeBlock is TryHandlerBlock) { + var tryHandlerBlock = (TryHandlerBlock)scopeBlock; + addScopeBlock(tryHandlerBlock.FilterHandlerBlock); + addScopeBlock(tryHandlerBlock.HandlerBlock); + } + } + } +} diff --git a/blocks/DotNetUtils.cs b/blocks/DotNetUtils.cs index c0a64426..3d377474 100644 --- a/blocks/DotNetUtils.cs +++ b/blocks/DotNetUtils.cs @@ -483,26 +483,44 @@ namespace de4dot.blocks { return !MemberReferenceHelper.verifyType(method.MethodReturnType.ReturnType, "mscorlib", "System.Void"); } - public static void updateStack(Instruction instr, ref int stack) { - if (instr.OpCode.FlowControl == FlowControl.Call) { - var method = (IMethodSignature)instr.Operand; - if (hasReturnValue(method)) - stack++; - if (method.HasThis && instr.OpCode.Code == Code.Newobj) - stack++; - - if (method.HasParameters) - stack -= method.Parameters.Count; - if (method.HasThis && instr.OpCode.Code != Code.Newobj) - stack--; - } + public static void updateStack(Instruction instr, ref int stack, bool methodHasReturnValue) { + int pushes, pops; + calculateStackUsage(instr, methodHasReturnValue, out pushes, out pops); + if (pops == -1) + stack = 0; else - updateStack_nonCall(instr, ref stack); + stack += pushes - pops; } - static void updateStack_nonCall(Instruction instr, ref int stack) { + // Sets pops to -1 if the stack is supposed to be cleared + public static void calculateStackUsage(Instruction instr, bool methodHasReturnValue, out int pushes, out int pops) { + if (instr.OpCode.FlowControl == FlowControl.Call) + calculateStackUsage_call(instr, out pushes, out pops); + else + calculateStackUsage_nonCall(instr, methodHasReturnValue, out pushes, out pops); + } + + static void calculateStackUsage_call(Instruction instr, out int pushes, out int pops) { + pushes = 0; + pops = 0; + + var method = (IMethodSignature)instr.Operand; + if (hasReturnValue(method) || (instr.OpCode.Code == Code.Newobj && method.HasThis)) + pushes++; + + if (method.HasParameters) + pops += method.Parameters.Count; + if (method.HasThis && instr.OpCode.Code != Code.Newobj) + pops++; + } + + // Sets pops to -1 if the stack is supposed to be cleared + static void calculateStackUsage_nonCall(Instruction instr, bool methodHasReturnValue, out int pushes, out int pops) { StackBehaviour stackBehavior; + pushes = 0; + pops = 0; + stackBehavior = instr.OpCode.StackBehaviourPush; switch (stackBehavior) { case StackBehaviour.Push0: @@ -514,11 +532,11 @@ namespace de4dot.blocks { case StackBehaviour.Pushr4: case StackBehaviour.Pushr8: case StackBehaviour.Pushref: - stack++; + pushes++; break; case StackBehaviour.Push1_push1: - stack += 2; + pushes += 2; break; case StackBehaviour.Varpush: // only call, calli, callvirt which are handled elsewhere @@ -534,7 +552,7 @@ namespace de4dot.blocks { case StackBehaviour.Pop1: case StackBehaviour.Popi: case StackBehaviour.Popref: - stack--; + pops++; break; case StackBehaviour.Pop1_pop1: @@ -545,7 +563,7 @@ namespace de4dot.blocks { case StackBehaviour.Popi_popr8: case StackBehaviour.Popref_pop1: case StackBehaviour.Popref_popi: - stack -= 2; + pops += 2; break; case StackBehaviour.Popi_popi_popi: @@ -554,16 +572,16 @@ namespace de4dot.blocks { case StackBehaviour.Popref_popi_popr4: case StackBehaviour.Popref_popi_popr8: case StackBehaviour.Popref_popi_popref: - stack -= 3; + pops += 3; break; case StackBehaviour.PopAll: - stack = 0; + pops = -1; break; case StackBehaviour.Varpop: // call, calli, callvirt, newobj (all handled elsewhere), and ret - // It's RET. Ignore decrementing stack once if method returns something - // since it's not important and we don't know whether it returns anything. + if (methodHasReturnValue) + pops++; break; default: @@ -583,8 +601,14 @@ namespace de4dot.blocks { } public static string getFullAssemblyName(IMetadataScope scope) { + //TODO: Returning scope.Name is probably best since the method could fail. var asmRef = getAssemblyNameReference(scope); return asmRef.FullName; } + + public static bool isAssembly(IMetadataScope scope, string assemblySimpleName) { + return scope.Name == assemblySimpleName || + scope.Name.StartsWith(assemblySimpleName + ",", StringComparison.Ordinal); + } } } diff --git a/blocks/ForwardScanOrder.cs b/blocks/ForwardScanOrder.cs index 5e1affc4..c8d3b498 100644 --- a/blocks/ForwardScanOrder.cs +++ b/blocks/ForwardScanOrder.cs @@ -52,7 +52,7 @@ namespace de4dot.blocks { int stack = stackStart; foreach (var instr in block.Instructions) - DotNetUtils.updateStack(instr.Instruction, ref stack); + DotNetUtils.updateStack(instr.Instruction, ref stack, false); stackEnd = stack; } } diff --git a/blocks/blocks.csproj b/blocks/blocks.csproj index 75874bed..f36efde1 100644 --- a/blocks/blocks.csproj +++ b/blocks/blocks.csproj @@ -35,8 +35,14 @@ + + + + + + diff --git a/blocks/cflow/BlockControlFlowDeobfuscator.cs b/blocks/cflow/BlockControlFlowDeobfuscator.cs new file mode 100644 index 00000000..8223f435 --- /dev/null +++ b/blocks/cflow/BlockControlFlowDeobfuscator.cs @@ -0,0 +1,564 @@ +/* + Copyright (C) 2011 de4dot@gmail.com + + This file is part of de4dot. + + de4dot is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + de4dot is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with de4dot. If not, see . +*/ + +using System.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace de4dot.blocks.cflow { + class BlockControlFlowDeobfuscator { + Block block; + InstructionEmulator instructionEmulator = new InstructionEmulator(); + + public void init(Block block, IList args, IList locals) { + this.block = block; + instructionEmulator.init(false, args, locals); + } + + // Returns true if code was updated, false otherwise + public bool deobfuscate() { + var instructions = block.Instructions; + if (instructions.Count == 0) + return false; + for (int i = 0; i < instructions.Count - 1; i++) { + instructionEmulator.emulate(instructions[i].Instruction); + } + + switch (block.LastInstr.OpCode.Code) { + case Code.Beq: + case Code.Beq_S: return emulate_Beq(); + case Code.Bge: + case Code.Bge_S: return emulate_Bge(); + case Code.Bge_Un: + case Code.Bge_Un_S: return emulate_Bge_Un(); + case Code.Bgt: + case Code.Bgt_S: return emulate_Bgt(); + case Code.Bgt_Un: + case Code.Bgt_Un_S: return emulate_Bgt_Un(); + case Code.Ble: + case Code.Ble_S: return emulate_Ble(); + case Code.Ble_Un: + case Code.Ble_Un_S: return emulate_Ble_Un(); + case Code.Blt: + case Code.Blt_S: return emulate_Blt(); + case Code.Blt_Un: + case Code.Blt_Un_S: return emulate_Blt_Un(); + case Code.Bne_Un: + case Code.Bne_Un_S: return emulate_Bne_Un(); + case Code.Brfalse: + case Code.Brfalse_S:return emulate_Brfalse(); + case Code.Brtrue: + case Code.Brtrue_S: return emulate_Brtrue(); + + default: + return false; + } + } + + bool emulateBranch(int stackArgs, bool isTaken) { + // Pop the arguments to the bcc instruction. The dead code remover will get rid of the + // pop and any pushed arguments. Insert the pops just before the bcc instr. + for (int i = 0; i < stackArgs; i++) + block.insert(block.Instructions.Count - 1, Instruction.Create(OpCodes.Pop)); + + block.replaceBccWithBranch(isTaken); + return true; + } + + bool emulate_Beq() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: If it's an unknown int32/64, push 1 if val1 is same ref as val2 + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, int1.value == int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, long1.value == long2.value); + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + return emulateBranch(2, real1.value == real2.value); + } + else if (val1.valueType == ValueType.Null && val2.valueType == ValueType.Null) { + return emulateBranch(2, true); + } + else { + return false; + } + } + + bool emulate_Bne_Un() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: If it's an unknown int32/64, push 1 if val1 is same ref as val2 + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, (uint)int1.value != (uint)int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, (ulong)long1.value != (ulong)long2.value); + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + return emulateBranch(2, real1.value != real2.value); + } + else if (val1.valueType == ValueType.Null && val2.valueType == ValueType.Null) { + return emulateBranch(2, false); + } + else { + return false; + } + } + + bool emulate_Bge() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, int1.value >= int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, long1.value >= long2.value); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == int.MaxValue) + return emulateBranch(2, true); // max >= x => true + else + return false; + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == int.MinValue) + return emulateBranch(2, true); // x >= min => true + else + return false; + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == long.MaxValue) + return emulateBranch(2, true); // max >= x => true + else + return false; + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == long.MinValue) + return emulateBranch(2, true); // x >= min => true + else + return false; + } + else { + return false; + } + } + + bool emulate_Bge_Un() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, (uint)int1.value >= (uint)int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, (ulong)long1.value >= (ulong)long2.value); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if ((uint)int1.value == uint.MaxValue) + return emulateBranch(2, true); // max >= x => true + else + return false; + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if ((uint)int2.value == uint.MinValue) + return emulateBranch(2, true); // x >= min => true + else + return false; + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if ((ulong)long1.value == ulong.MaxValue) + return emulateBranch(2, true); // max >= x => true + else + return false; + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if ((ulong)long2.value == ulong.MinValue) + return emulateBranch(2, true); // x >= min => true + else + return false; + } + else { + return false; + } + } + + bool emulate_Bgt() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, int1.value > int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, long1.value > long2.value); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == int.MinValue) + return emulateBranch(2, false); // min > x => false + else + return false; + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == int.MaxValue) + return emulateBranch(2, false); // x > max => false + else + return false; + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == long.MinValue) + return emulateBranch(2, false); // min > x => false + else + return false; + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == long.MaxValue) + return emulateBranch(2, false); // x > max => false + else + return false; + } + else { + return false; + } + } + + bool emulate_Bgt_Un() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, (uint)int1.value > (uint)int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, (ulong)long1.value > (ulong)long2.value); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if ((uint)int1.value == uint.MinValue) + return emulateBranch(2, false); // min > x => false + else + return false; + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if ((uint)int2.value == uint.MaxValue) + return emulateBranch(2, false); // x > max => false + else + return false; + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if ((ulong)long1.value == ulong.MinValue) + return emulateBranch(2, false); // min > x => false + else + return false; + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if ((ulong)long2.value == ulong.MaxValue) + return emulateBranch(2, false); // x > max => false + else + return false; + } + else { + return false; + } + } + + bool emulate_Ble() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, int1.value <= int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, long1.value <= long2.value); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == int.MinValue) + return emulateBranch(2, true); // min <= x => true + else + return false; + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == int.MaxValue) + return emulateBranch(2, true); // x <= max => true + else + return false; + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == long.MinValue) + return emulateBranch(2, true); // min <= x => true + else + return false; + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == long.MaxValue) + return emulateBranch(2, true); // x <= max => true + else + return false; + } + else { + return false; + } + } + + bool emulate_Ble_Un() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, (uint)int1.value <= (uint)int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, (ulong)long1.value <= (ulong)long2.value); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if ((uint)int1.value == uint.MinValue) + return emulateBranch(2, true); // min <= x => true + else + return false; + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if ((uint)int2.value == uint.MaxValue) + return emulateBranch(2, true); // x <= max => true + else + return false; + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if ((ulong)long1.value == ulong.MinValue) + return emulateBranch(2, true); // min <= x => true + else + return false; + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if ((ulong)long2.value == ulong.MaxValue) + return emulateBranch(2, true); // x <= max => true + else + return false; + } + else { + return false; + } + } + + bool emulate_Blt() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, int1.value < int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, long1.value < long2.value); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == int.MaxValue) + return emulateBranch(2, false); // max < x => false + else + return false; + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == int.MinValue) + return emulateBranch(2, false); // x < min => false + else + return false; + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == long.MaxValue) + return emulateBranch(2, false); // max < x => false + else + return false; + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == long.MinValue) + return emulateBranch(2, false); // x < min => false + else + return false; + } + else { + return false; + } + } + + bool emulate_Blt_Un() { + var val2 = instructionEmulator.pop(); + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + return emulateBranch(2, (uint)int1.value < (uint)int2.value); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + return emulateBranch(2, (ulong)long1.value < (ulong)long2.value); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if ((uint)int1.value == uint.MaxValue) + return emulateBranch(2, false); // max < x => false + else + return false; + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if ((uint)int2.value == uint.MinValue) + return emulateBranch(2, false); // x < min => false + else + return false; + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if ((ulong)long1.value == ulong.MaxValue) + return emulateBranch(2, false); // max < x => false + else + return false; + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if ((ulong)long2.value == ulong.MinValue) + return emulateBranch(2, false); // x < min => false + else + return false; + } + else { + return false; + } + } + + bool emulate_Brfalse() { + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32) + return emulateBranch(1, ((Int32Value)val1).value == 0); + else if (val1.valueType == ValueType.Int64) + return emulateBranch(1, ((Int64Value)val1).value == 0); + else if (val1.valueType == ValueType.Null) + return emulateBranch(1, true); + else + return false; + } + + bool emulate_Brtrue() { + var val1 = instructionEmulator.pop(); + + //TODO: Support floats + + if (val1.valueType == ValueType.Int32) + return emulateBranch(1, ((Int32Value)val1).value != 0); + else if (val1.valueType == ValueType.Int64) + return emulateBranch(1, ((Int64Value)val1).value != 0); + else if (val1.valueType == ValueType.Null) + return emulateBranch(1, false); + else + return false; + } + } +} diff --git a/blocks/cflow/BlocksControlFlowDeobfuscator.cs b/blocks/cflow/BlocksControlFlowDeobfuscator.cs new file mode 100644 index 00000000..3af5e0ad --- /dev/null +++ b/blocks/cflow/BlocksControlFlowDeobfuscator.cs @@ -0,0 +1,67 @@ +/* + 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; + +namespace de4dot.blocks.cflow { + public class BlocksControlFlowDeobfuscator { + BlockControlFlowDeobfuscator blockControlFlowDeobfuscator = new BlockControlFlowDeobfuscator(); + Blocks blocks; + int numRemovedDeadBlocks; + + public int NumberOfRemovedDeadBlocks { + get { return numRemovedDeadBlocks; } + } + + public void init(Blocks blocks) { + this.blocks = blocks; + numRemovedDeadBlocks = 0; + } + + public void deobfuscate() { + bool changed; + do { + changed = false; + removeDeadBlocks(); + mergeBlocks(); + foreach (var block in blocks.MethodBlocks.getAllBlocks()) { + //TODO: Only do this if it's a bcc block. switch blocks should use other code. + blockControlFlowDeobfuscator.init(block, blocks.Method.Parameters, blocks.Locals); + changed |= blockControlFlowDeobfuscator.deobfuscate(); + } + } while (changed); + } + + void removeDeadBlocks() { + numRemovedDeadBlocks += new DeadBlocksRemover(blocks.MethodBlocks).remove(); + } + + void mergeBlocks() { + foreach (var scopeBlock in getAllScopeBlocks(blocks.MethodBlocks)) + scopeBlock.mergeBlocks(); + } + + IEnumerable getAllScopeBlocks(ScopeBlock scopeBlock) { + var list = new List(); + list.Add(scopeBlock); + list.AddRange(scopeBlock.getAllScopeBlocks()); + return list; + } + } +} diff --git a/blocks/cflow/InstructionEmulator.cs b/blocks/cflow/InstructionEmulator.cs new file mode 100644 index 00000000..5a6298cc --- /dev/null +++ b/blocks/cflow/InstructionEmulator.cs @@ -0,0 +1,1336 @@ +/* + 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; +using Mono.Cecil.Cil; + +namespace de4dot.blocks.cflow { + class InstructionEmulator { + ValueStack valueStack = new ValueStack(); + IList parameterDefinitions; + IList variableDefinitions; + List args = new List(); + List locals = new List(); + + public void init(bool initLocals, IList parameterDefinitions, IList variableDefinitions) { + this.parameterDefinitions = parameterDefinitions; + this.variableDefinitions = variableDefinitions; + valueStack.init(); + + initValueList(args, parameterDefinitions.Count); + + if (initLocals) { + locals.Clear(); + for (int i = 0; i < variableDefinitions.Count; i++) { + var localType = variableDefinitions[i].VariableType; + if (!localType.IsValueType) + locals.Add(NullValue.Instance); + else if (DotNetUtils.isAssembly(localType.Scope, "mscorlib")) { + switch (localType.FullName) { + case "System.Boolean": + case "System.Byte": + case "System.SByte": + case "System.Int16": + case "System.Int32": + case "System.UInt16": + case "System.UInt32": + locals.Add(new Int32Value(0)); + break; + case "System.Int64": + case "System.UInt64": + locals.Add(new Int64Value(0)); + break; + case "System.Single": + case "System.Double": + locals.Add(new Real8Value(0)); + break; + default: + locals.Add(new UnknownValue()); + break; + } + } + else + locals.Add(new UnknownValue()); + } + } + else + initValueList(locals, variableDefinitions.Count); + } + + void initValueList(List list, int size) { + list.Clear(); + for (int i = 0; i < size; i++) + list.Add(new UnknownValue()); + } + + static Value getValue(List list, int i) { + if (0 <= i && i < list.Count) + return list[i]; + return new UnknownValue(); + } + + Value getArg(int i) { + return getValue(args, i); + } + + Value getArg(ParameterDefinition arg) { + return getArg(arg.Index); + } + + void setArg(int index, Value value) { + if (0 <= index && index < args.Count) + args[index] = value; + } + + Value getLocal(int i) { + return getValue(locals, i); + } + + Value getLocal(VariableDefinition local) { + return getLocal(local.Index); + } + + void setLocal(int index, Value value) { + if (0 <= index && index < locals.Count) + locals[index] = value; + } + + public Value pop() { + return valueStack.pop(); + } + + public void emulate(Instruction instr) { + switch (instr.OpCode.Code) { + case Code.Starg: + case Code.Starg_S: emulate_Starg(((ParameterDefinition)instr.Operand).Index); break; + case Code.Stloc: + case Code.Stloc_S: emulate_Stloc(((VariableDefinition)instr.Operand).Index); break; + case Code.Stloc_0: emulate_Stloc(0); break; + case Code.Stloc_1: emulate_Stloc(1); break; + case Code.Stloc_2: emulate_Stloc(2); break; + case Code.Stloc_3: emulate_Stloc(3); break; + + case Code.Ldarg: + case Code.Ldarg_S: valueStack.push(getArg((ParameterDefinition)instr.Operand)); break; + case Code.Ldarg_0: valueStack.push(getArg(0)); break; + case Code.Ldarg_1: valueStack.push(getArg(1)); break; + case Code.Ldarg_2: valueStack.push(getArg(2)); break; + case Code.Ldarg_3: valueStack.push(getArg(3)); break; + case Code.Ldloc: + case Code.Ldloc_S: valueStack.push(getLocal((VariableDefinition)instr.Operand)); break; + case Code.Ldloc_0: valueStack.push(getLocal(0)); break; + case Code.Ldloc_1: valueStack.push(getLocal(1)); break; + case Code.Ldloc_2: valueStack.push(getLocal(2)); break; + case Code.Ldloc_3: valueStack.push(getLocal(3)); break; + + case Code.Ldarga: + case Code.Ldarga_S: emulate_Ldarga(((ParameterDefinition)instr.Operand).Index); break; + case Code.Ldloca: + case Code.Ldloca_S: emulate_Ldloca(((VariableDefinition)instr.Operand).Index); break; + + case Code.Dup: valueStack.copyTop(); break; + + case Code.Ldc_I4: valueStack.push(new Int32Value((int)instr.Operand)); break; + case Code.Ldc_I4_S: valueStack.push(new Int32Value((sbyte)instr.Operand)); break; + case Code.Ldc_I8: valueStack.push(new Int64Value((long)instr.Operand)); break; + case Code.Ldc_R4: valueStack.push(new Real8Value((float)instr.Operand)); break; + case Code.Ldc_R8: valueStack.push(new Real8Value((double)instr.Operand)); break; + case Code.Ldc_I4_0: valueStack.push(new Int32Value(0)); break; + case Code.Ldc_I4_1: valueStack.push(new Int32Value(1)); break; + case Code.Ldc_I4_2: valueStack.push(new Int32Value(2)); break; + case Code.Ldc_I4_3: valueStack.push(new Int32Value(3)); break; + case Code.Ldc_I4_4: valueStack.push(new Int32Value(4)); break; + case Code.Ldc_I4_5: valueStack.push(new Int32Value(5)); break; + case Code.Ldc_I4_6: valueStack.push(new Int32Value(6)); break; + case Code.Ldc_I4_7: valueStack.push(new Int32Value(7)); break; + case Code.Ldc_I4_8: valueStack.push(new Int32Value(8)); break; + case Code.Ldc_I4_M1:valueStack.push(new Int32Value(-1)); break; + case Code.Ldnull: valueStack.push(NullValue.Instance); break; + case Code.Ldstr: valueStack.push(new StringValue((string)instr.Operand)); break; + case Code.Box: valueStack.push(new BoxedValue(valueStack.pop())); break; + + case Code.Conv_U1: emulate_Conv_U1(instr); break; + case Code.Conv_U2: emulate_Conv_U2(instr); break; + case Code.Conv_U4: emulate_Conv_U4(instr); break; + case Code.Conv_U8: emulate_Conv_U8(instr); break; + case Code.Conv_I1: emulate_Conv_I1(instr); break; + case Code.Conv_I2: emulate_Conv_I2(instr); break; + case Code.Conv_I4: emulate_Conv_I4(instr); break; + case Code.Conv_I8: emulate_Conv_I8(instr); break; + case Code.Add: emulate_Add(instr); break; + case Code.Sub: emulate_Sub(instr); break; + case Code.Mul: emulate_Mul(instr); break; + case Code.Div: emulate_Div(instr); break; + case Code.Div_Un: emulate_Div_Un(instr); break; + case Code.Rem: emulate_Rem(instr); break; + case Code.Rem_Un: emulate_Rem_Un(instr); break; + case Code.Neg: emulate_Neg(instr); break; + case Code.And: emulate_And(instr); break; + case Code.Or: emulate_Or(instr); break; + case Code.Xor: emulate_Xor(instr); break; + case Code.Not: emulate_Not(instr); break; + case Code.Shl: emulate_Shl(instr); break; + case Code.Shr: emulate_Shr(instr); break; + case Code.Shr_Un: emulate_Shr_Un(instr); break; + case Code.Ceq: emulate_Ceq(instr); break; + case Code.Cgt: emulate_Cgt(instr); break; + case Code.Cgt_Un: emulate_Cgt_Un(instr); break; + case Code.Clt: emulate_Clt(instr); break; + case Code.Clt_Un: emulate_Clt_Un(instr); break; + case Code.Unbox_Any:emulate_Unbox_Any(instr); break; + + case Code.Add_Ovf: + case Code.Add_Ovf_Un: + case Code.Sub_Ovf: + case Code.Sub_Ovf_Un: + case Code.Mul_Ovf: + case Code.Mul_Ovf_Un: + + case Code.Unbox: + + case Code.Conv_R_Un: + case Code.Conv_R4: + case Code.Conv_R8: + + case Code.Arglist: + case Code.Beq: + case Code.Beq_S: + case Code.Bge: + case Code.Bge_S: + case Code.Bge_Un: + case Code.Bge_Un_S: + case Code.Bgt: + case Code.Bgt_S: + case Code.Bgt_Un: + case Code.Bgt_Un_S: + case Code.Ble: + case Code.Ble_S: + case Code.Ble_Un: + case Code.Ble_Un_S: + case Code.Blt: + case Code.Blt_S: + case Code.Blt_Un: + case Code.Blt_Un_S: + case Code.Bne_Un: + case Code.Bne_Un_S: + case Code.Brfalse: + case Code.Brfalse_S: + case Code.Brtrue: + case Code.Brtrue_S: + case Code.Br: + case Code.Break: + case Code.Br_S: + case Code.Call: + case Code.Calli: + case Code.Callvirt: + case Code.Castclass: + case Code.Ckfinite: + case Code.Constrained: + case Code.Conv_I: + case Code.Conv_Ovf_I: + case Code.Conv_Ovf_I1: + case Code.Conv_Ovf_I1_Un: + case Code.Conv_Ovf_I2: + case Code.Conv_Ovf_I2_Un: + case Code.Conv_Ovf_I4: + case Code.Conv_Ovf_I4_Un: + case Code.Conv_Ovf_I8: + case Code.Conv_Ovf_I8_Un: + case Code.Conv_Ovf_I_Un: + case Code.Conv_Ovf_U: + case Code.Conv_Ovf_U1: + case Code.Conv_Ovf_U1_Un: + case Code.Conv_Ovf_U2: + case Code.Conv_Ovf_U2_Un: + case Code.Conv_Ovf_U4: + case Code.Conv_Ovf_U4_Un: + case Code.Conv_Ovf_U8: + case Code.Conv_Ovf_U8_Un: + case Code.Conv_Ovf_U_Un: + case Code.Conv_U: + case Code.Cpblk: + case Code.Cpobj: + case Code.Endfilter: + case Code.Endfinally: + case Code.Initblk: + case Code.Initobj: + case Code.Isinst: + case Code.Jmp: + case Code.Ldelema: + case Code.Ldelem_Any: + case Code.Ldelem_I: + case Code.Ldelem_I1: + case Code.Ldelem_I2: + case Code.Ldelem_I4: + case Code.Ldelem_I8: + case Code.Ldelem_R4: + case Code.Ldelem_R8: + case Code.Ldelem_Ref: + case Code.Ldelem_U1: + case Code.Ldelem_U2: + case Code.Ldelem_U4: + case Code.Ldfld: + case Code.Ldflda: + case Code.Ldftn: + case Code.Ldind_I: + case Code.Ldind_I1: + case Code.Ldind_I2: + case Code.Ldind_I4: + case Code.Ldind_I8: + case Code.Ldind_R4: + case Code.Ldind_R8: + case Code.Ldind_Ref: + case Code.Ldind_U1: + case Code.Ldind_U2: + case Code.Ldind_U4: + case Code.Ldlen: + case Code.Ldobj: + case Code.Ldsfld: + case Code.Ldsflda: + case Code.Ldtoken: + case Code.Ldvirtftn: + case Code.Leave: + case Code.Leave_S: + case Code.Localloc: + case Code.Mkrefany: + case Code.Newarr: + case Code.Newobj: + case Code.No: + case Code.Nop: + case Code.Pop: + case Code.Readonly: + case Code.Refanytype: + case Code.Refanyval: + case Code.Ret: + case Code.Rethrow: + case Code.Sizeof: + case Code.Stelem_Any: + case Code.Stelem_I: + case Code.Stelem_I1: + case Code.Stelem_I2: + case Code.Stelem_I4: + case Code.Stelem_I8: + case Code.Stelem_R4: + case Code.Stelem_R8: + case Code.Stelem_Ref: + case Code.Stfld: + case Code.Stind_I: + case Code.Stind_I1: + case Code.Stind_I2: + case Code.Stind_I4: + case Code.Stind_I8: + case Code.Stind_R4: + case Code.Stind_R8: + case Code.Stind_Ref: + case Code.Stobj: + case Code.Stsfld: + case Code.Switch: + case Code.Tail: + case Code.Throw: + case Code.Unaligned: + case Code.Volatile: + default: + updateStack(instr); + break; + } + } + + void updateStack(Instruction instr) { + int pushes, pops; + DotNetUtils.calculateStackUsage(instr, false, out pushes, out pops); + if (pops == -1) + valueStack.clear(); + else { + valueStack.pop(pops); + valueStack.push(pushes); + } + } + + void switchUnknownToSecondOperand(ref Value op1, ref Value op2) { + if (op2.valueType == ValueType.Unknown) + return; + if (op1.valueType != ValueType.Unknown) + return; + Value tmp = op1; + op1 = op2; + op2 = tmp; + } + + void emulate_Conv_U1(Instruction instr) { + //TODO: Doc says that result is sign-extended. You zero-extend it. Is that correct? + var val1 = valueStack.pop(); + switch (val1.valueType) { + case ValueType.Int32: + var val32 = (Int32Value)val1; + valueStack.push(new Int32Value((int)(byte)val32.value)); + break; + + case ValueType.Int64: + var val64 = (Int64Value)val1; + valueStack.push(new Int32Value((int)(byte)val64.value)); + break; + + case ValueType.Real8: + var valr8 = (Real8Value)val1; + valueStack.push(new Int32Value((int)(byte)valr8.value)); + break; + + default: + valueStack.pushUnknown(); + break; + } + } + + void emulate_Conv_I1(Instruction instr) { + var val1 = valueStack.pop(); + switch (val1.valueType) { + case ValueType.Int32: + var val32 = (Int32Value)val1; + valueStack.push(new Int32Value((int)(sbyte)val32.value)); + break; + + case ValueType.Int64: + var val64 = (Int64Value)val1; + valueStack.push(new Int32Value((int)(sbyte)val64.value)); + break; + + case ValueType.Real8: + var valr8 = (Real8Value)val1; + valueStack.push(new Int32Value((int)(sbyte)valr8.value)); + break; + + default: + valueStack.pushUnknown(); + break; + } + } + + void emulate_Conv_U2(Instruction instr) { + //TODO: Doc says that result is sign-extended. You zero-extend it. Is that correct? + var val1 = valueStack.pop(); + switch (val1.valueType) { + case ValueType.Int32: + var val32 = (Int32Value)val1; + valueStack.push(new Int32Value((int)(ushort)val32.value)); + break; + + case ValueType.Int64: + var val64 = (Int64Value)val1; + valueStack.push(new Int32Value((int)(ushort)val64.value)); + break; + + case ValueType.Real8: + var valr8 = (Real8Value)val1; + valueStack.push(new Int32Value((int)(ushort)valr8.value)); + break; + + default: + valueStack.pushUnknown(); + break; + } + } + + void emulate_Conv_I2(Instruction instr) { + var val1 = valueStack.pop(); + switch (val1.valueType) { + case ValueType.Int32: + var val32 = (Int32Value)val1; + valueStack.push(new Int32Value((int)(short)val32.value)); + break; + + case ValueType.Int64: + var val64 = (Int64Value)val1; + valueStack.push(new Int32Value((int)(short)val64.value)); + break; + + case ValueType.Real8: + var valr8 = (Real8Value)val1; + valueStack.push(new Int32Value((int)(short)valr8.value)); + break; + + default: + valueStack.pushUnknown(); + break; + } + } + + void emulate_Conv_U4(Instruction instr) { + var val1 = valueStack.pop(); + switch (val1.valueType) { + case ValueType.Int32: + valueStack.push(val1); + break; + + case ValueType.Int64: + var val64 = (Int64Value)val1; + valueStack.push(new Int32Value((int)(uint)val64.value)); + break; + + case ValueType.Real8: + var valr8 = (Real8Value)val1; + valueStack.push(new Int32Value((int)(uint)valr8.value)); + break; + + default: + valueStack.pushUnknown(); + break; + } + } + + void emulate_Conv_I4(Instruction instr) { + var val1 = valueStack.pop(); + switch (val1.valueType) { + case ValueType.Int32: + valueStack.push(val1); + break; + + case ValueType.Int64: + var val64 = (Int64Value)val1; + valueStack.push(new Int32Value((int)val64.value)); + break; + + case ValueType.Real8: + var valr8 = (Real8Value)val1; + valueStack.push(new Int32Value((int)valr8.value)); + break; + + default: + valueStack.pushUnknown(); + break; + } + } + + void emulate_Conv_U8(Instruction instr) { + //TODO: Doc says that result is sign-extended. You zero-extend it. Is that correct? + var val1 = valueStack.pop(); + switch (val1.valueType) { + case ValueType.Int32: + var val32 = (Int32Value)val1; + valueStack.push(new Int64Value((long)(ulong)(uint)val32.value)); + break; + + case ValueType.Int64: + valueStack.push(val1); + break; + + case ValueType.Real8: + var valr8 = (Real8Value)val1; + valueStack.push(new Int64Value((long)(ulong)valr8.value)); + break; + + default: + valueStack.pushUnknown(); + break; + } + } + + void emulate_Conv_I8(Instruction instr) { + var val1 = valueStack.pop(); + switch (val1.valueType) { + case ValueType.Int32: + var val32 = (Int32Value)val1; + valueStack.push(new Int64Value((long)val32.value)); + break; + + case ValueType.Int64: + valueStack.push(val1); + break; + + case ValueType.Real8: + var valr8 = (Real8Value)val1; + valueStack.push(new Int64Value((long)valr8.value)); + break; + + default: + valueStack.pushUnknown(); + break; + } + } + + void emulate_Add(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + switchUnknownToSecondOperand(ref val1, ref val2); + + if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == 0) + valueStack.push(val2); + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value + int2.value)); + } + else + valueStack.pushUnknown(); + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == 0) + valueStack.push(val2); + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + valueStack.push(new Int64Value(long1.value + long2.value)); + } + else + valueStack.pushUnknown(); + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + valueStack.push(new Real8Value(real1.value + real2.value)); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Sub(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value - int2.value)); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + valueStack.push(new Int64Value(long1.value - long2.value)); + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + valueStack.push(new Real8Value(real1.value - real2.value)); + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == 0) + valueStack.push(val1); + else + valueStack.pushUnknown(); + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == 0) + valueStack.push(val1); + else + valueStack.pushUnknown(); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Mul(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + switchUnknownToSecondOperand(ref val1, ref val2); + + if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == 1) + valueStack.push(val2); + else if (int1.value == 0) + valueStack.push(new Int32Value(0)); + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value * int2.value)); + } + else + valueStack.pushUnknown(); + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == 1) + valueStack.push(val2); + else if (long1.value == 0) + valueStack.push(new Int64Value(0)); + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + valueStack.push(new Int64Value(long1.value * long2.value)); + } + else + valueStack.pushUnknown(); + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + valueStack.push(new Real8Value(real1.value * real2.value)); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Div(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + try { + valueStack.push(new Int32Value(int1.value / int2.value)); + } + catch (ArithmeticException) { + valueStack.pushUnknown(); + } + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + try { + valueStack.push(new Int64Value(long1.value / long2.value)); + } + catch (ArithmeticException) { + valueStack.pushUnknown(); + } + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + valueStack.push(new Real8Value(real1.value / real2.value)); + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == 1) + valueStack.push(val1); + else + valueStack.pushUnknown(); + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == 1) + valueStack.push(val1); + else + valueStack.pushUnknown(); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Div_Un(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + try { + valueStack.push(new Int32Value((int)((uint)int1.value / (uint)int2.value))); + } + catch (ArithmeticException) { + valueStack.pushUnknown(); + } + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + try { + valueStack.push(new Int64Value((long)((ulong)long1.value / (ulong)long2.value))); + } + catch (ArithmeticException) { + valueStack.pushUnknown(); + } + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == 1) + valueStack.push(val1); + else + valueStack.pushUnknown(); + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == 1) + valueStack.push(val1); + else + valueStack.pushUnknown(); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Rem(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + try { + valueStack.push(new Int32Value(int1.value % int2.value)); + } + catch (ArithmeticException) { + valueStack.pushUnknown(); + } + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + try { + valueStack.push(new Int64Value(long1.value % long2.value)); + } + catch (ArithmeticException) { + valueStack.pushUnknown(); + } + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + valueStack.push(new Real8Value(real1.value % real2.value)); + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == 1) + valueStack.push(new Int32Value(0)); + else + valueStack.pushUnknown(); + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == 1) + valueStack.push(new Int64Value(0)); + else + valueStack.pushUnknown(); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Rem_Un(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + try { + valueStack.push(new Int32Value((int)((uint)int1.value % (uint)int2.value))); + } + catch (ArithmeticException) { + valueStack.pushUnknown(); + } + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + try { + valueStack.push(new Int64Value((long)((ulong)long1.value % (ulong)long2.value))); + } + catch (ArithmeticException) { + valueStack.pushUnknown(); + } + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == 1) + valueStack.push(new Int32Value(0)); + else + valueStack.pushUnknown(); + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == 1) + valueStack.push(new Int64Value(0)); + else + valueStack.pushUnknown(); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Neg(Instruction instr) { + var val1 = valueStack.pop(); + + if (val1.valueType == ValueType.Int32) + valueStack.push(new Int32Value(-((Int32Value)val1).value)); + else if (val1.valueType == ValueType.Int64) + valueStack.push(new Int64Value(-((Int64Value)val1).value)); + else if (val1.valueType == ValueType.Real8) + valueStack.push(new Real8Value(-((Real8Value)val1).value)); + else + valueStack.pushUnknown(); + } + + void emulate_And(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + switchUnknownToSecondOperand(ref val1, ref val2); + + if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == 0) + valueStack.push(new Int32Value(0)); + else if (int1.value == -1) + valueStack.push(val2); + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value & int2.value)); + } + else + valueStack.pushUnknown(); + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == 0) + valueStack.push(new Int64Value(0)); + else if (long1.value == -1) + valueStack.push(val2); + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + valueStack.push(new Int64Value(long1.value & long2.value)); + } + else + valueStack.pushUnknown(); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Or(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + switchUnknownToSecondOperand(ref val1, ref val2); + + if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == -1) + valueStack.push(new Int32Value(-1)); + else if (int1.value == 0) + valueStack.push(val2); + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value | int2.value)); + } + else + valueStack.pushUnknown(); + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == -1) + valueStack.push(new Int64Value(-1)); + else if (long1.value == 0) + valueStack.push(val2); + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + valueStack.push(new Int64Value(long1.value | long2.value)); + } + else + valueStack.pushUnknown(); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Xor(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + switchUnknownToSecondOperand(ref val1, ref val2); + + if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == 0) + valueStack.push(val2); + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value ^ int2.value)); + } + else + valueStack.pushUnknown(); + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == 0) + valueStack.push(val2); + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + valueStack.push(new Int64Value(long1.value ^ long2.value)); + } + else + valueStack.pushUnknown(); + } + else { + valueStack.pushUnknown(); + } + } + + void emulate_Not(Instruction instr) { + var val1 = valueStack.pop(); + + if (val1.valueType == ValueType.Int32) + valueStack.push(new Int32Value(~((Int32Value)val1).value)); + else if (val1.valueType == ValueType.Int64) + valueStack.push(new Int64Value(~((Int64Value)val1).value)); + else + valueStack.pushUnknown(); + } + + void emulate_Shl(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + int bits; + if (val2.valueType == ValueType.Int32) + bits = ((Int32Value)val2).value; + else if (val2.valueType == ValueType.Int64) + bits = (int)((Int64Value)val2).value; + else { + valueStack.pushUnknown(); + return; + } + + if (bits == 0) + valueStack.push(val1); + else if (val1.valueType == ValueType.Int32) + valueStack.push(new Int32Value(((Int32Value)val1).value << bits)); + else if (val1.valueType == ValueType.Int64) + valueStack.push(new Int64Value(((Int64Value)val1).value << bits)); + else + valueStack.pushUnknown(); + } + + void emulate_Shr(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + int bits; + if (val2.valueType == ValueType.Int32) + bits = ((Int32Value)val2).value; + else if (val2.valueType == ValueType.Int64) + bits = (int)((Int64Value)val2).value; + else { + valueStack.pushUnknown(); + return; + } + + if (bits == 0) + valueStack.push(val1); + else if (val1.valueType == ValueType.Int32) + valueStack.push(new Int32Value(((Int32Value)val1).value >> bits)); + else if (val1.valueType == ValueType.Int64) + valueStack.push(new Int64Value(((Int64Value)val1).value >> bits)); + else + valueStack.pushUnknown(); + } + + void emulate_Shr_Un(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + int bits; + if (val2.valueType == ValueType.Int32) + bits = ((Int32Value)val2).value; + else if (val2.valueType == ValueType.Int64) + bits = (int)((Int64Value)val2).value; + else { + valueStack.pushUnknown(); + return; + } + + if (bits == 0) + valueStack.push(val1); + else if (val1.valueType == ValueType.Int32) + valueStack.push(new Int32Value((int)((uint)((Int32Value)val1).value >> bits))); + else if (val1.valueType == ValueType.Int64) + valueStack.push(new Int64Value((long)((ulong)((Int64Value)val1).value >> bits))); + else + valueStack.pushUnknown(); + } + + void emulate_Ceq(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + //TODO: If it's an unknown int32/64, push 1 if val1 is same ref as val2 + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value == int2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + valueStack.push(new Int32Value(long1.value == long2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + valueStack.push(new Int32Value(real1.value == real2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Null && val2.valueType == ValueType.Null) { + valueStack.push(new Int32Value(1)); + } + else { + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + } + + void emulate_Cgt(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + //TODO: If it's an unknown int32/64, push 0 if val1 is same ref as val2 + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value > int2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + valueStack.push(new Int32Value(long1.value > long2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + valueStack.push(new Int32Value(real1.value > real2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == int.MinValue) + valueStack.push(new Int32Value(0)); // min > x => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == int.MaxValue) + valueStack.push(new Int32Value(0)); // x > max => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == long.MinValue) + valueStack.push(new Int32Value(0)); // min > x => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == long.MaxValue) + valueStack.push(new Int32Value(0)); // x > max => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else { + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + } + + void emulate_Cgt_Un(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + //TODO: If it's an unknown int32/64, push 0 if val1 is same ref as val2 + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value((uint)int1.value > (uint)int2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + valueStack.push(new Int32Value((ulong)long1.value > (ulong)long2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if ((uint)int1.value == uint.MinValue) + valueStack.push(new Int32Value(0)); // min > x => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if ((uint)int2.value == uint.MaxValue) + valueStack.push(new Int32Value(0)); // x > max => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if ((ulong)long1.value == ulong.MinValue) + valueStack.push(new Int32Value(0)); // min > x => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if ((ulong)long2.value == ulong.MaxValue) + valueStack.push(new Int32Value(0)); // x > max => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else { + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + } + + void emulate_Clt(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + //TODO: If it's an unknown int32/64, push 0 if val1 is same ref as val2 + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value(int1.value < int2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + valueStack.push(new Int32Value(long1.value < long2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Real8 && val2.valueType == ValueType.Real8) { + var real1 = (Real8Value)val1; + var real2 = (Real8Value)val2; + valueStack.push(new Int32Value(real1.value < real2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if (int1.value == int.MaxValue) + valueStack.push(new Int32Value(0)); // max < x => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if (int2.value == int.MinValue) + valueStack.push(new Int32Value(0)); // x < min => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if (long1.value == long.MaxValue) + valueStack.push(new Int32Value(0)); // max < x => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if (long2.value == long.MinValue) + valueStack.push(new Int32Value(0)); // x < min => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else { + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + } + + void emulate_Clt_Un(Instruction instr) { + var val2 = valueStack.pop(); + var val1 = valueStack.pop(); + + //TODO: If it's an unknown int32/64, push 0 if val1 is same ref as val2 + + if (val1.valueType == ValueType.Int32 && val2.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + var int2 = (Int32Value)val2; + valueStack.push(new Int32Value((uint)int1.value < (uint)int2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int64 && val2.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + var long2 = (Int64Value)val2; + valueStack.push(new Int32Value((ulong)long1.value < (ulong)long2.value ? 1 : 0)); + } + else if (val1.valueType == ValueType.Int32) { + var int1 = (Int32Value)val1; + if ((uint)int1.value == uint.MaxValue) + valueStack.push(new Int32Value(0)); // max < x => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val2.valueType == ValueType.Int32) { + var int2 = (Int32Value)val2; + if ((uint)int2.value == uint.MinValue) + valueStack.push(new Int32Value(0)); // x < min => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val1.valueType == ValueType.Int64) { + var long1 = (Int64Value)val1; + if ((ulong)long1.value == ulong.MaxValue) + valueStack.push(new Int32Value(0)); // max < x => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else if (val2.valueType == ValueType.Int64) { + var long2 = (Int64Value)val2; + if ((ulong)long2.value == ulong.MinValue) + valueStack.push(new Int32Value(0)); // x < min => false + else + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + else { + valueStack.pushUnknown(); //TODO: Push int32 with bit 0 unknown + } + } + + void emulate_Unbox_Any(Instruction instr) { + var val1 = valueStack.pop(); + if (val1.valueType == ValueType.Boxed) + valueStack.push(((BoxedValue)val1).value); + else + valueStack.pushUnknown(); + } + + void emulate_Starg(int index) { + //TODO: You should truncate the value if necessary, eg. from int32 -> bool, + // int32 -> int16, double -> float, etc. + setArg(index, valueStack.pop()); + } + + void emulate_Stloc(int index) { + //TODO: You should truncate the value if necessary, eg. from int32 -> bool, + // int32 -> int16, double -> float, etc. + setLocal(index, valueStack.pop()); + } + + void emulate_Ldarga(int index) { + valueStack.pushUnknown(); + setArg(index, new UnknownValue()); + } + + void emulate_Ldloca(int index) { + valueStack.pushUnknown(); + setLocal(index, new UnknownValue()); + } + } +} diff --git a/blocks/cflow/Value.cs b/blocks/cflow/Value.cs new file mode 100644 index 00000000..beaf0beb --- /dev/null +++ b/blocks/cflow/Value.cs @@ -0,0 +1,126 @@ +/* + 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 . +*/ + +namespace de4dot.blocks.cflow { + enum ValueType : byte { + Unknown, + Null, + Boxed, + Int32, + Int64, + Real8, + String, + } + + abstract class Value { + public readonly ValueType valueType; + + protected Value(ValueType valueType) { + this.valueType = valueType; + } + } + + class UnknownValue : Value { + public UnknownValue() + : base(ValueType.Unknown) { + } + + public override string ToString() { + return ""; + } + } + + class NullValue : Value { + // There's only one type of null + public static readonly NullValue Instance = new NullValue(); + + NullValue() + : base(ValueType.Null) { + } + + public override string ToString() { + return "null"; + } + } + + class BoxedValue : Value { + public readonly Value value; + + public BoxedValue(Value value) + : base(ValueType.Boxed) { + this.value = value; + } + + public override string ToString() { + return string.Format("box({0})", value.ToString()); + } + } + + class Int32Value : Value { + public readonly int value; + + public Int32Value(int value) + : base(ValueType.Int32) { + this.value = value; + } + + public override string ToString() { + return value.ToString(); + } + } + + class Int64Value : Value { + public readonly long value; + + public Int64Value(long value) + : base(ValueType.Int64) { + this.value = value; + } + + public override string ToString() { + return value.ToString() + "L"; + } + } + + class Real8Value : Value { + public readonly double value; + + public Real8Value(double value) + : base(ValueType.Real8) { + this.value = value; + } + + public override string ToString() { + return value.ToString() + "D"; + } + } + + class StringValue : Value { + public readonly string value; + + public StringValue(string value) + : base(ValueType.String) { + this.value = value; + } + + public override string ToString() { + return string.Format("\"{0}\"", value); + } + } +} diff --git a/blocks/cflow/ValueStack.cs b/blocks/cflow/ValueStack.cs new file mode 100644 index 00000000..39651c4f --- /dev/null +++ b/blocks/cflow/ValueStack.cs @@ -0,0 +1,96 @@ +/* + 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.Text; +using System.Collections.Generic; + +namespace de4dot.blocks.cflow { + class ValueStack { + List stack = new List(); + + public void init() { + stack.Clear(); + } + + public void clear() { + stack.Clear(); + } + + public void push(Value value) { + stack.Add(value); + } + + public Value peek() { + if (stack.Count == 0) + return new UnknownValue(); + return stack[stack.Count - 1]; + } + + public Value pop() { + Value value = peek(); + if (stack.Count != 0) + stack.RemoveAt(stack.Count - 1); + return value; + } + + public void push(int count) { + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + for (int i = 0; i < count; i++) + pushUnknown(); + } + + public void pushUnknown() { + push(new UnknownValue()); + } + + public void pop(int count) { + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + if (count >= stack.Count) + stack.Clear(); + else if (count > 0) + stack.RemoveRange(stack.Count - count, count); + } + + public void copyTop() { + push(peek()); + } + + public override string ToString() { + if (stack.Count == 0) + return ""; + + var sb = new StringBuilder(); + const int maxValues = 5; + for (int i = 0; i < maxValues; i++) { + int index = stack.Count - i - 1; + if (index < 0) + break; + if (i > 0) + sb.Append(", "); + sb.Append(stack[index].ToString()); + } + if (maxValues < stack.Count) + sb.Append(", ..."); + return sb.ToString(); + } + } +} diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index e4905e12..521c6bf2 100644 --- a/de4dot.code/ObfuscatedFile.cs +++ b/de4dot.code/ObfuscatedFile.cs @@ -26,6 +26,7 @@ using Mono.Cecil; using Mono.Cecil.Cil; using de4dot.deobfuscators; using de4dot.blocks; +using de4dot.blocks.cflow; using de4dot.AssemblyClient; namespace de4dot { @@ -441,6 +442,7 @@ namespace de4dot { Log.v("Deobfuscating methods"); var methodPrinter = new MethodPrinter(); + var cflowObfuscator = new BlocksControlFlowDeobfuscator(); foreach (var method in allMethods) { Log.v("Deobfuscating {0} ({1:X8})", method, method.MetadataToken.ToUInt32()); Log.indent(); @@ -450,7 +452,9 @@ namespace de4dot { deob.deobfuscateMethodBegin(blocks); if (options.ControlFlowDeobfuscation) { - int numDeadBlocks = blocks.deobfuscate(); + cflowObfuscator.init(blocks); + cflowObfuscator.deobfuscate(); + int numDeadBlocks = cflowObfuscator.NumberOfRemovedDeadBlocks; if (numDeadBlocks > 0) Log.v("Removed {0} dead block(s)", numDeadBlocks); } diff --git a/de4dot.code/deobfuscators/ProxyDelegateFinderBase.cs b/de4dot.code/deobfuscators/ProxyDelegateFinderBase.cs index 2b47313f..ec6be447 100644 --- a/de4dot.code/deobfuscators/ProxyDelegateFinderBase.cs +++ b/de4dot.code/deobfuscators/ProxyDelegateFinderBase.cs @@ -218,7 +218,7 @@ namespace de4dot.deobfuscators { if (stack <= 0) return null; var instr = instrs[i]; - DotNetUtils.updateStack(instr.Instruction, ref stack); + DotNetUtils.updateStack(instr.Instruction, ref stack, false); if (stack < 0) return null;