0
0
mirror of https://github.com/sp-tarkov/assembly-tool.git synced 2025-02-13 06:30:44 -05:00
assembly-tool/de4dot/de4dot.cui/CommandLineParser.cs
Archangel 77a24319b1 Update de4dot to use dnlib 4.4.0 and x64
Adds the source code used for this modification, this de4dot source code has been cleaned of any things not needed for deobfuscating the tarkov assembly

Co-authored-by: 静穏靄 <170472707+seionmoya@users.noreply.github.com>
2024-12-30 16:01:39 +01:00

438 lines
18 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.Collections.Generic;
using dnlib.DotNet;
using dnlib.DotNet.Writer;
using de4dot.code;
using de4dot.code.deobfuscators;
using de4dot.code.AssemblyClient;
using de4dot.code.renamer;
namespace de4dot.cui {
class CommandLineParser {
static Infos stringDecrypterTypes = new Infos();
ObfuscatedFile.Options newFileOptions = null;
IList<IObfuscatedFile> files = new List<IObfuscatedFile>();
Dictionary<string, Option> optionsDict = new Dictionary<string, Option>(StringComparer.Ordinal);
IList<IDeobfuscatorInfo> deobfuscatorInfos;
IList<Option> miscOptions = new List<Option>();
IList<Option> fileOptions = new List<Option>();
Option defaultOption;
FilesDeobfuscator.Options filesOptions;
FilesDeobfuscator.SearchDir searchDir;
DecrypterType? defaultStringDecrypterType;
List<string> defaultStringDecrypterMethods = new List<string>();
class Info {
public object value;
public string name;
public string desc;
public Info(object value, string name, string desc) {
this.value = value;
this.name = name;
this.desc = desc;
}
}
class Infos {
List<Info> infos = new List<Info>();
public void Add(object value, string name, string desc) => infos.Add(new Info(value, name, desc));
public IEnumerable<Info> GetInfos() => infos;
public bool GetValue(string name, out object value) {
foreach (var info in infos) {
if (name.Equals(info.name, StringComparison.OrdinalIgnoreCase)) {
value = info.value;
return true;
}
}
value = null;
return false;
}
}
static CommandLineParser() {
stringDecrypterTypes.Add(DecrypterType.None, "none", "Don't decrypt strings");
stringDecrypterTypes.Add(DecrypterType.Default, "default", "Use default string decrypter type (usually static)");
stringDecrypterTypes.Add(DecrypterType.Static, "static", "Use static string decrypter if available");
stringDecrypterTypes.Add(DecrypterType.Delegate, "delegate", "Use a delegate to call the real string decrypter");
stringDecrypterTypes.Add(DecrypterType.Emulate, "emulate", "Call real string decrypter and emulate certain instructions");
}
public CommandLineParser(IList<IDeobfuscatorInfo> deobfuscatorInfos, FilesDeobfuscator.Options filesOptions) {
this.deobfuscatorInfos = deobfuscatorInfos;
this.filesOptions = filesOptions;
this.filesOptions.DeobfuscatorInfos = deobfuscatorInfos;
this.filesOptions.AssemblyClientFactory = new NewAppDomainAssemblyClientFactory();
AddAllOptions();
}
void AddAllOptions() {
miscOptions.Add(new OneArgOption("r", null, "Scan for .NET files in all subdirs", "dir", (val) => {
AddSearchDir();
searchDir = new FilesDeobfuscator.SearchDir();
if (!Utils.PathExists(val))
ExitError($"Directory {val} does not exist");
searchDir.InputDirectory = val;
}));
miscOptions.Add(new OneArgOption("ro", null, "Output base dir for recursively found files", "dir", (val) => {
if (searchDir == null)
ExitError("Missing -r option");
searchDir.OutputDirectory = val;
}));
miscOptions.Add(new NoArgOption("ru", null, "Skip recursively found files with unsupported obfuscator", () => {
if (searchDir == null)
ExitError("Missing -r option");
searchDir.SkipUnknownObfuscators = true;
}));
miscOptions.Add(new NoArgOption("d", null, "Detect obfuscators and exit", () => {
filesOptions.DetectObfuscators = true;
}));
miscOptions.Add(new OneArgOption(null, "asm-path", "Add an assembly search path", "path", (val) => {
TheAssemblyResolver.Instance.AddSearchDirectory(val);
}));
miscOptions.Add(new NoArgOption(null, "dont-rename", "Don't rename classes, methods, etc.", () => {
filesOptions.RenameSymbols = false;
filesOptions.RenamerFlags = 0;
}));
miscOptions.Add(new OneArgOption(null, "keep-names", "Don't rename n(amespaces), t(ypes), p(rops), e(vents), f(ields), m(ethods), a(rgs), g(enericparams), d(elegate fields). Can be combined, eg. efm", "flags", (val) => {
foreach (var c in val) {
switch (c) {
case 'n': filesOptions.RenamerFlags &= ~RenamerFlags.RenameNamespaces; break;
case 't': filesOptions.RenamerFlags &= ~RenamerFlags.RenameTypes; break;
case 'p': filesOptions.RenamerFlags &= ~RenamerFlags.RenameProperties; break;
case 'e': filesOptions.RenamerFlags &= ~RenamerFlags.RenameEvents; break;
case 'f': filesOptions.RenamerFlags &= ~RenamerFlags.RenameFields; break;
case 'm': filesOptions.RenamerFlags &= ~RenamerFlags.RenameMethods; break;
case 'a': filesOptions.RenamerFlags &= ~RenamerFlags.RenameMethodArgs; break;
case 'g': filesOptions.RenamerFlags &= ~RenamerFlags.RenameGenericParams; break;
case 'd': filesOptions.RenamerFlags |= RenamerFlags.DontRenameDelegateFields; break;
default: throw new UserException($"Unrecognized --keep-names char: '{c}'");
}
}
}));
miscOptions.Add(new NoArgOption(null, "dont-create-params", "Don't create method params when renaming", () => {
filesOptions.RenamerFlags |= RenamerFlags.DontCreateNewParamDefs;
}));
miscOptions.Add(new NoArgOption(null, "dont-restore-props", "Don't restore properties/events", () => {
filesOptions.RenamerFlags &= ~(RenamerFlags.RestorePropertiesFromNames | RenamerFlags.RestoreEventsFromNames);
}));
miscOptions.Add(new OneArgOption(null, "default-strtyp", "Default string decrypter type", "type", (val) => {
if (!stringDecrypterTypes.GetValue(val, out object decrypterType))
ExitError($"Invalid string decrypter type '{val}'");
defaultStringDecrypterType = (DecrypterType)decrypterType;
}));
miscOptions.Add(new OneArgOption(null, "default-strtok", "Default string decrypter method token or [type::][name][(args,...)]", "method", (val) => {
defaultStringDecrypterMethods.Add(val);
}));
miscOptions.Add(new NoArgOption(null, "no-cflow-deob", "No control flow deobfuscation (NOT recommended)", () => {
filesOptions.ControlFlowDeobfuscation = false;
}));
miscOptions.Add(new NoArgOption(null, "only-cflow-deob", "Only control flow deobfuscation", () => {
filesOptions.ControlFlowDeobfuscation = true;
// --strtyp none
defaultStringDecrypterType = DecrypterType.None;
// --keep-types
filesOptions.KeepObfuscatorTypes = true;
// --preserve-tokens
filesOptions.MetadataFlags |= MetadataFlags.PreserveRids |
MetadataFlags.PreserveUSOffsets |
MetadataFlags.PreserveBlobOffsets |
MetadataFlags.PreserveExtraSignatureData;
// --dont-rename
filesOptions.RenameSymbols = false;
filesOptions.RenamerFlags = 0;
}));
miscOptions.Add(new NoArgOption(null, "load-new-process", "Load executed assemblies into a new process", () => {
filesOptions.AssemblyClientFactory = new NewProcessAssemblyClientFactory();
}));
miscOptions.Add(new NoArgOption(null, "keep-types", "Keep obfuscator types, fields, methods", () => {
filesOptions.KeepObfuscatorTypes = true;
}));
miscOptions.Add(new NoArgOption(null, "preserve-tokens", "Preserve important tokens, #US, #Blob, extra sig data", () => {
filesOptions.MetadataFlags |= MetadataFlags.PreserveRids |
MetadataFlags.PreserveUSOffsets |
MetadataFlags.PreserveBlobOffsets |
MetadataFlags.PreserveExtraSignatureData;
}));
miscOptions.Add(new OneArgOption(null, "preserve-table", "Preserve rids in table: tr (TypeRef), td (TypeDef), fd (Field), md (Method), pd (Param), mr (MemberRef), s (StandAloneSig), ed (Event), pr (Property), ts (TypeSpec), ms (MethodSpec), all (all previous tables). Use - to disable (eg. all,-pd). Can be combined: ed,fd,md", "flags", (val) => {
foreach (var t in val.Split(',')) {
var s = t.Trim();
if (s.Length == 0)
continue;
bool clear = s[0] == '-';
if (clear)
s = s.Substring(1);
MetadataFlags flag;
switch (s.Trim()) {
case "": flag = 0; break;
case "all": flag = MetadataFlags.PreserveRids; break;
case "tr": flag = MetadataFlags.PreserveTypeRefRids; break;
case "td": flag = MetadataFlags.PreserveTypeDefRids; break;
case "fd": flag = MetadataFlags.PreserveFieldRids; break;
case "md": flag = MetadataFlags.PreserveMethodRids; break;
case "pd": flag = MetadataFlags.PreserveParamRids; break;
case "mr": flag = MetadataFlags.PreserveMemberRefRids; break;
case "s": flag = MetadataFlags.PreserveStandAloneSigRids; break;
case "ed": flag = MetadataFlags.PreserveEventRids; break;
case "pr": flag = MetadataFlags.PreservePropertyRids; break;
case "ts": flag = MetadataFlags.PreserveTypeSpecRids; break;
case "ms": flag = MetadataFlags.PreserveMethodSpecRids; break;
default: throw new UserException($"Invalid --preserve-table option: {s}");
}
if (clear)
filesOptions.MetadataFlags &= ~flag;
else
filesOptions.MetadataFlags |= flag;
}
}));
miscOptions.Add(new NoArgOption(null, "preserve-strings", "Preserve #Strings heap offsets", () => {
filesOptions.MetadataFlags |= MetadataFlags.PreserveStringsOffsets;
}));
miscOptions.Add(new NoArgOption(null, "preserve-us", "Preserve #US heap offsets", () => {
filesOptions.MetadataFlags |= MetadataFlags.PreserveUSOffsets;
}));
miscOptions.Add(new NoArgOption(null, "preserve-blob", "Preserve #Blob heap offsets", () => {
filesOptions.MetadataFlags |= MetadataFlags.PreserveBlobOffsets;
}));
miscOptions.Add(new NoArgOption(null, "preserve-sig-data", "Preserve extra data at the end of signatures", () => {
filesOptions.MetadataFlags |= MetadataFlags.PreserveExtraSignatureData;
}));
miscOptions.Add(new NoArgOption(null, "one-file", "Deobfuscate one file at a time", () => {
filesOptions.OneFileAtATime = true;
}));
miscOptions.Add(new NoArgOption("v", null, "Verbose", () => {
Logger.Instance.MaxLoggerEvent = LoggerEvent.Verbose;
Logger.Instance.CanIgnoreMessages = false;
}));
miscOptions.Add(new NoArgOption("vv", null, "Very verbose", () => {
Logger.Instance.MaxLoggerEvent = LoggerEvent.VeryVerbose;
Logger.Instance.CanIgnoreMessages = false;
}));
miscOptions.Add(new NoArgOption("h", "help", "Show this help message", () => {
Usage();
Exit(0);
}));
defaultOption = new OneArgOption("f", null, "Name of .NET file", "file", (val) => {
AddFile();
if (!Utils.FileExists(val))
ExitError($"File \"{val}\" does not exist.");
newFileOptions = new ObfuscatedFile.Options {
Filename = val,
ControlFlowDeobfuscation = filesOptions.ControlFlowDeobfuscation,
KeepObfuscatorTypes = filesOptions.KeepObfuscatorTypes,
MetadataFlags = filesOptions.MetadataFlags,
RenamerFlags = filesOptions.RenamerFlags,
};
if (defaultStringDecrypterType != null)
newFileOptions.StringDecrypterType = defaultStringDecrypterType.Value;
newFileOptions.StringDecrypterMethods.AddRange(defaultStringDecrypterMethods);
});
fileOptions.Add(defaultOption);
fileOptions.Add(new OneArgOption("o", null, "Name of output file", "file", (val) => {
if (newFileOptions == null)
ExitError("Missing input file");
var newFilename = Utils.GetFullPath(val);
if (string.Equals(Utils.GetFullPath(newFileOptions.Filename), newFilename, StringComparison.OrdinalIgnoreCase))
ExitError($"Output file can't be same as input file ({newFilename})");
newFileOptions.NewFilename = newFilename;
}));
fileOptions.Add(new OneArgOption("p", null, "Obfuscator type (see below)", "type", (val) => {
if (newFileOptions == null)
ExitError("Missing input file");
if (!IsValidObfuscatorType(val))
ExitError($"Invalid obfuscator type '{val}'");
newFileOptions.ForcedObfuscatorType = val;
}));
fileOptions.Add(new OneArgOption(null, "strtyp", "String decrypter type", "type", (val) => {
if (newFileOptions == null)
ExitError("Missing input file");
if (!stringDecrypterTypes.GetValue(val, out object decrypterType))
ExitError($"Invalid string decrypter type '{val}'");
newFileOptions.StringDecrypterType = (DecrypterType)decrypterType;
}));
fileOptions.Add(new OneArgOption(null, "strtok", "String decrypter method token or [type::][name][(args,...)]", "method", (val) => {
if (newFileOptions == null)
ExitError("Missing input file");
newFileOptions.StringDecrypterMethods.Add(val);
}));
AddOptions(miscOptions);
AddOptions(fileOptions);
foreach (var info in deobfuscatorInfos)
AddOptions(info.GetOptions());
}
void AddOptions(IEnumerable<Option> options) {
foreach (var option in options) {
AddOption(option, option.ShortName);
AddOption(option, option.LongName);
}
}
void AddOption(Option option, string name) {
if (name == null)
return;
if (optionsDict.ContainsKey(name))
throw new ApplicationException($"Option {name} is present twice!");
optionsDict[name] = option;
}
public void Parse(string[] args) {
if (args.Length == 0) {
Usage();
Exit(1);
}
for (int i = 0; i < args.Length; i++) {
var arg = args[i];
string val = null;
if (optionsDict.TryGetValue(arg, out var option)) {
if (option.NeedArgument) {
if (++i >= args.Length)
ExitError("Missing options value");
val = args[i];
}
}
else {
option = defaultOption;
val = arg;
}
if (!option.Set(val, out string errorString))
ExitError(errorString);
}
AddFile();
AddSearchDir();
filesOptions.Files = files;
filesOptions.DefaultStringDecrypterMethods.AddRange(defaultStringDecrypterMethods);
filesOptions.DefaultStringDecrypterType = defaultStringDecrypterType;
}
void AddFile() {
if (newFileOptions == null)
return;
files.Add(new ObfuscatedFile(newFileOptions, filesOptions.ModuleContext, filesOptions.AssemblyClientFactory));
newFileOptions = null;
}
void AddSearchDir() {
if (searchDir == null)
return;
filesOptions.SearchDirs.Add(searchDir);
searchDir = null;
}
bool IsValidObfuscatorType(string type) {
foreach (var info in deobfuscatorInfos) {
if (string.Equals(info.Type, type, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
void ExitError(string msg) {
Usage();
Logger.Instance.LogErrorDontIgnore("\n\nERROR: {0}\n", msg);
Exit(2);
}
void Exit(int exitCode) => throw new ExitException(exitCode);
void Usage() {
string progName = GetProgramBaseName();
Logger.n("Some of the advanced options may be incompatible, causing a nice exception.");
Logger.n("With great power comes great responsibility.");
Logger.n("");
Logger.n("{0} <options> <file options>", progName);
Logger.n("Options:");
foreach (var option in miscOptions)
PrintOption(option);
Logger.n("");
Logger.n("File options:");
foreach (var option in fileOptions)
PrintOption(option);
Logger.n("");
Logger.n("Deobfuscator options:");
foreach (var info in deobfuscatorInfos) {
Logger.n("Type {0} ({1})", info.Type, info.Name);
foreach (var option in info.GetOptions())
PrintOption(option);
Logger.n("");
}
PrintInfos("String decrypter types", stringDecrypterTypes);
Logger.n("");
Logger.n("Multiple regexes can be used if separated by '{0}'.", NameRegexes.regexSeparatorChar);
Logger.n("Use '{0}' if you want to invert the regex. Example: {0}^[a-z\\d]{{1,2}}${1}{0}^[A-Z]_\\d+${1}^[\\w.]+$", NameRegex.invertChar, NameRegexes.regexSeparatorChar);
Logger.n("");
Logger.n("Examples:");
Logger.n("{0} -r c:\\my\\files -ro c:\\my\\output", progName);
Logger.n("{0} file1 file2 file3", progName);
Logger.n("{0} file1 -f file2 -o file2.out -f file3 -o file3.out", progName);
Logger.n("{0} file1 --strtyp delegate --strtok 06000123", progName);
}
string GetProgramBaseName() => Utils.GetBaseName(Environment.GetCommandLineArgs()[0]);
void PrintInfos(string desc, Infos infos) {
Logger.n("{0}", desc);
foreach (var info in infos.GetInfos())
PrintOptionAndExplanation(info.name, info.desc);
}
void PrintOption(Option option) {
string defaultAndDesc;
if (option.NeedArgument && option.Default != null)
defaultAndDesc = $"{option.Description} ({option.Default})";
else
defaultAndDesc = option.Description;
PrintOptionAndExplanation(GetOptionAndArgName(option, option.ShortName ?? option.LongName), defaultAndDesc);
if (option.ShortName != null && option.LongName != null)
PrintOptionAndExplanation(option.LongName, $"Same as {option.ShortName}");
}
void PrintOptionAndExplanation(string option, string explanation) {
const int maxCols = 16;
const string prefix = " ";
string left = string.Format($"{{0,-{maxCols}}}", option);
if (option.Length > maxCols) {
Logger.n("{0}{1}", prefix, left);
Logger.n("{0}{1} {2}", prefix, new string(' ', maxCols), explanation);
}
else
Logger.n("{0}{1} {2}", prefix, left, explanation);
}
string GetOptionAndArgName(Option option, string optionName) {
if (option.NeedArgument)
return optionName + " " + option.ArgumentValueName.ToUpperInvariant();
else
return optionName;
}
}
}