From b7255bc3b58c7358a9314151247c4488ddf5472a Mon Sep 17 00:00:00 2001 From: de4dot Date: Tue, 24 Sep 2013 21:44:21 +0200 Subject: [PATCH 01/58] Add assembly string separator detection code --- .../CryptoObfuscator/AssemblyResolver.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/de4dot.code/deobfuscators/CryptoObfuscator/AssemblyResolver.cs b/de4dot.code/deobfuscators/CryptoObfuscator/AssemblyResolver.cs index 5b107767..998e952f 100644 --- a/de4dot.code/deobfuscators/CryptoObfuscator/AssemblyResolver.cs +++ b/de4dot.code/deobfuscators/CryptoObfuscator/AssemblyResolver.cs @@ -30,6 +30,7 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { TypeDef resolverType; MethodDef resolverMethod; List assemblyInfos = new List(); + string asmSeparator; public class AssemblyInfo { public string assemblyName; @@ -86,6 +87,8 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { return false; if (!CheckInitMethod(initMethod)) return false; + if ((asmSeparator = FindAssemblySeparator(initMethod)) == null) + return false; List newAssemblyInfos = null; foreach (var s in DotNetUtils.GetCodeStrings(initMethod)) { @@ -134,7 +137,7 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { var sb = new StringBuilder(s.Length); foreach (var c in s) sb.Append((char)~c); - var tmpAssemblyInfos = sb.ToString().Split(new string[] { "##" }, StringSplitOptions.RemoveEmptyEntries); + var tmpAssemblyInfos = sb.ToString().Split(new string[] { asmSeparator }, StringSplitOptions.RemoveEmptyEntries); if (tmpAssemblyInfos.Length == 0 || (tmpAssemblyInfos.Length & 1) == 1) return null; @@ -151,5 +154,24 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { return newAssemblyInfos; } + + string FindAssemblySeparator(MethodDef initMethod) { + if (!initMethod.HasBody) + return null; + + foreach (var instr in initMethod.Body.Instructions) { + if (instr.OpCode.Code != Code.Newarr) + continue; + var op = module.CorLibTypes.GetCorLibTypeSig(instr.Operand as ITypeDefOrRef); + if (op == null) + continue; + if (op.ElementType == ElementType.String) + return "##"; + if (op.ElementType == ElementType.Char) + return "`"; + } + + return null; + } } } From 3d05b408c9c5beb9340661a373e30d602ac820f5 Mon Sep 17 00:00:00 2001 From: de4dot Date: Wed, 25 Sep 2013 01:37:53 +0200 Subject: [PATCH 02/58] Decrypt arrays --- .../CryptoObfuscator/ConstantsDecrypter.cs | 84 ++++++++++++++++++- .../CryptoObfuscator/Deobfuscator.cs | 3 +- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/de4dot.code/deobfuscators/CryptoObfuscator/ConstantsDecrypter.cs b/de4dot.code/deobfuscators/CryptoObfuscator/ConstantsDecrypter.cs index 295659f1..18ff0ee0 100644 --- a/de4dot.code/deobfuscators/CryptoObfuscator/ConstantsDecrypter.cs +++ b/de4dot.code/deobfuscators/CryptoObfuscator/ConstantsDecrypter.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.Text; using dnlib.IO; using dnlib.DotNet; +using dnlib.DotNet.Emit; using de4dot.blocks; namespace de4dot.code.deobfuscators.CryptoObfuscator { @@ -32,6 +33,8 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { MethodDef methodI8; MethodDef methodR4; MethodDef methodR8; + MethodDef methodArray; + InitializedDataCreator initializedDataCreator; EmbeddedResource encryptedResource; byte[] constantsData; @@ -63,8 +66,9 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { get { return decrypterType != null; } } - public ConstantsDecrypter(ModuleDefMD module) { + public ConstantsDecrypter(ModuleDefMD module, InitializedDataCreator initializedDataCreator) { this.module = module; + this.initializedDataCreator = initializedDataCreator; } public void Find() { @@ -98,9 +102,11 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { methodI8 = DotNetUtils.GetMethod(type, "System.Int64", "(System.Int32)"); methodR4 = DotNetUtils.GetMethod(type, "System.Single", "(System.Int32)"); methodR8 = DotNetUtils.GetMethod(type, "System.Double", "(System.Int32)"); + methodArray = DotNetUtils.GetMethod(type, "System.Void", "(System.Array,System.Int32)"); return methodI4 != null && methodI8 != null && - methodR4 != null && methodR8 != null; + methodR4 != null && methodR8 != null && + methodArray != null; } public void Initialize(ResourceDecrypter resourceDecrypter) { @@ -127,5 +133,79 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { public double DecryptDouble(int index) { return BitConverter.ToDouble(constantsData, index); } + + struct ArrayInfo { + public CorLibTypeSig arrayType; + public int start, len; + public int arySize, index; + + public ArrayInfo(int start, int len, CorLibTypeSig arrayType, int arySize, int index) { + this.start = start; + this.len = len; + this.arrayType = arrayType; + this.arySize = arySize; + this.index = index; + } + } + + public void Deobfuscate(Blocks blocks) { + var infos = new List(); + foreach (var block in blocks.MethodBlocks.GetAllBlocks()) { + var instrs = block.Instructions; + infos.Clear(); + + for (int i = 0; i < instrs.Count - 5; i++) { + int index = i; + + var ldci4_arySize = instrs[index++]; + if (!ldci4_arySize.IsLdcI4()) + continue; + + var newarr = instrs[index++]; + if (newarr.OpCode.Code != Code.Newarr) + continue; + var arrayType = module.CorLibTypes.GetCorLibTypeSig(newarr.Operand as ITypeDefOrRef); + if (arrayType == null) + continue; + + if (instrs[index++].OpCode.Code != Code.Dup) + continue; + + var ldci4_index = instrs[index++]; + if (!ldci4_index.IsLdcI4()) + continue; + + var call = instrs[index++]; + if (call.OpCode.Code != Code.Call && call.OpCode.Code != Code.Callvirt) + continue; + if (!MethodEqualityComparer.CompareDeclaringTypes.Equals(call.Operand as IMethod, methodArray)) + continue; + + if (arrayType.ElementType.GetPrimitiveSize() == -1) { + Logger.w("Can't decrypt non-primitive type array in method {0:X8}", blocks.Method.MDToken.ToInt32()); + continue; + } + + infos.Add(new ArrayInfo(i, index - i, arrayType, ldci4_arySize.GetLdcI4Value(), + ldci4_index.GetLdcI4Value())); + } + + infos.Reverse(); + foreach (var info in infos) { + var elemSize = info.arrayType.ElementType.GetPrimitiveSize(); + var decrypted = DecryptArray(info); + initializedDataCreator.AddInitializeArrayCode(block, info.start, info.len, info.arrayType.ToTypeDefOrRef(), decrypted); + Logger.v("Decrypted {0} array: {1} elements", info.arrayType.ToString(), decrypted.Length / elemSize); + } + } + } + + byte[] DecryptArray(ArrayInfo aryInfo) { + var ary = new byte[aryInfo.arySize * aryInfo.arrayType.ElementType.GetPrimitiveSize()]; + int dataIndex = aryInfo.index; + int len = DeobUtils.ReadVariableLengthInt32(constantsData, ref dataIndex); + Buffer.BlockCopy(constantsData, dataIndex, ary, 0, len); + return ary; + } } } diff --git a/de4dot.code/deobfuscators/CryptoObfuscator/Deobfuscator.cs b/de4dot.code/deobfuscators/CryptoObfuscator/Deobfuscator.cs index 1a9b8754..93d98fc0 100644 --- a/de4dot.code/deobfuscators/CryptoObfuscator/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/CryptoObfuscator/Deobfuscator.cs @@ -144,7 +144,7 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { stringDecrypter.Find(); tamperDetection = new TamperDetection(module); tamperDetection.Find(); - constantsDecrypter = new ConstantsDecrypter(module); + constantsDecrypter = new ConstantsDecrypter(module, initializedDataCreator); constantsDecrypter.Find(); foundObfuscatorUserString = Utils.StartsWith(module.ReadUserString(0x70000001), "\u0011\"3D9B94A98B-76A8-4810-B1A0-4BE7C4F9C98D", StringComparison.Ordinal); } @@ -245,6 +245,7 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { int64ValueInliner.Decrypt(blocks); singleValueInliner.Decrypt(blocks); doubleValueInliner.Decrypt(blocks); + constantsDecrypter.Deobfuscate(blocks); } base.DeobfuscateMethodEnd(blocks); } From 06bef669c52cc86c57bfe6f2bfe091c924780622 Mon Sep 17 00:00:00 2001 From: de4dot Date: Sat, 28 Sep 2013 14:54:00 +0200 Subject: [PATCH 03/58] Inline a few more instructions --- blocks/cflow/MethodCallInlinerBase.cs | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/blocks/cflow/MethodCallInlinerBase.cs b/blocks/cflow/MethodCallInlinerBase.cs index 873c4d62..d8ed9247 100644 --- a/blocks/cflow/MethodCallInlinerBase.cs +++ b/blocks/cflow/MethodCallInlinerBase.cs @@ -142,7 +142,9 @@ namespace de4dot.blocks.cflow { if (instr == null || loadIndex != methodArgsCount - popLastArgs) return null; - if (instr.OpCode.Code == Code.Call || instr.OpCode.Code == Code.Callvirt) { + switch (instr.OpCode.Code) { + case Code.Call: + case Code.Callvirt: if (foundLdarga) return null; var callInstr = instr; @@ -160,8 +162,8 @@ namespace de4dot.blocks.cflow { return null; return new InstructionPatcher(patchIndex, instrIndex, callInstr); - } - else if (instr.OpCode.Code == Code.Newobj) { + + case Code.Newobj: if (foundLdarga) return null; var newobjInstr = instr; @@ -185,20 +187,30 @@ namespace de4dot.blocks.cflow { return null; 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) { + + case Code.Ldfld: + case Code.Ldflda: + case Code.Ldftn: + case Code.Ldvirtftn: + case Code.Ldlen: + case Code.Initobj: + case Code.Isinst: + case Code.Castclass: + case Code.Newarr: + case Code.Ldtoken: + case Code.Unbox_Any: var ldInstr = instr; if (methodArgsCount != 1) return null; - if (!HasAccessTo(instr.Operand)) + if (instr.OpCode.OperandType != OperandType.InlineNone && !HasAccessTo(instr.Operand)) return null; return new InstructionPatcher(patchIndex, instrIndex, ldInstr); - } - return null; + default: + return null; + } } bool HasAccessTo(object operand) { From 13ef523d581cc3d0db6737cd863948b7264ec40d Mon Sep 17 00:00:00 2001 From: de4dot Date: Sat, 28 Sep 2013 14:54:27 +0200 Subject: [PATCH 04/58] Add OnInlinedMethod() method --- blocks/cflow/MethodCallInliner.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/blocks/cflow/MethodCallInliner.cs b/blocks/cflow/MethodCallInliner.cs index a44fc610..3504a658 100644 --- a/blocks/cflow/MethodCallInliner.cs +++ b/blocks/cflow/MethodCallInliner.cs @@ -71,6 +71,7 @@ namespace de4dot.blocks.cflow { if (instr == null) return false; + bool inlinedMethod; switch (instr.OpCode.Code) { case Code.Ldarg: case Code.Ldarg_S: @@ -83,7 +84,8 @@ namespace de4dot.blocks.cflow { case Code.Call: case Code.Callvirt: case Code.Newobj: - return InlineOtherMethod(instrIndex, methodToInline, instr, index); + inlinedMethod = InlineOtherMethod(instrIndex, methodToInline, instr, index); + break; case Code.Ldc_I4: case Code.Ldc_I4_0: @@ -106,11 +108,18 @@ namespace de4dot.blocks.cflow { case Code.Ldtoken: case Code.Ldsfld: case Code.Ldsflda: - return InlineLoadMethod(instrIndex, methodToInline, instr, index); + inlinedMethod = InlineLoadMethod(instrIndex, methodToInline, instr, index); + break; default: - return false; + inlinedMethod = false; + break; } + OnInlinedMethod(methodToInline, inlinedMethod); + return inlinedMethod; + } + + protected virtual void OnInlinedMethod(MethodDef methodToInline, bool inlinedMethod) { } protected override bool IsCompatibleType(int paramIndex, IType origType, IType newType) { From 67c9e762765f3773a78950da4851fb2e61f7e5d9 Mon Sep 17 00:00:00 2001 From: de4dot Date: Sat, 28 Sep 2013 14:55:29 +0200 Subject: [PATCH 05/58] Inline methods --- de4dot.code/de4dot.code.csproj | 2 + .../CryptoObfuscator/CoMethodCallInliner.cs | 53 +++++++++++ .../CryptoObfuscator/Deobfuscator.cs | 26 ++++++ .../CryptoObfuscator/InlinedMethodTypes.cs | 90 +++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 de4dot.code/deobfuscators/CryptoObfuscator/CoMethodCallInliner.cs create mode 100644 de4dot.code/deobfuscators/CryptoObfuscator/InlinedMethodTypes.cs diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index a2c1d13c..881d9378 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -126,9 +126,11 @@ + + diff --git a/de4dot.code/deobfuscators/CryptoObfuscator/CoMethodCallInliner.cs b/de4dot.code/deobfuscators/CryptoObfuscator/CoMethodCallInliner.cs new file mode 100644 index 00000000..312196f4 --- /dev/null +++ b/de4dot.code/deobfuscators/CryptoObfuscator/CoMethodCallInliner.cs @@ -0,0 +1,53 @@ +/* + Copyright (C) 2011-2013 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 dnlib.DotNet; +using de4dot.blocks.cflow; + +namespace de4dot.code.deobfuscators.CryptoObfuscator { + class CoMethodCallInliner : MethodCallInliner { + readonly InlinedMethodTypes inlinedMethodTypes; + + public CoMethodCallInliner(InlinedMethodTypes inlinedMethodTypes) + : base(false) { + this.inlinedMethodTypes = inlinedMethodTypes; + } + + protected override bool CanInline(MethodDef method) { + if (method == null) + return false; + + if (method.Attributes != (MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig)) + return false; + if (method.HasGenericParameters) + return false; + if (!inlinedMethodTypes.IsValidMethodType(method.DeclaringType)) + return false; + + return true; + } + + protected override void OnInlinedMethod(MethodDef methodToInline, bool inlinedMethod) { + if (inlinedMethod) + inlinedMethodTypes.Add(methodToInline.DeclaringType); + else + inlinedMethodTypes.DontRemoveType(methodToInline.DeclaringType); + } + } +} diff --git a/de4dot.code/deobfuscators/CryptoObfuscator/Deobfuscator.cs b/de4dot.code/deobfuscators/CryptoObfuscator/Deobfuscator.cs index 93d98fc0..874c4632 100644 --- a/de4dot.code/deobfuscators/CryptoObfuscator/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/CryptoObfuscator/Deobfuscator.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using dnlib.DotNet; using de4dot.blocks; +using de4dot.blocks.cflow; namespace de4dot.code.deobfuscators.CryptoObfuscator { public class DeobfuscatorInfo : DeobfuscatorInfoBase { @@ -30,11 +31,13 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { const string DEFAULT_REGEX = @"!^(get_|set_|add_|remove_)?[A-Z]{1,3}(?:`\d+)?$&!^(get_|set_|add_|remove_)?c[0-9a-f]{32}(?:`\d+)?$&" + DeobfuscatorBase.DEFAULT_VALID_NAME_REGEX; BoolOption removeTamperProtection; BoolOption decryptConstants; + BoolOption inlineMethods; public DeobfuscatorInfo() : base(DEFAULT_REGEX) { removeTamperProtection = new BoolOption(null, MakeArgName("tamper"), "Remove tamper protection code", true); decryptConstants = new BoolOption(null, MakeArgName("consts"), "Decrypt constants", true); + inlineMethods = new BoolOption(null, MakeArgName("inline"), "Inline short methods", true); } public override string Name { @@ -50,6 +53,7 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { ValidNameRegex = validNameRegex.get(), RemoveTamperProtection = removeTamperProtection.get(), DecryptConstants = decryptConstants.get(), + InlineMethods = inlineMethods.get(), }); } @@ -57,6 +61,7 @@ namespace de4dot.code.deobfuscators.CryptoObfuscator { return new List