/* Copyright (C) 2011 de4dot@gmail.com This file is part of de4dot. de4dot is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. de4dot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with de4dot. If not, see . */ using System; using System.IO; using System.Collections.Generic; using Mono.Cecil; using de4dot.renamer; using de4dot.deobfuscators; using de4dot.AssemblyClient; namespace de4dot { class FilesDeobfuscator { Options options; public class Options { public IList DeobfuscatorInfos { get; set; } public IList Files { get; set; } public IList SearchDirs { get; set; } public bool DetectObfuscators { get; set; } public bool RenameSymbols { get; set; } public bool ControlFlowDeobfuscation { get; set; } public bool KeepObfuscatorTypes { get; set; } public bool OneFileAtATime { get; set; } public DecrypterType? DefaultStringDecrypterType { get; set; } public List DefaultStringDecrypterMethods { get; private set; } public IAssemblyClientFactory AssemblyClientFactory { get; set; } public Options() { DeobfuscatorInfos = new List(); Files = new List(); SearchDirs = new List(); DefaultStringDecrypterMethods = new List(); RenameSymbols = true; ControlFlowDeobfuscation = true; } } public class SearchDir { public string InputDirectory { get; set; } public string OutputDirectory { get; set; } public bool SkipUnknownObfuscators { get; set; } } public FilesDeobfuscator(Options options) { this.options = options; } public void doIt() { if (options.DetectObfuscators) detectObfuscators(); else if (options.OneFileAtATime) deobfuscateOneAtATime(); else deobfuscateAll(); } void detectObfuscators() { foreach (var file in loadAllFiles()) { AssemblyResolver.Instance.removeModule(file.ModuleDefinition); } } void deobfuscateOneAtATime() { foreach (var file in loadAllFiles()) { try { file.deobfuscateBegin(); file.deobfuscate(); file.deobfuscateEnd(); if (options.RenameSymbols) new DefinitionsRenamer(new List { file }).renameAll(); file.save(); AssemblyResolver.Instance.removeModule(file.ModuleDefinition); } catch (Exception ex) { Log.w("Could not deobfuscate {0}. Use -v to see stack trace", file.Filename); Utils.printStackTrace(ex, Log.LogLevel.verbose); } finally { file.deobfuscateCleanUp(); } } } void deobfuscateAll() { var allFiles = new List(loadAllFiles()); deobfuscateAllFiles(allFiles); renameAllFiles(allFiles); saveAllFiles(allFiles); } IEnumerable loadAllFiles() { var loader = new DotNetFileLoader(new DotNetFileLoader.Options { PossibleFiles = options.Files, SearchDirs = options.SearchDirs, CreateDeobfuscators = () => createDeobfuscators(), DefaultStringDecrypterType = options.DefaultStringDecrypterType, DefaultStringDecrypterMethods = options.DefaultStringDecrypterMethods, AssemblyClientFactory = options.AssemblyClientFactory, RenameSymbols = options.RenameSymbols, ControlFlowDeobfuscation = options.ControlFlowDeobfuscation, KeepObfuscatorTypes = options.KeepObfuscatorTypes, }); return loader.load(); } class DotNetFileLoader { Options options; Dictionary allFiles = new Dictionary(StringComparer.OrdinalIgnoreCase); Dictionary visitedDirectory = new Dictionary(StringComparer.OrdinalIgnoreCase); public class Options { public IEnumerable PossibleFiles { get; set; } public IEnumerable SearchDirs { get; set; } public Func> CreateDeobfuscators { get; set; } public DecrypterType? DefaultStringDecrypterType { get; set; } public List DefaultStringDecrypterMethods { get; set; } public IAssemblyClientFactory AssemblyClientFactory { get; set; } public bool RenameSymbols { get; set; } public bool ControlFlowDeobfuscation { get; set; } public bool KeepObfuscatorTypes { get; set; } } public DotNetFileLoader(Options options) { this.options = options; } public IEnumerable load() { foreach (var file in options.PossibleFiles) { if (add(file)) yield return file; } foreach (var searchDir in options.SearchDirs) { foreach (var file in loadFiles(searchDir)) yield return file; } } bool add(IObfuscatedFile file, bool skipUnknownObfuscator = false) { var key = Utils.getFullPath(file.Filename); if (allFiles.ContainsKey(key)) { Log.w("Ingoring duplicate file: {0}", file.Filename); return false; } allFiles[key] = true; try { file.load(options.CreateDeobfuscators()); } catch (NotSupportedException) { return false; // Eg. unsupported architecture } catch (BadImageFormatException) { return false; // Not a .NET file } catch (ArgumentOutOfRangeException) { Log.w("Could not load file (argument out of range): {0}", file.Filename); return false; } catch (UnauthorizedAccessException) { Log.w("Could not load file (not authorized): {0}", file.Filename); return false; } catch (NullReferenceException) { Log.w("Could not load file (null ref): {0}", file.Filename); return false; } catch (IOException) { Log.w("Could not load file (io exception): {0}", file.Filename); return false; } if ((file.ModuleDefinition.Attributes & ModuleAttributes.ILOnly) == 0) { Log.w("Ignoring assembly with native code {0}", file.Filename); return false; } var deob = file.Deobfuscator; if (skipUnknownObfuscator && deob is deobfuscators.Unknown.Deobfuscator) { Log.v("Skipping unknown obfuscator: {0}", file.Filename); return false; } else { Log.n("Detected {0} ({1})", deob.Name, file.Filename); createDirectories(Path.GetDirectoryName(file.NewFilename)); return true; } } IEnumerable loadFiles(SearchDir searchDir) { DirectoryInfo di = null; bool ok = false; try { di = new DirectoryInfo(searchDir.InputDirectory); if (di.Exists) ok = true; } catch (System.Security.SecurityException) { } catch (ArgumentException) { } if (ok) { foreach (var filename in doDirectoryInfo(searchDir, di)) { var obfuscatedFile = createObfuscatedFile(searchDir, filename); if (obfuscatedFile != null) yield return obfuscatedFile; } } } IEnumerable recursiveAdd(SearchDir searchDir, IEnumerable fileSystemInfos) { foreach (var fsi in fileSystemInfos) { if ((int)(fsi.Attributes & FileAttributes.Directory) != 0) { foreach (var filename in doDirectoryInfo(searchDir, (DirectoryInfo)fsi)) yield return filename; } else { var fi = (FileInfo)fsi; if (fi.Exists) yield return fi.FullName; } } } IEnumerable doDirectoryInfo(SearchDir searchDir, DirectoryInfo di) { if (!di.Exists) return null; if (visitedDirectory.ContainsKey(di.FullName)) return null; visitedDirectory[di.FullName] = true; FileSystemInfo[] fsinfos; try { fsinfos = di.GetFileSystemInfos(); } catch (UnauthorizedAccessException) { return null; } catch (IOException) { return null; } return recursiveAdd(searchDir, fsinfos); } IObfuscatedFile createObfuscatedFile(SearchDir searchDir, string filename) { var fileOptions = new ObfuscatedFile.Options { Filename = Utils.getFullPath(filename), RenameSymbols = options.RenameSymbols, ControlFlowDeobfuscation = options.ControlFlowDeobfuscation, KeepObfuscatorTypes = options.KeepObfuscatorTypes, }; if (options.DefaultStringDecrypterType != null) fileOptions.StringDecrypterType = options.DefaultStringDecrypterType.Value; fileOptions.StringDecrypterMethods.AddRange(options.DefaultStringDecrypterMethods); if (!string.IsNullOrEmpty(searchDir.OutputDirectory)) { var inDir = Utils.getFullPath(searchDir.InputDirectory); var outDir = Utils.getFullPath(searchDir.OutputDirectory); if (!Utils.StartsWith(fileOptions.Filename, inDir, StringComparison.OrdinalIgnoreCase)) throw new UserException(string.Format("Filename {0} does not start with inDir {1}", fileOptions.Filename, inDir)); var subDirs = fileOptions.Filename.Substring(inDir.Length); if (subDirs.Length > 0 && subDirs[0] == Path.DirectorySeparatorChar) subDirs = subDirs.Substring(1); fileOptions.NewFilename = Utils.getFullPath(Path.Combine(outDir, subDirs)); if (fileOptions.Filename.Equals(fileOptions.NewFilename, StringComparison.OrdinalIgnoreCase)) throw new UserException(string.Format("Input and output filename is the same: {0}", fileOptions.Filename)); } var obfuscatedFile = new ObfuscatedFile(fileOptions, options.AssemblyClientFactory); if (add(obfuscatedFile, searchDir.SkipUnknownObfuscators)) return obfuscatedFile; return null; } void createDirectories(string path) { if (string.IsNullOrEmpty(path)) return; var di = new DirectoryInfo(path); if (!di.Exists) di.Create(); } } void deobfuscateAllFiles(IEnumerable allFiles) { try { foreach (var file in allFiles) file.deobfuscateBegin(); foreach (var file in allFiles) { file.deobfuscate(); file.deobfuscateEnd(); } } finally { foreach (var file in allFiles) file.deobfuscateCleanUp(); } } void renameAllFiles(IEnumerable allFiles) { if (!options.RenameSymbols) return; new DefinitionsRenamer(allFiles).renameAll(); } void saveAllFiles(IEnumerable allFiles) { foreach (var file in allFiles) file.save(); } IEnumerable createDeobfuscators() { var list = new List(options.DeobfuscatorInfos.Count); foreach (var info in options.DeobfuscatorInfos) list.Add(info.createDeobfuscator()); return list; } } }