2011-09-22 10:55:30 +08:00
/ *
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 < http : //www.gnu.org/licenses/>.
* /
using System ;
using System.IO ;
using System.Collections.Generic ;
using de4dot.deobfuscators ;
using de4dot.AssemblyClient ;
namespace de4dot {
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 ( ) {
return 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" ) ;
2011-09-28 22:06:10 +08:00
stringDecrypterTypes . add ( DecrypterType . Default , "default" , "Use default string decrypter type (usually static)" ) ;
2011-09-22 10:55:30 +08:00
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" ) ;
2011-09-24 16:26:29 +08:00
stringDecrypterTypes . add ( DecrypterType . Emulate , "emulate" , "Call real string decrypter and emulate certain instructions" ) ;
2011-09-22 10:55:30 +08:00
}
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 ( ) ;
2011-11-05 16:56:51 +08:00
if ( ! Utils . pathExists ( val ) )
2011-09-22 10:55:30 +08:00
exitError ( string . Format ( "Directory {0} does not exist" , val ) ) ;
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 ;
} ) ) ;
2011-10-08 18:30:35 +08:00
miscOptions . Add ( new NoArgOption ( "ru" , null , "Skip recursively found files with unsupported obfuscator" , ( ) = > {
2011-09-22 10:55:30 +08:00
if ( searchDir = = null )
exitError ( "Missing -r option" ) ;
searchDir . SkipUnknownObfuscators = true ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( "d" , null , "Detect obfuscators and exit" , ( ) = > {
filesOptions . DetectObfuscators = true ;
} ) ) ;
2011-11-05 15:43:40 +08:00
miscOptions . Add ( new OneArgOption ( null , "asm-path" , "Add an assembly search path" , "path" , ( val ) = > {
2011-09-22 10:55:30 +08:00
AssemblyResolver . Instance . addSearchDirectory ( val ) ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( null , "dont-rename" , "Don't rename classes, methods, etc." , ( ) = > {
filesOptions . RenameSymbols = false ;
} ) ) ;
miscOptions . Add ( new OneArgOption ( null , "default-strtyp" , "Default string decrypter type" , "type" , ( val ) = > {
object decrypterType ;
if ( ! stringDecrypterTypes . getValue ( val , out decrypterType ) )
exitError ( string . Format ( "Invalid string decrypter type '{0}'" , 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 ) ;
} ) ) ;
2011-10-21 16:38:27 +08:00
miscOptions . Add ( new NoArgOption ( null , "no-cflow-deob" , "No control flow deobfuscation (NOT recommended)" , ( ) = > {
2011-09-22 10:55:30 +08:00
filesOptions . ControlFlowDeobfuscation = false ;
} ) ) ;
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 ;
} ) ) ;
2011-09-28 07:19:19 +08:00
miscOptions . Add ( new NoArgOption ( null , "one-file" , "Deobfuscate one file at a time" , ( ) = > {
filesOptions . OneFileAtATime = true ;
} ) ) ;
2011-09-22 10:55:30 +08:00
miscOptions . Add ( new NoArgOption ( "v" , null , "Verbose" , ( ) = > {
Log . logLevel = Log . LogLevel . verbose ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( "vv" , null , "Very verbose" , ( ) = > {
Log . logLevel = Log . LogLevel . veryverbose ;
} ) ) ;
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 ( ) ;
2011-11-05 16:56:51 +08:00
if ( ! Utils . fileExists ( val ) )
2011-09-22 10:55:30 +08:00
exitError ( string . Format ( "File \"{0}\" does not exist." , val ) ) ;
newFileOptions = new ObfuscatedFile . Options {
Filename = val ,
RenameSymbols = filesOptions . RenameSymbols ,
ControlFlowDeobfuscation = filesOptions . ControlFlowDeobfuscation ,
KeepObfuscatorTypes = filesOptions . KeepObfuscatorTypes ,
} ;
if ( defaultStringDecrypterType ! = null )
newFileOptions . StringDecrypterType = defaultStringDecrypterType . Value ;
newFileOptions . StringDecrypterMethods . AddRange ( defaultStringDecrypterMethods ) ;
} ) ;
fileOptions . Add ( defaultOption ) ;
fileOptions . Add ( new OneArgOption ( "m" , null , "Name of .methods file" , "file" , ( val ) = > {
if ( newFileOptions = = null )
exitError ( "Missing input file" ) ;
2011-11-05 16:56:51 +08:00
if ( ! Utils . fileExists ( val ) )
2011-09-22 10:55:30 +08:00
exitError ( string . Format ( "File \"{0}\" does not exist." , val ) ) ;
newFileOptions . MethodsFilename = val ;
} ) ) ;
fileOptions . Add ( new OneArgOption ( "o" , null , "Name of output file" , "file" , ( val ) = > {
if ( newFileOptions = = null )
exitError ( "Missing input file" ) ;
if ( string . Equals ( Utils . getFullPath ( newFileOptions . Filename ) , Utils . getFullPath ( val ) , StringComparison . OrdinalIgnoreCase ) )
exitError ( string . Format ( "Output file can't be same as input file ({0})" , val ) ) ;
newFileOptions . NewFilename = val ;
} ) ) ;
fileOptions . Add ( new OneArgOption ( "p" , null , "Obfuscator type (see below)" , "type" , ( val ) = > {
if ( newFileOptions = = null )
exitError ( "Missing input file" ) ;
if ( ! isValidObfuscatorType ( val ) )
exitError ( string . Format ( "Invalid obfuscator type '{0}'" , val ) ) ;
newFileOptions . ForcedObfuscatorType = val ;
} ) ) ;
fileOptions . Add ( new OneArgOption ( null , "strtyp" , "String decrypter type" , "type" , ( val ) = > {
if ( newFileOptions = = null )
exitError ( "Missing input file" ) ;
object decrypterType ;
if ( ! stringDecrypterTypes . getValue ( val , out decrypterType ) )
exitError ( string . Format ( "Invalid string decrypter type '{0}'" , 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 ( string . Format ( "Option {0} is present twice!" , name ) ) ;
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 ;
Option option ;
if ( optionsDict . TryGetValue ( arg , out option ) ) {
if ( option . NeedArgument ) {
if ( + + i > = args . Length )
exitError ( "Missing options value" ) ;
val = args [ i ] ;
}
}
else {
option = defaultOption ;
val = arg ;
}
string errorString ;
if ( ! option . set ( val , out 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 . 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 ( ) ;
Log . e ( "\n\nERROR: {0}\n" , msg ) ;
exit ( 2 ) ;
}
void exit ( int exitCode ) {
Environment . Exit ( exitCode ) ;
}
void usage ( ) {
string progName = getProgramBaseName ( ) ;
Log . n ( "Some of the advanced options may be incompatible, causing a nice exception." ) ;
Log . n ( "With great power comes great responsibility." ) ;
Log . n ( "" ) ;
Log . n ( "{0} <options> <file options>" , progName ) ;
Log . n ( "Options:" ) ;
foreach ( var option in miscOptions )
printOption ( option ) ;
Log . n ( "" ) ;
Log . n ( "File options:" ) ;
foreach ( var option in fileOptions )
printOption ( option ) ;
Log . n ( "" ) ;
Log . n ( "Deobfuscator options:" ) ;
foreach ( var info in deobfuscatorInfos ) {
Log . n ( "Type {0}" , info . Type ) ;
foreach ( var option in info . getOptions ( ) )
printOption ( option ) ;
Log . n ( "" ) ;
}
printInfos ( "String decrypter types" , stringDecrypterTypes ) ;
Log . n ( "" ) ;
Log . n ( "Multiple regexes can be used if separated by '{0}'." , NameRegexes . regexSeparatorChar ) ;
Log . 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 ) ;
Log . n ( "" ) ;
Log . n ( "Examples:" ) ;
Log . n ( "{0} -r c:\\my\\files -ro c:\\my\\output" , progName ) ;
Log . n ( "{0} file1 file2 file3" , progName ) ;
Log . n ( "{0} file1 -f file2 -o file2.out -f file3 -m file3.methods -o file3.out" , progName ) ;
Log . n ( "{0} file1 --strtyp delegate --strtok \"<Module>::(System.String, System.Int32)\"" , progName ) ;
}
string getProgramBaseName ( ) {
return Utils . getBaseName ( Environment . GetCommandLineArgs ( ) [ 0 ] ) ;
}
void printInfos ( string desc , Infos infos ) {
Log . 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 = string . Format ( "{0} ({1})" , 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 , string . Format ( "Same as {0}" , option . ShortName ) ) ;
}
void printOptionAndExplanation ( string option , string explanation ) {
const int maxCols = 16 ;
const string prefix = " " ;
string left = string . Format ( string . Format ( "{{0,-{0}}}" , maxCols ) , option ) ;
if ( option . Length > maxCols ) {
Log . n ( "{0}{1}" , prefix , left ) ;
Log . n ( "{0}{1} {2}" , prefix , new string ( ' ' , maxCols ) , explanation ) ;
}
else
Log . n ( "{0}{1} {2}" , prefix , left , explanation ) ;
}
string getOptionAndArgName ( Option option , string optionName ) {
if ( option . NeedArgument )
return optionName + " " + option . ArgumentValueName . ToUpperInvariant ( ) ;
else
return optionName ;
}
}
}