de4dot-cex/de4dot.cui/FilesDeobfuscator.cs
ViR Dash d6a18082af Ensure BeaEngine.dll is present and misc changes
Copy BeaEngine.dll on build and check if it exists in runtime
Disable more exception handlers to help detect swallowed exceptions
Misc refactoring
2017-08-19 17:40:14 +03:00

404 lines
13 KiB
C#

/*
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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
using System.Collections.Generic;
using dnlib.DotNet;
using dnlib.DotNet.Writer;
using de4dot.blocks;
using de4dot.code;
using de4dot.code.renamer;
using de4dot.code.deobfuscators;
using de4dot.code.AssemblyClient;
namespace de4dot.cui {
class FilesDeobfuscator {
Options options;
IDeobfuscatorContext deobfuscatorContext = new DeobfuscatorContext();
public class Options {
public ModuleContext ModuleContext { get; set; }
public IList<IDeobfuscatorInfo> DeobfuscatorInfos { get; set; }
public IList<IObfuscatedFile> Files { get; set; }
public IList<SearchDir> SearchDirs { get; set; }
public MetaDataFlags MetaDataFlags { get; set; }
public bool DetectObfuscators { get; set; }
public RenamerFlags RenamerFlags { 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<string> DefaultStringDecrypterMethods { get; private set; }
public IAssemblyClientFactory AssemblyClientFactory { get; set; }
public Options() {
ModuleContext = new ModuleContext(TheAssemblyResolver.Instance);
DeobfuscatorInfos = new List<IDeobfuscatorInfo>();
Files = new List<IObfuscatedFile>();
SearchDirs = new List<SearchDir>();
DefaultStringDecrypterMethods = new List<string>();
RenamerFlags = RenamerFlags.RenameNamespaces |
RenamerFlags.RenameTypes |
RenamerFlags.RenameProperties |
RenamerFlags.RenameEvents |
RenamerFlags.RenameFields |
RenamerFlags.RenameMethods |
RenamerFlags.RenameMethodArgs |
RenamerFlags.RenameGenericParams |
RenamerFlags.RestorePropertiesFromNames |
RenamerFlags.RestoreEventsFromNames |
RenamerFlags.RestoreProperties |
RenamerFlags.RestoreEvents;
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();
}
static void RemoveModule(ModuleDef module) {
TheAssemblyResolver.Instance.Remove(module);
}
void DetectObfuscators() {
foreach (var file in LoadAllFiles(true)) {
RemoveModule(file.ModuleDefMD);
file.Dispose();
deobfuscatorContext.Clear();
}
}
void DeobfuscateOneAtATime() {
foreach (var file in LoadAllFiles()) {
int oldIndentLevel = Logger.Instance.IndentLevel;
try {
file.DeobfuscateBegin();
file.Deobfuscate();
file.DeobfuscateEnd();
Rename(new List<IObfuscatedFile> { file });
file.Save();
RemoveModule(file.ModuleDefMD);
TheAssemblyResolver.Instance.ClearAll();
deobfuscatorContext.Clear();
}
catch (Exception ex) {
Logger.Instance.Log(false, null, LoggerEvent.Warning, "Could not deobfuscate {0}. Use -v to see stack trace", file.Filename);
Program.PrintStackTrace(ex, LoggerEvent.Verbose);
}
finally {
file.Dispose();
Logger.Instance.IndentLevel = oldIndentLevel;
}
}
}
void DeobfuscateAll() {
var allFiles = new List<IObfuscatedFile>(LoadAllFiles());
try {
DeobfuscateAllFiles(allFiles);
Rename(allFiles);
SaveAllFiles(allFiles);
}
finally {
foreach (var file in allFiles) {
if (file != null)
file.Dispose();
}
}
}
IEnumerable<IObfuscatedFile> LoadAllFiles() {
return LoadAllFiles(false);
}
IEnumerable<IObfuscatedFile> LoadAllFiles(bool onlyScan) {
var loader = new DotNetFileLoader(new DotNetFileLoader.Options {
ModuleContext = options.ModuleContext,
PossibleFiles = options.Files,
SearchDirs = options.SearchDirs,
CreateDeobfuscators = () => CreateDeobfuscators(),
DefaultStringDecrypterType = options.DefaultStringDecrypterType,
DefaultStringDecrypterMethods = options.DefaultStringDecrypterMethods,
AssemblyClientFactory = options.AssemblyClientFactory,
DeobfuscatorContext = deobfuscatorContext,
ControlFlowDeobfuscation = options.ControlFlowDeobfuscation,
KeepObfuscatorTypes = options.KeepObfuscatorTypes,
MetaDataFlags = options.MetaDataFlags,
RenamerFlags = options.RenamerFlags,
CreateDestinationDir = !onlyScan,
});
foreach (var file in loader.Load())
yield return file;
}
class DotNetFileLoader {
Options options;
Dictionary<string, bool> allFiles = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, bool> visitedDirectory = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
public class Options {
public ModuleContext ModuleContext { get; set; }
public IEnumerable<IObfuscatedFile> PossibleFiles { get; set; }
public IEnumerable<SearchDir> SearchDirs { get; set; }
public Func<IList<IDeobfuscator>> CreateDeobfuscators { get; set; }
public DecrypterType? DefaultStringDecrypterType { get; set; }
public List<string> DefaultStringDecrypterMethods { get; set; }
public IAssemblyClientFactory AssemblyClientFactory { get; set; }
public IDeobfuscatorContext DeobfuscatorContext { get; set; }
public bool ControlFlowDeobfuscation { get; set; }
public bool KeepObfuscatorTypes { get; set; }
public MetaDataFlags MetaDataFlags { get; set; }
public RenamerFlags RenamerFlags { get; set; }
public bool CreateDestinationDir { get; set; }
}
public DotNetFileLoader(Options options) {
this.options = options;
}
public IEnumerable<IObfuscatedFile> Load() {
foreach (var file in options.PossibleFiles) {
if (Add(file, false, true))
yield return file;
}
foreach (var searchDir in options.SearchDirs) {
foreach (var file in LoadFiles(searchDir))
yield return file;
}
}
bool Add(IObfuscatedFile file, bool skipUnknownObfuscator, bool isFromPossibleFiles) {
var key = Utils.GetFullPath(file.Filename);
if (allFiles.ContainsKey(key)) {
Logger.Instance.Log(false, null, LoggerEvent.Warning, "Ingoring duplicate file: {0}", file.Filename);
return false;
}
allFiles[key] = true;
int oldIndentLevel = Logger.Instance.IndentLevel;
//try {
file.DeobfuscatorContext = options.DeobfuscatorContext;
file.Load(options.CreateDeobfuscators());
//}
/*catch (NotSupportedException) {
return false; // Eg. unsupported architecture
}
catch (BadImageFormatException) {
if (isFromPossibleFiles)
Logger.Instance.Log(false, null, LoggerEvent.Warning, "The file isn't a .NET PE file: {0}", file.Filename);
return false; // Not a .NET file
}
catch (EndOfStreamException) {
return false;
}
catch (IOException) {
if (isFromPossibleFiles)
Logger.Instance.Log(false, null, LoggerEvent.Warning, "The file isn't a .NET PE file: {0}", file.Filename);
return false; // Not a .NET file
}
catch (Exception ex) {
throw;
Logger.Instance.Log(false, null, LoggerEvent.Warning, "Could not load file ({0}): {1}", ex.GetType(), file.Filename);
return false;
}
finally {
Logger.Instance.IndentLevel = oldIndentLevel;
}*/
var deob = file.Deobfuscator;
if (skipUnknownObfuscator && deob.Type == "un") {
Logger.v("Skipping unknown obfuscator: {0}", file.Filename);
RemoveModule(file.ModuleDefMD);
return false;
}
else {
Logger.n("Detected {0} ({1})", deob.Name, file.Filename);
if (options.CreateDestinationDir)
CreateDirectories(Path.GetDirectoryName(file.NewFilename));
return true;
}
}
IEnumerable<IObfuscatedFile> 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<string> RecursiveAdd(SearchDir searchDir, IEnumerable<FileSystemInfo> fileSystemInfos) {
foreach (var fsi in fileSystemInfos) {
if ((int)(fsi.Attributes & System.IO.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<string> DoDirectoryInfo(SearchDir searchDir, DirectoryInfo di) {
if (!di.Exists)
return new List<string>();
if (visitedDirectory.ContainsKey(di.FullName))
return new List<string>();
visitedDirectory[di.FullName] = true;
FileSystemInfo[] fsinfos;
try {
fsinfos = di.GetFileSystemInfos();
}
catch (UnauthorizedAccessException) {
return new List<string>();
}
catch (IOException) {
return new List<string>();
}
catch (System.Security.SecurityException) {
return new List<string>();
}
return RecursiveAdd(searchDir, fsinfos);
}
IObfuscatedFile CreateObfuscatedFile(SearchDir searchDir, string filename) {
var fileOptions = new ObfuscatedFile.Options {
Filename = Utils.GetFullPath(filename),
ControlFlowDeobfuscation = options.ControlFlowDeobfuscation,
KeepObfuscatorTypes = options.KeepObfuscatorTypes,
MetaDataFlags = options.MetaDataFlags,
RenamerFlags = options.RenamerFlags,
};
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.ModuleContext, options.AssemblyClientFactory);
if (Add(obfuscatedFile, searchDir.SkipUnknownObfuscators, false))
return obfuscatedFile;
obfuscatedFile.Dispose();
return null;
}
void CreateDirectories(string path) {
if (string.IsNullOrEmpty(path))
return;
try {
var di = new DirectoryInfo(path);
if (!di.Exists)
di.Create();
}
catch (System.Security.SecurityException) {
}
catch (ArgumentException) {
}
}
}
void DeobfuscateAllFiles(IEnumerable<IObfuscatedFile> 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 SaveAllFiles(IEnumerable<IObfuscatedFile> allFiles) {
foreach (var file in allFiles)
file.Save();
}
IList<IDeobfuscator> CreateDeobfuscators() {
var list = new List<IDeobfuscator>(options.DeobfuscatorInfos.Count);
foreach (var info in options.DeobfuscatorInfos)
list.Add(info.CreateDeobfuscator());
return list;
}
void Rename(IEnumerable<IObfuscatedFile> theFiles) {
if (!options.RenameSymbols)
return;
var renamer = new Renamer(deobfuscatorContext, theFiles, options.RenamerFlags);
renamer.Rename();
}
}
}