mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-10-01 07:54:25 +00:00
Merge remote-tracking branch
'origin/GP-4707_ryanmkurtz_headless--SQUASHED' (Closes #6639)
This commit is contained in:
commit
2b73a6157f
|
@ -17,9 +17,9 @@ package ghidra.app.util.headless;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import generic.stl.Pair;
|
||||
import ghidra.*;
|
||||
|
@ -37,7 +37,76 @@ import ghidra.util.exception.InvalidInputException;
|
|||
*/
|
||||
public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
|
||||
/**
|
||||
* Headless command line arguments.
|
||||
* <p>
|
||||
* NOTE: Please update 'analyzeHeadlessREADME.html' if changing command line parameters
|
||||
*/
|
||||
private enum Arg {
|
||||
//@formatter:off
|
||||
IMPORT("-import", true, "[<directory>|<file>]+"),
|
||||
PROCESS("-process", true, "[<project_file>]"),
|
||||
PRE_SCRIPT("-prescript", true, "<ScriptName>"),
|
||||
POST_SCRIPT("-postscript", true, "<ScriptName>"),
|
||||
SCRIPT_PATH("-scriptPath", true, "\"<path1>[;<path2>...]\""),
|
||||
PROPERTIES_PATH("-propertiesPath", true, "\"<path1>[;<path2>...]\""),
|
||||
SCRIPT_LOG("-scriptlog", true, "<path to script log file>"),
|
||||
LOG("-log", true, "<path to log file>"),
|
||||
OVERWRITE("-overwrite", false),
|
||||
RECURSIVE("-recursive", false),
|
||||
READ_ONLY("-readOnly", false),
|
||||
DELETE_PROJECT("-deleteproject", false),
|
||||
NO_ANALYSIS("-noanalysis", false),
|
||||
PROCESSOR("-processor", true, "<languageID>"),
|
||||
CSPEC("-cspec", true, "<compilerSpecID>"),
|
||||
ANALYSIS_TIMEOUT_PER_FILE("-analysisTimeoutPerFile", true, "<timeout in seconds>"),
|
||||
KEYSTORE("-keystore", true, "<KeystorePath>"),
|
||||
CONNECT("-connect", false, "[<userID>]"),
|
||||
PASSWORD("-p", false),
|
||||
COMMIT("-commit", false, "[\"<comment>\"]]"),
|
||||
OK_TO_DELETE("-okToDelete", false),
|
||||
MAX_CPU("-max-cpu", true, "<max cpu cores to use>"),
|
||||
LIBRARY_SEARCH_PATHS("-librarySearchPaths", true, "<path1>[;<path2>...]"),
|
||||
LOADER("-loader", true, "<desired loader name>"),
|
||||
LOADER_ARGS(Loader.COMMAND_LINE_ARG_PREFIX + "-", true, "<loader argument value>") {
|
||||
@Override
|
||||
public boolean matches(String arg) {
|
||||
return arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX + "-");
|
||||
}
|
||||
};
|
||||
//@formatter:on
|
||||
|
||||
private String name;
|
||||
private boolean requiresSubArgs;
|
||||
private String subArgFormat;
|
||||
|
||||
private Arg(String name, boolean requiresSubArgs, String subArgFormat) {
|
||||
this.name = name;
|
||||
this.requiresSubArgs = requiresSubArgs;
|
||||
this.subArgFormat = subArgFormat;
|
||||
}
|
||||
|
||||
private Arg(String name, boolean requiresSubArgs) {
|
||||
this(name, requiresSubArgs, "");
|
||||
}
|
||||
|
||||
public String usage() {
|
||||
return "%s%s%s".formatted(name, subArgFormat.isEmpty() ? "" : " ", subArgFormat);
|
||||
}
|
||||
|
||||
public boolean matches(String arg) {
|
||||
return arg.equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int EXIT_CODE_ERROR = 1;
|
||||
private static final Set<String> ALL_ARG_NAMES =
|
||||
Arrays.stream(Arg.values()).map(a -> a.name).collect(Collectors.toSet());
|
||||
|
||||
/**
|
||||
* The entry point of 'analyzeHeadless.bat'. Parses the command line arguments to the script
|
||||
|
@ -64,7 +133,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
if (args[0].startsWith("ghidra:")) {
|
||||
optionStartIndex = 1;
|
||||
try {
|
||||
ghidraURL = new URL(args[0]);
|
||||
ghidraURL = new URI(args[0]).toURL();
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
System.err.println("Invalid Ghidra URL: " + args[0]);
|
||||
|
@ -98,10 +167,10 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
File logFile = null;
|
||||
File scriptLogFile = null;
|
||||
for (int argi = optionStartIndex; argi < args.length; argi++) {
|
||||
if (checkArgument("-log", args, argi)) {
|
||||
if (checkArgument(Arg.LOG, args, argi)) {
|
||||
logFile = new File(args[++argi]);
|
||||
}
|
||||
else if (checkArgument("-scriptlog", args, argi)) {
|
||||
else if (checkArgument(Arg.SCRIPT_LOG, args, argi)) {
|
||||
scriptLogFile = new File(args[++argi]);
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +227,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
String languageId = null;
|
||||
String compilerSpecId = null;
|
||||
String keystorePath = null;
|
||||
String serverUID = null;
|
||||
String userId = null;
|
||||
boolean allowPasswordPrompt = false;
|
||||
List<Pair<String, String[]>> preScripts = new LinkedList<>();
|
||||
List<Pair<String, String[]>> postScripts = new LinkedList<>();
|
||||
|
@ -166,57 +235,57 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
for (int argi = startIndex; argi < args.length; argi++) {
|
||||
|
||||
String arg = args[argi];
|
||||
if (checkArgument("-log", args, argi)) {
|
||||
if (checkArgument(Arg.LOG, args, argi)) {
|
||||
// Already processed
|
||||
argi++;
|
||||
}
|
||||
else if (checkArgument("-scriptlog", args, argi)) {
|
||||
else if (checkArgument(Arg.SCRIPT_LOG, args, argi)) {
|
||||
// Already processed
|
||||
argi++;
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("-overwrite")) {
|
||||
else if (checkArgument(Arg.OVERWRITE, args, argi)) {
|
||||
options.enableOverwriteOnConflict(true);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("-noanalysis")) {
|
||||
else if (checkArgument(Arg.NO_ANALYSIS, args, argi)) {
|
||||
options.enableAnalysis(false);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("-deleteproject")) {
|
||||
else if (checkArgument(Arg.DELETE_PROJECT, args, argi)) {
|
||||
options.setDeleteCreatedProjectOnClose(true);
|
||||
}
|
||||
else if (checkArgument("-loader", args, argi)) {
|
||||
else if (checkArgument(Arg.LOADER, args, argi)) {
|
||||
loaderName = args[++argi];
|
||||
}
|
||||
else if (arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX)) {
|
||||
if (args[argi + 1].startsWith("-")) {
|
||||
else if (checkArgument(Arg.LOADER_ARGS, args, argi)) {
|
||||
if (ALL_ARG_NAMES.contains(args[argi + 1])) {
|
||||
throw new InvalidInputException(args[argi] + " expects value to follow.");
|
||||
}
|
||||
loaderArgs.add(new Pair<>(arg, args[++argi]));
|
||||
}
|
||||
else if (checkArgument("-processor", args, argi)) {
|
||||
else if (checkArgument(Arg.PROCESSOR, args, argi)) {
|
||||
languageId = args[++argi];
|
||||
}
|
||||
else if (checkArgument("-cspec", args, argi)) {
|
||||
else if (checkArgument(Arg.CSPEC, args, argi)) {
|
||||
compilerSpecId = args[++argi];
|
||||
}
|
||||
else if (checkArgument("-prescript", args, argi)) {
|
||||
else if (checkArgument(Arg.PRE_SCRIPT, args, argi)) {
|
||||
String scriptName = args[++argi];
|
||||
String[] scriptArgs = getSubArguments(args, argi);
|
||||
String[] scriptArgs = getSubArguments(args, argi, ALL_ARG_NAMES);
|
||||
argi += scriptArgs.length;
|
||||
preScripts.add(new Pair<>(scriptName, scriptArgs));
|
||||
}
|
||||
else if (checkArgument("-postscript", args, argi)) {
|
||||
else if (checkArgument(Arg.POST_SCRIPT, args, argi)) {
|
||||
String scriptName = args[++argi];
|
||||
String[] scriptArgs = getSubArguments(args, argi);
|
||||
String[] scriptArgs = getSubArguments(args, argi, ALL_ARG_NAMES);
|
||||
argi += scriptArgs.length;
|
||||
postScripts.add(new Pair<>(scriptName, scriptArgs));
|
||||
}
|
||||
else if (checkArgument("-scriptPath", args, argi)) {
|
||||
else if (checkArgument(Arg.SCRIPT_PATH, args, argi)) {
|
||||
options.setScriptDirectories(args[++argi]);
|
||||
}
|
||||
else if (checkArgument("-propertiesPath", args, argi)) {
|
||||
else if (checkArgument(Arg.PROPERTIES_PATH, args, argi)) {
|
||||
options.setPropertiesFileDirectories(args[++argi]);
|
||||
}
|
||||
else if (checkArgument("-import", args, argi)) {
|
||||
else if (checkArgument(Arg.IMPORT, args, argi)) {
|
||||
File inputFile = null;
|
||||
try {
|
||||
inputFile = new File(args[++argi]);
|
||||
|
@ -242,7 +311,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
nextArg = args[++argi];
|
||||
|
||||
// Check if next argument is a parameter
|
||||
if (nextArg.charAt(0) == '-') {
|
||||
if (ALL_ARG_NAMES.contains(nextArg)) {
|
||||
argi--;
|
||||
break;
|
||||
}
|
||||
|
@ -258,29 +327,29 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
filesToImport.add(otherFile);
|
||||
}
|
||||
}
|
||||
else if ("-connect".equals(args[argi])) {
|
||||
else if (checkArgument(Arg.CONNECT, args, argi)) {
|
||||
if ((argi + 1) < args.length) {
|
||||
arg = args[argi + 1];
|
||||
if (!arg.startsWith("-")) {
|
||||
if (!ALL_ARG_NAMES.contains(arg)) {
|
||||
// serverUID is optional argument after -connect
|
||||
serverUID = arg;
|
||||
userId = arg;
|
||||
++argi;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("-commit".equals(args[argi])) {
|
||||
else if (checkArgument(Arg.COMMIT, args, argi)) {
|
||||
String comment = null;
|
||||
if ((argi + 1) < args.length) {
|
||||
arg = args[argi + 1];
|
||||
if (!arg.startsWith("-")) {
|
||||
// comment is optional argument after -commit
|
||||
if (!ALL_ARG_NAMES.contains(arg)) {
|
||||
// commit is optional argument after -commit
|
||||
comment = arg;
|
||||
++argi;
|
||||
}
|
||||
}
|
||||
options.setCommitFiles(true, comment);
|
||||
}
|
||||
else if (checkArgument("-keystore", args, argi)) {
|
||||
else if (checkArgument(Arg.KEYSTORE, args, argi)) {
|
||||
keystorePath = args[++argi];
|
||||
File keystore = new File(keystorePath);
|
||||
if (!keystore.isFile()) {
|
||||
|
@ -288,13 +357,13 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
keystore.getAbsolutePath() + " is not a valid keystore file.");
|
||||
}
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("-p")) {
|
||||
else if (checkArgument(Arg.PASSWORD, args, argi)) {
|
||||
allowPasswordPrompt = true;
|
||||
}
|
||||
else if ("-analysisTimeoutPerFile".equalsIgnoreCase(args[argi])) {
|
||||
else if (checkArgument(Arg.ANALYSIS_TIMEOUT_PER_FILE, args, argi)) {
|
||||
options.setPerFileAnalysisTimeout(args[++argi]);
|
||||
}
|
||||
else if ("-process".equals(args[argi])) {
|
||||
else if (checkArgument(Arg.PROCESS, args, argi)) {
|
||||
if (options.runScriptsNoImport) {
|
||||
throw new InvalidInputException(
|
||||
"The -process option may only be specified once.");
|
||||
|
@ -302,7 +371,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
String processBinary = null;
|
||||
if ((argi + 1) < args.length) {
|
||||
arg = args[argi + 1];
|
||||
if (!arg.startsWith("-")) {
|
||||
if (!ALL_ARG_NAMES.contains(arg)) {
|
||||
// processBinary is optional argument after -process
|
||||
processBinary = arg;
|
||||
++argi;
|
||||
|
@ -310,11 +379,11 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
}
|
||||
options.setRunScriptsNoImport(true, processBinary);
|
||||
}
|
||||
else if ("-recursive".equals(args[argi])) {
|
||||
else if (checkArgument(Arg.RECURSIVE, args, argi)) {
|
||||
Integer depth = null;
|
||||
if ((argi + 1) < args.length) {
|
||||
arg = args[argi + 1];
|
||||
if (!arg.startsWith("-")) {
|
||||
if (!ALL_ARG_NAMES.contains(arg)) {
|
||||
// depth is optional argument after -recursive
|
||||
try {
|
||||
depth = Integer.parseInt(arg);
|
||||
|
@ -327,10 +396,10 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
}
|
||||
options.enableRecursiveProcessing(true, depth);
|
||||
}
|
||||
else if ("-readOnly".equalsIgnoreCase(args[argi])) {
|
||||
else if (checkArgument(Arg.READ_ONLY, args, argi)) {
|
||||
options.enableReadOnlyProcessing(true);
|
||||
}
|
||||
else if (checkArgument("-max-cpu", args, argi)) {
|
||||
else if (checkArgument(Arg.MAX_CPU, args, argi)) {
|
||||
String cpuVal = args[++argi];
|
||||
try {
|
||||
options.setMaxCpu(Integer.parseInt(cpuVal));
|
||||
|
@ -339,12 +408,15 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
throw new InvalidInputException("Invalid value for max-cpu: " + cpuVal);
|
||||
}
|
||||
}
|
||||
else if ("-okToDelete".equalsIgnoreCase(args[argi])) {
|
||||
else if (checkArgument(Arg.OK_TO_DELETE, args, argi)) {
|
||||
options.setOkToDelete(true);
|
||||
}
|
||||
else if (checkArgument("-librarySearchPaths", args, argi)) {
|
||||
else if (checkArgument(Arg.LIBRARY_SEARCH_PATHS, args, argi)) {
|
||||
LibrarySearchPathManager.setLibraryPaths(args[++argi].split(";"));
|
||||
}
|
||||
else if (ALL_ARG_NAMES.contains(args[argi])) {
|
||||
throw new AssertionError("Valid option was not processed: " + args[argi]);
|
||||
}
|
||||
else {
|
||||
throw new InvalidInputException("Bad argument: " + arg);
|
||||
}
|
||||
|
@ -362,7 +434,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
|
||||
// Set up optional Ghidra Server authenticator
|
||||
try {
|
||||
options.setClientCredentials(serverUID, keystorePath, allowPasswordPrompt);
|
||||
options.setClientCredentials(userId, keystorePath, allowPasswordPrompt);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new InvalidInputException(
|
||||
|
@ -438,47 +510,48 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
* @param execCmd the command used to run the headless analyzer from the calling method.
|
||||
*/
|
||||
public static void usage(String execCmd) {
|
||||
System.out.println("Headless Analyzer Usage: " + execCmd);
|
||||
System.out.println(" <project_location> <project_name>[/<folder_path>]");
|
||||
System.out.println(
|
||||
" | ghidra://<server>[:<port>]/<repository_name>[/<folder_path>]");
|
||||
System.out.println(
|
||||
" [[-import [<directory>|<file>]+] | [-process [<project_file>]]]");
|
||||
System.out.println(" [-preScript <ScriptName>]");
|
||||
System.out.println(" [-postScript <ScriptName>]");
|
||||
System.out.println(" [-scriptPath \"<path1>[;<path2>...]\"]");
|
||||
System.out.println(" [-propertiesPath \"<path1>[;<path2>...]\"]");
|
||||
System.out.println(" [-scriptlog <path to script log file>]");
|
||||
System.out.println(" [-log <path to log file>]");
|
||||
System.out.println(" [-overwrite]");
|
||||
System.out.println(" [-recursive]");
|
||||
System.out.println(" [-readOnly]");
|
||||
System.out.println(" [-deleteProject]");
|
||||
System.out.println(" [-noanalysis]");
|
||||
System.out.println(" [-processor <languageID>]");
|
||||
System.out.println(" [-cspec <compilerSpecID>]");
|
||||
System.out.println(" [-analysisTimeoutPerFile <timeout in seconds>]");
|
||||
System.out.println(" [-keystore <KeystorePath>]");
|
||||
System.out.println(" [-connect <userID>]");
|
||||
System.out.println(" [-p]");
|
||||
System.out.println(" [-commit [\"<comment>\"]]");
|
||||
System.out.println(" [-okToDelete]");
|
||||
System.out.println(" [-max-cpu <max cpu cores to use>]");
|
||||
System.out.println(" [-loader <desired loader name>]");
|
||||
// ** NOTE: please update 'analyzeHeadlessREADME.html' if changing command line parameters **
|
||||
StringBuilder sb = new StringBuilder();
|
||||
final String INDENT = " ";
|
||||
|
||||
sb.append("Headless Analyzer Usage: %s\n".formatted(execCmd));
|
||||
sb.append(INDENT + "<project_location> <project_name>[/<folder_path>]\n");
|
||||
sb.append(INDENT + " | ghidra://<server>[:<port>]/<repository_name>[/<folder_path>]\n");
|
||||
for (Arg arg : Arg.values()) {
|
||||
switch (arg) {
|
||||
case IMPORT -> {
|
||||
// Can't use both IMPORT and PROCESS, so must handle the usage a little
|
||||
// differently
|
||||
sb.append(
|
||||
INDENT + "[[%s] | [%s]]\n".formatted(arg.usage(), Arg.PROCESS.usage()));
|
||||
}
|
||||
case PROCESS -> {
|
||||
// Handled above by IMPORT
|
||||
}
|
||||
case LOADER_ARGS -> {
|
||||
// Loader args are a little different because we don't know the full
|
||||
// argument name ahead of time...just what it starts with
|
||||
sb.append(INDENT + "[%s<loader argument name> %s]\n"
|
||||
.formatted(Arg.LOADER_ARGS.name, Arg.LOADER_ARGS.subArgFormat));
|
||||
}
|
||||
default -> {
|
||||
sb.append(INDENT + "[%s]\n".formatted(arg.usage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform.CURRENT_PLATFORM.getOperatingSystem() != OperatingSystem.WINDOWS) {
|
||||
System.out.println();
|
||||
System.out.println(
|
||||
sb.append("\n");
|
||||
sb.append(
|
||||
" - All uses of $GHIDRA_HOME or $USER_HOME in script path must be" +
|
||||
" preceded by '\\'");
|
||||
" preceded by '\\'\n");
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println(
|
||||
sb.append("\n");
|
||||
sb.append(
|
||||
"Please refer to 'analyzeHeadlessREADME.html' for detailed usage examples " +
|
||||
"and notes.");
|
||||
"and notes.\n");
|
||||
|
||||
System.out.println();
|
||||
sb.append("\n");
|
||||
System.out.println(sb);
|
||||
System.exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
|
||||
|
@ -486,23 +559,22 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
usage("analyzeHeadless");
|
||||
}
|
||||
|
||||
private String[] getSubArguments(String[] args, int argi) {
|
||||
List<String> subArgs = new LinkedList<>();
|
||||
private String[] getSubArguments(String[] args, int argi, Set<String> argNames) {
|
||||
List<String> subArgs = new ArrayList<>();
|
||||
int i = argi + 1;
|
||||
while (i < args.length && !args[i].startsWith("-")) {
|
||||
while (i < args.length && !argNames.contains(args[i])) {
|
||||
subArgs.add(args[i++]);
|
||||
}
|
||||
return subArgs.toArray(new String[0]);
|
||||
return subArgs.toArray(new String[subArgs.size()]);
|
||||
}
|
||||
|
||||
private boolean checkArgument(String optionName, String[] args, int argi)
|
||||
private boolean checkArgument(Arg arg, String[] args, int argi)
|
||||
throws InvalidInputException {
|
||||
// everything after this requires an argument
|
||||
if (!optionName.equalsIgnoreCase(args[argi])) {
|
||||
if (!arg.matches(args[argi])) {
|
||||
return false;
|
||||
}
|
||||
if (argi + 1 == args.length) {
|
||||
throw new InvalidInputException(optionName + " requires an argument");
|
||||
if (arg.requiresSubArgs && argi + 1 == args.length) {
|
||||
throw new InvalidInputException(args[argi] + " requires an argument");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -132,6 +132,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
|
|||
[<a href="#max-cpu">-max-cpu <max cpu cores to use></a>]
|
||||
[<a href="#librarySearchPaths">-librarySearchPaths <path1>[;<path2>...]</a>]
|
||||
[<a href="#loader">-loader <desired loader name></a>]
|
||||
[<a href="#loader">-loader-<loader argument name> <loader argument value></a>]
|
||||
|
||||
</PRE>
|
||||
|
||||
|
|
Loading…
Reference in a new issue