From 5df9406f89621ff6bf037136ea06c4b18f3e5bb8 Mon Sep 17 00:00:00 2001 From: doomedraven Date: Thu, 23 Jul 2026 21:33:35 +0200 Subject: [PATCH 1/4] fix: resolve cflow null-targets, KeepOldMaxStack, and enable cross-platform .NET Framework builds --- De4DotCommon.props | 7 ++++++- de4dot.blocks/cflow/BlockCflowDeobfuscator.cs | 7 ++++++- de4dot.code/ObfuscatedFile.cs | 3 +-- de4dot.cui/Program.cs | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/De4DotCommon.props b/De4DotCommon.props index 9a042a11..10cb099c 100644 --- a/De4DotCommon.props +++ b/De4DotCommon.props @@ -16,8 +16,13 @@ true 4.5.0 - + true + + + + + diff --git a/de4dot.blocks/cflow/BlockCflowDeobfuscator.cs b/de4dot.blocks/cflow/BlockCflowDeobfuscator.cs index 01d05e4e..bcec2e98 100644 --- a/de4dot.blocks/cflow/BlockCflowDeobfuscator.cs +++ b/de4dot.blocks/cflow/BlockCflowDeobfuscator.cs @@ -54,7 +54,12 @@ protected override bool Deobfuscate(Block block) { return false; } - return branchEmulator.Emulate(block.LastInstr.Instruction); + try { + return branchEmulator.Emulate(block.LastInstr.Instruction); + } + catch { + return false; + } } void PopPushedArgs(int stackArgs) { diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index dc76ee20..dc04cdd2 100644 --- a/de4dot.code/ObfuscatedFile.cs +++ b/de4dot.code/ObfuscatedFile.cs @@ -309,8 +309,7 @@ MetadataFlags GetMetadataFlags() { public void Save() { Logger.n("Saving {0}", options.NewFilename); var mdFlags = GetMetadataFlags(); - if (!options.ControlFlowDeobfuscation) - mdFlags |= MetadataFlags.KeepOldMaxStack; + mdFlags |= MetadataFlags.KeepOldMaxStack; assemblyModule.Save(options.NewFilename, mdFlags, new PrintNewTokens(module, deob as IModuleWriterListener)); } diff --git a/de4dot.cui/Program.cs b/de4dot.cui/Program.cs index 2b0f54ad..93b87f6e 100644 --- a/de4dot.cui/Program.cs +++ b/de4dot.cui/Program.cs @@ -32,7 +32,7 @@ class ExitException : Exception { public ExitException(int code) => this.code = code; } - class Program { + public class Program { static IList deobfuscatorInfos = CreateDeobfuscatorInfos(); static IList LoadPlugin(string assembly) { From 268aab8d0b81739bda23589683009a747891cfd9 Mon Sep 17 00:00:00 2001 From: doomedraven Date: Mon, 27 Jul 2026 21:26:31 +0200 Subject: [PATCH 2/4] Conditionalize KeepOldMaxStack flag based on options --- de4dot.code/ObfuscatedFile.cs | 1603 +++++++++++++++++---------------- 1 file changed, 802 insertions(+), 801 deletions(-) diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index dc04cdd2..33adffa9 100644 --- a/de4dot.code/ObfuscatedFile.cs +++ b/de4dot.code/ObfuscatedFile.cs @@ -1,802 +1,803 @@ -/* - Copyright (C) 2011-2015 de4dot@gmail.com - - This file is part of de4dot. - - de4dot is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - de4dot is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with de4dot. If not, see . -*/ - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using dnlib.DotNet; -using dnlib.DotNet.Emit; -using dnlib.DotNet.Writer; -using dnlib.PE; -using AssemblyData; -using de4dot.code.deobfuscators; -using de4dot.blocks; -using de4dot.blocks.cflow; -using de4dot.code.AssemblyClient; -using de4dot.code.renamer; - -namespace de4dot.code { - public class ObfuscatedFile : IObfuscatedFile, IDeobfuscatedFile { - Options options; - ModuleDefMD module; - IDeobfuscator deob; - IDeobfuscatorContext deobfuscatorContext; - AssemblyModule assemblyModule; - IAssemblyClient assemblyClient; - DynamicStringInliner dynamicStringInliner; - IAssemblyClientFactory assemblyClientFactory; - SavedMethodBodies savedMethodBodies; - bool userStringDecrypterMethods = false; - - class SavedMethodBodies { - Dictionary savedMethodBodies = new Dictionary(); - - class SavedMethodBody { - MethodDef method; - IList instructions; - IList exceptionHandlers; - - public SavedMethodBody(MethodDef method) { - this.method = method; - DotNetUtils.CopyBody(method, out instructions, out exceptionHandlers); - } - - public void Restore() => DotNetUtils.RestoreBody(method, instructions, exceptionHandlers); - } - - public void Save(MethodDef method) { - if (IsSaved(method)) - return; - savedMethodBodies[method] = new SavedMethodBody(method); - } - - public void RestoreAll() { - foreach (var smb in savedMethodBodies.Values) - smb.Restore(); - savedMethodBodies.Clear(); - } - - public bool IsSaved(MethodDef method) => savedMethodBodies.ContainsKey(method); - } - - public class Options { - public string Filename { get; set; } - public string NewFilename { get; set; } - public string ForcedObfuscatorType { get; set; } - public DecrypterType StringDecrypterType { get; set; } - public List StringDecrypterMethods { get; private set; } - public bool ControlFlowDeobfuscation { get; set; } - public bool KeepObfuscatorTypes { get; set; } - public bool PreserveTokens { get; set; } - public MetadataFlags MetadataFlags { get; set; } - public RenamerFlags RenamerFlags { get; set; } - - public Options() { - StringDecrypterType = DecrypterType.Default; - StringDecrypterMethods = new List(); - } - } - - public string Filename => options.Filename; - public string NewFilename => options.NewFilename; - public ModuleDefMD ModuleDefMD => module; - public INameChecker NameChecker => deob; - public bool RenameResourcesInCode => deob.TheOptions.RenameResourcesInCode; - public bool RemoveNamespaceWithOneType => (deob.RenamingOptions & RenamingOptions.RemoveNamespaceIfOneType) != 0; - public bool RenameResourceKeys => (deob.RenamingOptions & RenamingOptions.RenameResourceKeys) != 0; - public IDeobfuscator Deobfuscator => deob; - public IDeobfuscatorContext DeobfuscatorContext { - get => deobfuscatorContext; - set => deobfuscatorContext = value; - } - - public ObfuscatedFile(Options options, ModuleContext moduleContext, IAssemblyClientFactory assemblyClientFactory) { - this.assemblyClientFactory = assemblyClientFactory; - this.options = options; - userStringDecrypterMethods = options.StringDecrypterMethods.Count > 0; - options.Filename = Utils.GetFullPath(options.Filename); - assemblyModule = new AssemblyModule(options.Filename, moduleContext); - - if (options.NewFilename == null) - options.NewFilename = GetDefaultNewFilename(); - - if (string.Equals(options.Filename, options.NewFilename, StringComparison.OrdinalIgnoreCase)) - throw new UserException($"filename is same as new filename! ({options.Filename})"); - } - - string GetDefaultNewFilename() { - string newFilename = Path.GetFileNameWithoutExtension(options.Filename) + "-cleaned" + Path.GetExtension(options.Filename); - return Path.Combine(Path.GetDirectoryName(options.Filename), newFilename); - } - - public void Load(IList deobfuscators) { - try { - LoadModule(deobfuscators); - TheAssemblyResolver.Instance.AddSearchDirectory(Utils.GetDirName(Filename)); - TheAssemblyResolver.Instance.AddSearchDirectory(Utils.GetDirName(NewFilename)); - DetectObfuscator(deobfuscators); - if (deob == null) - throw new ApplicationException("Could not detect obfuscator!"); - InitializeDeobfuscator(); - } - finally { - foreach (var d in deobfuscators) { - if (d != deob && d != null) - d.Dispose(); - } - } - } - - void LoadModule(IEnumerable deobfuscators) { - var oldModule = module; - try { - module = assemblyModule.Load(); - } - catch (BadImageFormatException) { - if (!UnpackNativeImage(deobfuscators)) - throw new BadImageFormatException(); - Logger.v("Unpacked native file"); - } - finally { - if (oldModule != null) - oldModule.Dispose(); - } - } - - bool UnpackNativeImage(IEnumerable deobfuscators) { - using (var peImage = new PEImage(Filename)) { - foreach (var deob in deobfuscators) { - byte[] unpackedData = null; - try { - unpackedData = deob.UnpackNativeFile(peImage); - } - catch { - } - if (unpackedData == null) - continue; - - var oldModule = module; - try { - module = assemblyModule.Load(unpackedData); - } - catch { - Logger.w("Could not load unpacked data. File: {0}, deobfuscator: {0}", peImage.Filename ?? "(unknown filename)", deob.TypeLong); - continue; - } - finally { - if (oldModule != null) - oldModule.Dispose(); - } - this.deob = deob; - return true; - } - } - - return false; - } - - void InitializeDeobfuscator() { - if (options.StringDecrypterType == DecrypterType.Default) - options.StringDecrypterType = deob.DefaultDecrypterType; - if (options.StringDecrypterType == DecrypterType.Default) - options.StringDecrypterType = DecrypterType.Static; - - deob.Operations = CreateOperations(); - } - - IOperations CreateOperations() { - var op = new Operations(); - - switch (options.StringDecrypterType) { - case DecrypterType.None: - op.DecryptStrings = OpDecryptString.None; - break; - case DecrypterType.Static: - op.DecryptStrings = OpDecryptString.Static; - break; - default: - op.DecryptStrings = OpDecryptString.Dynamic; - break; - } - - op.KeepObfuscatorTypes = options.KeepObfuscatorTypes; - op.MetadataFlags = options.MetadataFlags; - op.RenamerFlags = options.RenamerFlags; - - return op; - } - - void DetectObfuscator(IEnumerable deobfuscators) { - - // The deobfuscators may call methods to deobfuscate control flow and decrypt - // strings (statically) in order to detect the obfuscator. - if (!options.ControlFlowDeobfuscation || options.StringDecrypterType == DecrypterType.None) - savedMethodBodies = new SavedMethodBodies(); - - // It's not null if it unpacked a native file - if (deob != null) { - deob.Initialize(module); - deob.DeobfuscatedFile = this; - deob.Detect(); - return; - } - - foreach (var deob in deobfuscators) { - deob.Initialize(module); - deob.DeobfuscatedFile = this; - } - - if (options.ForcedObfuscatorType != null) { - foreach (var deob in deobfuscators) { - if (string.Equals(options.ForcedObfuscatorType, deob.Type, StringComparison.OrdinalIgnoreCase)) { - this.deob = deob; - deob.Detect(); - return; - } - } - } - else - deob = DetectObfuscator2(deobfuscators); - } - - IDeobfuscator DetectObfuscator2(IEnumerable deobfuscators) { - var allDetected = new List(); - IDeobfuscator detected = null; - int detectVal = 0; - foreach (var deob in deobfuscators) { - this.deob = deob; // So we can call deob.CanInlineMethods in deobfuscate() - int val; - //TODO: Re-enable exception handler - //try { - val = deob.Detect(); - /*} - catch { - val = deob.Type == "un" ? 1 : 0; - }*/ - Logger.v("{0,3}: {1}", val, deob.TypeLong); - if (val > 0 && deob.Type != "un") - allDetected.Add(deob); - if (val > detectVal) { - detectVal = val; - detected = deob; - } - } - deob = null; - - if (allDetected.Count > 1) { - Logger.n("More than one obfuscator detected:"); - Logger.Instance.Indent(); - foreach (var deob in allDetected) - Logger.n("{0} (use: -p {1})", deob.Name, deob.Type); - Logger.Instance.DeIndent(); - } - - return detected; - } - - MetadataFlags GetMetadataFlags() { - var mdFlags = options.MetadataFlags | deob.MetadataFlags; - - // Always preserve tokens if it's an unknown obfuscator - if (deob.Type == "un") { - mdFlags |= MetadataFlags.PreserveRids | - MetadataFlags.PreserveUSOffsets | - MetadataFlags.PreserveBlobOffsets | - MetadataFlags.PreserveExtraSignatureData; - } - - return mdFlags; - } - - public void Save() { - Logger.n("Saving {0}", options.NewFilename); - var mdFlags = GetMetadataFlags(); - mdFlags |= MetadataFlags.KeepOldMaxStack; - assemblyModule.Save(options.NewFilename, mdFlags, new PrintNewTokens(module, deob as IModuleWriterListener)); - } - - IList GetAllMethods() { - var list = new List(); - - foreach (var type in module.GetTypes()) { - foreach (var method in type.Methods) - list.Add(method); - } - - return list; - } - - public void DeobfuscateBegin() { - switch (options.StringDecrypterType) { - case DecrypterType.None: - CheckSupportedStringDecrypter(StringFeatures.AllowNoDecryption); - break; - - case DecrypterType.Static: - CheckSupportedStringDecrypter(StringFeatures.AllowStaticDecryption); - break; - - case DecrypterType.Delegate: - case DecrypterType.Emulate: - CheckSupportedStringDecrypter(StringFeatures.AllowDynamicDecryption); - var newProcFactory = assemblyClientFactory as NewProcessAssemblyClientFactory; - if (newProcFactory != null) - assemblyClient = newProcFactory.Create(AssemblyServiceType.StringDecrypter, module); - else - assemblyClient = assemblyClientFactory.Create(AssemblyServiceType.StringDecrypter); - assemblyClient.Connect(); - break; - - default: - throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); - } - } - - public void CheckSupportedStringDecrypter(StringFeatures feature) { - if ((deob.StringFeatures & feature) == feature) - return; - throw new UserException($"Deobfuscator {deob.TypeLong} does not support this string decryption type"); - } - - public void Deobfuscate() { - Logger.n("Cleaning {0}", options.Filename); - InitAssemblyClient(); - - for (int i = 0; ; i++) { - byte[] fileData = null; - DumpedMethods dumpedMethods = null; - if (!deob.GetDecryptedModule(i, ref fileData, ref dumpedMethods)) - break; - ReloadModule(fileData, dumpedMethods); - } - - deob.DeobfuscateBegin(); - DeobfuscateMethods(); +/* + Copyright (C) 2011-2015 de4dot@gmail.com + + This file is part of de4dot. + + de4dot is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + de4dot is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with de4dot. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnlib.DotNet.Writer; +using dnlib.PE; +using AssemblyData; +using de4dot.code.deobfuscators; +using de4dot.blocks; +using de4dot.blocks.cflow; +using de4dot.code.AssemblyClient; +using de4dot.code.renamer; + +namespace de4dot.code { + public class ObfuscatedFile : IObfuscatedFile, IDeobfuscatedFile { + Options options; + ModuleDefMD module; + IDeobfuscator deob; + IDeobfuscatorContext deobfuscatorContext; + AssemblyModule assemblyModule; + IAssemblyClient assemblyClient; + DynamicStringInliner dynamicStringInliner; + IAssemblyClientFactory assemblyClientFactory; + SavedMethodBodies savedMethodBodies; + bool userStringDecrypterMethods = false; + + class SavedMethodBodies { + Dictionary savedMethodBodies = new Dictionary(); + + class SavedMethodBody { + MethodDef method; + IList instructions; + IList exceptionHandlers; + + public SavedMethodBody(MethodDef method) { + this.method = method; + DotNetUtils.CopyBody(method, out instructions, out exceptionHandlers); + } + + public void Restore() => DotNetUtils.RestoreBody(method, instructions, exceptionHandlers); + } + + public void Save(MethodDef method) { + if (IsSaved(method)) + return; + savedMethodBodies[method] = new SavedMethodBody(method); + } + + public void RestoreAll() { + foreach (var smb in savedMethodBodies.Values) + smb.Restore(); + savedMethodBodies.Clear(); + } + + public bool IsSaved(MethodDef method) => savedMethodBodies.ContainsKey(method); + } + + public class Options { + public string Filename { get; set; } + public string NewFilename { get; set; } + public string ForcedObfuscatorType { get; set; } + public DecrypterType StringDecrypterType { get; set; } + public List StringDecrypterMethods { get; private set; } + public bool ControlFlowDeobfuscation { get; set; } + public bool KeepObfuscatorTypes { get; set; } + public bool PreserveTokens { get; set; } + public MetadataFlags MetadataFlags { get; set; } + public RenamerFlags RenamerFlags { get; set; } + + public Options() { + StringDecrypterType = DecrypterType.Default; + StringDecrypterMethods = new List(); + } + } + + public string Filename => options.Filename; + public string NewFilename => options.NewFilename; + public ModuleDefMD ModuleDefMD => module; + public INameChecker NameChecker => deob; + public bool RenameResourcesInCode => deob.TheOptions.RenameResourcesInCode; + public bool RemoveNamespaceWithOneType => (deob.RenamingOptions & RenamingOptions.RemoveNamespaceIfOneType) != 0; + public bool RenameResourceKeys => (deob.RenamingOptions & RenamingOptions.RenameResourceKeys) != 0; + public IDeobfuscator Deobfuscator => deob; + public IDeobfuscatorContext DeobfuscatorContext { + get => deobfuscatorContext; + set => deobfuscatorContext = value; + } + + public ObfuscatedFile(Options options, ModuleContext moduleContext, IAssemblyClientFactory assemblyClientFactory) { + this.assemblyClientFactory = assemblyClientFactory; + this.options = options; + userStringDecrypterMethods = options.StringDecrypterMethods.Count > 0; + options.Filename = Utils.GetFullPath(options.Filename); + assemblyModule = new AssemblyModule(options.Filename, moduleContext); + + if (options.NewFilename == null) + options.NewFilename = GetDefaultNewFilename(); + + if (string.Equals(options.Filename, options.NewFilename, StringComparison.OrdinalIgnoreCase)) + throw new UserException($"filename is same as new filename! ({options.Filename})"); + } + + string GetDefaultNewFilename() { + string newFilename = Path.GetFileNameWithoutExtension(options.Filename) + "-cleaned" + Path.GetExtension(options.Filename); + return Path.Combine(Path.GetDirectoryName(options.Filename), newFilename); + } + + public void Load(IList deobfuscators) { + try { + LoadModule(deobfuscators); + TheAssemblyResolver.Instance.AddSearchDirectory(Utils.GetDirName(Filename)); + TheAssemblyResolver.Instance.AddSearchDirectory(Utils.GetDirName(NewFilename)); + DetectObfuscator(deobfuscators); + if (deob == null) + throw new ApplicationException("Could not detect obfuscator!"); + InitializeDeobfuscator(); + } + finally { + foreach (var d in deobfuscators) { + if (d != deob && d != null) + d.Dispose(); + } + } + } + + void LoadModule(IEnumerable deobfuscators) { + var oldModule = module; + try { + module = assemblyModule.Load(); + } + catch (BadImageFormatException) { + if (!UnpackNativeImage(deobfuscators)) + throw new BadImageFormatException(); + Logger.v("Unpacked native file"); + } + finally { + if (oldModule != null) + oldModule.Dispose(); + } + } + + bool UnpackNativeImage(IEnumerable deobfuscators) { + using (var peImage = new PEImage(Filename)) { + foreach (var deob in deobfuscators) { + byte[] unpackedData = null; + try { + unpackedData = deob.UnpackNativeFile(peImage); + } + catch { + } + if (unpackedData == null) + continue; + + var oldModule = module; + try { + module = assemblyModule.Load(unpackedData); + } + catch { + Logger.w("Could not load unpacked data. File: {0}, deobfuscator: {0}", peImage.Filename ?? "(unknown filename)", deob.TypeLong); + continue; + } + finally { + if (oldModule != null) + oldModule.Dispose(); + } + this.deob = deob; + return true; + } + } + + return false; + } + + void InitializeDeobfuscator() { + if (options.StringDecrypterType == DecrypterType.Default) + options.StringDecrypterType = deob.DefaultDecrypterType; + if (options.StringDecrypterType == DecrypterType.Default) + options.StringDecrypterType = DecrypterType.Static; + + deob.Operations = CreateOperations(); + } + + IOperations CreateOperations() { + var op = new Operations(); + + switch (options.StringDecrypterType) { + case DecrypterType.None: + op.DecryptStrings = OpDecryptString.None; + break; + case DecrypterType.Static: + op.DecryptStrings = OpDecryptString.Static; + break; + default: + op.DecryptStrings = OpDecryptString.Dynamic; + break; + } + + op.KeepObfuscatorTypes = options.KeepObfuscatorTypes; + op.MetadataFlags = options.MetadataFlags; + op.RenamerFlags = options.RenamerFlags; + + return op; + } + + void DetectObfuscator(IEnumerable deobfuscators) { + + // The deobfuscators may call methods to deobfuscate control flow and decrypt + // strings (statically) in order to detect the obfuscator. + if (!options.ControlFlowDeobfuscation || options.StringDecrypterType == DecrypterType.None) + savedMethodBodies = new SavedMethodBodies(); + + // It's not null if it unpacked a native file + if (deob != null) { + deob.Initialize(module); + deob.DeobfuscatedFile = this; + deob.Detect(); + return; + } + + foreach (var deob in deobfuscators) { + deob.Initialize(module); + deob.DeobfuscatedFile = this; + } + + if (options.ForcedObfuscatorType != null) { + foreach (var deob in deobfuscators) { + if (string.Equals(options.ForcedObfuscatorType, deob.Type, StringComparison.OrdinalIgnoreCase)) { + this.deob = deob; + deob.Detect(); + return; + } + } + } + else + deob = DetectObfuscator2(deobfuscators); + } + + IDeobfuscator DetectObfuscator2(IEnumerable deobfuscators) { + var allDetected = new List(); + IDeobfuscator detected = null; + int detectVal = 0; + foreach (var deob in deobfuscators) { + this.deob = deob; // So we can call deob.CanInlineMethods in deobfuscate() + int val; + //TODO: Re-enable exception handler + //try { + val = deob.Detect(); + /*} + catch { + val = deob.Type == "un" ? 1 : 0; + }*/ + Logger.v("{0,3}: {1}", val, deob.TypeLong); + if (val > 0 && deob.Type != "un") + allDetected.Add(deob); + if (val > detectVal) { + detectVal = val; + detected = deob; + } + } + deob = null; + + if (allDetected.Count > 1) { + Logger.n("More than one obfuscator detected:"); + Logger.Instance.Indent(); + foreach (var deob in allDetected) + Logger.n("{0} (use: -p {1})", deob.Name, deob.Type); + Logger.Instance.DeIndent(); + } + + return detected; + } + + MetadataFlags GetMetadataFlags() { + var mdFlags = options.MetadataFlags | deob.MetadataFlags; + + // Always preserve tokens if it's an unknown obfuscator + if (deob.Type == "un") { + mdFlags |= MetadataFlags.PreserveRids | + MetadataFlags.PreserveUSOffsets | + MetadataFlags.PreserveBlobOffsets | + MetadataFlags.PreserveExtraSignatureData; + } + + return mdFlags; + } + + public void Save() { + Logger.n("Saving {0}", options.NewFilename); + var mdFlags = GetMetadataFlags(); + if (!options.ControlFlowDeobfuscation) + mdFlags |= MetadataFlags.KeepOldMaxStack; + assemblyModule.Save(options.NewFilename, mdFlags, new PrintNewTokens(module, deob as IModuleWriterListener)); + } + + IList GetAllMethods() { + var list = new List(); + + foreach (var type in module.GetTypes()) { + foreach (var method in type.Methods) + list.Add(method); + } + + return list; + } + + public void DeobfuscateBegin() { + switch (options.StringDecrypterType) { + case DecrypterType.None: + CheckSupportedStringDecrypter(StringFeatures.AllowNoDecryption); + break; + + case DecrypterType.Static: + CheckSupportedStringDecrypter(StringFeatures.AllowStaticDecryption); + break; + + case DecrypterType.Delegate: + case DecrypterType.Emulate: + CheckSupportedStringDecrypter(StringFeatures.AllowDynamicDecryption); + var newProcFactory = assemblyClientFactory as NewProcessAssemblyClientFactory; + if (newProcFactory != null) + assemblyClient = newProcFactory.Create(AssemblyServiceType.StringDecrypter, module); + else + assemblyClient = assemblyClientFactory.Create(AssemblyServiceType.StringDecrypter); + assemblyClient.Connect(); + break; + + default: + throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); + } + } + + public void CheckSupportedStringDecrypter(StringFeatures feature) { + if ((deob.StringFeatures & feature) == feature) + return; + throw new UserException($"Deobfuscator {deob.TypeLong} does not support this string decryption type"); + } + + public void Deobfuscate() { + Logger.n("Cleaning {0}", options.Filename); + InitAssemblyClient(); + + for (int i = 0; ; i++) { + byte[] fileData = null; + DumpedMethods dumpedMethods = null; + if (!deob.GetDecryptedModule(i, ref fileData, ref dumpedMethods)) + break; + ReloadModule(fileData, dumpedMethods); + } + + deob.DeobfuscateBegin(); + DeobfuscateMethods(); if (options.StringDecrypterType == DecrypterType.Delegate || options.StringDecrypterType == DecrypterType.Emulate) - dynamicStringInliner.LogUnusedDecrypters(); - deob.DeobfuscateEnd(); - } - - void ReloadModule(byte[] newModuleData, DumpedMethods dumpedMethods) { - Logger.v("Reloading decrypted assembly (original filename: {0})", Filename); - simpleDeobfuscatorFlags.Clear(); - using (var oldModule = module) { - module = assemblyModule.Reload(newModuleData, CreateDumpedMethodsRestorer(dumpedMethods), deob as IStringDecrypter); - deob = deob.ModuleReloaded(module); - } - InitializeDeobfuscator(); - deob.DeobfuscatedFile = this; - UpdateDynamicStringInliner(); - } - - DumpedMethodsRestorer CreateDumpedMethodsRestorer(DumpedMethods dumpedMethods) { - if (dumpedMethods == null || dumpedMethods.Count == 0) - return null; - return new DumpedMethodsRestorer(dumpedMethods); - } - - void InitAssemblyClient() { - if (assemblyClient == null) - return; - - assemblyClient.WaitConnected(); - assemblyClient.StringDecrypterService.LoadAssembly(options.Filename); - - if (options.StringDecrypterType == DecrypterType.Delegate) - assemblyClient.StringDecrypterService.SetStringDecrypterType(AssemblyData.StringDecrypterType.Delegate); - else if (options.StringDecrypterType == DecrypterType.Emulate) - assemblyClient.StringDecrypterService.SetStringDecrypterType(AssemblyData.StringDecrypterType.Emulate); - else - throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); - - dynamicStringInliner = new DynamicStringInliner(assemblyClient); - UpdateDynamicStringInliner(); - } - - void UpdateDynamicStringInliner() { - if (dynamicStringInliner != null) - dynamicStringInliner.Initialize(GetMethodTokens()); - } - - IEnumerable GetMethodTokens() { - if (!userStringDecrypterMethods) - return deob.GetStringDecrypterMethods(); - - var tokens = new List(); - - foreach (var val in options.StringDecrypterMethods) { - var tokenStr = val.Trim(); - if (Utils.StartsWith(tokenStr, "0x", StringComparison.OrdinalIgnoreCase)) - tokenStr = tokenStr.Substring(2); - if (int.TryParse(tokenStr, NumberStyles.HexNumber, null, out int methodToken)) - tokens.Add(methodToken); - else - tokens.AddRange(FindMethodTokens(val)); - } - - return tokens; - } - - IEnumerable FindMethodTokens(string methodDesc) { - var tokens = new List(); - - SplitMethodDesc(methodDesc, out string typeString, out string methodName, out var argsStrings); - - foreach (var type in module.GetTypes()) { - if (typeString != null && typeString != type.FullName) - continue; - foreach (var method in type.Methods) { - if (!method.IsStatic) - continue; - if (method.MethodSig.GetRetType().GetElementType() != ElementType.String && method.MethodSig.GetRetType().GetElementType() != ElementType.Object) - continue; - if (methodName != null && methodName != method.Name) - continue; - - var sig = method.MethodSig; - if (argsStrings == null) { - if (sig.Params.Count == 0) - continue; - } - else { - if (argsStrings.Length != sig.Params.Count) - continue; - for (int i = 0; i < argsStrings.Length; i++) { - if (argsStrings[i] != sig.Params[i].FullName) - continue; - } - } - - Logger.v("Adding string decrypter; token: {0:X8}, method: {1}", method.MDToken.ToInt32(), Utils.RemoveNewlines(method.FullName)); - tokens.Add(method.MDToken.ToInt32()); - } - } - - return tokens; - } - - static void SplitMethodDesc(string methodDesc, out string type, out string name, out string[] args) { - string stringArgs = null; - args = null; - type = null; - name = null; - - var remaining = methodDesc; - int index = remaining.LastIndexOf("::"); - if (index >= 0) { - type = remaining.Substring(0, index); - remaining = remaining.Substring(index + 2); - } - - index = remaining.IndexOf('('); - if (index >= 0) { - name = remaining.Substring(0, index); - remaining = remaining.Substring(index); - } - else { - name = remaining; - remaining = ""; - } - - if (Utils.StartsWith(remaining, "(", StringComparison.Ordinal)) { - stringArgs = remaining; - } - else if (remaining.Length > 0) - throw new UserException($"Invalid method desc: '{methodDesc}'"); - - if (stringArgs != null) { - if (Utils.StartsWith(stringArgs, "(", StringComparison.Ordinal)) - stringArgs = stringArgs.Substring(1); - if (stringArgs.EndsWith(")", StringComparison.Ordinal)) - stringArgs = stringArgs.Substring(0, stringArgs.Length - 1); - args = stringArgs.Split(','); - for (int i = 0; i < args.Length; i++) - args[i] = args[i].Trim(); - } - - if (type == "") - type = null; - if (name == "") - name = null; - } - - public void DeobfuscateEnd() { - DeobfuscateCleanUp(); - } - - public void DeobfuscateCleanUp() { - if (assemblyClient != null) { - assemblyClient.Dispose(); - assemblyClient = null; - } - } - - void DeobfuscateMethods() { - if (savedMethodBodies != null) { - savedMethodBodies.RestoreAll(); - savedMethodBodies = null; - } - deob.DeobfuscatedFile = null; - - if (!options.ControlFlowDeobfuscation) { - if (options.KeepObfuscatorTypes || deob.Type == "un") - return; - } - - bool isVerbose = !Logger.Instance.IgnoresEvent(LoggerEvent.Verbose); - bool isVV = !Logger.Instance.IgnoresEvent(LoggerEvent.VeryVerbose); - if (isVerbose) - Logger.v("Deobfuscating methods"); - var methodPrinter = new MethodPrinter(); - var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators); - foreach (var method in GetAllMethods()) { - if (isVerbose) { - Logger.v("Deobfuscating {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); - Logger.Instance.Indent(); - } - - int oldIndentLevel = Logger.Instance.IndentLevel; - //TODO: Re-enable exception handler - //try { - Deobfuscate(method, cflowDeobfuscator, methodPrinter, isVerbose, isVV); - /*} - catch (Exception ex) { - if (!CanLoadMethodBody(method)) { - if (isVerbose) - Logger.v("Invalid method body. {0:X8}", method.MDToken.ToInt32()); - method.Body = new CilBody(); - } - else { - Logger.w("Could not deobfuscate method {0:X8}. Hello, E.T.: {1}", // E.T. = exception type - method.MDToken.ToInt32(), - ex.GetType()); - } - } - finally { - Logger.Instance.IndentLevel = oldIndentLevel; - }*/ - - RemoveNoInliningAttribute(method); - - if (isVerbose) - Logger.Instance.DeIndent(); - } - } - - static bool CanLoadMethodBody(MethodDef method) { - try { - var body = method.Body; - return true; - } - catch { - return false; - } - } - - bool CanOptimizeLocals() { - // Don't remove any locals if we must preserve StandAloneSig table - return (GetMetadataFlags() & MetadataFlags.PreserveStandAloneSigRids) == 0; - } - - void Deobfuscate(MethodDef method, BlocksCflowDeobfuscator cflowDeobfuscator, MethodPrinter methodPrinter, bool isVerbose, bool isVV) { - if (!HasNonEmptyBody(method)) - return; - - var blocks = new Blocks(method); - - int numRemovedLocals = 0; - int oldNumInstructions = method.Body.Instructions.Count; - - deob.DeobfuscateMethodBegin(blocks); - if (options.ControlFlowDeobfuscation) { - cflowDeobfuscator.Initialize(blocks); - try { - cflowDeobfuscator.Deobfuscate(); - } - catch (Exception) { - Logger.e("Error during cflow deobfuscation of {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); - throw; - } - } - - if (deob.DeobfuscateOther(blocks) && options.ControlFlowDeobfuscation) - cflowDeobfuscator.Deobfuscate(); - - if (options.ControlFlowDeobfuscation) { - if (CanOptimizeLocals()) - numRemovedLocals = blocks.OptimizeLocals(); - blocks.RepartitionBlocks(); - } - - DeobfuscateStrings(blocks); - deob.DeobfuscateMethodEnd(blocks); - - blocks.GetCode(out var allInstructions, out var allExceptionHandlers); - DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers); - - if (isVerbose && numRemovedLocals > 0) - Logger.v("Removed {0} unused local(s)", numRemovedLocals); - int numRemovedInstructions = oldNumInstructions - method.Body.Instructions.Count; - if (isVerbose && numRemovedInstructions > 0) - Logger.v("Removed {0} dead instruction(s)", numRemovedInstructions); - - if (isVV) { - Logger.Log(LoggerEvent.VeryVerbose, "Deobfuscated code:"); - Logger.Instance.Indent(); - methodPrinter.Print(LoggerEvent.VeryVerbose, allInstructions, allExceptionHandlers); - Logger.Instance.DeIndent(); - } - } - - bool HasNonEmptyBody(MethodDef method) => method.HasBody && method.Body.Instructions.Count > 0; - - void DeobfuscateStrings(Blocks blocks) { - switch (options.StringDecrypterType) { - case DecrypterType.None: - break; - - case DecrypterType.Static: - deob.DeobfuscateStrings(blocks); - break; - - case DecrypterType.Delegate: - case DecrypterType.Emulate: - dynamicStringInliner.Decrypt(blocks); - break; - - default: - throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); - } - } - - void RemoveNoInliningAttribute(MethodDef method) { - method.IsNoInlining = false; - for (int i = 0; i < method.CustomAttributes.Count; i++) { - var cattr = method.CustomAttributes[i]; - if (cattr.TypeFullName != "System.Runtime.CompilerServices.MethodImplAttribute") - continue; - int options = 0; - if (!GetMethodImplOptions(cattr, ref options)) - continue; - if (options != 0 && options != (int)MethodImplAttributes.NoInlining) - continue; - method.CustomAttributes.RemoveAt(i); - i--; - } - } - - static bool GetMethodImplOptions(CustomAttribute cattr, ref int value) { - if (cattr.IsRawBlob) - return false; - if (cattr.ConstructorArguments.Count != 1) - return false; - if (cattr.ConstructorArguments[0].Type.ElementType != ElementType.I2 && - cattr.ConstructorArguments[0].Type.FullName != "System.Runtime.CompilerServices.MethodImplOptions") - return false; - - var arg = cattr.ConstructorArguments[0].Value; - if (arg is short) { - value = (short)arg; - return true; - } - if (arg is int) { - value = (int)arg; - return true; - } - - return false; - } - - public override string ToString() { - if (options == null || options.Filename == null) - return base.ToString(); - return options.Filename; - } - - [Flags] - enum SimpleDeobFlags { - HasDeobfuscated = 0x1, - } - Dictionary simpleDeobfuscatorFlags = new Dictionary(); - bool Check(MethodDef method, SimpleDeobFlags flag) { - if (method == null) - return false; - simpleDeobfuscatorFlags.TryGetValue(method, out var oldFlags); - simpleDeobfuscatorFlags[method] = oldFlags | flag; - return (oldFlags & flag) == flag; - } - bool Clear(MethodDef method, SimpleDeobFlags flag) { - if (method == null) - return false; - if (!simpleDeobfuscatorFlags.TryGetValue(method, out var oldFlags)) - return false; - simpleDeobfuscatorFlags[method] = oldFlags & ~flag; - return true; - } - - void Deobfuscate(MethodDef method, string msg, Action handler) { - if (savedMethodBodies != null) - savedMethodBodies.Save(method); - - Logger.v("{0}: {1} ({2:X8})", msg, Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); - Logger.Instance.Indent(); - - if (HasNonEmptyBody(method)) { - try { - var blocks = new Blocks(method); - - handler(blocks); - - blocks.GetCode(out var allInstructions, out var allExceptionHandlers); - DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers); - } - catch { - Logger.v("Could not deobfuscate {0:X8}", method.MDToken.ToInt32()); - } - } - - Logger.Instance.DeIndent(); - } - - void ISimpleDeobfuscator.MethodModified(MethodDef method) => Clear(method, SimpleDeobFlags.HasDeobfuscated); - void ISimpleDeobfuscator.Deobfuscate(MethodDef method) => ((ISimpleDeobfuscator)this).Deobfuscate(method, 0); - - void ISimpleDeobfuscator.Deobfuscate(MethodDef method, SimpleDeobfuscatorFlags flags) { - bool force = (flags & SimpleDeobfuscatorFlags.Force) != 0; - if (method == null || (!force && Check(method, SimpleDeobFlags.HasDeobfuscated))) - return; - - Deobfuscate(method, "Deobfuscating control flow", (blocks) => { - bool disableNewCFCode = (flags & SimpleDeobfuscatorFlags.DisableConstantsFolderExtraInstrs) != 0; - var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators, disableNewCFCode); - cflowDeobfuscator.Initialize(blocks); - cflowDeobfuscator.Deobfuscate(); - blocks.RepartitionBlocks(); - }); - } - - void ISimpleDeobfuscator.DecryptStrings(MethodDef method, IDeobfuscator theDeob) => - Deobfuscate(method, "Static string decryption", (blocks) => theDeob.DeobfuscateStrings(blocks)); - - void IDeobfuscatedFile.CreateAssemblyFile(byte[] data, string assemblyName, string extension) { - if (extension == null) - extension = ".dll"; - var baseDir = Utils.GetDirName(options.NewFilename); - var newName = Path.Combine(baseDir, assemblyName + extension); - Logger.n("Creating file {0}", newName); - File.WriteAllBytes(newName, data); - } - - void IDeobfuscatedFile.StringDecryptersAdded() => UpdateDynamicStringInliner(); - - void IDeobfuscatedFile.SetDeobfuscator(IDeobfuscator deob) => this.deob = deob; - - public void Dispose() { - DeobfuscateCleanUp(); - if (module != null) - module.Dispose(); - if (deob != null) - deob.Dispose(); - module = null; - deob = null; - } - } -} + dynamicStringInliner.LogUnusedDecrypters(); + deob.DeobfuscateEnd(); + } + + void ReloadModule(byte[] newModuleData, DumpedMethods dumpedMethods) { + Logger.v("Reloading decrypted assembly (original filename: {0})", Filename); + simpleDeobfuscatorFlags.Clear(); + using (var oldModule = module) { + module = assemblyModule.Reload(newModuleData, CreateDumpedMethodsRestorer(dumpedMethods), deob as IStringDecrypter); + deob = deob.ModuleReloaded(module); + } + InitializeDeobfuscator(); + deob.DeobfuscatedFile = this; + UpdateDynamicStringInliner(); + } + + DumpedMethodsRestorer CreateDumpedMethodsRestorer(DumpedMethods dumpedMethods) { + if (dumpedMethods == null || dumpedMethods.Count == 0) + return null; + return new DumpedMethodsRestorer(dumpedMethods); + } + + void InitAssemblyClient() { + if (assemblyClient == null) + return; + + assemblyClient.WaitConnected(); + assemblyClient.StringDecrypterService.LoadAssembly(options.Filename); + + if (options.StringDecrypterType == DecrypterType.Delegate) + assemblyClient.StringDecrypterService.SetStringDecrypterType(AssemblyData.StringDecrypterType.Delegate); + else if (options.StringDecrypterType == DecrypterType.Emulate) + assemblyClient.StringDecrypterService.SetStringDecrypterType(AssemblyData.StringDecrypterType.Emulate); + else + throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); + + dynamicStringInliner = new DynamicStringInliner(assemblyClient); + UpdateDynamicStringInliner(); + } + + void UpdateDynamicStringInliner() { + if (dynamicStringInliner != null) + dynamicStringInliner.Initialize(GetMethodTokens()); + } + + IEnumerable GetMethodTokens() { + if (!userStringDecrypterMethods) + return deob.GetStringDecrypterMethods(); + + var tokens = new List(); + + foreach (var val in options.StringDecrypterMethods) { + var tokenStr = val.Trim(); + if (Utils.StartsWith(tokenStr, "0x", StringComparison.OrdinalIgnoreCase)) + tokenStr = tokenStr.Substring(2); + if (int.TryParse(tokenStr, NumberStyles.HexNumber, null, out int methodToken)) + tokens.Add(methodToken); + else + tokens.AddRange(FindMethodTokens(val)); + } + + return tokens; + } + + IEnumerable FindMethodTokens(string methodDesc) { + var tokens = new List(); + + SplitMethodDesc(methodDesc, out string typeString, out string methodName, out var argsStrings); + + foreach (var type in module.GetTypes()) { + if (typeString != null && typeString != type.FullName) + continue; + foreach (var method in type.Methods) { + if (!method.IsStatic) + continue; + if (method.MethodSig.GetRetType().GetElementType() != ElementType.String && method.MethodSig.GetRetType().GetElementType() != ElementType.Object) + continue; + if (methodName != null && methodName != method.Name) + continue; + + var sig = method.MethodSig; + if (argsStrings == null) { + if (sig.Params.Count == 0) + continue; + } + else { + if (argsStrings.Length != sig.Params.Count) + continue; + for (int i = 0; i < argsStrings.Length; i++) { + if (argsStrings[i] != sig.Params[i].FullName) + continue; + } + } + + Logger.v("Adding string decrypter; token: {0:X8}, method: {1}", method.MDToken.ToInt32(), Utils.RemoveNewlines(method.FullName)); + tokens.Add(method.MDToken.ToInt32()); + } + } + + return tokens; + } + + static void SplitMethodDesc(string methodDesc, out string type, out string name, out string[] args) { + string stringArgs = null; + args = null; + type = null; + name = null; + + var remaining = methodDesc; + int index = remaining.LastIndexOf("::"); + if (index >= 0) { + type = remaining.Substring(0, index); + remaining = remaining.Substring(index + 2); + } + + index = remaining.IndexOf('('); + if (index >= 0) { + name = remaining.Substring(0, index); + remaining = remaining.Substring(index); + } + else { + name = remaining; + remaining = ""; + } + + if (Utils.StartsWith(remaining, "(", StringComparison.Ordinal)) { + stringArgs = remaining; + } + else if (remaining.Length > 0) + throw new UserException($"Invalid method desc: '{methodDesc}'"); + + if (stringArgs != null) { + if (Utils.StartsWith(stringArgs, "(", StringComparison.Ordinal)) + stringArgs = stringArgs.Substring(1); + if (stringArgs.EndsWith(")", StringComparison.Ordinal)) + stringArgs = stringArgs.Substring(0, stringArgs.Length - 1); + args = stringArgs.Split(','); + for (int i = 0; i < args.Length; i++) + args[i] = args[i].Trim(); + } + + if (type == "") + type = null; + if (name == "") + name = null; + } + + public void DeobfuscateEnd() { + DeobfuscateCleanUp(); + } + + public void DeobfuscateCleanUp() { + if (assemblyClient != null) { + assemblyClient.Dispose(); + assemblyClient = null; + } + } + + void DeobfuscateMethods() { + if (savedMethodBodies != null) { + savedMethodBodies.RestoreAll(); + savedMethodBodies = null; + } + deob.DeobfuscatedFile = null; + + if (!options.ControlFlowDeobfuscation) { + if (options.KeepObfuscatorTypes || deob.Type == "un") + return; + } + + bool isVerbose = !Logger.Instance.IgnoresEvent(LoggerEvent.Verbose); + bool isVV = !Logger.Instance.IgnoresEvent(LoggerEvent.VeryVerbose); + if (isVerbose) + Logger.v("Deobfuscating methods"); + var methodPrinter = new MethodPrinter(); + var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators); + foreach (var method in GetAllMethods()) { + if (isVerbose) { + Logger.v("Deobfuscating {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); + Logger.Instance.Indent(); + } + + int oldIndentLevel = Logger.Instance.IndentLevel; + //TODO: Re-enable exception handler + //try { + Deobfuscate(method, cflowDeobfuscator, methodPrinter, isVerbose, isVV); + /*} + catch (Exception ex) { + if (!CanLoadMethodBody(method)) { + if (isVerbose) + Logger.v("Invalid method body. {0:X8}", method.MDToken.ToInt32()); + method.Body = new CilBody(); + } + else { + Logger.w("Could not deobfuscate method {0:X8}. Hello, E.T.: {1}", // E.T. = exception type + method.MDToken.ToInt32(), + ex.GetType()); + } + } + finally { + Logger.Instance.IndentLevel = oldIndentLevel; + }*/ + + RemoveNoInliningAttribute(method); + + if (isVerbose) + Logger.Instance.DeIndent(); + } + } + + static bool CanLoadMethodBody(MethodDef method) { + try { + var body = method.Body; + return true; + } + catch { + return false; + } + } + + bool CanOptimizeLocals() { + // Don't remove any locals if we must preserve StandAloneSig table + return (GetMetadataFlags() & MetadataFlags.PreserveStandAloneSigRids) == 0; + } + + void Deobfuscate(MethodDef method, BlocksCflowDeobfuscator cflowDeobfuscator, MethodPrinter methodPrinter, bool isVerbose, bool isVV) { + if (!HasNonEmptyBody(method)) + return; + + var blocks = new Blocks(method); + + int numRemovedLocals = 0; + int oldNumInstructions = method.Body.Instructions.Count; + + deob.DeobfuscateMethodBegin(blocks); + if (options.ControlFlowDeobfuscation) { + cflowDeobfuscator.Initialize(blocks); + try { + cflowDeobfuscator.Deobfuscate(); + } + catch (Exception) { + Logger.e("Error during cflow deobfuscation of {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); + throw; + } + } + + if (deob.DeobfuscateOther(blocks) && options.ControlFlowDeobfuscation) + cflowDeobfuscator.Deobfuscate(); + + if (options.ControlFlowDeobfuscation) { + if (CanOptimizeLocals()) + numRemovedLocals = blocks.OptimizeLocals(); + blocks.RepartitionBlocks(); + } + + DeobfuscateStrings(blocks); + deob.DeobfuscateMethodEnd(blocks); + + blocks.GetCode(out var allInstructions, out var allExceptionHandlers); + DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers); + + if (isVerbose && numRemovedLocals > 0) + Logger.v("Removed {0} unused local(s)", numRemovedLocals); + int numRemovedInstructions = oldNumInstructions - method.Body.Instructions.Count; + if (isVerbose && numRemovedInstructions > 0) + Logger.v("Removed {0} dead instruction(s)", numRemovedInstructions); + + if (isVV) { + Logger.Log(LoggerEvent.VeryVerbose, "Deobfuscated code:"); + Logger.Instance.Indent(); + methodPrinter.Print(LoggerEvent.VeryVerbose, allInstructions, allExceptionHandlers); + Logger.Instance.DeIndent(); + } + } + + bool HasNonEmptyBody(MethodDef method) => method.HasBody && method.Body.Instructions.Count > 0; + + void DeobfuscateStrings(Blocks blocks) { + switch (options.StringDecrypterType) { + case DecrypterType.None: + break; + + case DecrypterType.Static: + deob.DeobfuscateStrings(blocks); + break; + + case DecrypterType.Delegate: + case DecrypterType.Emulate: + dynamicStringInliner.Decrypt(blocks); + break; + + default: + throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); + } + } + + void RemoveNoInliningAttribute(MethodDef method) { + method.IsNoInlining = false; + for (int i = 0; i < method.CustomAttributes.Count; i++) { + var cattr = method.CustomAttributes[i]; + if (cattr.TypeFullName != "System.Runtime.CompilerServices.MethodImplAttribute") + continue; + int options = 0; + if (!GetMethodImplOptions(cattr, ref options)) + continue; + if (options != 0 && options != (int)MethodImplAttributes.NoInlining) + continue; + method.CustomAttributes.RemoveAt(i); + i--; + } + } + + static bool GetMethodImplOptions(CustomAttribute cattr, ref int value) { + if (cattr.IsRawBlob) + return false; + if (cattr.ConstructorArguments.Count != 1) + return false; + if (cattr.ConstructorArguments[0].Type.ElementType != ElementType.I2 && + cattr.ConstructorArguments[0].Type.FullName != "System.Runtime.CompilerServices.MethodImplOptions") + return false; + + var arg = cattr.ConstructorArguments[0].Value; + if (arg is short) { + value = (short)arg; + return true; + } + if (arg is int) { + value = (int)arg; + return true; + } + + return false; + } + + public override string ToString() { + if (options == null || options.Filename == null) + return base.ToString(); + return options.Filename; + } + + [Flags] + enum SimpleDeobFlags { + HasDeobfuscated = 0x1, + } + Dictionary simpleDeobfuscatorFlags = new Dictionary(); + bool Check(MethodDef method, SimpleDeobFlags flag) { + if (method == null) + return false; + simpleDeobfuscatorFlags.TryGetValue(method, out var oldFlags); + simpleDeobfuscatorFlags[method] = oldFlags | flag; + return (oldFlags & flag) == flag; + } + bool Clear(MethodDef method, SimpleDeobFlags flag) { + if (method == null) + return false; + if (!simpleDeobfuscatorFlags.TryGetValue(method, out var oldFlags)) + return false; + simpleDeobfuscatorFlags[method] = oldFlags & ~flag; + return true; + } + + void Deobfuscate(MethodDef method, string msg, Action handler) { + if (savedMethodBodies != null) + savedMethodBodies.Save(method); + + Logger.v("{0}: {1} ({2:X8})", msg, Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); + Logger.Instance.Indent(); + + if (HasNonEmptyBody(method)) { + try { + var blocks = new Blocks(method); + + handler(blocks); + + blocks.GetCode(out var allInstructions, out var allExceptionHandlers); + DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers); + } + catch { + Logger.v("Could not deobfuscate {0:X8}", method.MDToken.ToInt32()); + } + } + + Logger.Instance.DeIndent(); + } + + void ISimpleDeobfuscator.MethodModified(MethodDef method) => Clear(method, SimpleDeobFlags.HasDeobfuscated); + void ISimpleDeobfuscator.Deobfuscate(MethodDef method) => ((ISimpleDeobfuscator)this).Deobfuscate(method, 0); + + void ISimpleDeobfuscator.Deobfuscate(MethodDef method, SimpleDeobfuscatorFlags flags) { + bool force = (flags & SimpleDeobfuscatorFlags.Force) != 0; + if (method == null || (!force && Check(method, SimpleDeobFlags.HasDeobfuscated))) + return; + + Deobfuscate(method, "Deobfuscating control flow", (blocks) => { + bool disableNewCFCode = (flags & SimpleDeobfuscatorFlags.DisableConstantsFolderExtraInstrs) != 0; + var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators, disableNewCFCode); + cflowDeobfuscator.Initialize(blocks); + cflowDeobfuscator.Deobfuscate(); + blocks.RepartitionBlocks(); + }); + } + + void ISimpleDeobfuscator.DecryptStrings(MethodDef method, IDeobfuscator theDeob) => + Deobfuscate(method, "Static string decryption", (blocks) => theDeob.DeobfuscateStrings(blocks)); + + void IDeobfuscatedFile.CreateAssemblyFile(byte[] data, string assemblyName, string extension) { + if (extension == null) + extension = ".dll"; + var baseDir = Utils.GetDirName(options.NewFilename); + var newName = Path.Combine(baseDir, assemblyName + extension); + Logger.n("Creating file {0}", newName); + File.WriteAllBytes(newName, data); + } + + void IDeobfuscatedFile.StringDecryptersAdded() => UpdateDynamicStringInliner(); + + void IDeobfuscatedFile.SetDeobfuscator(IDeobfuscator deob) => this.deob = deob; + + public void Dispose() { + DeobfuscateCleanUp(); + if (module != null) + module.Dispose(); + if (deob != null) + deob.Dispose(); + module = null; + deob = null; + } + } +} From 5ab1994c0d013e459b5faac22c3f45a5d9b89efc Mon Sep 17 00:00:00 2001 From: doomedraven Date: Mon, 27 Jul 2026 21:28:12 +0200 Subject: [PATCH 3/4] Revert "Conditionalize KeepOldMaxStack flag based on options" This reverts commit 268aab8d0b81739bda23589683009a747891cfd9. --- de4dot.code/ObfuscatedFile.cs | 1603 ++++++++++++++++----------------- 1 file changed, 801 insertions(+), 802 deletions(-) diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index 33adffa9..dc04cdd2 100644 --- a/de4dot.code/ObfuscatedFile.cs +++ b/de4dot.code/ObfuscatedFile.cs @@ -1,803 +1,802 @@ -/* - Copyright (C) 2011-2015 de4dot@gmail.com - - This file is part of de4dot. - - de4dot is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - de4dot is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with de4dot. If not, see . -*/ - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using dnlib.DotNet; -using dnlib.DotNet.Emit; -using dnlib.DotNet.Writer; -using dnlib.PE; -using AssemblyData; -using de4dot.code.deobfuscators; -using de4dot.blocks; -using de4dot.blocks.cflow; -using de4dot.code.AssemblyClient; -using de4dot.code.renamer; - -namespace de4dot.code { - public class ObfuscatedFile : IObfuscatedFile, IDeobfuscatedFile { - Options options; - ModuleDefMD module; - IDeobfuscator deob; - IDeobfuscatorContext deobfuscatorContext; - AssemblyModule assemblyModule; - IAssemblyClient assemblyClient; - DynamicStringInliner dynamicStringInliner; - IAssemblyClientFactory assemblyClientFactory; - SavedMethodBodies savedMethodBodies; - bool userStringDecrypterMethods = false; - - class SavedMethodBodies { - Dictionary savedMethodBodies = new Dictionary(); - - class SavedMethodBody { - MethodDef method; - IList instructions; - IList exceptionHandlers; - - public SavedMethodBody(MethodDef method) { - this.method = method; - DotNetUtils.CopyBody(method, out instructions, out exceptionHandlers); - } - - public void Restore() => DotNetUtils.RestoreBody(method, instructions, exceptionHandlers); - } - - public void Save(MethodDef method) { - if (IsSaved(method)) - return; - savedMethodBodies[method] = new SavedMethodBody(method); - } - - public void RestoreAll() { - foreach (var smb in savedMethodBodies.Values) - smb.Restore(); - savedMethodBodies.Clear(); - } - - public bool IsSaved(MethodDef method) => savedMethodBodies.ContainsKey(method); - } - - public class Options { - public string Filename { get; set; } - public string NewFilename { get; set; } - public string ForcedObfuscatorType { get; set; } - public DecrypterType StringDecrypterType { get; set; } - public List StringDecrypterMethods { get; private set; } - public bool ControlFlowDeobfuscation { get; set; } - public bool KeepObfuscatorTypes { get; set; } - public bool PreserveTokens { get; set; } - public MetadataFlags MetadataFlags { get; set; } - public RenamerFlags RenamerFlags { get; set; } - - public Options() { - StringDecrypterType = DecrypterType.Default; - StringDecrypterMethods = new List(); - } - } - - public string Filename => options.Filename; - public string NewFilename => options.NewFilename; - public ModuleDefMD ModuleDefMD => module; - public INameChecker NameChecker => deob; - public bool RenameResourcesInCode => deob.TheOptions.RenameResourcesInCode; - public bool RemoveNamespaceWithOneType => (deob.RenamingOptions & RenamingOptions.RemoveNamespaceIfOneType) != 0; - public bool RenameResourceKeys => (deob.RenamingOptions & RenamingOptions.RenameResourceKeys) != 0; - public IDeobfuscator Deobfuscator => deob; - public IDeobfuscatorContext DeobfuscatorContext { - get => deobfuscatorContext; - set => deobfuscatorContext = value; - } - - public ObfuscatedFile(Options options, ModuleContext moduleContext, IAssemblyClientFactory assemblyClientFactory) { - this.assemblyClientFactory = assemblyClientFactory; - this.options = options; - userStringDecrypterMethods = options.StringDecrypterMethods.Count > 0; - options.Filename = Utils.GetFullPath(options.Filename); - assemblyModule = new AssemblyModule(options.Filename, moduleContext); - - if (options.NewFilename == null) - options.NewFilename = GetDefaultNewFilename(); - - if (string.Equals(options.Filename, options.NewFilename, StringComparison.OrdinalIgnoreCase)) - throw new UserException($"filename is same as new filename! ({options.Filename})"); - } - - string GetDefaultNewFilename() { - string newFilename = Path.GetFileNameWithoutExtension(options.Filename) + "-cleaned" + Path.GetExtension(options.Filename); - return Path.Combine(Path.GetDirectoryName(options.Filename), newFilename); - } - - public void Load(IList deobfuscators) { - try { - LoadModule(deobfuscators); - TheAssemblyResolver.Instance.AddSearchDirectory(Utils.GetDirName(Filename)); - TheAssemblyResolver.Instance.AddSearchDirectory(Utils.GetDirName(NewFilename)); - DetectObfuscator(deobfuscators); - if (deob == null) - throw new ApplicationException("Could not detect obfuscator!"); - InitializeDeobfuscator(); - } - finally { - foreach (var d in deobfuscators) { - if (d != deob && d != null) - d.Dispose(); - } - } - } - - void LoadModule(IEnumerable deobfuscators) { - var oldModule = module; - try { - module = assemblyModule.Load(); - } - catch (BadImageFormatException) { - if (!UnpackNativeImage(deobfuscators)) - throw new BadImageFormatException(); - Logger.v("Unpacked native file"); - } - finally { - if (oldModule != null) - oldModule.Dispose(); - } - } - - bool UnpackNativeImage(IEnumerable deobfuscators) { - using (var peImage = new PEImage(Filename)) { - foreach (var deob in deobfuscators) { - byte[] unpackedData = null; - try { - unpackedData = deob.UnpackNativeFile(peImage); - } - catch { - } - if (unpackedData == null) - continue; - - var oldModule = module; - try { - module = assemblyModule.Load(unpackedData); - } - catch { - Logger.w("Could not load unpacked data. File: {0}, deobfuscator: {0}", peImage.Filename ?? "(unknown filename)", deob.TypeLong); - continue; - } - finally { - if (oldModule != null) - oldModule.Dispose(); - } - this.deob = deob; - return true; - } - } - - return false; - } - - void InitializeDeobfuscator() { - if (options.StringDecrypterType == DecrypterType.Default) - options.StringDecrypterType = deob.DefaultDecrypterType; - if (options.StringDecrypterType == DecrypterType.Default) - options.StringDecrypterType = DecrypterType.Static; - - deob.Operations = CreateOperations(); - } - - IOperations CreateOperations() { - var op = new Operations(); - - switch (options.StringDecrypterType) { - case DecrypterType.None: - op.DecryptStrings = OpDecryptString.None; - break; - case DecrypterType.Static: - op.DecryptStrings = OpDecryptString.Static; - break; - default: - op.DecryptStrings = OpDecryptString.Dynamic; - break; - } - - op.KeepObfuscatorTypes = options.KeepObfuscatorTypes; - op.MetadataFlags = options.MetadataFlags; - op.RenamerFlags = options.RenamerFlags; - - return op; - } - - void DetectObfuscator(IEnumerable deobfuscators) { - - // The deobfuscators may call methods to deobfuscate control flow and decrypt - // strings (statically) in order to detect the obfuscator. - if (!options.ControlFlowDeobfuscation || options.StringDecrypterType == DecrypterType.None) - savedMethodBodies = new SavedMethodBodies(); - - // It's not null if it unpacked a native file - if (deob != null) { - deob.Initialize(module); - deob.DeobfuscatedFile = this; - deob.Detect(); - return; - } - - foreach (var deob in deobfuscators) { - deob.Initialize(module); - deob.DeobfuscatedFile = this; - } - - if (options.ForcedObfuscatorType != null) { - foreach (var deob in deobfuscators) { - if (string.Equals(options.ForcedObfuscatorType, deob.Type, StringComparison.OrdinalIgnoreCase)) { - this.deob = deob; - deob.Detect(); - return; - } - } - } - else - deob = DetectObfuscator2(deobfuscators); - } - - IDeobfuscator DetectObfuscator2(IEnumerable deobfuscators) { - var allDetected = new List(); - IDeobfuscator detected = null; - int detectVal = 0; - foreach (var deob in deobfuscators) { - this.deob = deob; // So we can call deob.CanInlineMethods in deobfuscate() - int val; - //TODO: Re-enable exception handler - //try { - val = deob.Detect(); - /*} - catch { - val = deob.Type == "un" ? 1 : 0; - }*/ - Logger.v("{0,3}: {1}", val, deob.TypeLong); - if (val > 0 && deob.Type != "un") - allDetected.Add(deob); - if (val > detectVal) { - detectVal = val; - detected = deob; - } - } - deob = null; - - if (allDetected.Count > 1) { - Logger.n("More than one obfuscator detected:"); - Logger.Instance.Indent(); - foreach (var deob in allDetected) - Logger.n("{0} (use: -p {1})", deob.Name, deob.Type); - Logger.Instance.DeIndent(); - } - - return detected; - } - - MetadataFlags GetMetadataFlags() { - var mdFlags = options.MetadataFlags | deob.MetadataFlags; - - // Always preserve tokens if it's an unknown obfuscator - if (deob.Type == "un") { - mdFlags |= MetadataFlags.PreserveRids | - MetadataFlags.PreserveUSOffsets | - MetadataFlags.PreserveBlobOffsets | - MetadataFlags.PreserveExtraSignatureData; - } - - return mdFlags; - } - - public void Save() { - Logger.n("Saving {0}", options.NewFilename); - var mdFlags = GetMetadataFlags(); - if (!options.ControlFlowDeobfuscation) - mdFlags |= MetadataFlags.KeepOldMaxStack; - assemblyModule.Save(options.NewFilename, mdFlags, new PrintNewTokens(module, deob as IModuleWriterListener)); - } - - IList GetAllMethods() { - var list = new List(); - - foreach (var type in module.GetTypes()) { - foreach (var method in type.Methods) - list.Add(method); - } - - return list; - } - - public void DeobfuscateBegin() { - switch (options.StringDecrypterType) { - case DecrypterType.None: - CheckSupportedStringDecrypter(StringFeatures.AllowNoDecryption); - break; - - case DecrypterType.Static: - CheckSupportedStringDecrypter(StringFeatures.AllowStaticDecryption); - break; - - case DecrypterType.Delegate: - case DecrypterType.Emulate: - CheckSupportedStringDecrypter(StringFeatures.AllowDynamicDecryption); - var newProcFactory = assemblyClientFactory as NewProcessAssemblyClientFactory; - if (newProcFactory != null) - assemblyClient = newProcFactory.Create(AssemblyServiceType.StringDecrypter, module); - else - assemblyClient = assemblyClientFactory.Create(AssemblyServiceType.StringDecrypter); - assemblyClient.Connect(); - break; - - default: - throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); - } - } - - public void CheckSupportedStringDecrypter(StringFeatures feature) { - if ((deob.StringFeatures & feature) == feature) - return; - throw new UserException($"Deobfuscator {deob.TypeLong} does not support this string decryption type"); - } - - public void Deobfuscate() { - Logger.n("Cleaning {0}", options.Filename); - InitAssemblyClient(); - - for (int i = 0; ; i++) { - byte[] fileData = null; - DumpedMethods dumpedMethods = null; - if (!deob.GetDecryptedModule(i, ref fileData, ref dumpedMethods)) - break; - ReloadModule(fileData, dumpedMethods); - } - - deob.DeobfuscateBegin(); - DeobfuscateMethods(); +/* + Copyright (C) 2011-2015 de4dot@gmail.com + + This file is part of de4dot. + + de4dot is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + de4dot is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with de4dot. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnlib.DotNet.Writer; +using dnlib.PE; +using AssemblyData; +using de4dot.code.deobfuscators; +using de4dot.blocks; +using de4dot.blocks.cflow; +using de4dot.code.AssemblyClient; +using de4dot.code.renamer; + +namespace de4dot.code { + public class ObfuscatedFile : IObfuscatedFile, IDeobfuscatedFile { + Options options; + ModuleDefMD module; + IDeobfuscator deob; + IDeobfuscatorContext deobfuscatorContext; + AssemblyModule assemblyModule; + IAssemblyClient assemblyClient; + DynamicStringInliner dynamicStringInliner; + IAssemblyClientFactory assemblyClientFactory; + SavedMethodBodies savedMethodBodies; + bool userStringDecrypterMethods = false; + + class SavedMethodBodies { + Dictionary savedMethodBodies = new Dictionary(); + + class SavedMethodBody { + MethodDef method; + IList instructions; + IList exceptionHandlers; + + public SavedMethodBody(MethodDef method) { + this.method = method; + DotNetUtils.CopyBody(method, out instructions, out exceptionHandlers); + } + + public void Restore() => DotNetUtils.RestoreBody(method, instructions, exceptionHandlers); + } + + public void Save(MethodDef method) { + if (IsSaved(method)) + return; + savedMethodBodies[method] = new SavedMethodBody(method); + } + + public void RestoreAll() { + foreach (var smb in savedMethodBodies.Values) + smb.Restore(); + savedMethodBodies.Clear(); + } + + public bool IsSaved(MethodDef method) => savedMethodBodies.ContainsKey(method); + } + + public class Options { + public string Filename { get; set; } + public string NewFilename { get; set; } + public string ForcedObfuscatorType { get; set; } + public DecrypterType StringDecrypterType { get; set; } + public List StringDecrypterMethods { get; private set; } + public bool ControlFlowDeobfuscation { get; set; } + public bool KeepObfuscatorTypes { get; set; } + public bool PreserveTokens { get; set; } + public MetadataFlags MetadataFlags { get; set; } + public RenamerFlags RenamerFlags { get; set; } + + public Options() { + StringDecrypterType = DecrypterType.Default; + StringDecrypterMethods = new List(); + } + } + + public string Filename => options.Filename; + public string NewFilename => options.NewFilename; + public ModuleDefMD ModuleDefMD => module; + public INameChecker NameChecker => deob; + public bool RenameResourcesInCode => deob.TheOptions.RenameResourcesInCode; + public bool RemoveNamespaceWithOneType => (deob.RenamingOptions & RenamingOptions.RemoveNamespaceIfOneType) != 0; + public bool RenameResourceKeys => (deob.RenamingOptions & RenamingOptions.RenameResourceKeys) != 0; + public IDeobfuscator Deobfuscator => deob; + public IDeobfuscatorContext DeobfuscatorContext { + get => deobfuscatorContext; + set => deobfuscatorContext = value; + } + + public ObfuscatedFile(Options options, ModuleContext moduleContext, IAssemblyClientFactory assemblyClientFactory) { + this.assemblyClientFactory = assemblyClientFactory; + this.options = options; + userStringDecrypterMethods = options.StringDecrypterMethods.Count > 0; + options.Filename = Utils.GetFullPath(options.Filename); + assemblyModule = new AssemblyModule(options.Filename, moduleContext); + + if (options.NewFilename == null) + options.NewFilename = GetDefaultNewFilename(); + + if (string.Equals(options.Filename, options.NewFilename, StringComparison.OrdinalIgnoreCase)) + throw new UserException($"filename is same as new filename! ({options.Filename})"); + } + + string GetDefaultNewFilename() { + string newFilename = Path.GetFileNameWithoutExtension(options.Filename) + "-cleaned" + Path.GetExtension(options.Filename); + return Path.Combine(Path.GetDirectoryName(options.Filename), newFilename); + } + + public void Load(IList deobfuscators) { + try { + LoadModule(deobfuscators); + TheAssemblyResolver.Instance.AddSearchDirectory(Utils.GetDirName(Filename)); + TheAssemblyResolver.Instance.AddSearchDirectory(Utils.GetDirName(NewFilename)); + DetectObfuscator(deobfuscators); + if (deob == null) + throw new ApplicationException("Could not detect obfuscator!"); + InitializeDeobfuscator(); + } + finally { + foreach (var d in deobfuscators) { + if (d != deob && d != null) + d.Dispose(); + } + } + } + + void LoadModule(IEnumerable deobfuscators) { + var oldModule = module; + try { + module = assemblyModule.Load(); + } + catch (BadImageFormatException) { + if (!UnpackNativeImage(deobfuscators)) + throw new BadImageFormatException(); + Logger.v("Unpacked native file"); + } + finally { + if (oldModule != null) + oldModule.Dispose(); + } + } + + bool UnpackNativeImage(IEnumerable deobfuscators) { + using (var peImage = new PEImage(Filename)) { + foreach (var deob in deobfuscators) { + byte[] unpackedData = null; + try { + unpackedData = deob.UnpackNativeFile(peImage); + } + catch { + } + if (unpackedData == null) + continue; + + var oldModule = module; + try { + module = assemblyModule.Load(unpackedData); + } + catch { + Logger.w("Could not load unpacked data. File: {0}, deobfuscator: {0}", peImage.Filename ?? "(unknown filename)", deob.TypeLong); + continue; + } + finally { + if (oldModule != null) + oldModule.Dispose(); + } + this.deob = deob; + return true; + } + } + + return false; + } + + void InitializeDeobfuscator() { + if (options.StringDecrypterType == DecrypterType.Default) + options.StringDecrypterType = deob.DefaultDecrypterType; + if (options.StringDecrypterType == DecrypterType.Default) + options.StringDecrypterType = DecrypterType.Static; + + deob.Operations = CreateOperations(); + } + + IOperations CreateOperations() { + var op = new Operations(); + + switch (options.StringDecrypterType) { + case DecrypterType.None: + op.DecryptStrings = OpDecryptString.None; + break; + case DecrypterType.Static: + op.DecryptStrings = OpDecryptString.Static; + break; + default: + op.DecryptStrings = OpDecryptString.Dynamic; + break; + } + + op.KeepObfuscatorTypes = options.KeepObfuscatorTypes; + op.MetadataFlags = options.MetadataFlags; + op.RenamerFlags = options.RenamerFlags; + + return op; + } + + void DetectObfuscator(IEnumerable deobfuscators) { + + // The deobfuscators may call methods to deobfuscate control flow and decrypt + // strings (statically) in order to detect the obfuscator. + if (!options.ControlFlowDeobfuscation || options.StringDecrypterType == DecrypterType.None) + savedMethodBodies = new SavedMethodBodies(); + + // It's not null if it unpacked a native file + if (deob != null) { + deob.Initialize(module); + deob.DeobfuscatedFile = this; + deob.Detect(); + return; + } + + foreach (var deob in deobfuscators) { + deob.Initialize(module); + deob.DeobfuscatedFile = this; + } + + if (options.ForcedObfuscatorType != null) { + foreach (var deob in deobfuscators) { + if (string.Equals(options.ForcedObfuscatorType, deob.Type, StringComparison.OrdinalIgnoreCase)) { + this.deob = deob; + deob.Detect(); + return; + } + } + } + else + deob = DetectObfuscator2(deobfuscators); + } + + IDeobfuscator DetectObfuscator2(IEnumerable deobfuscators) { + var allDetected = new List(); + IDeobfuscator detected = null; + int detectVal = 0; + foreach (var deob in deobfuscators) { + this.deob = deob; // So we can call deob.CanInlineMethods in deobfuscate() + int val; + //TODO: Re-enable exception handler + //try { + val = deob.Detect(); + /*} + catch { + val = deob.Type == "un" ? 1 : 0; + }*/ + Logger.v("{0,3}: {1}", val, deob.TypeLong); + if (val > 0 && deob.Type != "un") + allDetected.Add(deob); + if (val > detectVal) { + detectVal = val; + detected = deob; + } + } + deob = null; + + if (allDetected.Count > 1) { + Logger.n("More than one obfuscator detected:"); + Logger.Instance.Indent(); + foreach (var deob in allDetected) + Logger.n("{0} (use: -p {1})", deob.Name, deob.Type); + Logger.Instance.DeIndent(); + } + + return detected; + } + + MetadataFlags GetMetadataFlags() { + var mdFlags = options.MetadataFlags | deob.MetadataFlags; + + // Always preserve tokens if it's an unknown obfuscator + if (deob.Type == "un") { + mdFlags |= MetadataFlags.PreserveRids | + MetadataFlags.PreserveUSOffsets | + MetadataFlags.PreserveBlobOffsets | + MetadataFlags.PreserveExtraSignatureData; + } + + return mdFlags; + } + + public void Save() { + Logger.n("Saving {0}", options.NewFilename); + var mdFlags = GetMetadataFlags(); + mdFlags |= MetadataFlags.KeepOldMaxStack; + assemblyModule.Save(options.NewFilename, mdFlags, new PrintNewTokens(module, deob as IModuleWriterListener)); + } + + IList GetAllMethods() { + var list = new List(); + + foreach (var type in module.GetTypes()) { + foreach (var method in type.Methods) + list.Add(method); + } + + return list; + } + + public void DeobfuscateBegin() { + switch (options.StringDecrypterType) { + case DecrypterType.None: + CheckSupportedStringDecrypter(StringFeatures.AllowNoDecryption); + break; + + case DecrypterType.Static: + CheckSupportedStringDecrypter(StringFeatures.AllowStaticDecryption); + break; + + case DecrypterType.Delegate: + case DecrypterType.Emulate: + CheckSupportedStringDecrypter(StringFeatures.AllowDynamicDecryption); + var newProcFactory = assemblyClientFactory as NewProcessAssemblyClientFactory; + if (newProcFactory != null) + assemblyClient = newProcFactory.Create(AssemblyServiceType.StringDecrypter, module); + else + assemblyClient = assemblyClientFactory.Create(AssemblyServiceType.StringDecrypter); + assemblyClient.Connect(); + break; + + default: + throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); + } + } + + public void CheckSupportedStringDecrypter(StringFeatures feature) { + if ((deob.StringFeatures & feature) == feature) + return; + throw new UserException($"Deobfuscator {deob.TypeLong} does not support this string decryption type"); + } + + public void Deobfuscate() { + Logger.n("Cleaning {0}", options.Filename); + InitAssemblyClient(); + + for (int i = 0; ; i++) { + byte[] fileData = null; + DumpedMethods dumpedMethods = null; + if (!deob.GetDecryptedModule(i, ref fileData, ref dumpedMethods)) + break; + ReloadModule(fileData, dumpedMethods); + } + + deob.DeobfuscateBegin(); + DeobfuscateMethods(); if (options.StringDecrypterType == DecrypterType.Delegate || options.StringDecrypterType == DecrypterType.Emulate) - dynamicStringInliner.LogUnusedDecrypters(); - deob.DeobfuscateEnd(); - } - - void ReloadModule(byte[] newModuleData, DumpedMethods dumpedMethods) { - Logger.v("Reloading decrypted assembly (original filename: {0})", Filename); - simpleDeobfuscatorFlags.Clear(); - using (var oldModule = module) { - module = assemblyModule.Reload(newModuleData, CreateDumpedMethodsRestorer(dumpedMethods), deob as IStringDecrypter); - deob = deob.ModuleReloaded(module); - } - InitializeDeobfuscator(); - deob.DeobfuscatedFile = this; - UpdateDynamicStringInliner(); - } - - DumpedMethodsRestorer CreateDumpedMethodsRestorer(DumpedMethods dumpedMethods) { - if (dumpedMethods == null || dumpedMethods.Count == 0) - return null; - return new DumpedMethodsRestorer(dumpedMethods); - } - - void InitAssemblyClient() { - if (assemblyClient == null) - return; - - assemblyClient.WaitConnected(); - assemblyClient.StringDecrypterService.LoadAssembly(options.Filename); - - if (options.StringDecrypterType == DecrypterType.Delegate) - assemblyClient.StringDecrypterService.SetStringDecrypterType(AssemblyData.StringDecrypterType.Delegate); - else if (options.StringDecrypterType == DecrypterType.Emulate) - assemblyClient.StringDecrypterService.SetStringDecrypterType(AssemblyData.StringDecrypterType.Emulate); - else - throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); - - dynamicStringInliner = new DynamicStringInliner(assemblyClient); - UpdateDynamicStringInliner(); - } - - void UpdateDynamicStringInliner() { - if (dynamicStringInliner != null) - dynamicStringInliner.Initialize(GetMethodTokens()); - } - - IEnumerable GetMethodTokens() { - if (!userStringDecrypterMethods) - return deob.GetStringDecrypterMethods(); - - var tokens = new List(); - - foreach (var val in options.StringDecrypterMethods) { - var tokenStr = val.Trim(); - if (Utils.StartsWith(tokenStr, "0x", StringComparison.OrdinalIgnoreCase)) - tokenStr = tokenStr.Substring(2); - if (int.TryParse(tokenStr, NumberStyles.HexNumber, null, out int methodToken)) - tokens.Add(methodToken); - else - tokens.AddRange(FindMethodTokens(val)); - } - - return tokens; - } - - IEnumerable FindMethodTokens(string methodDesc) { - var tokens = new List(); - - SplitMethodDesc(methodDesc, out string typeString, out string methodName, out var argsStrings); - - foreach (var type in module.GetTypes()) { - if (typeString != null && typeString != type.FullName) - continue; - foreach (var method in type.Methods) { - if (!method.IsStatic) - continue; - if (method.MethodSig.GetRetType().GetElementType() != ElementType.String && method.MethodSig.GetRetType().GetElementType() != ElementType.Object) - continue; - if (methodName != null && methodName != method.Name) - continue; - - var sig = method.MethodSig; - if (argsStrings == null) { - if (sig.Params.Count == 0) - continue; - } - else { - if (argsStrings.Length != sig.Params.Count) - continue; - for (int i = 0; i < argsStrings.Length; i++) { - if (argsStrings[i] != sig.Params[i].FullName) - continue; - } - } - - Logger.v("Adding string decrypter; token: {0:X8}, method: {1}", method.MDToken.ToInt32(), Utils.RemoveNewlines(method.FullName)); - tokens.Add(method.MDToken.ToInt32()); - } - } - - return tokens; - } - - static void SplitMethodDesc(string methodDesc, out string type, out string name, out string[] args) { - string stringArgs = null; - args = null; - type = null; - name = null; - - var remaining = methodDesc; - int index = remaining.LastIndexOf("::"); - if (index >= 0) { - type = remaining.Substring(0, index); - remaining = remaining.Substring(index + 2); - } - - index = remaining.IndexOf('('); - if (index >= 0) { - name = remaining.Substring(0, index); - remaining = remaining.Substring(index); - } - else { - name = remaining; - remaining = ""; - } - - if (Utils.StartsWith(remaining, "(", StringComparison.Ordinal)) { - stringArgs = remaining; - } - else if (remaining.Length > 0) - throw new UserException($"Invalid method desc: '{methodDesc}'"); - - if (stringArgs != null) { - if (Utils.StartsWith(stringArgs, "(", StringComparison.Ordinal)) - stringArgs = stringArgs.Substring(1); - if (stringArgs.EndsWith(")", StringComparison.Ordinal)) - stringArgs = stringArgs.Substring(0, stringArgs.Length - 1); - args = stringArgs.Split(','); - for (int i = 0; i < args.Length; i++) - args[i] = args[i].Trim(); - } - - if (type == "") - type = null; - if (name == "") - name = null; - } - - public void DeobfuscateEnd() { - DeobfuscateCleanUp(); - } - - public void DeobfuscateCleanUp() { - if (assemblyClient != null) { - assemblyClient.Dispose(); - assemblyClient = null; - } - } - - void DeobfuscateMethods() { - if (savedMethodBodies != null) { - savedMethodBodies.RestoreAll(); - savedMethodBodies = null; - } - deob.DeobfuscatedFile = null; - - if (!options.ControlFlowDeobfuscation) { - if (options.KeepObfuscatorTypes || deob.Type == "un") - return; - } - - bool isVerbose = !Logger.Instance.IgnoresEvent(LoggerEvent.Verbose); - bool isVV = !Logger.Instance.IgnoresEvent(LoggerEvent.VeryVerbose); - if (isVerbose) - Logger.v("Deobfuscating methods"); - var methodPrinter = new MethodPrinter(); - var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators); - foreach (var method in GetAllMethods()) { - if (isVerbose) { - Logger.v("Deobfuscating {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); - Logger.Instance.Indent(); - } - - int oldIndentLevel = Logger.Instance.IndentLevel; - //TODO: Re-enable exception handler - //try { - Deobfuscate(method, cflowDeobfuscator, methodPrinter, isVerbose, isVV); - /*} - catch (Exception ex) { - if (!CanLoadMethodBody(method)) { - if (isVerbose) - Logger.v("Invalid method body. {0:X8}", method.MDToken.ToInt32()); - method.Body = new CilBody(); - } - else { - Logger.w("Could not deobfuscate method {0:X8}. Hello, E.T.: {1}", // E.T. = exception type - method.MDToken.ToInt32(), - ex.GetType()); - } - } - finally { - Logger.Instance.IndentLevel = oldIndentLevel; - }*/ - - RemoveNoInliningAttribute(method); - - if (isVerbose) - Logger.Instance.DeIndent(); - } - } - - static bool CanLoadMethodBody(MethodDef method) { - try { - var body = method.Body; - return true; - } - catch { - return false; - } - } - - bool CanOptimizeLocals() { - // Don't remove any locals if we must preserve StandAloneSig table - return (GetMetadataFlags() & MetadataFlags.PreserveStandAloneSigRids) == 0; - } - - void Deobfuscate(MethodDef method, BlocksCflowDeobfuscator cflowDeobfuscator, MethodPrinter methodPrinter, bool isVerbose, bool isVV) { - if (!HasNonEmptyBody(method)) - return; - - var blocks = new Blocks(method); - - int numRemovedLocals = 0; - int oldNumInstructions = method.Body.Instructions.Count; - - deob.DeobfuscateMethodBegin(blocks); - if (options.ControlFlowDeobfuscation) { - cflowDeobfuscator.Initialize(blocks); - try { - cflowDeobfuscator.Deobfuscate(); - } - catch (Exception) { - Logger.e("Error during cflow deobfuscation of {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); - throw; - } - } - - if (deob.DeobfuscateOther(blocks) && options.ControlFlowDeobfuscation) - cflowDeobfuscator.Deobfuscate(); - - if (options.ControlFlowDeobfuscation) { - if (CanOptimizeLocals()) - numRemovedLocals = blocks.OptimizeLocals(); - blocks.RepartitionBlocks(); - } - - DeobfuscateStrings(blocks); - deob.DeobfuscateMethodEnd(blocks); - - blocks.GetCode(out var allInstructions, out var allExceptionHandlers); - DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers); - - if (isVerbose && numRemovedLocals > 0) - Logger.v("Removed {0} unused local(s)", numRemovedLocals); - int numRemovedInstructions = oldNumInstructions - method.Body.Instructions.Count; - if (isVerbose && numRemovedInstructions > 0) - Logger.v("Removed {0} dead instruction(s)", numRemovedInstructions); - - if (isVV) { - Logger.Log(LoggerEvent.VeryVerbose, "Deobfuscated code:"); - Logger.Instance.Indent(); - methodPrinter.Print(LoggerEvent.VeryVerbose, allInstructions, allExceptionHandlers); - Logger.Instance.DeIndent(); - } - } - - bool HasNonEmptyBody(MethodDef method) => method.HasBody && method.Body.Instructions.Count > 0; - - void DeobfuscateStrings(Blocks blocks) { - switch (options.StringDecrypterType) { - case DecrypterType.None: - break; - - case DecrypterType.Static: - deob.DeobfuscateStrings(blocks); - break; - - case DecrypterType.Delegate: - case DecrypterType.Emulate: - dynamicStringInliner.Decrypt(blocks); - break; - - default: - throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); - } - } - - void RemoveNoInliningAttribute(MethodDef method) { - method.IsNoInlining = false; - for (int i = 0; i < method.CustomAttributes.Count; i++) { - var cattr = method.CustomAttributes[i]; - if (cattr.TypeFullName != "System.Runtime.CompilerServices.MethodImplAttribute") - continue; - int options = 0; - if (!GetMethodImplOptions(cattr, ref options)) - continue; - if (options != 0 && options != (int)MethodImplAttributes.NoInlining) - continue; - method.CustomAttributes.RemoveAt(i); - i--; - } - } - - static bool GetMethodImplOptions(CustomAttribute cattr, ref int value) { - if (cattr.IsRawBlob) - return false; - if (cattr.ConstructorArguments.Count != 1) - return false; - if (cattr.ConstructorArguments[0].Type.ElementType != ElementType.I2 && - cattr.ConstructorArguments[0].Type.FullName != "System.Runtime.CompilerServices.MethodImplOptions") - return false; - - var arg = cattr.ConstructorArguments[0].Value; - if (arg is short) { - value = (short)arg; - return true; - } - if (arg is int) { - value = (int)arg; - return true; - } - - return false; - } - - public override string ToString() { - if (options == null || options.Filename == null) - return base.ToString(); - return options.Filename; - } - - [Flags] - enum SimpleDeobFlags { - HasDeobfuscated = 0x1, - } - Dictionary simpleDeobfuscatorFlags = new Dictionary(); - bool Check(MethodDef method, SimpleDeobFlags flag) { - if (method == null) - return false; - simpleDeobfuscatorFlags.TryGetValue(method, out var oldFlags); - simpleDeobfuscatorFlags[method] = oldFlags | flag; - return (oldFlags & flag) == flag; - } - bool Clear(MethodDef method, SimpleDeobFlags flag) { - if (method == null) - return false; - if (!simpleDeobfuscatorFlags.TryGetValue(method, out var oldFlags)) - return false; - simpleDeobfuscatorFlags[method] = oldFlags & ~flag; - return true; - } - - void Deobfuscate(MethodDef method, string msg, Action handler) { - if (savedMethodBodies != null) - savedMethodBodies.Save(method); - - Logger.v("{0}: {1} ({2:X8})", msg, Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); - Logger.Instance.Indent(); - - if (HasNonEmptyBody(method)) { - try { - var blocks = new Blocks(method); - - handler(blocks); - - blocks.GetCode(out var allInstructions, out var allExceptionHandlers); - DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers); - } - catch { - Logger.v("Could not deobfuscate {0:X8}", method.MDToken.ToInt32()); - } - } - - Logger.Instance.DeIndent(); - } - - void ISimpleDeobfuscator.MethodModified(MethodDef method) => Clear(method, SimpleDeobFlags.HasDeobfuscated); - void ISimpleDeobfuscator.Deobfuscate(MethodDef method) => ((ISimpleDeobfuscator)this).Deobfuscate(method, 0); - - void ISimpleDeobfuscator.Deobfuscate(MethodDef method, SimpleDeobfuscatorFlags flags) { - bool force = (flags & SimpleDeobfuscatorFlags.Force) != 0; - if (method == null || (!force && Check(method, SimpleDeobFlags.HasDeobfuscated))) - return; - - Deobfuscate(method, "Deobfuscating control flow", (blocks) => { - bool disableNewCFCode = (flags & SimpleDeobfuscatorFlags.DisableConstantsFolderExtraInstrs) != 0; - var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators, disableNewCFCode); - cflowDeobfuscator.Initialize(blocks); - cflowDeobfuscator.Deobfuscate(); - blocks.RepartitionBlocks(); - }); - } - - void ISimpleDeobfuscator.DecryptStrings(MethodDef method, IDeobfuscator theDeob) => - Deobfuscate(method, "Static string decryption", (blocks) => theDeob.DeobfuscateStrings(blocks)); - - void IDeobfuscatedFile.CreateAssemblyFile(byte[] data, string assemblyName, string extension) { - if (extension == null) - extension = ".dll"; - var baseDir = Utils.GetDirName(options.NewFilename); - var newName = Path.Combine(baseDir, assemblyName + extension); - Logger.n("Creating file {0}", newName); - File.WriteAllBytes(newName, data); - } - - void IDeobfuscatedFile.StringDecryptersAdded() => UpdateDynamicStringInliner(); - - void IDeobfuscatedFile.SetDeobfuscator(IDeobfuscator deob) => this.deob = deob; - - public void Dispose() { - DeobfuscateCleanUp(); - if (module != null) - module.Dispose(); - if (deob != null) - deob.Dispose(); - module = null; - deob = null; - } - } -} + dynamicStringInliner.LogUnusedDecrypters(); + deob.DeobfuscateEnd(); + } + + void ReloadModule(byte[] newModuleData, DumpedMethods dumpedMethods) { + Logger.v("Reloading decrypted assembly (original filename: {0})", Filename); + simpleDeobfuscatorFlags.Clear(); + using (var oldModule = module) { + module = assemblyModule.Reload(newModuleData, CreateDumpedMethodsRestorer(dumpedMethods), deob as IStringDecrypter); + deob = deob.ModuleReloaded(module); + } + InitializeDeobfuscator(); + deob.DeobfuscatedFile = this; + UpdateDynamicStringInliner(); + } + + DumpedMethodsRestorer CreateDumpedMethodsRestorer(DumpedMethods dumpedMethods) { + if (dumpedMethods == null || dumpedMethods.Count == 0) + return null; + return new DumpedMethodsRestorer(dumpedMethods); + } + + void InitAssemblyClient() { + if (assemblyClient == null) + return; + + assemblyClient.WaitConnected(); + assemblyClient.StringDecrypterService.LoadAssembly(options.Filename); + + if (options.StringDecrypterType == DecrypterType.Delegate) + assemblyClient.StringDecrypterService.SetStringDecrypterType(AssemblyData.StringDecrypterType.Delegate); + else if (options.StringDecrypterType == DecrypterType.Emulate) + assemblyClient.StringDecrypterService.SetStringDecrypterType(AssemblyData.StringDecrypterType.Emulate); + else + throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); + + dynamicStringInliner = new DynamicStringInliner(assemblyClient); + UpdateDynamicStringInliner(); + } + + void UpdateDynamicStringInliner() { + if (dynamicStringInliner != null) + dynamicStringInliner.Initialize(GetMethodTokens()); + } + + IEnumerable GetMethodTokens() { + if (!userStringDecrypterMethods) + return deob.GetStringDecrypterMethods(); + + var tokens = new List(); + + foreach (var val in options.StringDecrypterMethods) { + var tokenStr = val.Trim(); + if (Utils.StartsWith(tokenStr, "0x", StringComparison.OrdinalIgnoreCase)) + tokenStr = tokenStr.Substring(2); + if (int.TryParse(tokenStr, NumberStyles.HexNumber, null, out int methodToken)) + tokens.Add(methodToken); + else + tokens.AddRange(FindMethodTokens(val)); + } + + return tokens; + } + + IEnumerable FindMethodTokens(string methodDesc) { + var tokens = new List(); + + SplitMethodDesc(methodDesc, out string typeString, out string methodName, out var argsStrings); + + foreach (var type in module.GetTypes()) { + if (typeString != null && typeString != type.FullName) + continue; + foreach (var method in type.Methods) { + if (!method.IsStatic) + continue; + if (method.MethodSig.GetRetType().GetElementType() != ElementType.String && method.MethodSig.GetRetType().GetElementType() != ElementType.Object) + continue; + if (methodName != null && methodName != method.Name) + continue; + + var sig = method.MethodSig; + if (argsStrings == null) { + if (sig.Params.Count == 0) + continue; + } + else { + if (argsStrings.Length != sig.Params.Count) + continue; + for (int i = 0; i < argsStrings.Length; i++) { + if (argsStrings[i] != sig.Params[i].FullName) + continue; + } + } + + Logger.v("Adding string decrypter; token: {0:X8}, method: {1}", method.MDToken.ToInt32(), Utils.RemoveNewlines(method.FullName)); + tokens.Add(method.MDToken.ToInt32()); + } + } + + return tokens; + } + + static void SplitMethodDesc(string methodDesc, out string type, out string name, out string[] args) { + string stringArgs = null; + args = null; + type = null; + name = null; + + var remaining = methodDesc; + int index = remaining.LastIndexOf("::"); + if (index >= 0) { + type = remaining.Substring(0, index); + remaining = remaining.Substring(index + 2); + } + + index = remaining.IndexOf('('); + if (index >= 0) { + name = remaining.Substring(0, index); + remaining = remaining.Substring(index); + } + else { + name = remaining; + remaining = ""; + } + + if (Utils.StartsWith(remaining, "(", StringComparison.Ordinal)) { + stringArgs = remaining; + } + else if (remaining.Length > 0) + throw new UserException($"Invalid method desc: '{methodDesc}'"); + + if (stringArgs != null) { + if (Utils.StartsWith(stringArgs, "(", StringComparison.Ordinal)) + stringArgs = stringArgs.Substring(1); + if (stringArgs.EndsWith(")", StringComparison.Ordinal)) + stringArgs = stringArgs.Substring(0, stringArgs.Length - 1); + args = stringArgs.Split(','); + for (int i = 0; i < args.Length; i++) + args[i] = args[i].Trim(); + } + + if (type == "") + type = null; + if (name == "") + name = null; + } + + public void DeobfuscateEnd() { + DeobfuscateCleanUp(); + } + + public void DeobfuscateCleanUp() { + if (assemblyClient != null) { + assemblyClient.Dispose(); + assemblyClient = null; + } + } + + void DeobfuscateMethods() { + if (savedMethodBodies != null) { + savedMethodBodies.RestoreAll(); + savedMethodBodies = null; + } + deob.DeobfuscatedFile = null; + + if (!options.ControlFlowDeobfuscation) { + if (options.KeepObfuscatorTypes || deob.Type == "un") + return; + } + + bool isVerbose = !Logger.Instance.IgnoresEvent(LoggerEvent.Verbose); + bool isVV = !Logger.Instance.IgnoresEvent(LoggerEvent.VeryVerbose); + if (isVerbose) + Logger.v("Deobfuscating methods"); + var methodPrinter = new MethodPrinter(); + var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators); + foreach (var method in GetAllMethods()) { + if (isVerbose) { + Logger.v("Deobfuscating {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); + Logger.Instance.Indent(); + } + + int oldIndentLevel = Logger.Instance.IndentLevel; + //TODO: Re-enable exception handler + //try { + Deobfuscate(method, cflowDeobfuscator, methodPrinter, isVerbose, isVV); + /*} + catch (Exception ex) { + if (!CanLoadMethodBody(method)) { + if (isVerbose) + Logger.v("Invalid method body. {0:X8}", method.MDToken.ToInt32()); + method.Body = new CilBody(); + } + else { + Logger.w("Could not deobfuscate method {0:X8}. Hello, E.T.: {1}", // E.T. = exception type + method.MDToken.ToInt32(), + ex.GetType()); + } + } + finally { + Logger.Instance.IndentLevel = oldIndentLevel; + }*/ + + RemoveNoInliningAttribute(method); + + if (isVerbose) + Logger.Instance.DeIndent(); + } + } + + static bool CanLoadMethodBody(MethodDef method) { + try { + var body = method.Body; + return true; + } + catch { + return false; + } + } + + bool CanOptimizeLocals() { + // Don't remove any locals if we must preserve StandAloneSig table + return (GetMetadataFlags() & MetadataFlags.PreserveStandAloneSigRids) == 0; + } + + void Deobfuscate(MethodDef method, BlocksCflowDeobfuscator cflowDeobfuscator, MethodPrinter methodPrinter, bool isVerbose, bool isVV) { + if (!HasNonEmptyBody(method)) + return; + + var blocks = new Blocks(method); + + int numRemovedLocals = 0; + int oldNumInstructions = method.Body.Instructions.Count; + + deob.DeobfuscateMethodBegin(blocks); + if (options.ControlFlowDeobfuscation) { + cflowDeobfuscator.Initialize(blocks); + try { + cflowDeobfuscator.Deobfuscate(); + } + catch (Exception) { + Logger.e("Error during cflow deobfuscation of {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); + throw; + } + } + + if (deob.DeobfuscateOther(blocks) && options.ControlFlowDeobfuscation) + cflowDeobfuscator.Deobfuscate(); + + if (options.ControlFlowDeobfuscation) { + if (CanOptimizeLocals()) + numRemovedLocals = blocks.OptimizeLocals(); + blocks.RepartitionBlocks(); + } + + DeobfuscateStrings(blocks); + deob.DeobfuscateMethodEnd(blocks); + + blocks.GetCode(out var allInstructions, out var allExceptionHandlers); + DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers); + + if (isVerbose && numRemovedLocals > 0) + Logger.v("Removed {0} unused local(s)", numRemovedLocals); + int numRemovedInstructions = oldNumInstructions - method.Body.Instructions.Count; + if (isVerbose && numRemovedInstructions > 0) + Logger.v("Removed {0} dead instruction(s)", numRemovedInstructions); + + if (isVV) { + Logger.Log(LoggerEvent.VeryVerbose, "Deobfuscated code:"); + Logger.Instance.Indent(); + methodPrinter.Print(LoggerEvent.VeryVerbose, allInstructions, allExceptionHandlers); + Logger.Instance.DeIndent(); + } + } + + bool HasNonEmptyBody(MethodDef method) => method.HasBody && method.Body.Instructions.Count > 0; + + void DeobfuscateStrings(Blocks blocks) { + switch (options.StringDecrypterType) { + case DecrypterType.None: + break; + + case DecrypterType.Static: + deob.DeobfuscateStrings(blocks); + break; + + case DecrypterType.Delegate: + case DecrypterType.Emulate: + dynamicStringInliner.Decrypt(blocks); + break; + + default: + throw new ApplicationException($"Invalid string decrypter type '{options.StringDecrypterType}'"); + } + } + + void RemoveNoInliningAttribute(MethodDef method) { + method.IsNoInlining = false; + for (int i = 0; i < method.CustomAttributes.Count; i++) { + var cattr = method.CustomAttributes[i]; + if (cattr.TypeFullName != "System.Runtime.CompilerServices.MethodImplAttribute") + continue; + int options = 0; + if (!GetMethodImplOptions(cattr, ref options)) + continue; + if (options != 0 && options != (int)MethodImplAttributes.NoInlining) + continue; + method.CustomAttributes.RemoveAt(i); + i--; + } + } + + static bool GetMethodImplOptions(CustomAttribute cattr, ref int value) { + if (cattr.IsRawBlob) + return false; + if (cattr.ConstructorArguments.Count != 1) + return false; + if (cattr.ConstructorArguments[0].Type.ElementType != ElementType.I2 && + cattr.ConstructorArguments[0].Type.FullName != "System.Runtime.CompilerServices.MethodImplOptions") + return false; + + var arg = cattr.ConstructorArguments[0].Value; + if (arg is short) { + value = (short)arg; + return true; + } + if (arg is int) { + value = (int)arg; + return true; + } + + return false; + } + + public override string ToString() { + if (options == null || options.Filename == null) + return base.ToString(); + return options.Filename; + } + + [Flags] + enum SimpleDeobFlags { + HasDeobfuscated = 0x1, + } + Dictionary simpleDeobfuscatorFlags = new Dictionary(); + bool Check(MethodDef method, SimpleDeobFlags flag) { + if (method == null) + return false; + simpleDeobfuscatorFlags.TryGetValue(method, out var oldFlags); + simpleDeobfuscatorFlags[method] = oldFlags | flag; + return (oldFlags & flag) == flag; + } + bool Clear(MethodDef method, SimpleDeobFlags flag) { + if (method == null) + return false; + if (!simpleDeobfuscatorFlags.TryGetValue(method, out var oldFlags)) + return false; + simpleDeobfuscatorFlags[method] = oldFlags & ~flag; + return true; + } + + void Deobfuscate(MethodDef method, string msg, Action handler) { + if (savedMethodBodies != null) + savedMethodBodies.Save(method); + + Logger.v("{0}: {1} ({2:X8})", msg, Utils.RemoveNewlines(method), method.MDToken.ToUInt32()); + Logger.Instance.Indent(); + + if (HasNonEmptyBody(method)) { + try { + var blocks = new Blocks(method); + + handler(blocks); + + blocks.GetCode(out var allInstructions, out var allExceptionHandlers); + DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers); + } + catch { + Logger.v("Could not deobfuscate {0:X8}", method.MDToken.ToInt32()); + } + } + + Logger.Instance.DeIndent(); + } + + void ISimpleDeobfuscator.MethodModified(MethodDef method) => Clear(method, SimpleDeobFlags.HasDeobfuscated); + void ISimpleDeobfuscator.Deobfuscate(MethodDef method) => ((ISimpleDeobfuscator)this).Deobfuscate(method, 0); + + void ISimpleDeobfuscator.Deobfuscate(MethodDef method, SimpleDeobfuscatorFlags flags) { + bool force = (flags & SimpleDeobfuscatorFlags.Force) != 0; + if (method == null || (!force && Check(method, SimpleDeobFlags.HasDeobfuscated))) + return; + + Deobfuscate(method, "Deobfuscating control flow", (blocks) => { + bool disableNewCFCode = (flags & SimpleDeobfuscatorFlags.DisableConstantsFolderExtraInstrs) != 0; + var cflowDeobfuscator = new BlocksCflowDeobfuscator(deob.BlocksDeobfuscators, disableNewCFCode); + cflowDeobfuscator.Initialize(blocks); + cflowDeobfuscator.Deobfuscate(); + blocks.RepartitionBlocks(); + }); + } + + void ISimpleDeobfuscator.DecryptStrings(MethodDef method, IDeobfuscator theDeob) => + Deobfuscate(method, "Static string decryption", (blocks) => theDeob.DeobfuscateStrings(blocks)); + + void IDeobfuscatedFile.CreateAssemblyFile(byte[] data, string assemblyName, string extension) { + if (extension == null) + extension = ".dll"; + var baseDir = Utils.GetDirName(options.NewFilename); + var newName = Path.Combine(baseDir, assemblyName + extension); + Logger.n("Creating file {0}", newName); + File.WriteAllBytes(newName, data); + } + + void IDeobfuscatedFile.StringDecryptersAdded() => UpdateDynamicStringInliner(); + + void IDeobfuscatedFile.SetDeobfuscator(IDeobfuscator deob) => this.deob = deob; + + public void Dispose() { + DeobfuscateCleanUp(); + if (module != null) + module.Dispose(); + if (deob != null) + deob.Dispose(); + module = null; + deob = null; + } + } +} From ed762dd9c823b59ab8fe5a4b3af9e9ee5a207544 Mon Sep 17 00:00:00 2001 From: doomedraven Date: Mon, 27 Jul 2026 21:42:30 +0200 Subject: [PATCH 4/4] Update ObfuscatedFile.cs --- de4dot.code/ObfuscatedFile.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index dc04cdd2..dc76ee20 100644 --- a/de4dot.code/ObfuscatedFile.cs +++ b/de4dot.code/ObfuscatedFile.cs @@ -309,7 +309,8 @@ MetadataFlags GetMetadataFlags() { public void Save() { Logger.n("Saving {0}", options.NewFilename); var mdFlags = GetMetadataFlags(); - mdFlags |= MetadataFlags.KeepOldMaxStack; + if (!options.ControlFlowDeobfuscation) + mdFlags |= MetadataFlags.KeepOldMaxStack; assemblyModule.Save(options.NewFilename, mdFlags, new PrintNewTokens(module, deob as IModuleWriterListener)); }