From dc4ca3ddfc0e04fb7e7493df5b56426fb7a298c3 Mon Sep 17 00:00:00 2001 From: Jiayang Li Date: Wed, 16 Jul 2025 01:33:12 -0400 Subject: [PATCH 1/2] Make `libghidra` under src symlinks to `GhidraLib` --- Sample1/src/com/nosecurecode/libghidra | 1 + .../com/nosecurecode/libghidra/LibGhidra.java | 497 ----- .../libghidra/LibHeadlessAnalyzer.java | 1881 ----------------- .../libghidra/LibHeadlessErrorLogger.java | 157 -- .../libghidra/LibHeadlessOptions.java | 508 ----- .../libghidra/LibHeadlessScript.java | 508 ----- .../LibHeadlessTimedTaskMonitor.java | 147 -- .../libghidra/LibProgramHandler.java | 15 - Sample2/src/com/nosecurecode/libghidra | 1 + .../com/nosecurecode/libghidra/LibGhidra.java | 497 ----- .../libghidra/LibHeadlessAnalyzer.java | 1881 ----------------- .../libghidra/LibHeadlessErrorLogger.java | 157 -- .../libghidra/LibHeadlessOptions.java | 508 ----- .../libghidra/LibHeadlessScript.java | 508 ----- .../LibHeadlessTimedTaskMonitor.java | 147 -- .../libghidra/LibProgramHandler.java | 15 - 16 files changed, 2 insertions(+), 7426 deletions(-) create mode 120000 Sample1/src/com/nosecurecode/libghidra delete mode 100644 Sample1/src/com/nosecurecode/libghidra/LibGhidra.java delete mode 100644 Sample1/src/com/nosecurecode/libghidra/LibHeadlessAnalyzer.java delete mode 100644 Sample1/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java delete mode 100644 Sample1/src/com/nosecurecode/libghidra/LibHeadlessOptions.java delete mode 100644 Sample1/src/com/nosecurecode/libghidra/LibHeadlessScript.java delete mode 100644 Sample1/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java delete mode 100644 Sample1/src/com/nosecurecode/libghidra/LibProgramHandler.java create mode 120000 Sample2/src/com/nosecurecode/libghidra delete mode 100644 Sample2/src/com/nosecurecode/libghidra/LibGhidra.java delete mode 100644 Sample2/src/com/nosecurecode/libghidra/LibHeadlessAnalyzer.java delete mode 100644 Sample2/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java delete mode 100644 Sample2/src/com/nosecurecode/libghidra/LibHeadlessOptions.java delete mode 100644 Sample2/src/com/nosecurecode/libghidra/LibHeadlessScript.java delete mode 100644 Sample2/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java delete mode 100644 Sample2/src/com/nosecurecode/libghidra/LibProgramHandler.java diff --git a/Sample1/src/com/nosecurecode/libghidra b/Sample1/src/com/nosecurecode/libghidra new file mode 120000 index 0000000..1dce847 --- /dev/null +++ b/Sample1/src/com/nosecurecode/libghidra @@ -0,0 +1 @@ +../../../../GhidraLib \ No newline at end of file diff --git a/Sample1/src/com/nosecurecode/libghidra/LibGhidra.java b/Sample1/src/com/nosecurecode/libghidra/LibGhidra.java deleted file mode 100644 index 6fa6efa..0000000 --- a/Sample1/src/com/nosecurecode/libghidra/LibGhidra.java +++ /dev/null @@ -1,497 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.*; - -import generic.stl.Pair; -import ghidra.app.util.opinion.Loader; -import ghidra.framework.OperatingSystem; -import ghidra.framework.Platform; -import ghidra.framework.model.DomainFolder; -import ghidra.framework.protocol.ghidra.Handler; -import ghidra.util.Msg; -import ghidra.util.exception.InvalidInputException; - -/** - * Launcher entry point for running headless Ghidra. - */ -public class LibGhidra { - - private static final int EXIT_CODE_ERROR = 1; - - /** - * Runs headless command - * @param headlessCmd - * @param handler - * @throws Exception - */ - public static void runHeadlessCmd(String headlessCmd, - LibProgramHandler handler) throws Exception { - new LibGhidra(headlessCmd.split("\\s+"), handler); - } - - /** - * Run heqdless command (command line arguments) - * @param headlessCmdArgs - * @param handler - * @throws Exception - */ - public static void runHeadlessCmd(String [] headlessCmdArgs, - LibProgramHandler handler) throws Exception { - new LibGhidra(headlessCmdArgs, handler); - } - - /** - * This is the main entry point - * @param args - * @param handler - * @throws Exception - */ - private LibGhidra(String args[], LibProgramHandler handler) throws Exception { - String projectName = null; - String rootFolderPath = null; - URL ghidraURL = null; - List filesToImport = new ArrayList<>(); - int optionStartIndex; - - // Make sure there are arguments - if (args.length < 1) { - usage(); - } - - // Ghidra URL handler registration - Handler.registerHandler(); - - if (args[0].startsWith("ghidra:")) { - optionStartIndex = 1; - try { - ghidraURL = new URL(args[0]); - } - catch (MalformedURLException e) { - System.err.println("Invalid Ghidra URL: " + args[0]); - usage(); - } - } - else { - if (args.length < 2) { - usage(); - } - optionStartIndex = 2; - String projectNameAndFolder = args[1]; - - // Check to see if projectName uses back-slashes (likely if they are using Windows) - projectNameAndFolder = projectNameAndFolder.replaceAll("\\\\", DomainFolder.SEPARATOR); - projectName = projectNameAndFolder; - - rootFolderPath = "/"; - int folderIndex = projectNameAndFolder.indexOf(DomainFolder.SEPARATOR); - if (folderIndex == 0) { - System.err.println(args[1] + " is an invalid project_name/folder_path."); - usage(); - } - else if (folderIndex > 0) { - projectName = projectNameAndFolder.substring(0, folderIndex); - rootFolderPath = projectNameAndFolder.substring(folderIndex); - } - } - - // Determine the desired logging. - File logFile = null; - File scriptLogFile = null; - for (int argi = optionStartIndex; argi < args.length; argi++) { - if (checkArgument("-log", args, argi)) { - logFile = new File(args[++argi]); - } - else if (checkArgument("-scriptlog", args, argi)) { - scriptLogFile = new File(args[++argi]); - } - } - - // Instantiate new headless analyzer and parse options. - LibHeadlessAnalyzer analyzer = - LibHeadlessAnalyzer.getLoggableInstance(logFile, scriptLogFile, true, handler); - LibHeadlessOptions options = analyzer.getOptions(); - parseOptions(options, args, optionStartIndex, ghidraURL, filesToImport); - - // Do the headless processing - try { - if (ghidraURL != null) { - analyzer.processURL(ghidraURL, filesToImport); - } - else { - analyzer.processLocal(args[0], projectName, rootFolderPath, filesToImport); - } - } - catch (Throwable e) { - Msg.error(LibHeadlessAnalyzer.class, - "Abort due to Headless analyzer error: " + e.getMessage(), e); - System.exit(EXIT_CODE_ERROR); - } - } - - /** - * Parses the command line arguments and uses them to set the headless options. - * - * @param options The headless options to set. - * @param args The command line arguments to parse. - * @param startIndex The index into the args array of where to start parsing. - * @param ghidraURL The ghidra server url to connect to, or null if not using a url. - * @param filesToImport A list to put files to import into. - * @throws InvalidInputException if an error occurred parsing the arguments or setting - * the options. - */ - private void parseOptions(LibHeadlessOptions options, String[] args, int startIndex, URL ghidraURL, - List filesToImport) throws InvalidInputException { - - String loaderName = null; - List> loaderArgs = new LinkedList<>(); - String languageId = null; - String compilerSpecId = null; - String keystorePath = null; - String serverUID = null; - boolean allowPasswordPrompt = false; - List> preScripts = new LinkedList<>(); - List> postScripts = new LinkedList<>(); - - for (int argi = startIndex; argi < args.length; argi++) { - - String arg = args[argi]; - if (checkArgument("-log", args, argi)) { - // Already processed - argi++; - } - else if (checkArgument("-scriptlog", args, argi)) { - // Already processed - argi++; - } - else if (arg.equalsIgnoreCase("-overwrite")) { - options.enableOverwriteOnConflict(true); - } - else if (arg.equalsIgnoreCase("-noanalysis")) { - options.enableAnalysis(false); - } - else if (arg.equalsIgnoreCase("-deleteproject")) { - options.setDeleteCreatedProjectOnClose(true); - } - else if (checkArgument("-loader", args, argi)) { - loaderName = args[++argi]; - } - else if (arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX)) { - if (args[argi + 1].startsWith("-")) { - throw new InvalidInputException(args[argi] + " expects value to follow."); - } - loaderArgs.add(new Pair<>(arg, args[++argi])); - } - else if (checkArgument("-processor", args, argi)) { - languageId = args[++argi]; - } - else if (checkArgument("-cspec", args, argi)) { - compilerSpecId = args[++argi]; - } - else if (checkArgument("-prescript", args, argi)) { - String scriptName = args[++argi]; - String[] scriptArgs = getSubArguments(args, argi); - argi += scriptArgs.length; - preScripts.add(new Pair<>(scriptName, scriptArgs)); - } - else if (checkArgument("-postscript", args, argi)) { - String scriptName = args[++argi]; - String[] scriptArgs = getSubArguments(args, argi); - argi += scriptArgs.length; - postScripts.add(new Pair<>(scriptName, scriptArgs)); - } - else if (checkArgument("-scriptPath", args, argi)) { - options.setScriptDirectories(args[++argi]); - } - else if (checkArgument("-propertiesPath", args, argi)) { - options.setPropertiesFileDirectories(args[++argi]); - } - else if (checkArgument("-import", args, argi)) { - File inputFile = new File(args[++argi]); - if (!inputFile.isDirectory() && !inputFile.isFile()) { - throw new InvalidInputException( - inputFile.getAbsolutePath() + " is not a valid directory or file."); - } - - LibHeadlessAnalyzer.checkValidFilename(inputFile); - - filesToImport.add(inputFile); - - // Keep checking for OS-expanded files - String nextArg; - - while (argi < (args.length - 1)) { - nextArg = args[++argi]; - - // Check if next argument is a parameter - if (nextArg.charAt(0) == '-') { - argi--; - break; - } - - File otherFile = new File(nextArg); - if (!otherFile.isFile() && !otherFile.isDirectory()) { - throw new InvalidInputException( - otherFile.getAbsolutePath() + " is not a valid directory or file."); - } - - LibHeadlessAnalyzer.checkValidFilename(otherFile); - - filesToImport.add(otherFile); - } - } - else if ("-connect".equals(args[argi])) { - if ((argi + 1) < args.length) { - arg = args[argi + 1]; - if (!arg.startsWith("-")) { - // serverUID is optional argument after -connect - serverUID = arg; - ++argi; - } - } - } - else if ("-commit".equals(args[argi])) { - String comment = null; - if ((argi + 1) < args.length) { - arg = args[argi + 1]; - if (!arg.startsWith("-")) { - // comment is optional argument after -commit - comment = arg; - ++argi; - } - } - options.setCommitFiles(true, comment); - } - else if (checkArgument("-keystore", args, argi)) { - keystorePath = args[++argi]; - File keystore = new File(keystorePath); - if (!keystore.isFile()) { - throw new InvalidInputException( - keystore.getAbsolutePath() + " is not a valid keystore file."); - } - } - else if (arg.equalsIgnoreCase("-p")) { - allowPasswordPrompt = true; - } - else if ("-analysisTimeoutPerFile".equalsIgnoreCase(args[argi])) { - options.setPerFileAnalysisTimeout(args[++argi]); - } - else if ("-process".equals(args[argi])) { - if (options.runScriptsNoImport) { - throw new InvalidInputException( - "The -process option may only be specified once."); - } - String processBinary = null; - if ((argi + 1) < args.length) { - arg = args[argi + 1]; - if (!arg.startsWith("-")) { - // processBinary is optional argument after -process - processBinary = arg; - ++argi; - } - } - options.setRunScriptsNoImport(true, processBinary); - } - else if ("-recursive".equals(args[argi])) { - options.enableRecursiveProcessing(true); - } - else if ("-readOnly".equalsIgnoreCase(args[argi])) { - options.enableReadOnlyProcessing(true); - } - else if (checkArgument("-max-cpu", args, argi)) { - String cpuVal = args[++argi]; - try { - options.setMaxCpu(Integer.parseInt(cpuVal)); - } - catch (NumberFormatException nfe) { - throw new InvalidInputException("Invalid value for max-cpu: " + cpuVal); - } - } - else if ("-okToDelete".equalsIgnoreCase(args[argi])) { - options.setOkToDelete(true); - } - else { - throw new InvalidInputException("Bad argument: " + arg); - } - } - - // Set up pre and post scripts - options.setPreScriptsWithArgs(preScripts); - options.setPostScriptsWithArgs(postScripts); - - // Set loader and loader args - options.setLoader(loaderName, loaderArgs); - - // Set user-specified language and compiler spec - options.setLanguageAndCompiler(languageId, compilerSpecId); - - // Set up optional Ghidra Server authenticator - try { - options.setClientCredentials(serverUID, keystorePath, allowPasswordPrompt); - } - catch (IOException e) { - throw new InvalidInputException( - "Failed to install Ghidra Server authenticator: " + e.getMessage()); - } - - // If -process was specified, inputFiles must be null or inputFiles.size must be 0. - // Otherwise when not in -process mode, inputFiles can be null or inputFiles.size can be 0, - // only if there are scripts to be run. - if (options.runScriptsNoImport) { - - if (filesToImport != null && filesToImport.size() > 0) { - System.err.print("Must use either -process or -import parameters, but not both."); - System.err.print(" -process runs scripts over existing program(s) in a project, " + - "whereas -import"); - System.err.println(" imports new programs and runs scripts and/or analyzes them " + - "after import."); - System.exit(EXIT_CODE_ERROR); - } - - if (options.overwrite) { - Msg.warn(LibHeadlessAnalyzer.class, - "The -overwrite parameter does not apply to -process mode. Ignoring overwrite " + - "and continuing."); - } - - if (options.readOnly && options.okToDelete) { - System.err.println("You have specified the conflicting parameters -readOnly and " + - "-okToDelete. Please pick one and try again."); - System.exit(EXIT_CODE_ERROR); - } - } - else { - if (filesToImport == null || filesToImport.size() == 0) { - if (options.preScripts.isEmpty() && options.postScripts.isEmpty()) { - System.err.println("Nothing to do ... must specify -import, -process, or " + - "prescript and/or postscript."); - System.exit(EXIT_CODE_ERROR); - } - else { - Msg.warn(LibHeadlessAnalyzer.class, - "Neither the -import parameter nor the -process parameter was specified; " + - "therefore, the specified prescripts and/or postscripts will be " + - "executed without any type of program context."); - } - } - } - - if (options.commit) { - if (options.readOnly) { - System.err.println("Can not use -commit and -readOnly at the same time."); - System.exit(EXIT_CODE_ERROR); - } - } - - // Implied commit, only if not in process mode - if (!options.commit && ghidraURL != null) { - if (!options.readOnly) { - // implied commit - options.setCommitFiles(true, null); - } - else { - Msg.warn(LibHeadlessAnalyzer.class, - "-readOnly mode is on: for -process, changes will not be saved."); - } - } - } - - /** - * Prints out the usage details and exits the Java application with an exit code that - * indicates error. - * - * @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(" [/]"); - System.out.println( - " | ghidra://[:]/[/]"); - System.out.println( - " [[-import [|]+] | [-process []]]"); - System.out.println(" [-preScript ]"); - System.out.println(" [-postScript ]"); - System.out.println(" [-scriptPath \"[;...]\"]"); - System.out.println(" [-propertiesPath \"[;...]\"]"); - System.out.println(" [-scriptlog ]"); - System.out.println(" [-log ]"); - System.out.println(" [-overwrite]"); - System.out.println(" [-recursive]"); - System.out.println(" [-readOnly]"); - System.out.println(" [-deleteProject]"); - System.out.println(" [-noanalysis]"); - System.out.println(" [-processor ]"); - System.out.println(" [-cspec ]"); - System.out.println(" [-analysisTimeoutPerFile ]"); - System.out.println(" [-keystore ]"); - System.out.println(" [-connect ]"); - System.out.println(" [-p]"); - System.out.println(" [-commit [\"\"]]"); - System.out.println(" [-okToDelete]"); - System.out.println(" [-max-cpu ]"); - System.out.println(" [-loader ]"); - // ** NOTE: please update 'analyzeHeadlessREADME.html' if changing command line parameters ** - - if (Platform.CURRENT_PLATFORM.getOperatingSystem() != OperatingSystem.WINDOWS) { - System.out.println(); - System.out.println( - " - All uses of $GHIDRA_HOME or $USER_HOME in script path must be" + - " preceded by '\\'"); - } - System.out.println(); - System.out.println( - "Please refer to 'analyzeHeadlessREADME.html' for detailed usage examples " + - "and notes."); - - System.out.println(); - System.exit(EXIT_CODE_ERROR); - } - - private void usage() { - usage("analyzeHeadless"); - } - - private String[] getSubArguments(String[] args, int argi) { - List subArgs = new LinkedList<>(); - int i = argi + 1; - while (i < args.length && !args[i].startsWith("-")) { - subArgs.add(args[i++]); - } - return subArgs.toArray(new String[0]); - } - - private boolean checkArgument(String optionName, String[] args, int argi) - throws InvalidInputException { - // everything after this requires an argument - if (!optionName.equalsIgnoreCase(args[argi])) { - return false; - } - if (argi + 1 == args.length) { - throw new InvalidInputException(optionName + " requires an argument"); - } - return true; - } -} diff --git a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessAnalyzer.java b/Sample1/src/com/nosecurecode/libghidra/LibHeadlessAnalyzer.java deleted file mode 100644 index 4c638cd..0000000 --- a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessAnalyzer.java +++ /dev/null @@ -1,1881 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.*; -import java.net.*; -import java.util.*; -import java.util.regex.Pattern; - -import generic.jar.ResourceFile; -import generic.stl.Pair; -import generic.util.Path; -import ghidra.GhidraApplicationLayout; -import ghidra.GhidraJarApplicationLayout; -import ghidra.app.plugin.core.analysis.AutoAnalysisManager; -import ghidra.app.plugin.core.osgi.BundleHost; -import ghidra.app.script.*; -import com.nosecurecode.libghidra.LibHeadlessScript.LibHeadlessContinuationOption; -import ghidra.app.util.importer.AutoImporter; -import ghidra.app.util.importer.MessageLog; -import ghidra.app.util.opinion.BinaryLoader; -import ghidra.framework.*; -import ghidra.framework.client.ClientUtil; -import ghidra.framework.client.RepositoryAdapter; -import ghidra.framework.data.*; -import ghidra.framework.model.*; -import ghidra.framework.project.DefaultProject; -import ghidra.framework.project.DefaultProjectManager; -import ghidra.framework.protocol.ghidra.*; -import ghidra.framework.remote.User; -import ghidra.framework.store.LockException; -import ghidra.framework.store.local.LocalFileSystem; -import ghidra.program.database.ProgramContentHandler; -import ghidra.program.database.ProgramDB; -import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.listing.Program; -import ghidra.program.util.GhidraProgramUtilities; -import ghidra.program.util.ProgramLocation; -import ghidra.util.*; -import ghidra.util.exception.*; -import ghidra.util.task.TaskMonitor; -import utilities.util.FileUtilities; - -/** - * The class used kick-off and interact with headless processing. All headless options have been - * broken out into their own class: {@link LibHeadlessOptions}. This class is intended to be used - * one of two ways: - *
    - *
  • Used by {@link LibGhidra} to perform headless analysis based on arguments specified - * on the command line.
  • - *
  • Used by another tool as a library to perform headless analysis.
  • - *
- *

- * Note: This class is not thread safe. - */ -public class LibHeadlessAnalyzer { - - private static LibHeadlessAnalyzer instance; - - private LibHeadlessOptions options; - private HeadlessGhidraProjectManager projectManager; - private Project project; - private boolean analysisTimedOut; - private DomainFolder saveDomainFolder; - private Map storage; - private URLClassLoader classLoaderForDotClassScripts; - - private LibProgramHandler programHandler = null; - - /** - * Gets a headless analyzer, initializing the application if necessary with the specified - * logging parameters. An {@link IllegalStateException} will be thrown if the application has - * already been initialized or a headless analyzer has already been retrieved. In these cases, - * the headless analyzer should be gotten with {@link LibHeadlessAnalyzer#getInstance()}. - * - * @param logFile The desired application log file. If null, no application logging will take place. - * @param scriptLogFile The desired scripting log file. If null, no script logging will take place. - * @param useLog4j true if log4j is to be used; otherwise, false. If this class is being used by - * another tool as a library, using log4j might interfere with that tool. - * @return An instance of a new headless analyzer. - * @throws IllegalStateException if an application or headless analyzer instance has already been initialized. - * @throws IOException if there was a problem reading the application.properties file. - */ - public static LibHeadlessAnalyzer getLoggableInstance(File logFile, File scriptLogFile, - boolean useLog4j, LibProgramHandler handler) throws IllegalStateException, IOException { - - // Prevent more than one headless analyzer from being instantiated. Too much about it - // messes with global system settings, so under the current design of Ghidra, allowing - // more than one to exist could result in unpredictable behavior. - if (instance != null) { - throw new IllegalStateException( - "A headless analzyer instance has already been retrieved. " + - "Use HeadlessAnalyzer.getInstance() to get it."); - } - - // Cannot set logging because application has already been initialized. - if (Application.isInitialized()) { - throw new IllegalStateException( - "Logging cannot be set because the application has already been initialized. " + - "Use HeadlessAnalyzer.getInstance() to get the headless analyzer."); - } - - // Initialize application with the provided logging parameters - ApplicationConfiguration configuration = new HeadlessGhidraApplicationConfiguration(); - if (useLog4j) { - if (logFile != null) { - configuration.setApplicationLogFile(logFile); - } - if (scriptLogFile != null) { - configuration.setScriptLogFile(scriptLogFile); - } - } - else { - configuration.setInitializeLogging(false); - Msg.setErrorLogger(new LibHeadlessErrorLogger(logFile)); - } - Application.initializeApplication(getApplicationLayout(), configuration); - - // Instantiate and return singleton headless analyzer - instance = new LibHeadlessAnalyzer(); - - // Set our program handler - instance.programHandler = handler; - - return instance; - } - - /** - * Gets a headless analyzer instance, with the assumption that the application has already been - * initialized. If this is called before the application has been initialized, it will - * initialize the application with no logging. - * - * @return An instance of a new headless analyzer. - * @throws IOException if there was a problem reading the application.properties file (only possible - * if the application had not be initialized). - */ - public static LibHeadlessAnalyzer getInstance(LibProgramHandler handler) throws IOException { - - // Prevent more than one headless analyzer from being instantiated. Too much about it - // messes with global system settings, so under the current design of Ghidra, allowing - // more than one to exist could result in unpredictable behavior. - if (instance != null) { - return instance; - } - - // Initialize application (if necessary) - if (!Application.isInitialized()) { - ApplicationConfiguration configuration = new HeadlessGhidraApplicationConfiguration(); - configuration.setInitializeLogging(false); - Msg.setErrorLogger(new LibHeadlessErrorLogger(null)); - Application.initializeApplication(getApplicationLayout(), configuration); - } - - // Instantiate and return singleton headless analyzer - instance = new LibHeadlessAnalyzer(); - - // Set our program handler - instance.programHandler = handler; - - return instance; - } - - /** - * Gets the appropriate Ghidra application layout for this headless analyzer. - *

- * The headless analyzer can be used in both "normal" mode and single jar mode, so - * we need to use the appropriate layout for either case. - * - * @return The appropriate Ghidra application layout for this headless analyzer. - * @throws IOException if there was a problem getting an appropriate application layout. - */ - private static GhidraApplicationLayout getApplicationLayout() throws IOException { - GhidraApplicationLayout layout; - try { - layout = new GhidraApplicationLayout(); - } - catch (IOException e) { - layout = new GhidraJarApplicationLayout(); - - } - return layout; - } - - /** - * Creates a new headless analyzer object with default settings. - */ - private LibHeadlessAnalyzer() { - // Create default options which the caller can later set prior to processing. - options = new LibHeadlessOptions(); - - // Ghidra URL handler registration. There's no harm in doing this more than once. - Handler.registerHandler(); - - // Ensure that we are running in "headless mode", preventing Swing-based methods from - // running (causing headless operation to lose focus). - System.setProperty("java.awt.headless", "true"); - System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.TRUE.toString()); - - // Allows handling of old content which did not have a content type property - DomainObjectAdapter.setDefaultContentClass(ProgramDB.class); - - // Put analyzer in its default state - reset(); - } - - /** - * Resets the state of the headless analyzer to the default settings. - */ - public void reset() { - options.reset(); - project = null; - analysisTimedOut = false; - saveDomainFolder = null; - storage = new HashMap<>(); - classLoaderForDotClassScripts = null; - } - - /** - * Gets the headless analyzer's options. - * - * @return The headless analyer's options. - */ - public LibHeadlessOptions getOptions() { - return options; - } - - /** - * Process the optional import file/directory list and process each imported file: - *

    - *
  1. execute ordered list of pre-scripts
  2. - *
  3. perform auto-analysis if not disabled
  4. - *
  5. execute ordered list of post-scripts
  6. - *
- * If no import files or directories have been specified the ordered list - * of pre/post scripts will be executed once. - * - * @param ghidraURL ghidra URL for existing server repository and optional - * folder path - * @param filesToImport directories and files to be imported (null or empty - * is acceptable if we are in -process mode) - * @throws IOException if there was an IO-related problem - * @throws MalformedURLException specified URL is invalid - */ - public void processURL(URL ghidraURL, List filesToImport) - throws IOException, MalformedURLException { - - if (options.readOnly && options.commit) { - Msg.error(this, - "Abort due to Headless analyzer error: The requested readOnly option is in conflict " + - "with the commit option"); - return; - } - - if (!"ghidra".equals(ghidraURL.getProtocol())) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); - } - - if (GhidraURL.isLocalProjectURL(ghidraURL)) { - Msg.error(this, - "Ghidra URL command form does not supported local project URLs (ghidra:/path...)"); - return; - } - - String path = ghidraURL.getPath(); - if (path == null) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); - } - - path = path.trim(); - if (path.length() == 0) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); - } - - if (!options.runScriptsNoImport) { // Running in -import mode - if ((filesToImport == null || filesToImport.size() == 0) && - options.preScripts.isEmpty() && options.postScripts.isEmpty()) { - Msg.warn(this, "REPORT: Nothing to do ... must specify files for import."); - return; - } - - if (!path.endsWith("/")) { - // force explicit folder path so that non-existent folders are created on import - ghidraURL = new URL("ghidra", ghidraURL.getHost(), ghidraURL.getPort(), path + "/"); - } - } - else { // Running in -process mode - if (path.endsWith("/") && path.length() > 1) { - ghidraURL = new URL("ghidra", ghidraURL.getHost(), ghidraURL.getPort(), - path.substring(0, path.length() - 1)); - } - } - - List parsedScriptPaths = parseScriptPaths(options.scriptPaths); - GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths); - try { - showConfiguredScriptPaths(); - compileScripts(); - - Msg.info(LibHeadlessAnalyzer.class, "HEADLESS: execution starts"); - - GhidraURLConnection c = (GhidraURLConnection) ghidraURL.openConnection(); - c.setReadOnly(options.readOnly); // writable repository connection - - if (c.getRepositoryName() == null) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); - } - - Msg.info(this, "Opening ghidra repository project: " + ghidraURL); - Object obj = c.getContent(); - if (!(obj instanceof GhidraURLWrappedContent)) { - throw new IOException( - "Connect to repository folder failed. Response code: " + c.getResponseCode()); - } - GhidraURLWrappedContent wrappedContent = (GhidraURLWrappedContent) obj; - Object content = null; - try { - content = wrappedContent.getContent(this); - if (!(content instanceof DomainFolder)) { - throw new IOException("Connect to repository folder failed"); - } - - DomainFolder folder = (DomainFolder) content; - project = new HeadlessProject(getProjectManager(), c); - - if (!checkUpdateOptions()) { - return; // TODO: Should an exception be thrown? - } - - if (options.runScriptsNoImport) { - processNoImport(folder.getPathname()); - } - else { - processWithImport(folder.getPathname(), filesToImport); - } - } - catch (NotFoundException e) { - throw new IOException("Connect to repository folder failed"); - } - finally { - if (content != null) { - wrappedContent.release(content, this); - } - if (project != null) { - project.close(); - } - } - } - finally { - GhidraScriptUtil.dispose(); - } - } - - /** - * Process the optional import file/directory list and process each imported file: - *
    - *
  1. execute ordered list of pre-scripts
  2. - *
  3. perform auto-analysis if not disabled
  4. - *
  5. execute ordered list of post-scripts
  6. - *
- * If no import files or directories have been specified the ordered list - * of pre/post scripts will be executed once. - * - * @param projectLocation directory path of project - * If project exists it will be opened, otherwise it will be created. - * @param projectName project name - * @param rootFolderPath root folder for imports - * @param filesToImport directories and files to be imported (null or empty is acceptable if - * we are in -process mode) - * @throws IOException if there was an IO-related problem - */ - public void processLocal(String projectLocation, String projectName, String rootFolderPath, - List filesToImport) throws IOException { - - if (options.readOnly && options.commit) { - Msg.error(this, - "Abort due to Headless analyzer error: The requested readOnly option is " + - "in conflict with the commit option"); - return; - } - - // If not importing, remove trailing slash so that non-existent folders aren't created - if (options.runScriptsNoImport) { - if ((rootFolderPath.endsWith("/")) && (rootFolderPath.length() > 1)) { - rootFolderPath = rootFolderPath.substring(0, rootFolderPath.length() - 1); - } - } - else { - // If we are importing, need some files to import or at least a script to run! - if ((filesToImport == null || filesToImport.size() == 0) && - options.preScripts.isEmpty() && options.postScripts.isEmpty()) { - Msg.warn(this, "REPORT: Nothing to do ... must specify file(s) for import."); - return; - } - - // If importing, add trailing slash if it isn't there so that non-existent folders are created - if (!rootFolderPath.endsWith("/")) { - rootFolderPath += "/"; - } - } - - List parsedScriptPaths = parseScriptPaths(options.scriptPaths); - GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths); - try { - showConfiguredScriptPaths(); - compileScripts(); - - Msg.info(LibHeadlessAnalyzer.class, "HEADLESS: execution starts"); - - File dir = new File(projectLocation); - ProjectLocator locator = new ProjectLocator(dir.getAbsolutePath(), projectName); - - if (locator.getProjectDir().exists()) { - project = openProject(locator); - } - else { - if (options.runScriptsNoImport) { - Msg.error(this, "Could not find project: " + locator + - " -- should already exist in -process mode."); - throw new IOException("Could not find project: " + locator); - } - - if (!options.runScriptsNoImport && options.readOnly) { - // assume temporary when importing with readOnly option - options.deleteProject = true; - } - - Msg.info(this, "Creating " + (options.deleteProject ? "temporary " : "") + - "project: " + locator); - project = getProjectManager().createProject(locator, null, false); - } - - try { - - if (!checkUpdateOptions()) { - return; // TODO: Should an exception be thrown? - } - - if (options.runScriptsNoImport) { - processNoImport(rootFolderPath); - } - else { - processWithImport(rootFolderPath, filesToImport); - } - } - finally { - project.close(); - if (!options.runScriptsNoImport && options.deleteProject) { - FileUtilities.deleteDir(locator.getProjectDir()); - locator.getMarkerFile().delete(); - } - } - } - finally { - GhidraScriptUtil.dispose(); - } - } - - /** - * Checks to see if the most recent analysis timed out. - * - * @return true if the most recent analysis timed out; otherwise, false. - */ - public boolean checkAnalysisTimedOut() { - return analysisTimedOut; - } - - void setSaveFolder(DomainFolder domFolder) { - saveDomainFolder = domFolder; - - if (domFolder != null) { - Msg.info(this, "Save location changed to: " + domFolder.getPathname()); - } - } - - void addVariableToStorage(String nameOfVar, Object valOfVar) { - if (storage.containsKey(nameOfVar)) { - Msg.warn(this, "Overwriting existing storage variable: " + nameOfVar); - } - - storage.put(nameOfVar, valOfVar); - } - - Set getStorageKeys() { - return storage.keySet(); - } - - Object getVariableFromStorage(String nameOfVar) { - if (!storage.containsKey(nameOfVar)) { - Msg.warn(this, "The storage variable '" + nameOfVar + - "' does not exist in HeadlessAnalyzer storage."); - return null; - } - - return storage.get(nameOfVar); - } - - /** - * Get/Create specified folder path within project - * - * @param folderPath the folder path within the project - * @param create if true, folder will be created if it does not exist - * @return DomainFolder for specified path - * @throws InvalidNameException if folder name is invalid - * @throws IOException if folder can not be created - */ - DomainFolder getDomainFolder(String folderPath, boolean create) - throws IOException, InvalidNameException { - - DomainFolder domFolder = project.getProjectData().getFolder(folderPath); - - if (create && domFolder == null) { - // Create any folder that doesn't exist - String cleanPath = folderPath.replaceAll("^" + DomainFolder.SEPARATOR + "+", ""); - cleanPath = cleanPath.replaceAll(DomainFolder.SEPARATOR + "+$", ""); - - String[] subfolders = cleanPath.split(DomainFolder.SEPARATOR + "+"); - - int folderIndex = 0; - String currPath = DomainFolder.SEPARATOR + subfolders[folderIndex]; - - DomainFolder testFolder = project.getProjectData().getFolder(currPath); - DomainFolder baseFolder = null; - - // Stay in loop while we see folders that exist - while ((testFolder != null) && (folderIndex < (subfolders.length - 1))) { - folderIndex++; - baseFolder = testFolder; - testFolder = baseFolder.getFolder(subfolders[folderIndex]); - } - - // If none of the folders exist, create new files starting from the root - if (folderIndex == 0) { - baseFolder = project.getProjectData().getRootFolder(); - } - - // Since this method is only called by import, we create any folder that - // does not exist. - for (int i = folderIndex; i < subfolders.length; i++) { - baseFolder = baseFolder.createFolder(subfolders[i]); - Msg.info(this, "Created project folder: " + subfolders[i]); - } - - domFolder = baseFolder; - } - - return domFolder; - } - - boolean storageContainsKey(String nameOfVar) { - return storage.containsKey(nameOfVar); - } - - /** - * Runs the specified script with the specified state. - * - * @param scriptState State representing environment variables that the script is able - * to access. - * @param script Script to be run. - * @return whether the script successfully completed running - */ - private boolean runScript(GhidraState scriptState, GhidraScript script) { - if (script instanceof LibHeadlessScript) { - ((LibHeadlessScript) script).setHeadlessInstance(this); - } - - ResourceFile srcFile = script.getSourceFile(); - String scriptName = - srcFile != null ? srcFile.getAbsolutePath() : (script.getClass().getName() + ".class"); - - try { - PrintWriter writer = new PrintWriter(System.out); - Msg.info(this, "SCRIPT: " + scriptName); - script.execute(scriptState, TaskMonitor.DUMMY, writer); - writer.flush(); - } - catch (Exception exc) { - Program prog = scriptState.getCurrentProgram(); - String path = (prog != null ? " ( " + prog.getExecutablePath() + " ) " : ""); - String logErrorMsg = - "REPORT SCRIPT ERROR: " + path + " " + scriptName + " : " + exc.getMessage(); - Msg.error(this, logErrorMsg, exc); - return false; - } - - return true; - } - - /** - * Check file update options (i.e., readOnly, commit) and change defaults if needed. - * @return true if OK to continue - */ - private boolean checkUpdateOptions() { - - boolean isImport = !options.runScriptsNoImport; - boolean commitAllowed = isCommitAllowed(); - - if (options.readOnly) { - String readOnlyError = - "Abort due to Headless analyzer error: The requested -readOnly option " + - "is in conflict with the "; - - if (options.commit) { - Msg.error(this, readOnlyError + "-commit option."); - return false; - } - - if (options.okToDelete) { - Msg.error(this, readOnlyError + "-okToDelete option."); - return false; - } - } - - if (options.commit && !commitAllowed) { - Msg.error(this, - "Commit to repository not possible (due to permission or connection issue)"); - return false; - } - - if (project.getProjectLocator().isTransient()) { - if (!options.commit) { - if (commitAllowed && !options.readOnly) { - Msg.info(this, - "When processing a URL, -commit is automatically enabled unless -readOnly mode " + - "is specified. Enabling -commit and continuing."); - options.commit = true; - } - } - } - - if (options.overwrite) { - if (!isImport) { - Msg.info(this, - "Ignoring -overwrite because it is not applicable to -process mode."); - } - else if (options.readOnly) { - Msg.info(this, - "Ignoring -overwrite because it is not applicable to -readOnly import mode."); - options.overwrite = false; - } - } - - return true; - } - - private boolean isCommitAllowed() { - RepositoryAdapter repository = project.getRepository(); - if (repository == null) { - return true; - } - try { - repository.connect(); - if (!repository.isConnected()) { - return false; - } - User user = repository.getUser(); - if (!user.hasWritePermission()) { - Msg.warn(this, "User '" + user.getName() + - "' does not have write permission to repository - commit not allowed"); - return false; - } - return true; - } - catch (IOException e) { - Msg.error(this, "Repository connection failed (" + repository.getServerInfo() + - ") - commit not allowed"); - return false; - } - } - - private List parseScriptPaths(List scriptPaths) { - if (scriptPaths == null) { - return null; - } - List parsedScriptPaths = new ArrayList<>(); - for (String path : scriptPaths) { - ResourceFile pathFile = Path.fromPathString(path); - String absPath = pathFile.getAbsolutePath(); - if (pathFile.exists()) { - parsedScriptPaths.add(absPath); - } - else { - - Msg.warn(this, "REPORT: Could not find -scriptPath entry, skipping: " + absPath); - } - } - return parsedScriptPaths; - } - - private void showConfiguredScriptPaths() { - StringBuffer buf = new StringBuffer("HEADLESS Script Paths:"); - for (ResourceFile dir : GhidraScriptUtil.getScriptSourceDirectories()) { - buf.append("\n "); - buf.append(dir.getAbsolutePath()); - } - Msg.info(LibHeadlessAnalyzer.class, buf.toString()); - } - - private ResourceFile findScript(String scriptName) { - ResourceFile scriptSource = new ResourceFile(scriptName); - scriptSource = scriptSource.getCanonicalFile(); - if (scriptSource.exists()) { - return scriptSource; - } - scriptSource = GhidraScriptUtil.findScriptByName(scriptName); - if (scriptSource != null) { - return scriptSource; - } - throw new IllegalArgumentException("Script not found: " + scriptName); - } - - /** - * Checks the script name to ensure it exists. If the script type has a GhidraScriptProvider - * (any type of script but .class), then return the ResourceFile that represents that script. - * - * If the script is a class file, return null (one class loader is stored to allow the - * Headless Analyzer to find all the class files). - * - * GhidraScript is not instantiated here, because it is important that each script be - * instantiated at the time it's used. If a GhidraScript object is re-used, this causes - * problems where GhidraScript variables aren't being re-initialized at each use of the script. - * - * @param scriptName The name of the script to check - * @return ResourceFile representing the source file, or null (if script is a .class file) - */ - private ResourceFile checkScript(String scriptName) { - - // Check for pre-compiled GhidraScript (e.g., my.package.Impl.class) - String classExtension = ".class"; - - if (scriptName.endsWith(classExtension)) { - String className = - scriptName.substring(0, scriptName.length() - classExtension.length()); - try { - - // Create a classloader that contains all the ghidra_script paths (especially the one - // specified in -scriptPath!) - List dirs = GhidraScriptUtil.getScriptSourceDirectories(); - List urls = new ArrayList<>(); - - for (ResourceFile dir : dirs) { - try { - urls.add(dir.toURL()); - } - catch (MalformedURLException e) { - // Do nothing. If can't make a URL out of the dir, don't add it. - } - } - - classLoaderForDotClassScripts = - URLClassLoader.newInstance(urls.toArray(new URL[0])); - - Class c = Class.forName(className, true, classLoaderForDotClassScripts); - - if (GhidraScript.class.isAssignableFrom(c)) { - // No issues, but return null, which signifies we don't actually have a - // ResourceFile to associate with the script name - return null; - } - - Msg.error(this, - "REPORT SCRIPT ERROR: java class '" + className + "' is not a GhidraScript"); - } - catch (ClassNotFoundException e) { - Msg.error(this, - "REPORT SCRIPT ERROR: java class not found for '" + className + "'"); - } - throw new IllegalArgumentException("Invalid script: " + scriptName); - } - - try { - ResourceFile scriptSource = findScript(scriptName); - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource); - - if (provider == null) { - throw new IOException("Missing plugin needed to run scripts of this type. Please " + - "ensure you have installed the necessary plugin."); - } - - return scriptSource; - } - catch (Exception | NoClassDefFoundError exc) { - String logErrorMsg = "REPORT SCRIPT ERROR: " + scriptName + " : " + exc.getMessage(); - Msg.error(this, logErrorMsg); - } - throw new IllegalArgumentException("Invalid script: " + scriptName); - } - - /** - * Creates mapping from script name to actual Script object - * - * @param scriptsList List of scripts - * @return mapping of script name to its associated Script object - */ - private Map checkScriptsList(List> scriptsList) { - Map map = new HashMap<>(); - for (Pair scriptPair : scriptsList) { - String scriptName = scriptPair.first; - ResourceFile scriptFile = checkScript(scriptName); - map.put(scriptName, scriptFile); - } - return map; - } - - private void compileScripts() throws IOException { - - // Check that given locations for .properties files are valid - if (options.propertiesFileStrPaths.size() > 0) { - - options.propertiesFilePaths.clear(); - - for (String path : options.propertiesFileStrPaths) { - Path currPath = new Path(path, true, false, true); - - ResourceFile resource = currPath.getPath(); - - if (!resource.isDirectory()) { - throw new IOException("Properties file path: '" + path + - "' either does not exist, " + "or is not a valid directory."); - } - - if (currPath.isEnabled() && !options.propertiesFilePaths.contains(resource)) { - options.propertiesFilePaths.add(resource); - } - } - } - - if (options.preScriptFileMap == null) { - options.preScriptFileMap = checkScriptsList(options.preScripts); - } - - if (options.postScriptFileMap == null) { - options.postScriptFileMap = checkScriptsList(options.postScripts); - } - } - - /** - * Run a list of scripts - * - * @param scriptsList list of script names to run - * @param scriptFileMap mapping of script names to Script objects - * @param scriptState the GhidraState to be passed into each script - * @param continueOption option that could have been set by script(s) - * @return option that could have been set by script(s) - */ - private LibHeadlessContinuationOption runScriptsList(List> scriptsList, - Map scriptFileMap, GhidraState scriptState, - LibHeadlessContinuationOption continueOption) { - - ResourceFile currScriptFile; - LibHeadlessContinuationOption retOption = continueOption; - - boolean scriptSuccess; - boolean isHeadlessScript = false; - String scriptName = ""; - GhidraScript currScript; - - try { - for (Pair scriptPair : scriptsList) { - scriptName = scriptPair.first; - String[] scriptArgs = scriptPair.second; - - // For .class files, there is no ResourceFile mapping. Need to load from the - // stored 'classLoaderForDotClassScripts' - if (scriptName.endsWith(".class")) { - - if (classLoaderForDotClassScripts == null) { - throw new IllegalArgumentException("Invalid script: " + scriptName); - } - - String className = scriptName.substring(0, scriptName.length() - 6); - Class c = Class.forName(className, true, classLoaderForDotClassScripts); - - // Get parent folder to pass to GhidraScript - File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI()) - .getParentFile(); - - currScript = (GhidraScript) c.getConstructor().newInstance(); - currScript.setScriptArgs(scriptArgs); - - if (options.propertiesFilePaths.size() > 0) { - currScript.setPotentialPropertiesFileLocations(options.propertiesFilePaths); - } - - currScript.setPropertiesFileLocation(parentFile.getAbsolutePath(), className); - } - else { - currScriptFile = scriptFileMap.get(scriptName); - - // GhidraScriptProvider case - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(currScriptFile); - PrintWriter writer = new PrintWriter(System.out); - currScript = provider.getScriptInstance(currScriptFile, writer); - currScript.setScriptArgs(scriptArgs); - - if (options.propertiesFilePaths.size() > 0) { - currScript.setPotentialPropertiesFileLocations(options.propertiesFilePaths); - } - } - - isHeadlessScript = currScript instanceof LibHeadlessScript ? true : false; - - if (isHeadlessScript) { - ((LibHeadlessScript) currScript).setInitialContinuationOption(retOption); - } - - scriptSuccess = runScript(scriptState, currScript); - - if (isHeadlessScript) { - if (scriptSuccess) { - retOption = ((LibHeadlessScript) currScript).getContinuationOption(); - - // If script wants to abort, return without running any scripts that follow - if ((retOption == LibHeadlessContinuationOption.ABORT) || - (retOption == LibHeadlessContinuationOption.ABORT_AND_DELETE)) { - return retOption; - } - - } - else { - // If script did not run successfully, abort further processing automatically - Msg.warn(this, - "Script does not exist or encountered problems; further processing is aborted."); - - return LibHeadlessContinuationOption.ABORT; - } - } - } - } - catch (Exception exc) { - String logErrorMsg = "REPORT SCRIPT ERROR: " + scriptName + " : " + exc.getMessage(); - Msg.error(this, logErrorMsg, exc); - } - - return retOption; - } - - private GhidraState getInitialProgramState(Program program) { - ProgramLocation location = null; - AddressSetView initializedMem = program.getMemory().getLoadedAndInitializedAddressSet(); - if (!initializedMem.isEmpty()) { - location = new ProgramLocation(program, initializedMem.getMinAddress()); - } - return new GhidraState(null, project, program, location, null, null); - } - - /** - *{@literal Run prescripts -> analysis -> postscripts (any of these steps is optional).} - * @param fileAbsolutePath Path of the file to analyze. - * @param program The program to analyze. - * @return true if the program file should be kept. If analysis or scripts have marked - * the program as temporary changes should not be saved. Returns false in - * these cases: - * - One of the scripts sets the Headless Continuation Option to "ABORT_AND_DELETE" or - * "CONTINUE_THEN_DELETE". - */ - private boolean analyzeProgram(String fileAbsolutePath, Program program) { - - analysisTimedOut = false; - - AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program); - mgr.initializeOptions(); - - GhidraState scriptState = null; - LibHeadlessContinuationOption scriptStatus = LibHeadlessContinuationOption.CONTINUE; - - boolean abortProcessing = false; - boolean deleteProgram = false; - - if (!options.preScripts.isEmpty()) { - // create one state, in case each script might want to modify it to pass information - scriptState = getInitialProgramState(program); - - scriptStatus = runScriptsList(options.preScripts, options.preScriptFileMap, scriptState, - scriptStatus); - } - - switch (scriptStatus) { - case ABORT_AND_DELETE: - abortProcessing = true; - deleteProgram = true; - break; - - case CONTINUE_THEN_DELETE: - abortProcessing = false; - deleteProgram = true; - break; - - case ABORT: - abortProcessing = true; - deleteProgram = false; - break; - - default: - // do nothing - } - - if (abortProcessing) { - Msg.info(this, "Processing aborted as a result of pre-script."); - return !deleteProgram; - } - - int txId = program.startTransaction("Analysis"); - try { - if (options.analyze) { - Msg.info(this, "ANALYZING all memory and code: " + fileAbsolutePath); - mgr.initializeOptions(); - - // Note: Want to analyze regardless of whether we have already analyzed or not - // (user could have changed options). - mgr.reAnalyzeAll(null); - - if (options.perFileTimeout == -1) { - mgr.startAnalysis(TaskMonitor.DUMMY); // kick start - - Msg.info(this, "REPORT: Analysis succeeded for file: " + fileAbsolutePath); - GhidraProgramUtilities.setAnalyzedFlag(program, true); - } - else { - LibHeadlessTimedTaskMonitor timerMonitor = - new LibHeadlessTimedTaskMonitor(options.perFileTimeout); - mgr.startAnalysis(timerMonitor); - - if (timerMonitor.isCancelled()) { - Msg.error(this, "REPORT: Analysis timed out at " + options.perFileTimeout + - " seconds. Processing not completed for file: " + fileAbsolutePath); - - // If no further scripts, just return the current program disposition - if (options.postScripts.isEmpty()) { - return !deleteProgram; - } - - analysisTimedOut = true; - } - else { - // If timeout didn't already happen at this point, cancel the monitor - timerMonitor.cancel(); - - Msg.info(this, "REPORT: Analysis succeeded for file: " + fileAbsolutePath); - GhidraProgramUtilities.setAnalyzedFlag(program, true); - } - } - } - } - finally { - program.endTransaction(txId, true); - } - - if (!options.postScripts.isEmpty()) { - - if (scriptState == null) { - scriptState = getInitialProgramState(program); - } - - scriptStatus = runScriptsList(options.postScripts, options.postScriptFileMap, - scriptState, scriptStatus); - - switch (scriptStatus) { - case ABORT_AND_DELETE: - abortProcessing = true; - deleteProgram = true; - break; - - case CONTINUE_THEN_DELETE: - abortProcessing = false; - deleteProgram = true; - break; - - case ABORT: - abortProcessing = true; - // If deleteProgram is already true, don't change it to false - // (basically, leave as-is) - break; - - default: - // Do nothing, assume want to carry over options from before - - } - - if (abortProcessing) { - Msg.info(this, "Processing aborted as a result of post-script."); - } - else if (options.analyze && !options.postScripts.isEmpty()) { - Msg.info(this, "ANALYZING changes made by post scripts: " + fileAbsolutePath); - txId = program.startTransaction("Post-Analysis"); - try { - mgr.startAnalysis(TaskMonitor.DUMMY); // kick start - } - finally { - program.endTransaction(txId, true); - } - Msg.info(this, "REPORT: Post-analysis succeeded for file: " + fileAbsolutePath); - } - - } - - // Our hook after the analysis - if (programHandler != null) { - programHandler.PostProcessHandler(program); - } - - return !deleteProgram; - } - - private void processFileNoImport(DomainFile domFile) throws IOException { - - if (domFile.isHijacked()) { - Msg.error(this, - "Skipped processing for " + domFile.getPathname() + " -- file is hijacked"); - return; - } - - if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { - return; // skip non-Program files - } - - Program program = null; - boolean keepFile = true; // if false file should be deleted after release - boolean terminateCheckoutWhenDone = false; - - boolean readOnlyFile = options.readOnly || domFile.isReadOnly(); - - try { - // Exclusive checkout required when commit option specified - if (!readOnlyFile) { - if (domFile.isVersioned()) { - if (!domFile.isCheckedOut()) { - if (!domFile.checkout(options.commit, TaskMonitor.DUMMY)) { - Msg.warn(this, "Skipped processing for " + domFile.getPathname() + - " -- failed to get exclusive file checkout required for commit"); - return; - } - } - else if (options.commit && !domFile.isCheckedOutExclusive()) { - Msg.error(this, "Skipped processing for " + domFile.getPathname() + - " -- file is checked-out non-exclusive (commit requires exclusive checkout)"); - return; - } - } - terminateCheckoutWhenDone = true; - } - - program = (Program) domFile.getDomainObject(this, true, false, TaskMonitor.DUMMY); - - Msg.info(this, "REPORT: Processing project file: " + domFile.getPathname()); - - // This method already takes into account whether the user has set the "noanalysis" - // flag or not - keepFile = analyzeProgram(domFile.getPathname(), program) || readOnlyFile; - - if (!keepFile) { - program.setTemporary(true); // don't save changes - if (!options.okToDelete) { - // Don't remove file unless okToDelete was specified - Msg.warn(this, "Due to script activity, " + domFile.getPathname() + - " deletion was requested but denied -- 'okToDelete' parameter was not specified"); - keepFile = true; - } - } - - if (readOnlyFile) { - if (program.isChanged()) { - Msg.info(this, "REPORT: Discarding changes to the following read-only file: " + - domFile.getPathname()); - } - return; - } - - if (program.isTemporary()) { - if (program.isChanged()) { - Msg.info(this, - "REPORT: Discarding changes to the following file as a result of script activity: " + - domFile.getPathname()); - } - return; - } - - if (domFile.canSave()) { - domFile.save(TaskMonitor.DUMMY); - Msg.info(this, - "REPORT: Save succeeded for processed file: " + domFile.getPathname()); - } - if (program.isChanged()) { - Msg.error(this, - "REPORT: Error trying to save changes to file: " + domFile.getPathname()); - } - - if (options.commit) { - - AutoAnalysisManager.getAnalysisManager(program).dispose(); - program.release(this); - program = null; - - // Only commit if it's a shared project. - commitProgram(domFile); - } - } - catch (VersionException e) { - - if (e.isUpgradable()) { - Msg.error(this, - domFile.getPathname() + - ": this file was created with an older version of Ghidra. Automatic " + - "upgrading of the file to the current version is possible, but " + - "requires an exclusive check-out of the file. Please check out the file " + - " using the Ghidra GUI and then re-run Headless."); - } - else { - Msg.error(this, domFile.getPathname() + - ": this file was created with a newer version of Ghidra, and can not be processed."); - } - } - catch (CancelledException e) { - // This can never happen because there is no user interaction in headless! - } - catch (Exception exc) { - Msg.error(this, domFile.getPathname() + " Error during analysis: " + exc.getMessage(), - exc); - } - finally { - - if (program != null) { - AutoAnalysisManager.getAnalysisManager(program).dispose(); - program.release(this); - program = null; - } - - if (!readOnlyFile) { // can't change anything if read-only file - - // Undo checkout of it is still checked-out and either the file is to be - // deleted, or we just checked it out and file changes have been committed - if (domFile.isCheckedOut()) { - if (!keepFile || - (terminateCheckoutWhenDone && !domFile.modifiedSinceCheckout())) { - domFile.undoCheckout(false); - } - } - - if (!keepFile) { - deleteDomainFile(domFile); - } - } - } - } - - private void deleteDomainFile(DomainFile domFile) { - if (domFile.isCheckedOut()) { - Msg.error(this, "Failed to delete file as requested due to pre-existing checkout: " + - domFile.getPathname()); - return; - } - - try { - domFile.delete(); - } - catch (IOException e) { - Msg.error(this, "Failed to delete file as requested - " + e.getMessage() + ": " + - domFile.getPathname()); - } - } - - /** - * Process all files within parentFolder which satisfies the specified filenamePattern. - * If filenamePattern is null, all files will be processed - * @param parentFolder domain folder to be searched - * @param filenamePattern filename pattern or null for all files - * @return true if one or more files processed - * @throws IOException if an IO problem occurred. - */ - private boolean processFolderNoImport(DomainFolder parentFolder, Pattern filenamePattern) - throws IOException { - - if (parentFolder.isEmpty()) { - return false; - } - - boolean filesProcessed = false; - - for (DomainFile domFile : parentFolder.getFiles()) { - if (filenamePattern == null || filenamePattern.matcher(domFile.getName()).matches()) { - if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { - filesProcessed = true; - processFileNoImport(domFile); - } - } - } - - if (options.recursive) { - for (DomainFolder folder : parentFolder.getFolders()) { - filesProcessed |= processFolderNoImport(folder, filenamePattern); - } - } - - return filesProcessed; - } - - /** - * Process the specified filename within parentFolder. - * @param parentFolder domain folder to be searched - * @param filename name of file to be imported - * @return true if one or more files processed - * @throws IOException if an IO problem occurred. - */ - private boolean processFolderNoImport(DomainFolder parentFolder, String filename) - throws IOException { - - if (parentFolder.isEmpty()) { - return false; - } - - boolean filesProcessed = false; - - DomainFile domFile = parentFolder.getFile(filename); - if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { - filesProcessed = true; - processFileNoImport(domFile); - } - - if (options.recursive) { - for (DomainFolder folder : parentFolder.getFolders()) { - filesProcessed |= processFolderNoImport(folder, filename); - } - } - - return filesProcessed; - } - - private void processNoImport(String rootFolderPath) throws IOException { - - storage.clear(); - - DomainFolder domFolder = project.getProjectData().getFolder(rootFolderPath); - if (domFolder == null) { - throw new IOException("Specified project folder not found: " + rootFolderPath); - } - - Pattern filenamePattern = null; - if (options.domainFileNameToProcess != null) { - filenamePattern = createFilenamePattern(options.domainFileNameToProcess); - } - - boolean filesProcessed = false; - if (filenamePattern == null && options.domainFileNameToProcess != null) { - // assume domainFileNameToProcess was a specific filename and not a pattern - filesProcessed = processFolderNoImport(domFolder, options.domainFileNameToProcess); - } - else { - filesProcessed = processFolderNoImport(domFolder, filenamePattern); - } - - if (!filesProcessed) { - if (options.domainFileNameToProcess != null) { - throw new IOException("Requested project program file(s) not found: " + - options.domainFileNameToProcess); - } - throw new IOException("No program files found within specified project folder: " + - domFolder.getPathname()); - } - } - - private Pattern createFilenamePattern(String name) { - - if ((name.indexOf('*') == -1) && (name.indexOf('?') == -1)) { - // not a 'search' pattern - return null; - } - - // If surrounded by single-quotes, strip them, as to not interfere with the Pattern - if ((name.startsWith("\'")) && (name.endsWith("\'"))) { - name = name.substring(1, name.length() - 1); - } - - // Find files that match the wildcard pattern - Pattern p = UserSearchUtils.createSearchPattern(name, true); - return p; - } - - private boolean checkOverwrite(DomainFile df) throws IOException { - if (options.overwrite) { - try { - if (df.isHijacked()) { - Msg.error(this, - "REPORT: Found conflicting program file in project which is hijacked - overwrite denied: " + - df.getPathname()); - return false; - } - if (df.isVersioned()) { - if (!options.commit) { - Msg.error(this, - "REPORT: Found conflicting versioned program file in project with changes - overwrite denied when commit disabled: " + - df.getPathname()); - return false; - } - if (df.isCheckedOut()) { - df.undoCheckout(false); - } - } - try { - df.delete(); - } - catch (IOException e) { - Msg.error(this, "REPORT: Failed to remove conflicting program file (" + - e.getMessage() + "): " + df.getPathname()); - return false; - } - } - catch (UserAccessException e) { - Msg.error(this, - "REPORT: Found conflicting program file in project which user is unable to overwrite: " + - df.getPathname()); - return false; - } - Msg.warn(this, - "REPORT: Removed conflicting program file from project: " + df.getPathname()); - } - else { - Msg.error(this, - "REPORT: Found conflicting program file in project: " + df.getPathname()); - return false; - } - return true; - } - - private void commitProgram(DomainFile df) throws IOException { - - RepositoryAdapter rep = project.getRepository(); - if (rep != null) { - try { - rep.connect(); - } - catch (IOException e) { - ClientUtil.handleException(rep, e, "Connect", null); - } - if (!rep.isConnected()) { - Msg.error(this, - df.getPathname() + ": File check-in failed - repository connection error"); - throw new IOException( - df.getPathname() + ": File check-in failed - repository connection error"); - } - } - - if (df.canAddToRepository()) { - try { - df.addToVersionControl(options.commitComment, false, TaskMonitor.DUMMY); - Msg.info(this, "REPORT: Added file to repository: " + df.getPathname()); - } - catch (IOException e) { - Msg.error(this, df.getPathname() + ": File check-in failed - " + e.getMessage()); - throw e; - } - catch (CancelledException e) { - // this can never happen because there is no user interaction in headless! - } - } - else if (df.canCheckin()) { - try { - df.checkin(new CheckinHandler() { - @Override - public boolean keepCheckedOut() throws CancelledException { - return true; - } - - @Override - public String getComment() throws CancelledException { - return options.commitComment; - } - - @Override - public boolean createKeepFile() throws CancelledException { - return false; - } - }, true, TaskMonitor.DUMMY); - Msg.info(this, "REPORT: Committed file changes to repository: " + df.getPathname()); - } - catch (IOException e) { - Msg.error(this, df.getPathname() + ": File check-in failed - " + e.getMessage()); - throw e; - } - catch (VersionException e) { - Msg.error(this, - df.getPathname() + ": File check-in failed - version error occurred"); - } - catch (CancelledException e) { - // this can never happen because there is no user interaction in headless! - } - } - else { - Msg.error(this, df.getPathname() + ": Unable to commit file"); - } - } - - private boolean processFileWithImport(File file, String folderPath) { - - Msg.info(this, "IMPORTING: " + file.getAbsolutePath()); - - Program program = null; - - try { - String dfName = null; - DomainFile df = null; - DomainFolder domainFolder = null; - try { - // Gets parent folder for import (creates path if doesn't exist) - domainFolder = getDomainFolder(folderPath, false); - - dfName = file.getName(); - - if (dfName.toLowerCase().endsWith(".gzf") || - dfName.toLowerCase().endsWith(".xml")) { - // Use filename without .gzf - int index = dfName.lastIndexOf('.'); - dfName = dfName.substring(0, index); - } - - if (!options.readOnly) { - if (domainFolder != null) { - df = domainFolder.getFile(dfName); - } - if (df != null && !checkOverwrite(df)) { - return false; - } - df = null; - } - - program = loadProgram(file); - if (program == null) { - return false; - } - - // Check if there are defined memory blocks; abort if not (there is nothing - // to work with!) - if (program.getMemory().getAllInitializedAddressSet().isEmpty()) { - Msg.error(this, "REPORT: Error: No memory blocks were defined for file '" + - file.getAbsolutePath() + "'."); - return false; - } - } - catch (Exception exc) { - Msg.error(this, "REPORT: " + exc.getMessage(), exc); - exc.printStackTrace(); - return false; - } - - Msg.info(this, - "REPORT: Import succeeded with language \"" + - program.getLanguageID().getIdAsString() + "\" and cspec \"" + - program.getCompilerSpec().getCompilerSpecID().getIdAsString() + - "\" for file: " + file.getAbsolutePath()); - - boolean doSave; - try { - - doSave = analyzeProgram(file.getAbsolutePath(), program) && !options.readOnly; - - if (!doSave) { - program.setTemporary(true); - } - - // The act of marking the program as temporary by a script will signal - // us to discard any program changes. - if (program.isTemporary()) { - if (options.readOnly) { - Msg.info(this, "REPORT: Discarded file import due to readOnly option: " + - file.getAbsolutePath()); - } - else { - Msg.info(this, "REPORT: Discarded file import as a result of script " + - "activity or analysis timeout: " + file.getAbsolutePath()); - } - return true; - } - - try { - if (saveDomainFolder != null) { - - df = saveDomainFolder.getFile(dfName); - - // Return if file already exists and overwrite == false - if (df != null && !checkOverwrite(df)) { - return false; - } - - domainFolder = saveDomainFolder; - } - else if (domainFolder == null) { - domainFolder = getDomainFolder(folderPath, true); - } - df = domainFolder.createFile(dfName, program, TaskMonitor.DUMMY); - Msg.info(this, "REPORT: Save succeeded for file: " + df.getPathname()); - - if (options.commit) { - - AutoAnalysisManager.getAnalysisManager(program).dispose(); - program.release(this); - program = null; - - commitProgram(df); - } - } - catch (IOException e) { - e.printStackTrace(); - throw new IOException("Cannot create file: " + domainFolder.getPathname() + - DomainFolder.SEPARATOR + dfName, e); - } - } - catch (Exception exc) { - String logErrorMsg = - file.getAbsolutePath() + " Error during analysis: " + exc.getMessage(); - Msg.info(this, logErrorMsg); - return false; - } - finally { - if (program != null) { - AutoAnalysisManager.getAnalysisManager(program).dispose(); - } - } - - return true; - } - finally { - // Program must be released here, since the AutoAnalysisManager uses program to - // call dispose() in the finally() block above. - if (program != null) { - program.release(this); - program = null; - } - } - } - - private Program loadProgram(File file) throws VersionException, InvalidNameException, - DuplicateNameException, CancelledException, IOException { - - MessageLog messageLog = new MessageLog(); - Program program = null; - - // NOTE: we must pass a null DomainFolder to the AutoImporter so as not to - // allow the DomainFile to be saved at this point. DomainFile should be - // saved after all applicable analysis/scripts are run. - - if (options.loaderClass == null) { - // User did not specify a loader - if (options.language == null) { - program = AutoImporter.importByUsingBestGuess(file, null, this, messageLog, - TaskMonitor.DUMMY); - } - else { - program = AutoImporter.importByLookingForLcs(file, null, options.language, - options.compilerSpec, this, messageLog, TaskMonitor.DUMMY); - } - } - else { - // User specified a loader - if (options.language == null) { - program = AutoImporter.importByUsingSpecificLoaderClass(file, null, - options.loaderClass, options.loaderArgs, this, messageLog, TaskMonitor.DUMMY); - } - else { - program = AutoImporter.importByUsingSpecificLoaderClassAndLcs(file, null, - options.loaderClass, options.loaderArgs, options.language, options.compilerSpec, - this, messageLog, TaskMonitor.DUMMY); - } - } - - if (program == null) { - Msg.error(this, "The AutoImporter could not successfully load " + - file.getAbsolutePath() + - " with the provided import parameters. Please ensure that any specified" + - " processor/cspec arguments are compatible with the loader that is used during" + - " import and try again."); - - if (options.loaderClass != null && options.loaderClass != BinaryLoader.class) { - Msg.error(this, - "NOTE: Import failure may be due to missing opinion for \"" + - options.loaderClass.getSimpleName() + - "\". If so, please contact Ghidra team for assistance."); - } - - return null; - } - - return program; - } - - private void processWithImport(File file, String folderPath, boolean isFirstTime) - throws IOException { - - boolean importSucceeded; - - if (file.isFile()) { - - importSucceeded = processFileWithImport(file, folderPath); - - // Check to see if there are transient programs lying around due - // to programs not being released during Importing - List domainFileContainer = new ArrayList<>(); - TransientDataManager.getTransients(domainFileContainer); - if (domainFileContainer.size() > 0) { - TransientDataManager.releaseFiles(this); - } - - if (!importSucceeded) { - Msg.error(this, "REPORT: Import failed for file: " + file.getAbsolutePath()); - } - - return; - } - - // Looks inside the folder if one of two situations is applicable: - // - If user supplied a directory to import, and it is currently being - // processed (if so, this will be the first time that this method is called) - // - If -recursive is specified - if ((isFirstTime) || (!isFirstTime && options.recursive)) { - // Otherwise, is a directory - Msg.info(this, "REPORT: Importing all files from " + file.getName()); - - File dirFile = file; - - if (!folderPath.endsWith(DomainFolder.SEPARATOR)) { - folderPath += DomainFolder.SEPARATOR; - } - - String subfolderPath = folderPath + file.getName(); - - String[] names = dirFile.list(); - if (names != null) { - Collections.sort(Arrays.asList(names)); - for (String name : names) { - if (name.charAt(0) == '.') { - Msg.warn(this, "Ignoring file '" + name + "'."); - continue; - } - file = new File(dirFile, name); - - // Even a directory name has to have valid characters -- - // can't create a folder if it's not valid - try { - checkValidFilename(file); - processWithImport(file, subfolderPath, false); - } - catch (InvalidInputException e) { - // Just move on if not valid - } - } - } - } - } - - private void processWithImport(String folderPath, List inputDirFiles) throws IOException { - - storage.clear(); - - if (inputDirFiles != null && !inputDirFiles.isEmpty()) { - Msg.info(this, "REPORT: Processing input files: "); - Msg.info(this, " project: " + project.getProjectLocator()); - for (File f : inputDirFiles) { - processWithImport(f, folderPath, true); - } - } - else { - //no input, just run the scripts - - //create one state, in case each script might want to modify it to pass information - GhidraState scriptState = new GhidraState(null, project, null, null, null, null); - - LibHeadlessContinuationOption scriptStatus = LibHeadlessContinuationOption.CONTINUE; - - scriptStatus = runScriptsList(options.preScripts, options.preScriptFileMap, scriptState, - scriptStatus); - - // Since there is no program, "DELETE" is meaningless here. - // If status asks for ABORT, then don't continue running the postscript. - switch (scriptStatus) { - case ABORT: - case ABORT_AND_DELETE: - return; - - default: - // Just continue - } - - runScriptsList(options.postScripts, options.postScriptFileMap, scriptState, - scriptStatus); - } - } - - private Project openProject(ProjectLocator locator) throws IOException { - Project tempProject; - - if (options.deleteProject) { - Msg.warn(this, "Project already exists and will not be deleted: " + locator); - options.deleteProject = false; - } - - Msg.info(this, "Opening existing project: " + locator); - try { - tempProject = new HeadlessProject(getProjectManager(), locator); - } - catch (NotOwnerException e) { - throw new IOException(e); - } - catch (LockException e) { - throw new IOException(e); - } - - return tempProject; - - } - - /** - * Checks to make sure the given file contains only valid characters in its name. - * - * @param currFile The file to check. - * @throws InvalidInputException if the given file contains invalid characters in it. - */ - static void checkValidFilename(File currFile) throws InvalidInputException { - boolean isDir = currFile.isDirectory(); - String filename = currFile.getName(); - - for (int i = 0; i < filename.length(); i++) { - char c = filename.charAt(i); - if (!LocalFileSystem.isValidNameCharacter(c)) { - if (isDir) { - throw new InvalidInputException("The directory '" + filename + - "' contains the invalid characgter: \'" + c + - "\' and can not be created in the project (full path: " + - currFile.getAbsolutePath() + - "). To allow successful import of the directory and its contents, please rename the directory."); - } - throw new InvalidInputException( - "The file '" + filename + "' contains the invalid character: \'" + c + - "\' and can not be imported (full path: " + currFile.getAbsolutePath() + - "). Please rename the file."); - } - } - } - - private HeadlessGhidraProjectManager getProjectManager() { - if (projectManager == null) { - projectManager = new HeadlessGhidraProjectManager(); - } - return projectManager; - } - - /** - * Ghidra project class required to gain access to specialized project constructor - * for URL connection. - */ - private static class HeadlessProject extends DefaultProject { - - HeadlessProject(HeadlessGhidraProjectManager projectManager, GhidraURLConnection connection) - throws IOException { - super(projectManager, connection); - } - - HeadlessProject(HeadlessGhidraProjectManager projectManager, ProjectLocator projectLocator) - throws NotOwnerException, LockException, IOException { - super(projectManager, projectLocator, false); - } - } - - private static class HeadlessGhidraProjectManager extends DefaultProjectManager { - // this exists just to allow access to the constructor - } -} diff --git a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java b/Sample1/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java deleted file mode 100644 index d63c67c..0000000 --- a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java +++ /dev/null @@ -1,157 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.*; - -import ghidra.util.ErrorLogger; - -/** - * Custom headless error logger which is used when log4j is disabled. - */ -class LibHeadlessErrorLogger implements ErrorLogger { - - private PrintWriter logWriter; - - LibHeadlessErrorLogger(File logFile) { - if (logFile != null) { - setLogFile(logFile); - } - } - - synchronized void setLogFile(File logFile) { - try { - if (logFile == null) { - if (logWriter != null) { - writeLog("INFO", "File logging disabled"); - logWriter.close(); - logWriter = null; - } - return; - } - PrintWriter w = new PrintWriter(new FileWriter(logFile)); - if (logWriter != null) { - writeLog("INFO ", "Switching log file to: " + logFile); - logWriter.close(); - } - logWriter = w; - } - catch (IOException e) { - System.err.println("Failed to open log file " + logFile + ": " + e.getMessage()); - } - } - - private synchronized void writeLog(String line) { - if (logWriter == null) { - return; - } - logWriter.println(line); - } - - private synchronized void writeLog(String level, String[] lines) { - if (logWriter == null) { - return; - } - for (String line : lines) { - writeLog(level + " " + line); - } - logWriter.flush(); - } - - private synchronized void writeLog(String level, String text) { - if (logWriter == null) { - return; - } - writeLog(level, chopLines(text)); - } - - private synchronized void writeLog(String level, String text, Throwable throwable) { - if (logWriter == null) { - return; - } - writeLog(level, chopLines(text)); - for (StackTraceElement element : throwable.getStackTrace()) { - writeLog(level + " " + element.toString()); - } - logWriter.flush(); - } - - private String[] chopLines(String text) { - text = text.replace("\r", ""); - return text.split("\n"); - } - - @Override - public void debug(Object originator, Object message) { - // TODO for some reason debug is off - // writeLog("DEBUG", message.toString()); - } - - @Override - public void debug(Object originator, Object message, Throwable throwable) { - // TODO for some reason debug is off - // writeLog("DEBUG", message.toString(), throwable); - } - - @Override - public void error(Object originator, Object message) { - writeLog("ERROR", message.toString()); - } - - @Override - public void error(Object originator, Object message, Throwable throwable) { - writeLog("ERROR", message.toString(), throwable); - } - - @Override - public void info(Object originator, Object message) { - writeLog("INFO ", message.toString()); - } - - @Override - public void info(Object originator, Object message, Throwable throwable) { - // TODO for some reason tracing is off - // writeLog("INFO ", message.toString(), throwable); - } - - @Override - public void trace(Object originator, Object message) { - // TODO for some reason tracing i soff - // writeLog("TRACE", message.toString()); - } - - @Override - public void trace(Object originator, Object message, Throwable throwable) { - // TODO for some reason tracing is off - // writeLog("TRACE", message.toString(), throwable); - } - - @Override - public void warn(Object originator, Object message) { - writeLog("WARN ", message.toString()); - } - - @Override - public void warn(Object originator, Object message, Throwable throwable) { - writeLog("WARN ", message.toString(), throwable); - } - -} diff --git a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessOptions.java b/Sample1/src/com/nosecurecode/libghidra/LibHeadlessOptions.java deleted file mode 100644 index e3ac5c4..0000000 --- a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessOptions.java +++ /dev/null @@ -1,508 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.IOException; -import java.util.*; - -import generic.jar.ResourceFile; -import generic.stl.Pair; -import ghidra.app.util.opinion.Loader; -import ghidra.app.util.opinion.LoaderService; -import ghidra.framework.client.HeadlessClientAuthenticator; -import ghidra.program.model.lang.*; -import ghidra.program.util.DefaultLanguageService; -import ghidra.util.exception.InvalidInputException; - -/** - * Options for headless analyzer. - *

- * Option state may be adjusted to reflect assumed options - * during processing. If multiple invocations of either - * {@link LibHeadlessAnalyzer#processLocal(String, String, String, List)} or - * {@link LibHeadlessAnalyzer#processURL(java.net.URL, List)} are performed, - * these options should be reset and adjusted as necessary. - */ - -public class LibHeadlessOptions { - - // -process and -import - String domainFileNameToProcess; // may include pattern - boolean runScriptsNoImport; - - // -preScript - List> preScripts; - Map preScriptFileMap; - - // -postScript - List> postScripts; - Map postScriptFileMap; - - // -scriptPath - List scriptPaths; - - // -propertiesPath - List propertiesFileStrPaths; - List propertiesFilePaths; - - // -overwrite - boolean overwrite; - - // -recursive - boolean recursive; - - // -readOnly - boolean readOnly; - - // -deleteProject - boolean deleteProject; - - // -noanalysis - boolean analyze; - - // -processor - Language language; - - // -cspec - CompilerSpec compilerSpec; - - // -analysisTimeoutPerFile - int perFileTimeout; - - // -keystore - String keystore; - - // -connect - String connectUserID; - - // -p - boolean allowPasswordPrompt; - - // -commit - boolean commit; - String commitComment; - - // -okToDelete - boolean okToDelete; - - // -max-cpu - int maxcpu; - - // -loader - Class loaderClass; - List> loaderArgs; - - // ------------------------------------------------------------------------------------------- - - /** - * Creates a new headless options object with default settings. - */ - LibHeadlessOptions() { - reset(); - } - - /** - * Resets the options to its default settings. - */ - public void reset() { - domainFileNameToProcess = null; - runScriptsNoImport = false; - preScripts = new LinkedList<>(); - preScriptFileMap = null; - postScripts = new LinkedList<>(); - postScriptFileMap = null; - scriptPaths = null; - propertiesFileStrPaths = new ArrayList<>(); - propertiesFilePaths = new ArrayList<>(); - overwrite = false; - recursive = false; - readOnly = false; - deleteProject = false; - analyze = true; - language = null; - compilerSpec = null; - perFileTimeout = -1; - keystore = null; - connectUserID = null; - allowPasswordPrompt = false; - commit = false; - commitComment = null; - okToDelete = false; - maxcpu = 0; - loaderClass = null; - loaderArgs = null; - } - - /** - * Set to run scripts (and optionally, analysis) without importing a - * program. Scripts will run on specified folder or program that already - * exists in the project. - * - * @param runScriptsOnly if true, no imports will occur and scripts - * (and analysis, if enabled) will run on the specified existing program - * or directory of programs. - * @param filename name of specific project file or folder to be processed (the location - * is passed in elsewhere by the user). If null, user has not specified - * a file to process -- therefore, the entire directory will be processed. - * The filename should not include folder path elements which should be - * specified separately via project or URL specification. - * @throws IllegalArgumentException if the specified filename is invalid and contains the - * path separator character '/'. - */ - public void setRunScriptsNoImport(boolean runScriptsOnly, String filename) { - if (filename != null) { - filename = filename.trim(); - if (filename.indexOf("/") >= 0) { - throw new IllegalArgumentException("invalid filename specified"); - } - } - this.runScriptsNoImport = runScriptsOnly; - this.domainFileNameToProcess = filename; - } - - /** - * Set the ordered list of scripts to execute immediately following import and - * prior to analyzing an imported program. If import not performed, - * these scripts will execute once prior to any post-scripts. - * - * @param preScripts list of script names - */ - public void setPreScripts(List preScripts) { - List> preScriptsEmptyArgs = new LinkedList<>(); - for (String preScript : preScripts) { - preScriptsEmptyArgs.add(new Pair<>(preScript, new String[0])); - } - setPreScriptsWithArgs(preScriptsEmptyArgs); - } - - /** - * Set the ordered list of scripts and their arguments to execute immediately following import - * and prior to analyzing an imported program. If import not performed, - * these scripts will execute once prior to any post-scripts. - * - * @param preScripts list of script names/script argument pairs - */ - public void setPreScriptsWithArgs(List> preScripts) { - this.preScripts = preScripts; - this.preScriptFileMap = null; - } - - /** - * Set the ordered list of scripts to execute immediately following import and - * and analysis of a program. If import not performed, - * these scripts will execute once following any pre-scripts. - * - * @param postScripts list of script names - */ - public void setPostScripts(List postScripts) { - List> postScriptsEmptyArgs = new LinkedList<>(); - for (String postScript : postScripts) { - postScriptsEmptyArgs.add(new Pair<>(postScript, new String[0])); - } - setPostScriptsWithArgs(postScriptsEmptyArgs); - } - - /** - * Set the ordered list of scripts to execute immediately following import and - * and analysis of a program. If import not performed, - * these scripts will execute once following any pre-scripts. - * - * @param postScripts list of script names/script argument pairs - */ - public void setPostScriptsWithArgs(List> postScripts) { - this.postScripts = postScripts; - this.postScriptFileMap = null; - } - - /** - * Set the script source directories to be searched for secondary scripts. - * The default set of enabled script directories within the Ghidra installation - * will be appended to the specified list of newPaths. - * Individual Paths may be constructed relative to Ghidra installation directory, - * User home directory, or absolute system paths. Examples: - *

-	 *     Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
-	 *     Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
-	 *     "/shared/ghidra_scripts"
-	 * 
- * - * @param newPaths list of directories to be searched. - */ - public void setScriptDirectories(List newPaths) { - scriptPaths = newPaths; - } - - /** - * List of valid script directory paths separated by a ';'. - * The default set of enabled script directories within the Ghidra installation - * will be appended to the specified list of newPaths. - * Individual Paths may be constructed relative to Ghidra installation directory, - * User home directory, or absolute system paths. Examples: - *
-	 * 		Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
-	 *      Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
-	 *		"/shared/ghidra_scripts"
-	 * 
- * @param paths semicolon (';') separated list of directory paths - */ - public void setScriptDirectories(String paths) { - String[] pathArray = paths.split(";"); - setScriptDirectories(Arrays.asList(pathArray)); - } - - /** - * Sets a single location for .properties files associated with GhidraScripts. - * - * Typically, .properties files should be located in the same directory as their corresponding - * scripts. However, this method may need to be used when circumstances make it impossible to - * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). - * - * @param path location of .properties file(s) - */ - public void setPropertiesFileDirectory(String path) { - propertiesFileStrPaths = new ArrayList<>(); - propertiesFileStrPaths.add(path); - } - - /** - * Sets one or more locations to find .properties files associated with GhidraScripts. - * - * Typically, .properties files should be located in the same directory as their corresponding - * scripts. However, this method may need to be used when circumstances make it impossible to - * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). - * - * @param newPaths potential locations of .properties file(s) - */ - public void setPropertiesFileDirectories(List newPaths) { - propertiesFileStrPaths = newPaths; - } - - /** - * List of valid .properties file directory paths, separated by a ';'. - * - * Typically, .properties files should be located in the same directory as their corresponding - * scripts. However, this method may need to be used when circumstances make it impossible to - * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). - * - * @param paths String representation of directories (each separated by ';') - */ - public void setPropertiesFileDirectories(String paths) { - String[] pathArray = paths.split(";"); - setPropertiesFileDirectories(Arrays.asList(pathArray)); - } - - /** - * During import, the default behavior is to skip the import if a conflict occurs - * within the destination folder. This method can be used to force the original - * conflicting file to be removed prior to import. - * If the pre-existing file is versioned, the commit option must also be - * enabled to have the overwrite remove the versioned file. - * - * @param enabled if true conflicting domain files will be removed from the - * project prior to importing the new file. - */ - public void enableOverwriteOnConflict(boolean enabled) { - this.overwrite = enabled; - } - - /** - * This method can be used to enable recursive processing of files during - * -import or -process modes. In order for recursive processing of files to - * occur, the user must have specified a directory (and not a specific file) - * for the Headless Analyzer to import or process. - * - * @param enabled if true, enables recursive processing - */ - public void enableRecursiveProcessing(boolean enabled) { - this.recursive = enabled; - } - - /** - * When readOnly processing is enabled, any changes made by script or analyzers - * are discarded when the Headless Analyzer exits. When used with import mode, - * the imported program file will not be saved to the project or repository. - * - * @param enabled if true, enables readOnly processing or import - */ - public void enableReadOnlyProcessing(boolean enabled) { - this.readOnly = enabled; - } - - /** - * Set project delete flag which allows temporary projects created - * to be deleted upon completion. This option has no effect if a - * Ghidra URL or an existing project was specified. This option - * will be assumed when importing with the readOnly option enabled. - * - * @param enabled if true a created project will be deleted when - * processing is complete. - */ - public void setDeleteCreatedProjectOnClose(boolean enabled) { - this.deleteProject = enabled; - } - - /** - * Auto-analysis is enabled by default following import. This method can be - * used to change the enablement of auto-analysis. - * - * @param enabled True if auto-analysis should be enabled; otherwise, false. - */ - public void enableAnalysis(boolean enabled) { - this.analyze = enabled; - } - - /** - * Sets the language and compiler spec from the provided input. Any null value will attempt - * a "best-guess" if possible. - * - * @param languageId The language to set. - * @param compilerSpecId The compiler spec to set. - * @throws InvalidInputException if the language and compiler spec combination is not valid. - */ - public void setLanguageAndCompiler(String languageId, String compilerSpecId) - throws InvalidInputException { - if (languageId == null && compilerSpecId == null) { - return; - } - if (languageId == null) { - throw new InvalidInputException("Compiler spec specified without specifying language."); - } - try { - language = - DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(languageId)); - if (compilerSpecId == null) { - compilerSpec = language.getDefaultCompilerSpec(); - } - else { - compilerSpec = language.getCompilerSpecByID(new CompilerSpecID(compilerSpecId)); - } - } - catch (LanguageNotFoundException e) { - language = null; - compilerSpec = null; - throw new InvalidInputException("Unsupported language: " + languageId); - } - catch (CompilerSpecNotFoundException e) { - language = null; - compilerSpec = null; - throw new InvalidInputException("Compiler spec \"" + compilerSpecId + - "\" is not supported for language \"" + languageId + "\""); - } - } - - /** - * Set analyzer timeout on a per-file basis. - * - * @param stringInSecs timeout value in seconds (as a String) - * @throws InvalidInputException if the timeout value was not a valid value - */ - public void setPerFileAnalysisTimeout(String stringInSecs) throws InvalidInputException { - try { - perFileTimeout = Integer.parseInt(stringInSecs); - } - catch (NumberFormatException nfe) { - throw new InvalidInputException( - "'" + stringInSecs + "' is not a valid integer representation."); - } - } - - public void setPerFileAnalysisTimeout(int secs) { - perFileTimeout = secs; - } - - /** - * Set Ghidra Server client credentials to be used with "shared" projects. - * - * @param userID optional userId to use if server permits the user to use - * a userId which differs from the process owner name. - * @param keystorePath file path to keystore file containing users private key - * to be used with PKI or SSH based authentication. - * @param allowPasswordPrompt if true the user may be prompted for passwords - * via the console (stdin). Please note that the Java console will echo - * the password entry to the terminal which may be undesirable. - * @throws IOException if an error occurs while opening the specified keystorePath. - */ - public void setClientCredentials(String userID, String keystorePath, - boolean allowPasswordPrompt) throws IOException { - this.connectUserID = userID; - this.keystore = keystorePath; - this.allowPasswordPrompt = allowPasswordPrompt; - HeadlessClientAuthenticator.installHeadlessClientAuthenticator(userID, keystorePath, - allowPasswordPrompt); - } - - /** - * Enable committing of processed files to the repository which backs the specified - * project. - * - * @param commit if true imported files will be committed - * @param comment optional comment to use when committing - */ - public void setCommitFiles(boolean commit, String comment) { - this.commit = commit; - this.commitComment = comment; - } - - public void setOkToDelete(boolean deleteOk) { - okToDelete = deleteOk; - } - - /** - * Sets the maximum number of cpu cores to use during headless processing. - * - * @param cpu The maximum number of cpu cores to use during headless processing. - * Setting it to 0 or a negative integer is equivalent to setting it to 1. - */ - public void setMaxCpu(int cpu) { - this.maxcpu = cpu; - System.setProperty("cpu.core.limit", Integer.toString(cpu)); - - } - - /** - * Sets the loader to use for imports, as well as any loader-specific arguments. A null loader - * will attempt "best-guess" if possible. Loader arguments are not supported if a "best-guess" - * is made. - * - * @param loaderName The name (simple class name) of the loader to use. - * @param loaderArgs A list of loader-specific arguments. Could be null if there are none. - * @throws InvalidInputException if an invalid loader name was specified, or if loader arguments - * were specified but a loader was not. - */ - public void setLoader(String loaderName, List> loaderArgs) - throws InvalidInputException { - if (loaderName != null) { - this.loaderClass = LoaderService.getLoaderClassByName(loaderName); - if (this.loaderClass == null) { - throw new InvalidInputException("Invalid loader name specified: " + loaderName); - } - this.loaderArgs = loaderArgs; - } - else { - if (loaderArgs != null && loaderArgs.size() > 0) { - throw new InvalidInputException( - "Loader arguments defined without a loader being specified."); - } - this.loaderClass = null; - this.loaderArgs = null; - } - } -} diff --git a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessScript.java b/Sample1/src/com/nosecurecode/libghidra/LibHeadlessScript.java deleted file mode 100644 index 7708ab7..0000000 --- a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessScript.java +++ /dev/null @@ -1,508 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.IOException; - -import generic.jar.ResourceFile; -import ghidra.app.script.*; -import ghidra.framework.model.DomainFolder; -import ghidra.util.InvalidNameException; - -/** - * This class is analogous to GhidraScript, except that is only meant to be used with - * the HeadlessAnalyzer. That is, if a user writes a script that extends HeadlessScript, - * it should only be run in the Headless environment. - */ -public abstract class LibHeadlessScript extends GhidraScript { - - /** - * Options for controlling disposition of program after the current script completes. - */ - public enum LibHeadlessContinuationOption { - /** - * Continue running scripts and/or analysis; -import and -process - * modes complete normally. - */ - CONTINUE, - - /** - * Continue running scripts and/or analysis; - * -import mode does not save program, - * -process mode deletes program. - */ - CONTINUE_THEN_DELETE, - - /** - * Abort any scripts or analysis that come after this script; - * -import mode does not save program, -process mode deletes program. - */ - ABORT_AND_DELETE, - - /** - * Abort any scripts or analysis that come after this script; -import mode does - * save program (but it may not be processed completely), - * -process mode completes normally, minus scripts or analysis that - * runs after the ABORT request. - */ - ABORT - } - - private LibHeadlessAnalyzer headless = null; - - private LibHeadlessContinuationOption currentOption = LibHeadlessContinuationOption.CONTINUE; - private LibHeadlessContinuationOption scriptSetOption = null; - - private boolean runningInnerScript = false; - - // This is necessary because it determine when we nullify the 'scriptSetOption' variable - private void setRunningInnerScript(boolean b) { - runningInnerScript = b; - } - - /** - * Sets the current headless instance -- doing so gives the user the ability to manipulate - * headless analyzer-specific parameters. - *

- * This method is declared with no access modifier to only allow package-level (no subclass) - * access. This method is meant to only be used by the HeadlessAnalyzer class. - * - * @param ha HeadlessAnalyzer instance - */ - void setHeadlessInstance(LibHeadlessAnalyzer ha) { - headless = ha; - } - - /** - * Sets the "beginning-of-script" continuation status. - *

- * This method is declare with no access modifier to only allow package-level (no - * subclass) access. This method is meant to only be used by the HeadlessAnalyzer class. - * - * @param option initial continuation option for this script - */ - void setInitialContinuationOption(LibHeadlessContinuationOption option) { - currentOption = option; - } - - /** - * Returns the final resolved continuation option (after script processing is done). - *

- * The continuation option specifies whether to continue or abort follow-on processing, - * and whether to delete or keep the current program. - *

- * This method is declared with no access modifier to only allow package-level (no - * subclass) access. This method is meant to only be used by the HeadlessAnalyzer class. - * - * @return the script's final HeadlessContinuationOption - */ - LibHeadlessContinuationOption getContinuationOption() { - return currentOption; - } - - /** - * Checks to see if this script is running in headless mode (it should be!). - *

- * This method should be called at the beginning of every public method in HeadlessScript - * that accesses HeadlessAnalyzer methods (for instance, 'headless.isAnalysisEnabled()'). - * The call to this method can not be placed in the constructor, because 'setHeadlessInstance', - * which connects the script with the current headless instance, is not called until after the - * call to the constructor. - * - * @throws ImproperUseException if not in headless mode or headless instance not set - */ - private void checkHeadlessStatus() throws ImproperUseException { - if (headless == null || !isRunningHeadless()) { - throw new ImproperUseException("This method can only be used in the headless case!"); - } - } - - /** - * Stores a key/value pair in the HeadlessAnalyzer instance for later use. - *

- * This method, along with the 'getStoredHeadlessValue' method, is useful for debugging and - * testing the Headless Analyzer (when the user has directly instantiated the HeadlessAnalyzer - * instead of running it from analyzeHeadless.sh or analyzeHeadless.bat). This method is - * intended to allow a HeadlessScript to store variables that reflect the current state of - * processing (at the time the script is being run). Storing variables in the HeadlessAnalyzer - * instance may be the only way to access the state of processing during cases when the user - * is forced to run in -readOnly mode, or if there is a value that is only accessible at the - * scripts stage. - * - * @param key storage key in String form - * @param value value to store - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #getStoredHeadlessValue(String) - * @see #headlessStorageContainsKey(String) - */ - public void storeHeadlessValue(String key, Object value) throws ImproperUseException { - checkHeadlessStatus(); - headless.addVariableToStorage(key, value); - } - - /** - * Get stored value by key from the HeadlessAnalyzer instance. - *

- * This method, along with the 'storedHeadlessValue' method, is useful for debugging and - * testing the Headless Analyzer (when the user has directly instantiated the HeadlessAnalyzer - * instead of running it from analyzeHeadless.sh or analyzeHeadless.bat). This method is - * intended to allow a HeadlessScript to store variables that reflect the current state of - * processing (at the time the script is being run). Storing variables in the HeadlessAnalyzer - * instance may be the only way to access the state of processing during cases when the user - * is forced to run in -readOnly mode, or if there is a value that is only accessible at the - * scripts stage. - * - * @param key key to retrieve the desired stored value - * @return stored Object, or null if none exists for that key - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #storeHeadlessValue(String, Object) - * @see #headlessStorageContainsKey(String) - */ - public Object getStoredHeadlessValue(String key) throws ImproperUseException { - checkHeadlessStatus(); - return headless.getVariableFromStorage(key); - } - - /** - * Returns whether the specified key was stored in the HeadlessAnalyzer instance. - * - * @param key value of key to check for in Headless Analyzer instance - * @return true if the specified key exists - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #storeHeadlessValue(String, Object) - * @see #getStoredHeadlessValue(String) - */ - public boolean headlessStorageContainsKey(String key) throws ImproperUseException { - checkHeadlessStatus(); - return headless.storageContainsKey(key); - } - - /** - * Sets the continuation option for this script - *

- * The continuation option specifies whether to continue or abort follow-on processing, - * and whether to delete or keep the current program. - * - * @param option HeadlessContinuationOption set by this script - * @see #getHeadlessContinuationOption() - */ - public void setHeadlessContinuationOption(LibHeadlessContinuationOption option) { - scriptSetOption = option; - } - - /** - * Returns the continuation option for the current script (if one has not been set in this - * script, the option defaults to CONTINUE). - *

- * The continuation option specifies whether to continue or abort follow-on processing, - * and whether to delete or keep the current program. - * - * @return the current HeadlessContinuationOption - * @see #setHeadlessContinuationOption(LibHeadlessContinuationOption) - */ - public LibHeadlessContinuationOption getHeadlessContinuationOption() { - if (scriptSetOption == null) { - return LibHeadlessContinuationOption.CONTINUE; - } - - return scriptSetOption; - } - - /** - * Enables or disables analysis according to the passed-in boolean value. - *

- * A script that calls this method should run as a 'preScript', since preScripts - * execute before analysis would typically run. Running the script as a 'postScript' - * is ineffective, since the stage at which analysis would have happened has already - * passed. - *

- * This change will persist throughout the current HeadlessAnalyzer session, unless - * changed again (in other words, once analysis is enabled via script for one program, - * it will also be enabled for future programs in the current session, unless changed). - * - * @param b true to enable analysis, false to disable analysis - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #isHeadlessAnalysisEnabled() - */ - public void enableHeadlessAnalysis(boolean b) throws ImproperUseException { - checkHeadlessStatus(); - - headless.getOptions().enableAnalysis(b); - } - - /** - * Returns whether analysis is currently enabled or disabled in the HeadlessAnalyzer. - * - * @return whether analysis has been enabled or not - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #enableHeadlessAnalysis(boolean) - */ - public boolean isHeadlessAnalysisEnabled() throws ImproperUseException { - checkHeadlessStatus(); - - return headless.getOptions().analyze; - } - - /** - * Returns whether the headless analyzer is currently set to -import mode or not (if not, - * it is in -process mode). The use of -import mode implies that binaries are actively being - * imported into the project (with optional scripts/analysis). The use of -process mode implies - * that existing project files are being processed (using scripts and/or analysis). - * - * @return whether we are in -import mode or not - * @throws ImproperUseException if not in headless mode or headless instance not set - */ - public boolean isImporting() throws ImproperUseException { - checkHeadlessStatus(); - - return !headless.getOptions().runScriptsNoImport; - } - - /** - * Changes the path in the Ghidra project where imported files are saved. - * The passed-in path is assumed to be relative to the project root. For example, - * if the directory structure for the Ghidra project looks like this: - * - *

-	 * 		MyGhidraProject:
-	 * 		  /dir1
-	 * 		    /innerDir1
-	 * 		    /innerDir2
-	 * 
- * - * Then the following usage would ensure that any files imported after this call would - * be saved in the MyGhidraProject:/dir1/innerDir2 folder. - *
-	 * 		setHeadlessImportDirectory("dir1/innerDir2");
-	 * 
- * In contrast, the following usages would add new folders to the Ghidra project and save - * the imported files into the newly-created path: - *
-	 * 		setHeadlessImportDirectory("innerDir2/my/folder");
-	 * 
- * changes the directory structure to: - *
-	 * 		MyGhidraProject:
-	 * 		  /dir1
-	 * 		    /innerDir1
-	 * 		    /innerDir2
-	 * 		      /my
-	 * 		        /folder
-	 * 
- * and: - *
-	 * 		setHeadlessImportDirectory("newDir/saveHere");
-	 * 
- * changes the directory structure to: - *
-	 * 		MyGhidraProject:
-	 * 		  /dir1
-	 * 		    /innerDir1
-	 * 			/innerDir2
-	 *		  /newDir
-	 * 		    /saveHere
-	 * 
- * As in the examples above, if the desired folder does not already exist, it is created. - *

- * A change in the import save folder will persist throughout the current HeadlessAnalyzer - * session, unless changed again (in other words, once the import directory has been changed, - * it will remain the 'save' directory for import files in the current session, unless changed). - *

- * To revert back to the default import location (that which was specified via command line), - * pass the null object as the argument to this method, as below: - *

-	 * 		setHeadlessImportDirectory(null);	// Sets import save directory to default
-	 * 
- * If a file with the same name already exists in the desired location, it will only be - * overwritten if "-overwrite" is true. - *

- * This method is only applicable when using the HeadlessAnalyzer -import mode and - * is ineffective in -process mode. - * - * @param importDir the absolute path (relative to root) where inputs will be saved - * @throws ImproperUseException if not in headless mode or headless instance not set - * @throws IOException if there are issues creating the folder - * @throws InvalidNameException if folder name is invalid - */ - public void setHeadlessImportDirectory(String importDir) - throws ImproperUseException, IOException, InvalidNameException { - checkHeadlessStatus(); - - // Do nothing if not importing -- we don't want to have arbitrary folders - // created when not being used! - - if (!headless.getOptions().runScriptsNoImport) { - DomainFolder saveFolder = null; - - if (importDir != null) { - - if (!importDir.startsWith("/")) { - importDir = "/" + importDir; - } - - // Add ending slash so the dir gets created for server projects - if (!importDir.endsWith("/")) { - importDir += "/"; - } - - // Gets folder -- creates path if it doesn't already exist - saveFolder = headless.getDomainFolder(importDir, true); - } - - headless.setSaveFolder(saveFolder); - } - } - - /** - * Returns whether analysis for the current program has timed out. - *

- * Analysis will time out only in the case where: - *

    - *
  1. the users has set an analysis timeout period using the -analysisTimeoutPerFile - * parameter
  2. - *
  3. analysis is enabled and has completed
  4. - *
  5. the current script is being run as a postScript (since postScripts run after - * analysis)
  6. - *
- * - * @return whether analysis timeout occurred - * @throws ImproperUseException if not in headless mode or headless instance not set - */ - public boolean analysisTimeoutOccurred() throws ImproperUseException { - checkHeadlessStatus(); - return headless.checkAnalysisTimedOut(); - } - - @Override - public void runScript(String scriptName, String[] scriptArguments, GhidraState scriptState) - throws Exception { - - boolean isHeadlessScript = false; - - if (scriptSetOption != null) { - resolveContinuationOptionWith(scriptSetOption); - scriptSetOption = null; - } - ResourceFile scriptSource = GhidraScriptUtil.findScriptByName(scriptName); - if (scriptSource != null) { - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource); - - if (provider == null) { - throw new IOException("Attempting to run subscript '" + scriptName + - "': unable to run this script type."); - } - - GhidraScript script = provider.getScriptInstance(scriptSource, writer); - isHeadlessScript = script instanceof LibHeadlessScript ? true : false; - - if (potentialPropertiesFileLocs.size() > 0) { - script.setPotentialPropertiesFileLocations(potentialPropertiesFileLocs); - } - - if (scriptState == state) { - updateStateFromVariables(); - } - - if (isHeadlessScript) { - ((LibHeadlessScript) script).setHeadlessInstance(headless); - ((LibHeadlessScript) script).setRunningInnerScript(true); - } - - script.setScriptArgs(scriptArguments); - - script.execute(scriptState, monitor, writer); - - if (scriptState == state) { - loadVariablesFromState(); - } - - // Resolve continuations options, if they have changed - if (isHeadlessScript) { - LibHeadlessContinuationOption innerScriptOpt = - ((LibHeadlessScript) script).getHeadlessContinuationOption(); - - if (innerScriptOpt != null) { - resolveContinuationOptionWith(innerScriptOpt); - } - - ((LibHeadlessScript) script).setRunningInnerScript(false); - } - - return; - } - - throw new IllegalArgumentException("Script does not exist: " + scriptName); - } - - @Override - public void cleanup(boolean success) { - resolveContinuationOption(); - - if (!runningInnerScript) { - scriptSetOption = null; - } - } - - private void resolveContinuationOption() { - resolveContinuationOptionWith(scriptSetOption); - } - - /** - * Resolve continuation options according to the table in 'analyzeHeadlessREADME.html'. - * (See "Multiple Scripts" section). - * - * @param opt continuation option to combine with current continuation option - */ - private void resolveContinuationOptionWith(LibHeadlessContinuationOption opt) { - - if (opt == null) { - return; - } - - switch (currentOption) { - - case CONTINUE: - currentOption = opt; - break; - - case CONTINUE_THEN_DELETE: - switch (opt) { - case ABORT: - - case ABORT_AND_DELETE: - currentOption = LibHeadlessContinuationOption.ABORT_AND_DELETE; - break; - - default: - break; - } - break; - - case ABORT_AND_DELETE: - // nothing changes - break; - - case ABORT: - // nothing changes - break; - } - } -} diff --git a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java b/Sample1/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java deleted file mode 100644 index 42e2fad..0000000 --- a/Sample1/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java +++ /dev/null @@ -1,147 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.util.Timer; -import java.util.TimerTask; - -import ghidra.util.exception.CancelledException; -import ghidra.util.task.CancelledListener; -import ghidra.util.task.TaskMonitor; - -/** - * Monitor used by Headless Analyzer for "timeout" functionality - */ -public class LibHeadlessTimedTaskMonitor implements TaskMonitor { - - private Timer timer = new Timer(); - private volatile boolean isCancelled; - - LibHeadlessTimedTaskMonitor(int timeoutSecs) { - isCancelled = false; - timer.schedule(new TimeOutTask(), timeoutSecs * 1000); - } - - private class TimeOutTask extends TimerTask { - @Override - public void run() { - LibHeadlessTimedTaskMonitor.this.cancel(); - } - } - - @Override - public boolean isCancelled() { - return isCancelled; - } - - @Override - public void setShowProgressValue(boolean showProgressValue) { - // stub - } - - @Override - public void setMessage(String message) { - // stub - } - - @Override - public String getMessage() { - return null; - } - - @Override - public void setProgress(long value) { - // stub - } - - @Override - public void initialize(long max) { - // stub - } - - @Override - public void setMaximum(long max) { - // stub - } - - @Override - public long getMaximum() { - return 0; - } - - @Override - public void setIndeterminate(boolean indeterminate) { - // stub - } - - @Override - public boolean isIndeterminate() { - return false; - } - - @Override - public void checkCanceled() throws CancelledException { - if (isCancelled()) { - throw new CancelledException(); - } - } - - @Override - public void incrementProgress(long incrementAmount) { - // stub - } - - @Override - public long getProgress() { - return 0; - } - - @Override - public void cancel() { - timer.cancel(); // Terminate the timer thread - isCancelled = true; - } - - @Override - public void addCancelledListener(CancelledListener listener) { - // stub - } - - @Override - public void removeCancelledListener(CancelledListener listener) { - // stub - } - - @Override - public void setCancelEnabled(boolean enable) { - // stub - } - - @Override - public boolean isCancelEnabled() { - return true; - } - - @Override - public void clearCanceled() { - isCancelled = false; - } -} diff --git a/Sample1/src/com/nosecurecode/libghidra/LibProgramHandler.java b/Sample1/src/com/nosecurecode/libghidra/LibProgramHandler.java deleted file mode 100644 index fcae24e..0000000 --- a/Sample1/src/com/nosecurecode/libghidra/LibProgramHandler.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - This interface implements the callback functionality, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - - -package com.nosecurecode.libghidra; - -import ghidra.program.model.listing.Program; - -/** - * Implement this interface in the class that need to have a callback after Ghidra processing - */ -public interface LibProgramHandler { - public void PostProcessHandler(Program program); -} diff --git a/Sample2/src/com/nosecurecode/libghidra b/Sample2/src/com/nosecurecode/libghidra new file mode 120000 index 0000000..1dce847 --- /dev/null +++ b/Sample2/src/com/nosecurecode/libghidra @@ -0,0 +1 @@ +../../../../GhidraLib \ No newline at end of file diff --git a/Sample2/src/com/nosecurecode/libghidra/LibGhidra.java b/Sample2/src/com/nosecurecode/libghidra/LibGhidra.java deleted file mode 100644 index 6fa6efa..0000000 --- a/Sample2/src/com/nosecurecode/libghidra/LibGhidra.java +++ /dev/null @@ -1,497 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.*; - -import generic.stl.Pair; -import ghidra.app.util.opinion.Loader; -import ghidra.framework.OperatingSystem; -import ghidra.framework.Platform; -import ghidra.framework.model.DomainFolder; -import ghidra.framework.protocol.ghidra.Handler; -import ghidra.util.Msg; -import ghidra.util.exception.InvalidInputException; - -/** - * Launcher entry point for running headless Ghidra. - */ -public class LibGhidra { - - private static final int EXIT_CODE_ERROR = 1; - - /** - * Runs headless command - * @param headlessCmd - * @param handler - * @throws Exception - */ - public static void runHeadlessCmd(String headlessCmd, - LibProgramHandler handler) throws Exception { - new LibGhidra(headlessCmd.split("\\s+"), handler); - } - - /** - * Run heqdless command (command line arguments) - * @param headlessCmdArgs - * @param handler - * @throws Exception - */ - public static void runHeadlessCmd(String [] headlessCmdArgs, - LibProgramHandler handler) throws Exception { - new LibGhidra(headlessCmdArgs, handler); - } - - /** - * This is the main entry point - * @param args - * @param handler - * @throws Exception - */ - private LibGhidra(String args[], LibProgramHandler handler) throws Exception { - String projectName = null; - String rootFolderPath = null; - URL ghidraURL = null; - List filesToImport = new ArrayList<>(); - int optionStartIndex; - - // Make sure there are arguments - if (args.length < 1) { - usage(); - } - - // Ghidra URL handler registration - Handler.registerHandler(); - - if (args[0].startsWith("ghidra:")) { - optionStartIndex = 1; - try { - ghidraURL = new URL(args[0]); - } - catch (MalformedURLException e) { - System.err.println("Invalid Ghidra URL: " + args[0]); - usage(); - } - } - else { - if (args.length < 2) { - usage(); - } - optionStartIndex = 2; - String projectNameAndFolder = args[1]; - - // Check to see if projectName uses back-slashes (likely if they are using Windows) - projectNameAndFolder = projectNameAndFolder.replaceAll("\\\\", DomainFolder.SEPARATOR); - projectName = projectNameAndFolder; - - rootFolderPath = "/"; - int folderIndex = projectNameAndFolder.indexOf(DomainFolder.SEPARATOR); - if (folderIndex == 0) { - System.err.println(args[1] + " is an invalid project_name/folder_path."); - usage(); - } - else if (folderIndex > 0) { - projectName = projectNameAndFolder.substring(0, folderIndex); - rootFolderPath = projectNameAndFolder.substring(folderIndex); - } - } - - // Determine the desired logging. - File logFile = null; - File scriptLogFile = null; - for (int argi = optionStartIndex; argi < args.length; argi++) { - if (checkArgument("-log", args, argi)) { - logFile = new File(args[++argi]); - } - else if (checkArgument("-scriptlog", args, argi)) { - scriptLogFile = new File(args[++argi]); - } - } - - // Instantiate new headless analyzer and parse options. - LibHeadlessAnalyzer analyzer = - LibHeadlessAnalyzer.getLoggableInstance(logFile, scriptLogFile, true, handler); - LibHeadlessOptions options = analyzer.getOptions(); - parseOptions(options, args, optionStartIndex, ghidraURL, filesToImport); - - // Do the headless processing - try { - if (ghidraURL != null) { - analyzer.processURL(ghidraURL, filesToImport); - } - else { - analyzer.processLocal(args[0], projectName, rootFolderPath, filesToImport); - } - } - catch (Throwable e) { - Msg.error(LibHeadlessAnalyzer.class, - "Abort due to Headless analyzer error: " + e.getMessage(), e); - System.exit(EXIT_CODE_ERROR); - } - } - - /** - * Parses the command line arguments and uses them to set the headless options. - * - * @param options The headless options to set. - * @param args The command line arguments to parse. - * @param startIndex The index into the args array of where to start parsing. - * @param ghidraURL The ghidra server url to connect to, or null if not using a url. - * @param filesToImport A list to put files to import into. - * @throws InvalidInputException if an error occurred parsing the arguments or setting - * the options. - */ - private void parseOptions(LibHeadlessOptions options, String[] args, int startIndex, URL ghidraURL, - List filesToImport) throws InvalidInputException { - - String loaderName = null; - List> loaderArgs = new LinkedList<>(); - String languageId = null; - String compilerSpecId = null; - String keystorePath = null; - String serverUID = null; - boolean allowPasswordPrompt = false; - List> preScripts = new LinkedList<>(); - List> postScripts = new LinkedList<>(); - - for (int argi = startIndex; argi < args.length; argi++) { - - String arg = args[argi]; - if (checkArgument("-log", args, argi)) { - // Already processed - argi++; - } - else if (checkArgument("-scriptlog", args, argi)) { - // Already processed - argi++; - } - else if (arg.equalsIgnoreCase("-overwrite")) { - options.enableOverwriteOnConflict(true); - } - else if (arg.equalsIgnoreCase("-noanalysis")) { - options.enableAnalysis(false); - } - else if (arg.equalsIgnoreCase("-deleteproject")) { - options.setDeleteCreatedProjectOnClose(true); - } - else if (checkArgument("-loader", args, argi)) { - loaderName = args[++argi]; - } - else if (arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX)) { - if (args[argi + 1].startsWith("-")) { - throw new InvalidInputException(args[argi] + " expects value to follow."); - } - loaderArgs.add(new Pair<>(arg, args[++argi])); - } - else if (checkArgument("-processor", args, argi)) { - languageId = args[++argi]; - } - else if (checkArgument("-cspec", args, argi)) { - compilerSpecId = args[++argi]; - } - else if (checkArgument("-prescript", args, argi)) { - String scriptName = args[++argi]; - String[] scriptArgs = getSubArguments(args, argi); - argi += scriptArgs.length; - preScripts.add(new Pair<>(scriptName, scriptArgs)); - } - else if (checkArgument("-postscript", args, argi)) { - String scriptName = args[++argi]; - String[] scriptArgs = getSubArguments(args, argi); - argi += scriptArgs.length; - postScripts.add(new Pair<>(scriptName, scriptArgs)); - } - else if (checkArgument("-scriptPath", args, argi)) { - options.setScriptDirectories(args[++argi]); - } - else if (checkArgument("-propertiesPath", args, argi)) { - options.setPropertiesFileDirectories(args[++argi]); - } - else if (checkArgument("-import", args, argi)) { - File inputFile = new File(args[++argi]); - if (!inputFile.isDirectory() && !inputFile.isFile()) { - throw new InvalidInputException( - inputFile.getAbsolutePath() + " is not a valid directory or file."); - } - - LibHeadlessAnalyzer.checkValidFilename(inputFile); - - filesToImport.add(inputFile); - - // Keep checking for OS-expanded files - String nextArg; - - while (argi < (args.length - 1)) { - nextArg = args[++argi]; - - // Check if next argument is a parameter - if (nextArg.charAt(0) == '-') { - argi--; - break; - } - - File otherFile = new File(nextArg); - if (!otherFile.isFile() && !otherFile.isDirectory()) { - throw new InvalidInputException( - otherFile.getAbsolutePath() + " is not a valid directory or file."); - } - - LibHeadlessAnalyzer.checkValidFilename(otherFile); - - filesToImport.add(otherFile); - } - } - else if ("-connect".equals(args[argi])) { - if ((argi + 1) < args.length) { - arg = args[argi + 1]; - if (!arg.startsWith("-")) { - // serverUID is optional argument after -connect - serverUID = arg; - ++argi; - } - } - } - else if ("-commit".equals(args[argi])) { - String comment = null; - if ((argi + 1) < args.length) { - arg = args[argi + 1]; - if (!arg.startsWith("-")) { - // comment is optional argument after -commit - comment = arg; - ++argi; - } - } - options.setCommitFiles(true, comment); - } - else if (checkArgument("-keystore", args, argi)) { - keystorePath = args[++argi]; - File keystore = new File(keystorePath); - if (!keystore.isFile()) { - throw new InvalidInputException( - keystore.getAbsolutePath() + " is not a valid keystore file."); - } - } - else if (arg.equalsIgnoreCase("-p")) { - allowPasswordPrompt = true; - } - else if ("-analysisTimeoutPerFile".equalsIgnoreCase(args[argi])) { - options.setPerFileAnalysisTimeout(args[++argi]); - } - else if ("-process".equals(args[argi])) { - if (options.runScriptsNoImport) { - throw new InvalidInputException( - "The -process option may only be specified once."); - } - String processBinary = null; - if ((argi + 1) < args.length) { - arg = args[argi + 1]; - if (!arg.startsWith("-")) { - // processBinary is optional argument after -process - processBinary = arg; - ++argi; - } - } - options.setRunScriptsNoImport(true, processBinary); - } - else if ("-recursive".equals(args[argi])) { - options.enableRecursiveProcessing(true); - } - else if ("-readOnly".equalsIgnoreCase(args[argi])) { - options.enableReadOnlyProcessing(true); - } - else if (checkArgument("-max-cpu", args, argi)) { - String cpuVal = args[++argi]; - try { - options.setMaxCpu(Integer.parseInt(cpuVal)); - } - catch (NumberFormatException nfe) { - throw new InvalidInputException("Invalid value for max-cpu: " + cpuVal); - } - } - else if ("-okToDelete".equalsIgnoreCase(args[argi])) { - options.setOkToDelete(true); - } - else { - throw new InvalidInputException("Bad argument: " + arg); - } - } - - // Set up pre and post scripts - options.setPreScriptsWithArgs(preScripts); - options.setPostScriptsWithArgs(postScripts); - - // Set loader and loader args - options.setLoader(loaderName, loaderArgs); - - // Set user-specified language and compiler spec - options.setLanguageAndCompiler(languageId, compilerSpecId); - - // Set up optional Ghidra Server authenticator - try { - options.setClientCredentials(serverUID, keystorePath, allowPasswordPrompt); - } - catch (IOException e) { - throw new InvalidInputException( - "Failed to install Ghidra Server authenticator: " + e.getMessage()); - } - - // If -process was specified, inputFiles must be null or inputFiles.size must be 0. - // Otherwise when not in -process mode, inputFiles can be null or inputFiles.size can be 0, - // only if there are scripts to be run. - if (options.runScriptsNoImport) { - - if (filesToImport != null && filesToImport.size() > 0) { - System.err.print("Must use either -process or -import parameters, but not both."); - System.err.print(" -process runs scripts over existing program(s) in a project, " + - "whereas -import"); - System.err.println(" imports new programs and runs scripts and/or analyzes them " + - "after import."); - System.exit(EXIT_CODE_ERROR); - } - - if (options.overwrite) { - Msg.warn(LibHeadlessAnalyzer.class, - "The -overwrite parameter does not apply to -process mode. Ignoring overwrite " + - "and continuing."); - } - - if (options.readOnly && options.okToDelete) { - System.err.println("You have specified the conflicting parameters -readOnly and " + - "-okToDelete. Please pick one and try again."); - System.exit(EXIT_CODE_ERROR); - } - } - else { - if (filesToImport == null || filesToImport.size() == 0) { - if (options.preScripts.isEmpty() && options.postScripts.isEmpty()) { - System.err.println("Nothing to do ... must specify -import, -process, or " + - "prescript and/or postscript."); - System.exit(EXIT_CODE_ERROR); - } - else { - Msg.warn(LibHeadlessAnalyzer.class, - "Neither the -import parameter nor the -process parameter was specified; " + - "therefore, the specified prescripts and/or postscripts will be " + - "executed without any type of program context."); - } - } - } - - if (options.commit) { - if (options.readOnly) { - System.err.println("Can not use -commit and -readOnly at the same time."); - System.exit(EXIT_CODE_ERROR); - } - } - - // Implied commit, only if not in process mode - if (!options.commit && ghidraURL != null) { - if (!options.readOnly) { - // implied commit - options.setCommitFiles(true, null); - } - else { - Msg.warn(LibHeadlessAnalyzer.class, - "-readOnly mode is on: for -process, changes will not be saved."); - } - } - } - - /** - * Prints out the usage details and exits the Java application with an exit code that - * indicates error. - * - * @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(" [/]"); - System.out.println( - " | ghidra://[:]/[/]"); - System.out.println( - " [[-import [|]+] | [-process []]]"); - System.out.println(" [-preScript ]"); - System.out.println(" [-postScript ]"); - System.out.println(" [-scriptPath \"[;...]\"]"); - System.out.println(" [-propertiesPath \"[;...]\"]"); - System.out.println(" [-scriptlog ]"); - System.out.println(" [-log ]"); - System.out.println(" [-overwrite]"); - System.out.println(" [-recursive]"); - System.out.println(" [-readOnly]"); - System.out.println(" [-deleteProject]"); - System.out.println(" [-noanalysis]"); - System.out.println(" [-processor ]"); - System.out.println(" [-cspec ]"); - System.out.println(" [-analysisTimeoutPerFile ]"); - System.out.println(" [-keystore ]"); - System.out.println(" [-connect ]"); - System.out.println(" [-p]"); - System.out.println(" [-commit [\"\"]]"); - System.out.println(" [-okToDelete]"); - System.out.println(" [-max-cpu ]"); - System.out.println(" [-loader ]"); - // ** NOTE: please update 'analyzeHeadlessREADME.html' if changing command line parameters ** - - if (Platform.CURRENT_PLATFORM.getOperatingSystem() != OperatingSystem.WINDOWS) { - System.out.println(); - System.out.println( - " - All uses of $GHIDRA_HOME or $USER_HOME in script path must be" + - " preceded by '\\'"); - } - System.out.println(); - System.out.println( - "Please refer to 'analyzeHeadlessREADME.html' for detailed usage examples " + - "and notes."); - - System.out.println(); - System.exit(EXIT_CODE_ERROR); - } - - private void usage() { - usage("analyzeHeadless"); - } - - private String[] getSubArguments(String[] args, int argi) { - List subArgs = new LinkedList<>(); - int i = argi + 1; - while (i < args.length && !args[i].startsWith("-")) { - subArgs.add(args[i++]); - } - return subArgs.toArray(new String[0]); - } - - private boolean checkArgument(String optionName, String[] args, int argi) - throws InvalidInputException { - // everything after this requires an argument - if (!optionName.equalsIgnoreCase(args[argi])) { - return false; - } - if (argi + 1 == args.length) { - throw new InvalidInputException(optionName + " requires an argument"); - } - return true; - } -} diff --git a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessAnalyzer.java b/Sample2/src/com/nosecurecode/libghidra/LibHeadlessAnalyzer.java deleted file mode 100644 index 4c638cd..0000000 --- a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessAnalyzer.java +++ /dev/null @@ -1,1881 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.*; -import java.net.*; -import java.util.*; -import java.util.regex.Pattern; - -import generic.jar.ResourceFile; -import generic.stl.Pair; -import generic.util.Path; -import ghidra.GhidraApplicationLayout; -import ghidra.GhidraJarApplicationLayout; -import ghidra.app.plugin.core.analysis.AutoAnalysisManager; -import ghidra.app.plugin.core.osgi.BundleHost; -import ghidra.app.script.*; -import com.nosecurecode.libghidra.LibHeadlessScript.LibHeadlessContinuationOption; -import ghidra.app.util.importer.AutoImporter; -import ghidra.app.util.importer.MessageLog; -import ghidra.app.util.opinion.BinaryLoader; -import ghidra.framework.*; -import ghidra.framework.client.ClientUtil; -import ghidra.framework.client.RepositoryAdapter; -import ghidra.framework.data.*; -import ghidra.framework.model.*; -import ghidra.framework.project.DefaultProject; -import ghidra.framework.project.DefaultProjectManager; -import ghidra.framework.protocol.ghidra.*; -import ghidra.framework.remote.User; -import ghidra.framework.store.LockException; -import ghidra.framework.store.local.LocalFileSystem; -import ghidra.program.database.ProgramContentHandler; -import ghidra.program.database.ProgramDB; -import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.listing.Program; -import ghidra.program.util.GhidraProgramUtilities; -import ghidra.program.util.ProgramLocation; -import ghidra.util.*; -import ghidra.util.exception.*; -import ghidra.util.task.TaskMonitor; -import utilities.util.FileUtilities; - -/** - * The class used kick-off and interact with headless processing. All headless options have been - * broken out into their own class: {@link LibHeadlessOptions}. This class is intended to be used - * one of two ways: - *
    - *
  • Used by {@link LibGhidra} to perform headless analysis based on arguments specified - * on the command line.
  • - *
  • Used by another tool as a library to perform headless analysis.
  • - *
- *

- * Note: This class is not thread safe. - */ -public class LibHeadlessAnalyzer { - - private static LibHeadlessAnalyzer instance; - - private LibHeadlessOptions options; - private HeadlessGhidraProjectManager projectManager; - private Project project; - private boolean analysisTimedOut; - private DomainFolder saveDomainFolder; - private Map storage; - private URLClassLoader classLoaderForDotClassScripts; - - private LibProgramHandler programHandler = null; - - /** - * Gets a headless analyzer, initializing the application if necessary with the specified - * logging parameters. An {@link IllegalStateException} will be thrown if the application has - * already been initialized or a headless analyzer has already been retrieved. In these cases, - * the headless analyzer should be gotten with {@link LibHeadlessAnalyzer#getInstance()}. - * - * @param logFile The desired application log file. If null, no application logging will take place. - * @param scriptLogFile The desired scripting log file. If null, no script logging will take place. - * @param useLog4j true if log4j is to be used; otherwise, false. If this class is being used by - * another tool as a library, using log4j might interfere with that tool. - * @return An instance of a new headless analyzer. - * @throws IllegalStateException if an application or headless analyzer instance has already been initialized. - * @throws IOException if there was a problem reading the application.properties file. - */ - public static LibHeadlessAnalyzer getLoggableInstance(File logFile, File scriptLogFile, - boolean useLog4j, LibProgramHandler handler) throws IllegalStateException, IOException { - - // Prevent more than one headless analyzer from being instantiated. Too much about it - // messes with global system settings, so under the current design of Ghidra, allowing - // more than one to exist could result in unpredictable behavior. - if (instance != null) { - throw new IllegalStateException( - "A headless analzyer instance has already been retrieved. " + - "Use HeadlessAnalyzer.getInstance() to get it."); - } - - // Cannot set logging because application has already been initialized. - if (Application.isInitialized()) { - throw new IllegalStateException( - "Logging cannot be set because the application has already been initialized. " + - "Use HeadlessAnalyzer.getInstance() to get the headless analyzer."); - } - - // Initialize application with the provided logging parameters - ApplicationConfiguration configuration = new HeadlessGhidraApplicationConfiguration(); - if (useLog4j) { - if (logFile != null) { - configuration.setApplicationLogFile(logFile); - } - if (scriptLogFile != null) { - configuration.setScriptLogFile(scriptLogFile); - } - } - else { - configuration.setInitializeLogging(false); - Msg.setErrorLogger(new LibHeadlessErrorLogger(logFile)); - } - Application.initializeApplication(getApplicationLayout(), configuration); - - // Instantiate and return singleton headless analyzer - instance = new LibHeadlessAnalyzer(); - - // Set our program handler - instance.programHandler = handler; - - return instance; - } - - /** - * Gets a headless analyzer instance, with the assumption that the application has already been - * initialized. If this is called before the application has been initialized, it will - * initialize the application with no logging. - * - * @return An instance of a new headless analyzer. - * @throws IOException if there was a problem reading the application.properties file (only possible - * if the application had not be initialized). - */ - public static LibHeadlessAnalyzer getInstance(LibProgramHandler handler) throws IOException { - - // Prevent more than one headless analyzer from being instantiated. Too much about it - // messes with global system settings, so under the current design of Ghidra, allowing - // more than one to exist could result in unpredictable behavior. - if (instance != null) { - return instance; - } - - // Initialize application (if necessary) - if (!Application.isInitialized()) { - ApplicationConfiguration configuration = new HeadlessGhidraApplicationConfiguration(); - configuration.setInitializeLogging(false); - Msg.setErrorLogger(new LibHeadlessErrorLogger(null)); - Application.initializeApplication(getApplicationLayout(), configuration); - } - - // Instantiate and return singleton headless analyzer - instance = new LibHeadlessAnalyzer(); - - // Set our program handler - instance.programHandler = handler; - - return instance; - } - - /** - * Gets the appropriate Ghidra application layout for this headless analyzer. - *

- * The headless analyzer can be used in both "normal" mode and single jar mode, so - * we need to use the appropriate layout for either case. - * - * @return The appropriate Ghidra application layout for this headless analyzer. - * @throws IOException if there was a problem getting an appropriate application layout. - */ - private static GhidraApplicationLayout getApplicationLayout() throws IOException { - GhidraApplicationLayout layout; - try { - layout = new GhidraApplicationLayout(); - } - catch (IOException e) { - layout = new GhidraJarApplicationLayout(); - - } - return layout; - } - - /** - * Creates a new headless analyzer object with default settings. - */ - private LibHeadlessAnalyzer() { - // Create default options which the caller can later set prior to processing. - options = new LibHeadlessOptions(); - - // Ghidra URL handler registration. There's no harm in doing this more than once. - Handler.registerHandler(); - - // Ensure that we are running in "headless mode", preventing Swing-based methods from - // running (causing headless operation to lose focus). - System.setProperty("java.awt.headless", "true"); - System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.TRUE.toString()); - - // Allows handling of old content which did not have a content type property - DomainObjectAdapter.setDefaultContentClass(ProgramDB.class); - - // Put analyzer in its default state - reset(); - } - - /** - * Resets the state of the headless analyzer to the default settings. - */ - public void reset() { - options.reset(); - project = null; - analysisTimedOut = false; - saveDomainFolder = null; - storage = new HashMap<>(); - classLoaderForDotClassScripts = null; - } - - /** - * Gets the headless analyzer's options. - * - * @return The headless analyer's options. - */ - public LibHeadlessOptions getOptions() { - return options; - } - - /** - * Process the optional import file/directory list and process each imported file: - *

    - *
  1. execute ordered list of pre-scripts
  2. - *
  3. perform auto-analysis if not disabled
  4. - *
  5. execute ordered list of post-scripts
  6. - *
- * If no import files or directories have been specified the ordered list - * of pre/post scripts will be executed once. - * - * @param ghidraURL ghidra URL for existing server repository and optional - * folder path - * @param filesToImport directories and files to be imported (null or empty - * is acceptable if we are in -process mode) - * @throws IOException if there was an IO-related problem - * @throws MalformedURLException specified URL is invalid - */ - public void processURL(URL ghidraURL, List filesToImport) - throws IOException, MalformedURLException { - - if (options.readOnly && options.commit) { - Msg.error(this, - "Abort due to Headless analyzer error: The requested readOnly option is in conflict " + - "with the commit option"); - return; - } - - if (!"ghidra".equals(ghidraURL.getProtocol())) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); - } - - if (GhidraURL.isLocalProjectURL(ghidraURL)) { - Msg.error(this, - "Ghidra URL command form does not supported local project URLs (ghidra:/path...)"); - return; - } - - String path = ghidraURL.getPath(); - if (path == null) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); - } - - path = path.trim(); - if (path.length() == 0) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); - } - - if (!options.runScriptsNoImport) { // Running in -import mode - if ((filesToImport == null || filesToImport.size() == 0) && - options.preScripts.isEmpty() && options.postScripts.isEmpty()) { - Msg.warn(this, "REPORT: Nothing to do ... must specify files for import."); - return; - } - - if (!path.endsWith("/")) { - // force explicit folder path so that non-existent folders are created on import - ghidraURL = new URL("ghidra", ghidraURL.getHost(), ghidraURL.getPort(), path + "/"); - } - } - else { // Running in -process mode - if (path.endsWith("/") && path.length() > 1) { - ghidraURL = new URL("ghidra", ghidraURL.getHost(), ghidraURL.getPort(), - path.substring(0, path.length() - 1)); - } - } - - List parsedScriptPaths = parseScriptPaths(options.scriptPaths); - GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths); - try { - showConfiguredScriptPaths(); - compileScripts(); - - Msg.info(LibHeadlessAnalyzer.class, "HEADLESS: execution starts"); - - GhidraURLConnection c = (GhidraURLConnection) ghidraURL.openConnection(); - c.setReadOnly(options.readOnly); // writable repository connection - - if (c.getRepositoryName() == null) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); - } - - Msg.info(this, "Opening ghidra repository project: " + ghidraURL); - Object obj = c.getContent(); - if (!(obj instanceof GhidraURLWrappedContent)) { - throw new IOException( - "Connect to repository folder failed. Response code: " + c.getResponseCode()); - } - GhidraURLWrappedContent wrappedContent = (GhidraURLWrappedContent) obj; - Object content = null; - try { - content = wrappedContent.getContent(this); - if (!(content instanceof DomainFolder)) { - throw new IOException("Connect to repository folder failed"); - } - - DomainFolder folder = (DomainFolder) content; - project = new HeadlessProject(getProjectManager(), c); - - if (!checkUpdateOptions()) { - return; // TODO: Should an exception be thrown? - } - - if (options.runScriptsNoImport) { - processNoImport(folder.getPathname()); - } - else { - processWithImport(folder.getPathname(), filesToImport); - } - } - catch (NotFoundException e) { - throw new IOException("Connect to repository folder failed"); - } - finally { - if (content != null) { - wrappedContent.release(content, this); - } - if (project != null) { - project.close(); - } - } - } - finally { - GhidraScriptUtil.dispose(); - } - } - - /** - * Process the optional import file/directory list and process each imported file: - *
    - *
  1. execute ordered list of pre-scripts
  2. - *
  3. perform auto-analysis if not disabled
  4. - *
  5. execute ordered list of post-scripts
  6. - *
- * If no import files or directories have been specified the ordered list - * of pre/post scripts will be executed once. - * - * @param projectLocation directory path of project - * If project exists it will be opened, otherwise it will be created. - * @param projectName project name - * @param rootFolderPath root folder for imports - * @param filesToImport directories and files to be imported (null or empty is acceptable if - * we are in -process mode) - * @throws IOException if there was an IO-related problem - */ - public void processLocal(String projectLocation, String projectName, String rootFolderPath, - List filesToImport) throws IOException { - - if (options.readOnly && options.commit) { - Msg.error(this, - "Abort due to Headless analyzer error: The requested readOnly option is " + - "in conflict with the commit option"); - return; - } - - // If not importing, remove trailing slash so that non-existent folders aren't created - if (options.runScriptsNoImport) { - if ((rootFolderPath.endsWith("/")) && (rootFolderPath.length() > 1)) { - rootFolderPath = rootFolderPath.substring(0, rootFolderPath.length() - 1); - } - } - else { - // If we are importing, need some files to import or at least a script to run! - if ((filesToImport == null || filesToImport.size() == 0) && - options.preScripts.isEmpty() && options.postScripts.isEmpty()) { - Msg.warn(this, "REPORT: Nothing to do ... must specify file(s) for import."); - return; - } - - // If importing, add trailing slash if it isn't there so that non-existent folders are created - if (!rootFolderPath.endsWith("/")) { - rootFolderPath += "/"; - } - } - - List parsedScriptPaths = parseScriptPaths(options.scriptPaths); - GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths); - try { - showConfiguredScriptPaths(); - compileScripts(); - - Msg.info(LibHeadlessAnalyzer.class, "HEADLESS: execution starts"); - - File dir = new File(projectLocation); - ProjectLocator locator = new ProjectLocator(dir.getAbsolutePath(), projectName); - - if (locator.getProjectDir().exists()) { - project = openProject(locator); - } - else { - if (options.runScriptsNoImport) { - Msg.error(this, "Could not find project: " + locator + - " -- should already exist in -process mode."); - throw new IOException("Could not find project: " + locator); - } - - if (!options.runScriptsNoImport && options.readOnly) { - // assume temporary when importing with readOnly option - options.deleteProject = true; - } - - Msg.info(this, "Creating " + (options.deleteProject ? "temporary " : "") + - "project: " + locator); - project = getProjectManager().createProject(locator, null, false); - } - - try { - - if (!checkUpdateOptions()) { - return; // TODO: Should an exception be thrown? - } - - if (options.runScriptsNoImport) { - processNoImport(rootFolderPath); - } - else { - processWithImport(rootFolderPath, filesToImport); - } - } - finally { - project.close(); - if (!options.runScriptsNoImport && options.deleteProject) { - FileUtilities.deleteDir(locator.getProjectDir()); - locator.getMarkerFile().delete(); - } - } - } - finally { - GhidraScriptUtil.dispose(); - } - } - - /** - * Checks to see if the most recent analysis timed out. - * - * @return true if the most recent analysis timed out; otherwise, false. - */ - public boolean checkAnalysisTimedOut() { - return analysisTimedOut; - } - - void setSaveFolder(DomainFolder domFolder) { - saveDomainFolder = domFolder; - - if (domFolder != null) { - Msg.info(this, "Save location changed to: " + domFolder.getPathname()); - } - } - - void addVariableToStorage(String nameOfVar, Object valOfVar) { - if (storage.containsKey(nameOfVar)) { - Msg.warn(this, "Overwriting existing storage variable: " + nameOfVar); - } - - storage.put(nameOfVar, valOfVar); - } - - Set getStorageKeys() { - return storage.keySet(); - } - - Object getVariableFromStorage(String nameOfVar) { - if (!storage.containsKey(nameOfVar)) { - Msg.warn(this, "The storage variable '" + nameOfVar + - "' does not exist in HeadlessAnalyzer storage."); - return null; - } - - return storage.get(nameOfVar); - } - - /** - * Get/Create specified folder path within project - * - * @param folderPath the folder path within the project - * @param create if true, folder will be created if it does not exist - * @return DomainFolder for specified path - * @throws InvalidNameException if folder name is invalid - * @throws IOException if folder can not be created - */ - DomainFolder getDomainFolder(String folderPath, boolean create) - throws IOException, InvalidNameException { - - DomainFolder domFolder = project.getProjectData().getFolder(folderPath); - - if (create && domFolder == null) { - // Create any folder that doesn't exist - String cleanPath = folderPath.replaceAll("^" + DomainFolder.SEPARATOR + "+", ""); - cleanPath = cleanPath.replaceAll(DomainFolder.SEPARATOR + "+$", ""); - - String[] subfolders = cleanPath.split(DomainFolder.SEPARATOR + "+"); - - int folderIndex = 0; - String currPath = DomainFolder.SEPARATOR + subfolders[folderIndex]; - - DomainFolder testFolder = project.getProjectData().getFolder(currPath); - DomainFolder baseFolder = null; - - // Stay in loop while we see folders that exist - while ((testFolder != null) && (folderIndex < (subfolders.length - 1))) { - folderIndex++; - baseFolder = testFolder; - testFolder = baseFolder.getFolder(subfolders[folderIndex]); - } - - // If none of the folders exist, create new files starting from the root - if (folderIndex == 0) { - baseFolder = project.getProjectData().getRootFolder(); - } - - // Since this method is only called by import, we create any folder that - // does not exist. - for (int i = folderIndex; i < subfolders.length; i++) { - baseFolder = baseFolder.createFolder(subfolders[i]); - Msg.info(this, "Created project folder: " + subfolders[i]); - } - - domFolder = baseFolder; - } - - return domFolder; - } - - boolean storageContainsKey(String nameOfVar) { - return storage.containsKey(nameOfVar); - } - - /** - * Runs the specified script with the specified state. - * - * @param scriptState State representing environment variables that the script is able - * to access. - * @param script Script to be run. - * @return whether the script successfully completed running - */ - private boolean runScript(GhidraState scriptState, GhidraScript script) { - if (script instanceof LibHeadlessScript) { - ((LibHeadlessScript) script).setHeadlessInstance(this); - } - - ResourceFile srcFile = script.getSourceFile(); - String scriptName = - srcFile != null ? srcFile.getAbsolutePath() : (script.getClass().getName() + ".class"); - - try { - PrintWriter writer = new PrintWriter(System.out); - Msg.info(this, "SCRIPT: " + scriptName); - script.execute(scriptState, TaskMonitor.DUMMY, writer); - writer.flush(); - } - catch (Exception exc) { - Program prog = scriptState.getCurrentProgram(); - String path = (prog != null ? " ( " + prog.getExecutablePath() + " ) " : ""); - String logErrorMsg = - "REPORT SCRIPT ERROR: " + path + " " + scriptName + " : " + exc.getMessage(); - Msg.error(this, logErrorMsg, exc); - return false; - } - - return true; - } - - /** - * Check file update options (i.e., readOnly, commit) and change defaults if needed. - * @return true if OK to continue - */ - private boolean checkUpdateOptions() { - - boolean isImport = !options.runScriptsNoImport; - boolean commitAllowed = isCommitAllowed(); - - if (options.readOnly) { - String readOnlyError = - "Abort due to Headless analyzer error: The requested -readOnly option " + - "is in conflict with the "; - - if (options.commit) { - Msg.error(this, readOnlyError + "-commit option."); - return false; - } - - if (options.okToDelete) { - Msg.error(this, readOnlyError + "-okToDelete option."); - return false; - } - } - - if (options.commit && !commitAllowed) { - Msg.error(this, - "Commit to repository not possible (due to permission or connection issue)"); - return false; - } - - if (project.getProjectLocator().isTransient()) { - if (!options.commit) { - if (commitAllowed && !options.readOnly) { - Msg.info(this, - "When processing a URL, -commit is automatically enabled unless -readOnly mode " + - "is specified. Enabling -commit and continuing."); - options.commit = true; - } - } - } - - if (options.overwrite) { - if (!isImport) { - Msg.info(this, - "Ignoring -overwrite because it is not applicable to -process mode."); - } - else if (options.readOnly) { - Msg.info(this, - "Ignoring -overwrite because it is not applicable to -readOnly import mode."); - options.overwrite = false; - } - } - - return true; - } - - private boolean isCommitAllowed() { - RepositoryAdapter repository = project.getRepository(); - if (repository == null) { - return true; - } - try { - repository.connect(); - if (!repository.isConnected()) { - return false; - } - User user = repository.getUser(); - if (!user.hasWritePermission()) { - Msg.warn(this, "User '" + user.getName() + - "' does not have write permission to repository - commit not allowed"); - return false; - } - return true; - } - catch (IOException e) { - Msg.error(this, "Repository connection failed (" + repository.getServerInfo() + - ") - commit not allowed"); - return false; - } - } - - private List parseScriptPaths(List scriptPaths) { - if (scriptPaths == null) { - return null; - } - List parsedScriptPaths = new ArrayList<>(); - for (String path : scriptPaths) { - ResourceFile pathFile = Path.fromPathString(path); - String absPath = pathFile.getAbsolutePath(); - if (pathFile.exists()) { - parsedScriptPaths.add(absPath); - } - else { - - Msg.warn(this, "REPORT: Could not find -scriptPath entry, skipping: " + absPath); - } - } - return parsedScriptPaths; - } - - private void showConfiguredScriptPaths() { - StringBuffer buf = new StringBuffer("HEADLESS Script Paths:"); - for (ResourceFile dir : GhidraScriptUtil.getScriptSourceDirectories()) { - buf.append("\n "); - buf.append(dir.getAbsolutePath()); - } - Msg.info(LibHeadlessAnalyzer.class, buf.toString()); - } - - private ResourceFile findScript(String scriptName) { - ResourceFile scriptSource = new ResourceFile(scriptName); - scriptSource = scriptSource.getCanonicalFile(); - if (scriptSource.exists()) { - return scriptSource; - } - scriptSource = GhidraScriptUtil.findScriptByName(scriptName); - if (scriptSource != null) { - return scriptSource; - } - throw new IllegalArgumentException("Script not found: " + scriptName); - } - - /** - * Checks the script name to ensure it exists. If the script type has a GhidraScriptProvider - * (any type of script but .class), then return the ResourceFile that represents that script. - * - * If the script is a class file, return null (one class loader is stored to allow the - * Headless Analyzer to find all the class files). - * - * GhidraScript is not instantiated here, because it is important that each script be - * instantiated at the time it's used. If a GhidraScript object is re-used, this causes - * problems where GhidraScript variables aren't being re-initialized at each use of the script. - * - * @param scriptName The name of the script to check - * @return ResourceFile representing the source file, or null (if script is a .class file) - */ - private ResourceFile checkScript(String scriptName) { - - // Check for pre-compiled GhidraScript (e.g., my.package.Impl.class) - String classExtension = ".class"; - - if (scriptName.endsWith(classExtension)) { - String className = - scriptName.substring(0, scriptName.length() - classExtension.length()); - try { - - // Create a classloader that contains all the ghidra_script paths (especially the one - // specified in -scriptPath!) - List dirs = GhidraScriptUtil.getScriptSourceDirectories(); - List urls = new ArrayList<>(); - - for (ResourceFile dir : dirs) { - try { - urls.add(dir.toURL()); - } - catch (MalformedURLException e) { - // Do nothing. If can't make a URL out of the dir, don't add it. - } - } - - classLoaderForDotClassScripts = - URLClassLoader.newInstance(urls.toArray(new URL[0])); - - Class c = Class.forName(className, true, classLoaderForDotClassScripts); - - if (GhidraScript.class.isAssignableFrom(c)) { - // No issues, but return null, which signifies we don't actually have a - // ResourceFile to associate with the script name - return null; - } - - Msg.error(this, - "REPORT SCRIPT ERROR: java class '" + className + "' is not a GhidraScript"); - } - catch (ClassNotFoundException e) { - Msg.error(this, - "REPORT SCRIPT ERROR: java class not found for '" + className + "'"); - } - throw new IllegalArgumentException("Invalid script: " + scriptName); - } - - try { - ResourceFile scriptSource = findScript(scriptName); - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource); - - if (provider == null) { - throw new IOException("Missing plugin needed to run scripts of this type. Please " + - "ensure you have installed the necessary plugin."); - } - - return scriptSource; - } - catch (Exception | NoClassDefFoundError exc) { - String logErrorMsg = "REPORT SCRIPT ERROR: " + scriptName + " : " + exc.getMessage(); - Msg.error(this, logErrorMsg); - } - throw new IllegalArgumentException("Invalid script: " + scriptName); - } - - /** - * Creates mapping from script name to actual Script object - * - * @param scriptsList List of scripts - * @return mapping of script name to its associated Script object - */ - private Map checkScriptsList(List> scriptsList) { - Map map = new HashMap<>(); - for (Pair scriptPair : scriptsList) { - String scriptName = scriptPair.first; - ResourceFile scriptFile = checkScript(scriptName); - map.put(scriptName, scriptFile); - } - return map; - } - - private void compileScripts() throws IOException { - - // Check that given locations for .properties files are valid - if (options.propertiesFileStrPaths.size() > 0) { - - options.propertiesFilePaths.clear(); - - for (String path : options.propertiesFileStrPaths) { - Path currPath = new Path(path, true, false, true); - - ResourceFile resource = currPath.getPath(); - - if (!resource.isDirectory()) { - throw new IOException("Properties file path: '" + path + - "' either does not exist, " + "or is not a valid directory."); - } - - if (currPath.isEnabled() && !options.propertiesFilePaths.contains(resource)) { - options.propertiesFilePaths.add(resource); - } - } - } - - if (options.preScriptFileMap == null) { - options.preScriptFileMap = checkScriptsList(options.preScripts); - } - - if (options.postScriptFileMap == null) { - options.postScriptFileMap = checkScriptsList(options.postScripts); - } - } - - /** - * Run a list of scripts - * - * @param scriptsList list of script names to run - * @param scriptFileMap mapping of script names to Script objects - * @param scriptState the GhidraState to be passed into each script - * @param continueOption option that could have been set by script(s) - * @return option that could have been set by script(s) - */ - private LibHeadlessContinuationOption runScriptsList(List> scriptsList, - Map scriptFileMap, GhidraState scriptState, - LibHeadlessContinuationOption continueOption) { - - ResourceFile currScriptFile; - LibHeadlessContinuationOption retOption = continueOption; - - boolean scriptSuccess; - boolean isHeadlessScript = false; - String scriptName = ""; - GhidraScript currScript; - - try { - for (Pair scriptPair : scriptsList) { - scriptName = scriptPair.first; - String[] scriptArgs = scriptPair.second; - - // For .class files, there is no ResourceFile mapping. Need to load from the - // stored 'classLoaderForDotClassScripts' - if (scriptName.endsWith(".class")) { - - if (classLoaderForDotClassScripts == null) { - throw new IllegalArgumentException("Invalid script: " + scriptName); - } - - String className = scriptName.substring(0, scriptName.length() - 6); - Class c = Class.forName(className, true, classLoaderForDotClassScripts); - - // Get parent folder to pass to GhidraScript - File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI()) - .getParentFile(); - - currScript = (GhidraScript) c.getConstructor().newInstance(); - currScript.setScriptArgs(scriptArgs); - - if (options.propertiesFilePaths.size() > 0) { - currScript.setPotentialPropertiesFileLocations(options.propertiesFilePaths); - } - - currScript.setPropertiesFileLocation(parentFile.getAbsolutePath(), className); - } - else { - currScriptFile = scriptFileMap.get(scriptName); - - // GhidraScriptProvider case - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(currScriptFile); - PrintWriter writer = new PrintWriter(System.out); - currScript = provider.getScriptInstance(currScriptFile, writer); - currScript.setScriptArgs(scriptArgs); - - if (options.propertiesFilePaths.size() > 0) { - currScript.setPotentialPropertiesFileLocations(options.propertiesFilePaths); - } - } - - isHeadlessScript = currScript instanceof LibHeadlessScript ? true : false; - - if (isHeadlessScript) { - ((LibHeadlessScript) currScript).setInitialContinuationOption(retOption); - } - - scriptSuccess = runScript(scriptState, currScript); - - if (isHeadlessScript) { - if (scriptSuccess) { - retOption = ((LibHeadlessScript) currScript).getContinuationOption(); - - // If script wants to abort, return without running any scripts that follow - if ((retOption == LibHeadlessContinuationOption.ABORT) || - (retOption == LibHeadlessContinuationOption.ABORT_AND_DELETE)) { - return retOption; - } - - } - else { - // If script did not run successfully, abort further processing automatically - Msg.warn(this, - "Script does not exist or encountered problems; further processing is aborted."); - - return LibHeadlessContinuationOption.ABORT; - } - } - } - } - catch (Exception exc) { - String logErrorMsg = "REPORT SCRIPT ERROR: " + scriptName + " : " + exc.getMessage(); - Msg.error(this, logErrorMsg, exc); - } - - return retOption; - } - - private GhidraState getInitialProgramState(Program program) { - ProgramLocation location = null; - AddressSetView initializedMem = program.getMemory().getLoadedAndInitializedAddressSet(); - if (!initializedMem.isEmpty()) { - location = new ProgramLocation(program, initializedMem.getMinAddress()); - } - return new GhidraState(null, project, program, location, null, null); - } - - /** - *{@literal Run prescripts -> analysis -> postscripts (any of these steps is optional).} - * @param fileAbsolutePath Path of the file to analyze. - * @param program The program to analyze. - * @return true if the program file should be kept. If analysis or scripts have marked - * the program as temporary changes should not be saved. Returns false in - * these cases: - * - One of the scripts sets the Headless Continuation Option to "ABORT_AND_DELETE" or - * "CONTINUE_THEN_DELETE". - */ - private boolean analyzeProgram(String fileAbsolutePath, Program program) { - - analysisTimedOut = false; - - AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program); - mgr.initializeOptions(); - - GhidraState scriptState = null; - LibHeadlessContinuationOption scriptStatus = LibHeadlessContinuationOption.CONTINUE; - - boolean abortProcessing = false; - boolean deleteProgram = false; - - if (!options.preScripts.isEmpty()) { - // create one state, in case each script might want to modify it to pass information - scriptState = getInitialProgramState(program); - - scriptStatus = runScriptsList(options.preScripts, options.preScriptFileMap, scriptState, - scriptStatus); - } - - switch (scriptStatus) { - case ABORT_AND_DELETE: - abortProcessing = true; - deleteProgram = true; - break; - - case CONTINUE_THEN_DELETE: - abortProcessing = false; - deleteProgram = true; - break; - - case ABORT: - abortProcessing = true; - deleteProgram = false; - break; - - default: - // do nothing - } - - if (abortProcessing) { - Msg.info(this, "Processing aborted as a result of pre-script."); - return !deleteProgram; - } - - int txId = program.startTransaction("Analysis"); - try { - if (options.analyze) { - Msg.info(this, "ANALYZING all memory and code: " + fileAbsolutePath); - mgr.initializeOptions(); - - // Note: Want to analyze regardless of whether we have already analyzed or not - // (user could have changed options). - mgr.reAnalyzeAll(null); - - if (options.perFileTimeout == -1) { - mgr.startAnalysis(TaskMonitor.DUMMY); // kick start - - Msg.info(this, "REPORT: Analysis succeeded for file: " + fileAbsolutePath); - GhidraProgramUtilities.setAnalyzedFlag(program, true); - } - else { - LibHeadlessTimedTaskMonitor timerMonitor = - new LibHeadlessTimedTaskMonitor(options.perFileTimeout); - mgr.startAnalysis(timerMonitor); - - if (timerMonitor.isCancelled()) { - Msg.error(this, "REPORT: Analysis timed out at " + options.perFileTimeout + - " seconds. Processing not completed for file: " + fileAbsolutePath); - - // If no further scripts, just return the current program disposition - if (options.postScripts.isEmpty()) { - return !deleteProgram; - } - - analysisTimedOut = true; - } - else { - // If timeout didn't already happen at this point, cancel the monitor - timerMonitor.cancel(); - - Msg.info(this, "REPORT: Analysis succeeded for file: " + fileAbsolutePath); - GhidraProgramUtilities.setAnalyzedFlag(program, true); - } - } - } - } - finally { - program.endTransaction(txId, true); - } - - if (!options.postScripts.isEmpty()) { - - if (scriptState == null) { - scriptState = getInitialProgramState(program); - } - - scriptStatus = runScriptsList(options.postScripts, options.postScriptFileMap, - scriptState, scriptStatus); - - switch (scriptStatus) { - case ABORT_AND_DELETE: - abortProcessing = true; - deleteProgram = true; - break; - - case CONTINUE_THEN_DELETE: - abortProcessing = false; - deleteProgram = true; - break; - - case ABORT: - abortProcessing = true; - // If deleteProgram is already true, don't change it to false - // (basically, leave as-is) - break; - - default: - // Do nothing, assume want to carry over options from before - - } - - if (abortProcessing) { - Msg.info(this, "Processing aborted as a result of post-script."); - } - else if (options.analyze && !options.postScripts.isEmpty()) { - Msg.info(this, "ANALYZING changes made by post scripts: " + fileAbsolutePath); - txId = program.startTransaction("Post-Analysis"); - try { - mgr.startAnalysis(TaskMonitor.DUMMY); // kick start - } - finally { - program.endTransaction(txId, true); - } - Msg.info(this, "REPORT: Post-analysis succeeded for file: " + fileAbsolutePath); - } - - } - - // Our hook after the analysis - if (programHandler != null) { - programHandler.PostProcessHandler(program); - } - - return !deleteProgram; - } - - private void processFileNoImport(DomainFile domFile) throws IOException { - - if (domFile.isHijacked()) { - Msg.error(this, - "Skipped processing for " + domFile.getPathname() + " -- file is hijacked"); - return; - } - - if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { - return; // skip non-Program files - } - - Program program = null; - boolean keepFile = true; // if false file should be deleted after release - boolean terminateCheckoutWhenDone = false; - - boolean readOnlyFile = options.readOnly || domFile.isReadOnly(); - - try { - // Exclusive checkout required when commit option specified - if (!readOnlyFile) { - if (domFile.isVersioned()) { - if (!domFile.isCheckedOut()) { - if (!domFile.checkout(options.commit, TaskMonitor.DUMMY)) { - Msg.warn(this, "Skipped processing for " + domFile.getPathname() + - " -- failed to get exclusive file checkout required for commit"); - return; - } - } - else if (options.commit && !domFile.isCheckedOutExclusive()) { - Msg.error(this, "Skipped processing for " + domFile.getPathname() + - " -- file is checked-out non-exclusive (commit requires exclusive checkout)"); - return; - } - } - terminateCheckoutWhenDone = true; - } - - program = (Program) domFile.getDomainObject(this, true, false, TaskMonitor.DUMMY); - - Msg.info(this, "REPORT: Processing project file: " + domFile.getPathname()); - - // This method already takes into account whether the user has set the "noanalysis" - // flag or not - keepFile = analyzeProgram(domFile.getPathname(), program) || readOnlyFile; - - if (!keepFile) { - program.setTemporary(true); // don't save changes - if (!options.okToDelete) { - // Don't remove file unless okToDelete was specified - Msg.warn(this, "Due to script activity, " + domFile.getPathname() + - " deletion was requested but denied -- 'okToDelete' parameter was not specified"); - keepFile = true; - } - } - - if (readOnlyFile) { - if (program.isChanged()) { - Msg.info(this, "REPORT: Discarding changes to the following read-only file: " + - domFile.getPathname()); - } - return; - } - - if (program.isTemporary()) { - if (program.isChanged()) { - Msg.info(this, - "REPORT: Discarding changes to the following file as a result of script activity: " + - domFile.getPathname()); - } - return; - } - - if (domFile.canSave()) { - domFile.save(TaskMonitor.DUMMY); - Msg.info(this, - "REPORT: Save succeeded for processed file: " + domFile.getPathname()); - } - if (program.isChanged()) { - Msg.error(this, - "REPORT: Error trying to save changes to file: " + domFile.getPathname()); - } - - if (options.commit) { - - AutoAnalysisManager.getAnalysisManager(program).dispose(); - program.release(this); - program = null; - - // Only commit if it's a shared project. - commitProgram(domFile); - } - } - catch (VersionException e) { - - if (e.isUpgradable()) { - Msg.error(this, - domFile.getPathname() + - ": this file was created with an older version of Ghidra. Automatic " + - "upgrading of the file to the current version is possible, but " + - "requires an exclusive check-out of the file. Please check out the file " + - " using the Ghidra GUI and then re-run Headless."); - } - else { - Msg.error(this, domFile.getPathname() + - ": this file was created with a newer version of Ghidra, and can not be processed."); - } - } - catch (CancelledException e) { - // This can never happen because there is no user interaction in headless! - } - catch (Exception exc) { - Msg.error(this, domFile.getPathname() + " Error during analysis: " + exc.getMessage(), - exc); - } - finally { - - if (program != null) { - AutoAnalysisManager.getAnalysisManager(program).dispose(); - program.release(this); - program = null; - } - - if (!readOnlyFile) { // can't change anything if read-only file - - // Undo checkout of it is still checked-out and either the file is to be - // deleted, or we just checked it out and file changes have been committed - if (domFile.isCheckedOut()) { - if (!keepFile || - (terminateCheckoutWhenDone && !domFile.modifiedSinceCheckout())) { - domFile.undoCheckout(false); - } - } - - if (!keepFile) { - deleteDomainFile(domFile); - } - } - } - } - - private void deleteDomainFile(DomainFile domFile) { - if (domFile.isCheckedOut()) { - Msg.error(this, "Failed to delete file as requested due to pre-existing checkout: " + - domFile.getPathname()); - return; - } - - try { - domFile.delete(); - } - catch (IOException e) { - Msg.error(this, "Failed to delete file as requested - " + e.getMessage() + ": " + - domFile.getPathname()); - } - } - - /** - * Process all files within parentFolder which satisfies the specified filenamePattern. - * If filenamePattern is null, all files will be processed - * @param parentFolder domain folder to be searched - * @param filenamePattern filename pattern or null for all files - * @return true if one or more files processed - * @throws IOException if an IO problem occurred. - */ - private boolean processFolderNoImport(DomainFolder parentFolder, Pattern filenamePattern) - throws IOException { - - if (parentFolder.isEmpty()) { - return false; - } - - boolean filesProcessed = false; - - for (DomainFile domFile : parentFolder.getFiles()) { - if (filenamePattern == null || filenamePattern.matcher(domFile.getName()).matches()) { - if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { - filesProcessed = true; - processFileNoImport(domFile); - } - } - } - - if (options.recursive) { - for (DomainFolder folder : parentFolder.getFolders()) { - filesProcessed |= processFolderNoImport(folder, filenamePattern); - } - } - - return filesProcessed; - } - - /** - * Process the specified filename within parentFolder. - * @param parentFolder domain folder to be searched - * @param filename name of file to be imported - * @return true if one or more files processed - * @throws IOException if an IO problem occurred. - */ - private boolean processFolderNoImport(DomainFolder parentFolder, String filename) - throws IOException { - - if (parentFolder.isEmpty()) { - return false; - } - - boolean filesProcessed = false; - - DomainFile domFile = parentFolder.getFile(filename); - if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { - filesProcessed = true; - processFileNoImport(domFile); - } - - if (options.recursive) { - for (DomainFolder folder : parentFolder.getFolders()) { - filesProcessed |= processFolderNoImport(folder, filename); - } - } - - return filesProcessed; - } - - private void processNoImport(String rootFolderPath) throws IOException { - - storage.clear(); - - DomainFolder domFolder = project.getProjectData().getFolder(rootFolderPath); - if (domFolder == null) { - throw new IOException("Specified project folder not found: " + rootFolderPath); - } - - Pattern filenamePattern = null; - if (options.domainFileNameToProcess != null) { - filenamePattern = createFilenamePattern(options.domainFileNameToProcess); - } - - boolean filesProcessed = false; - if (filenamePattern == null && options.domainFileNameToProcess != null) { - // assume domainFileNameToProcess was a specific filename and not a pattern - filesProcessed = processFolderNoImport(domFolder, options.domainFileNameToProcess); - } - else { - filesProcessed = processFolderNoImport(domFolder, filenamePattern); - } - - if (!filesProcessed) { - if (options.domainFileNameToProcess != null) { - throw new IOException("Requested project program file(s) not found: " + - options.domainFileNameToProcess); - } - throw new IOException("No program files found within specified project folder: " + - domFolder.getPathname()); - } - } - - private Pattern createFilenamePattern(String name) { - - if ((name.indexOf('*') == -1) && (name.indexOf('?') == -1)) { - // not a 'search' pattern - return null; - } - - // If surrounded by single-quotes, strip them, as to not interfere with the Pattern - if ((name.startsWith("\'")) && (name.endsWith("\'"))) { - name = name.substring(1, name.length() - 1); - } - - // Find files that match the wildcard pattern - Pattern p = UserSearchUtils.createSearchPattern(name, true); - return p; - } - - private boolean checkOverwrite(DomainFile df) throws IOException { - if (options.overwrite) { - try { - if (df.isHijacked()) { - Msg.error(this, - "REPORT: Found conflicting program file in project which is hijacked - overwrite denied: " + - df.getPathname()); - return false; - } - if (df.isVersioned()) { - if (!options.commit) { - Msg.error(this, - "REPORT: Found conflicting versioned program file in project with changes - overwrite denied when commit disabled: " + - df.getPathname()); - return false; - } - if (df.isCheckedOut()) { - df.undoCheckout(false); - } - } - try { - df.delete(); - } - catch (IOException e) { - Msg.error(this, "REPORT: Failed to remove conflicting program file (" + - e.getMessage() + "): " + df.getPathname()); - return false; - } - } - catch (UserAccessException e) { - Msg.error(this, - "REPORT: Found conflicting program file in project which user is unable to overwrite: " + - df.getPathname()); - return false; - } - Msg.warn(this, - "REPORT: Removed conflicting program file from project: " + df.getPathname()); - } - else { - Msg.error(this, - "REPORT: Found conflicting program file in project: " + df.getPathname()); - return false; - } - return true; - } - - private void commitProgram(DomainFile df) throws IOException { - - RepositoryAdapter rep = project.getRepository(); - if (rep != null) { - try { - rep.connect(); - } - catch (IOException e) { - ClientUtil.handleException(rep, e, "Connect", null); - } - if (!rep.isConnected()) { - Msg.error(this, - df.getPathname() + ": File check-in failed - repository connection error"); - throw new IOException( - df.getPathname() + ": File check-in failed - repository connection error"); - } - } - - if (df.canAddToRepository()) { - try { - df.addToVersionControl(options.commitComment, false, TaskMonitor.DUMMY); - Msg.info(this, "REPORT: Added file to repository: " + df.getPathname()); - } - catch (IOException e) { - Msg.error(this, df.getPathname() + ": File check-in failed - " + e.getMessage()); - throw e; - } - catch (CancelledException e) { - // this can never happen because there is no user interaction in headless! - } - } - else if (df.canCheckin()) { - try { - df.checkin(new CheckinHandler() { - @Override - public boolean keepCheckedOut() throws CancelledException { - return true; - } - - @Override - public String getComment() throws CancelledException { - return options.commitComment; - } - - @Override - public boolean createKeepFile() throws CancelledException { - return false; - } - }, true, TaskMonitor.DUMMY); - Msg.info(this, "REPORT: Committed file changes to repository: " + df.getPathname()); - } - catch (IOException e) { - Msg.error(this, df.getPathname() + ": File check-in failed - " + e.getMessage()); - throw e; - } - catch (VersionException e) { - Msg.error(this, - df.getPathname() + ": File check-in failed - version error occurred"); - } - catch (CancelledException e) { - // this can never happen because there is no user interaction in headless! - } - } - else { - Msg.error(this, df.getPathname() + ": Unable to commit file"); - } - } - - private boolean processFileWithImport(File file, String folderPath) { - - Msg.info(this, "IMPORTING: " + file.getAbsolutePath()); - - Program program = null; - - try { - String dfName = null; - DomainFile df = null; - DomainFolder domainFolder = null; - try { - // Gets parent folder for import (creates path if doesn't exist) - domainFolder = getDomainFolder(folderPath, false); - - dfName = file.getName(); - - if (dfName.toLowerCase().endsWith(".gzf") || - dfName.toLowerCase().endsWith(".xml")) { - // Use filename without .gzf - int index = dfName.lastIndexOf('.'); - dfName = dfName.substring(0, index); - } - - if (!options.readOnly) { - if (domainFolder != null) { - df = domainFolder.getFile(dfName); - } - if (df != null && !checkOverwrite(df)) { - return false; - } - df = null; - } - - program = loadProgram(file); - if (program == null) { - return false; - } - - // Check if there are defined memory blocks; abort if not (there is nothing - // to work with!) - if (program.getMemory().getAllInitializedAddressSet().isEmpty()) { - Msg.error(this, "REPORT: Error: No memory blocks were defined for file '" + - file.getAbsolutePath() + "'."); - return false; - } - } - catch (Exception exc) { - Msg.error(this, "REPORT: " + exc.getMessage(), exc); - exc.printStackTrace(); - return false; - } - - Msg.info(this, - "REPORT: Import succeeded with language \"" + - program.getLanguageID().getIdAsString() + "\" and cspec \"" + - program.getCompilerSpec().getCompilerSpecID().getIdAsString() + - "\" for file: " + file.getAbsolutePath()); - - boolean doSave; - try { - - doSave = analyzeProgram(file.getAbsolutePath(), program) && !options.readOnly; - - if (!doSave) { - program.setTemporary(true); - } - - // The act of marking the program as temporary by a script will signal - // us to discard any program changes. - if (program.isTemporary()) { - if (options.readOnly) { - Msg.info(this, "REPORT: Discarded file import due to readOnly option: " + - file.getAbsolutePath()); - } - else { - Msg.info(this, "REPORT: Discarded file import as a result of script " + - "activity or analysis timeout: " + file.getAbsolutePath()); - } - return true; - } - - try { - if (saveDomainFolder != null) { - - df = saveDomainFolder.getFile(dfName); - - // Return if file already exists and overwrite == false - if (df != null && !checkOverwrite(df)) { - return false; - } - - domainFolder = saveDomainFolder; - } - else if (domainFolder == null) { - domainFolder = getDomainFolder(folderPath, true); - } - df = domainFolder.createFile(dfName, program, TaskMonitor.DUMMY); - Msg.info(this, "REPORT: Save succeeded for file: " + df.getPathname()); - - if (options.commit) { - - AutoAnalysisManager.getAnalysisManager(program).dispose(); - program.release(this); - program = null; - - commitProgram(df); - } - } - catch (IOException e) { - e.printStackTrace(); - throw new IOException("Cannot create file: " + domainFolder.getPathname() + - DomainFolder.SEPARATOR + dfName, e); - } - } - catch (Exception exc) { - String logErrorMsg = - file.getAbsolutePath() + " Error during analysis: " + exc.getMessage(); - Msg.info(this, logErrorMsg); - return false; - } - finally { - if (program != null) { - AutoAnalysisManager.getAnalysisManager(program).dispose(); - } - } - - return true; - } - finally { - // Program must be released here, since the AutoAnalysisManager uses program to - // call dispose() in the finally() block above. - if (program != null) { - program.release(this); - program = null; - } - } - } - - private Program loadProgram(File file) throws VersionException, InvalidNameException, - DuplicateNameException, CancelledException, IOException { - - MessageLog messageLog = new MessageLog(); - Program program = null; - - // NOTE: we must pass a null DomainFolder to the AutoImporter so as not to - // allow the DomainFile to be saved at this point. DomainFile should be - // saved after all applicable analysis/scripts are run. - - if (options.loaderClass == null) { - // User did not specify a loader - if (options.language == null) { - program = AutoImporter.importByUsingBestGuess(file, null, this, messageLog, - TaskMonitor.DUMMY); - } - else { - program = AutoImporter.importByLookingForLcs(file, null, options.language, - options.compilerSpec, this, messageLog, TaskMonitor.DUMMY); - } - } - else { - // User specified a loader - if (options.language == null) { - program = AutoImporter.importByUsingSpecificLoaderClass(file, null, - options.loaderClass, options.loaderArgs, this, messageLog, TaskMonitor.DUMMY); - } - else { - program = AutoImporter.importByUsingSpecificLoaderClassAndLcs(file, null, - options.loaderClass, options.loaderArgs, options.language, options.compilerSpec, - this, messageLog, TaskMonitor.DUMMY); - } - } - - if (program == null) { - Msg.error(this, "The AutoImporter could not successfully load " + - file.getAbsolutePath() + - " with the provided import parameters. Please ensure that any specified" + - " processor/cspec arguments are compatible with the loader that is used during" + - " import and try again."); - - if (options.loaderClass != null && options.loaderClass != BinaryLoader.class) { - Msg.error(this, - "NOTE: Import failure may be due to missing opinion for \"" + - options.loaderClass.getSimpleName() + - "\". If so, please contact Ghidra team for assistance."); - } - - return null; - } - - return program; - } - - private void processWithImport(File file, String folderPath, boolean isFirstTime) - throws IOException { - - boolean importSucceeded; - - if (file.isFile()) { - - importSucceeded = processFileWithImport(file, folderPath); - - // Check to see if there are transient programs lying around due - // to programs not being released during Importing - List domainFileContainer = new ArrayList<>(); - TransientDataManager.getTransients(domainFileContainer); - if (domainFileContainer.size() > 0) { - TransientDataManager.releaseFiles(this); - } - - if (!importSucceeded) { - Msg.error(this, "REPORT: Import failed for file: " + file.getAbsolutePath()); - } - - return; - } - - // Looks inside the folder if one of two situations is applicable: - // - If user supplied a directory to import, and it is currently being - // processed (if so, this will be the first time that this method is called) - // - If -recursive is specified - if ((isFirstTime) || (!isFirstTime && options.recursive)) { - // Otherwise, is a directory - Msg.info(this, "REPORT: Importing all files from " + file.getName()); - - File dirFile = file; - - if (!folderPath.endsWith(DomainFolder.SEPARATOR)) { - folderPath += DomainFolder.SEPARATOR; - } - - String subfolderPath = folderPath + file.getName(); - - String[] names = dirFile.list(); - if (names != null) { - Collections.sort(Arrays.asList(names)); - for (String name : names) { - if (name.charAt(0) == '.') { - Msg.warn(this, "Ignoring file '" + name + "'."); - continue; - } - file = new File(dirFile, name); - - // Even a directory name has to have valid characters -- - // can't create a folder if it's not valid - try { - checkValidFilename(file); - processWithImport(file, subfolderPath, false); - } - catch (InvalidInputException e) { - // Just move on if not valid - } - } - } - } - } - - private void processWithImport(String folderPath, List inputDirFiles) throws IOException { - - storage.clear(); - - if (inputDirFiles != null && !inputDirFiles.isEmpty()) { - Msg.info(this, "REPORT: Processing input files: "); - Msg.info(this, " project: " + project.getProjectLocator()); - for (File f : inputDirFiles) { - processWithImport(f, folderPath, true); - } - } - else { - //no input, just run the scripts - - //create one state, in case each script might want to modify it to pass information - GhidraState scriptState = new GhidraState(null, project, null, null, null, null); - - LibHeadlessContinuationOption scriptStatus = LibHeadlessContinuationOption.CONTINUE; - - scriptStatus = runScriptsList(options.preScripts, options.preScriptFileMap, scriptState, - scriptStatus); - - // Since there is no program, "DELETE" is meaningless here. - // If status asks for ABORT, then don't continue running the postscript. - switch (scriptStatus) { - case ABORT: - case ABORT_AND_DELETE: - return; - - default: - // Just continue - } - - runScriptsList(options.postScripts, options.postScriptFileMap, scriptState, - scriptStatus); - } - } - - private Project openProject(ProjectLocator locator) throws IOException { - Project tempProject; - - if (options.deleteProject) { - Msg.warn(this, "Project already exists and will not be deleted: " + locator); - options.deleteProject = false; - } - - Msg.info(this, "Opening existing project: " + locator); - try { - tempProject = new HeadlessProject(getProjectManager(), locator); - } - catch (NotOwnerException e) { - throw new IOException(e); - } - catch (LockException e) { - throw new IOException(e); - } - - return tempProject; - - } - - /** - * Checks to make sure the given file contains only valid characters in its name. - * - * @param currFile The file to check. - * @throws InvalidInputException if the given file contains invalid characters in it. - */ - static void checkValidFilename(File currFile) throws InvalidInputException { - boolean isDir = currFile.isDirectory(); - String filename = currFile.getName(); - - for (int i = 0; i < filename.length(); i++) { - char c = filename.charAt(i); - if (!LocalFileSystem.isValidNameCharacter(c)) { - if (isDir) { - throw new InvalidInputException("The directory '" + filename + - "' contains the invalid characgter: \'" + c + - "\' and can not be created in the project (full path: " + - currFile.getAbsolutePath() + - "). To allow successful import of the directory and its contents, please rename the directory."); - } - throw new InvalidInputException( - "The file '" + filename + "' contains the invalid character: \'" + c + - "\' and can not be imported (full path: " + currFile.getAbsolutePath() + - "). Please rename the file."); - } - } - } - - private HeadlessGhidraProjectManager getProjectManager() { - if (projectManager == null) { - projectManager = new HeadlessGhidraProjectManager(); - } - return projectManager; - } - - /** - * Ghidra project class required to gain access to specialized project constructor - * for URL connection. - */ - private static class HeadlessProject extends DefaultProject { - - HeadlessProject(HeadlessGhidraProjectManager projectManager, GhidraURLConnection connection) - throws IOException { - super(projectManager, connection); - } - - HeadlessProject(HeadlessGhidraProjectManager projectManager, ProjectLocator projectLocator) - throws NotOwnerException, LockException, IOException { - super(projectManager, projectLocator, false); - } - } - - private static class HeadlessGhidraProjectManager extends DefaultProjectManager { - // this exists just to allow access to the constructor - } -} diff --git a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java b/Sample2/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java deleted file mode 100644 index d63c67c..0000000 --- a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java +++ /dev/null @@ -1,157 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.*; - -import ghidra.util.ErrorLogger; - -/** - * Custom headless error logger which is used when log4j is disabled. - */ -class LibHeadlessErrorLogger implements ErrorLogger { - - private PrintWriter logWriter; - - LibHeadlessErrorLogger(File logFile) { - if (logFile != null) { - setLogFile(logFile); - } - } - - synchronized void setLogFile(File logFile) { - try { - if (logFile == null) { - if (logWriter != null) { - writeLog("INFO", "File logging disabled"); - logWriter.close(); - logWriter = null; - } - return; - } - PrintWriter w = new PrintWriter(new FileWriter(logFile)); - if (logWriter != null) { - writeLog("INFO ", "Switching log file to: " + logFile); - logWriter.close(); - } - logWriter = w; - } - catch (IOException e) { - System.err.println("Failed to open log file " + logFile + ": " + e.getMessage()); - } - } - - private synchronized void writeLog(String line) { - if (logWriter == null) { - return; - } - logWriter.println(line); - } - - private synchronized void writeLog(String level, String[] lines) { - if (logWriter == null) { - return; - } - for (String line : lines) { - writeLog(level + " " + line); - } - logWriter.flush(); - } - - private synchronized void writeLog(String level, String text) { - if (logWriter == null) { - return; - } - writeLog(level, chopLines(text)); - } - - private synchronized void writeLog(String level, String text, Throwable throwable) { - if (logWriter == null) { - return; - } - writeLog(level, chopLines(text)); - for (StackTraceElement element : throwable.getStackTrace()) { - writeLog(level + " " + element.toString()); - } - logWriter.flush(); - } - - private String[] chopLines(String text) { - text = text.replace("\r", ""); - return text.split("\n"); - } - - @Override - public void debug(Object originator, Object message) { - // TODO for some reason debug is off - // writeLog("DEBUG", message.toString()); - } - - @Override - public void debug(Object originator, Object message, Throwable throwable) { - // TODO for some reason debug is off - // writeLog("DEBUG", message.toString(), throwable); - } - - @Override - public void error(Object originator, Object message) { - writeLog("ERROR", message.toString()); - } - - @Override - public void error(Object originator, Object message, Throwable throwable) { - writeLog("ERROR", message.toString(), throwable); - } - - @Override - public void info(Object originator, Object message) { - writeLog("INFO ", message.toString()); - } - - @Override - public void info(Object originator, Object message, Throwable throwable) { - // TODO for some reason tracing is off - // writeLog("INFO ", message.toString(), throwable); - } - - @Override - public void trace(Object originator, Object message) { - // TODO for some reason tracing i soff - // writeLog("TRACE", message.toString()); - } - - @Override - public void trace(Object originator, Object message, Throwable throwable) { - // TODO for some reason tracing is off - // writeLog("TRACE", message.toString(), throwable); - } - - @Override - public void warn(Object originator, Object message) { - writeLog("WARN ", message.toString()); - } - - @Override - public void warn(Object originator, Object message, Throwable throwable) { - writeLog("WARN ", message.toString(), throwable); - } - -} diff --git a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessOptions.java b/Sample2/src/com/nosecurecode/libghidra/LibHeadlessOptions.java deleted file mode 100644 index e3ac5c4..0000000 --- a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessOptions.java +++ /dev/null @@ -1,508 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.IOException; -import java.util.*; - -import generic.jar.ResourceFile; -import generic.stl.Pair; -import ghidra.app.util.opinion.Loader; -import ghidra.app.util.opinion.LoaderService; -import ghidra.framework.client.HeadlessClientAuthenticator; -import ghidra.program.model.lang.*; -import ghidra.program.util.DefaultLanguageService; -import ghidra.util.exception.InvalidInputException; - -/** - * Options for headless analyzer. - *

- * Option state may be adjusted to reflect assumed options - * during processing. If multiple invocations of either - * {@link LibHeadlessAnalyzer#processLocal(String, String, String, List)} or - * {@link LibHeadlessAnalyzer#processURL(java.net.URL, List)} are performed, - * these options should be reset and adjusted as necessary. - */ - -public class LibHeadlessOptions { - - // -process and -import - String domainFileNameToProcess; // may include pattern - boolean runScriptsNoImport; - - // -preScript - List> preScripts; - Map preScriptFileMap; - - // -postScript - List> postScripts; - Map postScriptFileMap; - - // -scriptPath - List scriptPaths; - - // -propertiesPath - List propertiesFileStrPaths; - List propertiesFilePaths; - - // -overwrite - boolean overwrite; - - // -recursive - boolean recursive; - - // -readOnly - boolean readOnly; - - // -deleteProject - boolean deleteProject; - - // -noanalysis - boolean analyze; - - // -processor - Language language; - - // -cspec - CompilerSpec compilerSpec; - - // -analysisTimeoutPerFile - int perFileTimeout; - - // -keystore - String keystore; - - // -connect - String connectUserID; - - // -p - boolean allowPasswordPrompt; - - // -commit - boolean commit; - String commitComment; - - // -okToDelete - boolean okToDelete; - - // -max-cpu - int maxcpu; - - // -loader - Class loaderClass; - List> loaderArgs; - - // ------------------------------------------------------------------------------------------- - - /** - * Creates a new headless options object with default settings. - */ - LibHeadlessOptions() { - reset(); - } - - /** - * Resets the options to its default settings. - */ - public void reset() { - domainFileNameToProcess = null; - runScriptsNoImport = false; - preScripts = new LinkedList<>(); - preScriptFileMap = null; - postScripts = new LinkedList<>(); - postScriptFileMap = null; - scriptPaths = null; - propertiesFileStrPaths = new ArrayList<>(); - propertiesFilePaths = new ArrayList<>(); - overwrite = false; - recursive = false; - readOnly = false; - deleteProject = false; - analyze = true; - language = null; - compilerSpec = null; - perFileTimeout = -1; - keystore = null; - connectUserID = null; - allowPasswordPrompt = false; - commit = false; - commitComment = null; - okToDelete = false; - maxcpu = 0; - loaderClass = null; - loaderArgs = null; - } - - /** - * Set to run scripts (and optionally, analysis) without importing a - * program. Scripts will run on specified folder or program that already - * exists in the project. - * - * @param runScriptsOnly if true, no imports will occur and scripts - * (and analysis, if enabled) will run on the specified existing program - * or directory of programs. - * @param filename name of specific project file or folder to be processed (the location - * is passed in elsewhere by the user). If null, user has not specified - * a file to process -- therefore, the entire directory will be processed. - * The filename should not include folder path elements which should be - * specified separately via project or URL specification. - * @throws IllegalArgumentException if the specified filename is invalid and contains the - * path separator character '/'. - */ - public void setRunScriptsNoImport(boolean runScriptsOnly, String filename) { - if (filename != null) { - filename = filename.trim(); - if (filename.indexOf("/") >= 0) { - throw new IllegalArgumentException("invalid filename specified"); - } - } - this.runScriptsNoImport = runScriptsOnly; - this.domainFileNameToProcess = filename; - } - - /** - * Set the ordered list of scripts to execute immediately following import and - * prior to analyzing an imported program. If import not performed, - * these scripts will execute once prior to any post-scripts. - * - * @param preScripts list of script names - */ - public void setPreScripts(List preScripts) { - List> preScriptsEmptyArgs = new LinkedList<>(); - for (String preScript : preScripts) { - preScriptsEmptyArgs.add(new Pair<>(preScript, new String[0])); - } - setPreScriptsWithArgs(preScriptsEmptyArgs); - } - - /** - * Set the ordered list of scripts and their arguments to execute immediately following import - * and prior to analyzing an imported program. If import not performed, - * these scripts will execute once prior to any post-scripts. - * - * @param preScripts list of script names/script argument pairs - */ - public void setPreScriptsWithArgs(List> preScripts) { - this.preScripts = preScripts; - this.preScriptFileMap = null; - } - - /** - * Set the ordered list of scripts to execute immediately following import and - * and analysis of a program. If import not performed, - * these scripts will execute once following any pre-scripts. - * - * @param postScripts list of script names - */ - public void setPostScripts(List postScripts) { - List> postScriptsEmptyArgs = new LinkedList<>(); - for (String postScript : postScripts) { - postScriptsEmptyArgs.add(new Pair<>(postScript, new String[0])); - } - setPostScriptsWithArgs(postScriptsEmptyArgs); - } - - /** - * Set the ordered list of scripts to execute immediately following import and - * and analysis of a program. If import not performed, - * these scripts will execute once following any pre-scripts. - * - * @param postScripts list of script names/script argument pairs - */ - public void setPostScriptsWithArgs(List> postScripts) { - this.postScripts = postScripts; - this.postScriptFileMap = null; - } - - /** - * Set the script source directories to be searched for secondary scripts. - * The default set of enabled script directories within the Ghidra installation - * will be appended to the specified list of newPaths. - * Individual Paths may be constructed relative to Ghidra installation directory, - * User home directory, or absolute system paths. Examples: - *

-	 *     Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
-	 *     Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
-	 *     "/shared/ghidra_scripts"
-	 * 
- * - * @param newPaths list of directories to be searched. - */ - public void setScriptDirectories(List newPaths) { - scriptPaths = newPaths; - } - - /** - * List of valid script directory paths separated by a ';'. - * The default set of enabled script directories within the Ghidra installation - * will be appended to the specified list of newPaths. - * Individual Paths may be constructed relative to Ghidra installation directory, - * User home directory, or absolute system paths. Examples: - *
-	 * 		Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
-	 *      Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
-	 *		"/shared/ghidra_scripts"
-	 * 
- * @param paths semicolon (';') separated list of directory paths - */ - public void setScriptDirectories(String paths) { - String[] pathArray = paths.split(";"); - setScriptDirectories(Arrays.asList(pathArray)); - } - - /** - * Sets a single location for .properties files associated with GhidraScripts. - * - * Typically, .properties files should be located in the same directory as their corresponding - * scripts. However, this method may need to be used when circumstances make it impossible to - * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). - * - * @param path location of .properties file(s) - */ - public void setPropertiesFileDirectory(String path) { - propertiesFileStrPaths = new ArrayList<>(); - propertiesFileStrPaths.add(path); - } - - /** - * Sets one or more locations to find .properties files associated with GhidraScripts. - * - * Typically, .properties files should be located in the same directory as their corresponding - * scripts. However, this method may need to be used when circumstances make it impossible to - * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). - * - * @param newPaths potential locations of .properties file(s) - */ - public void setPropertiesFileDirectories(List newPaths) { - propertiesFileStrPaths = newPaths; - } - - /** - * List of valid .properties file directory paths, separated by a ';'. - * - * Typically, .properties files should be located in the same directory as their corresponding - * scripts. However, this method may need to be used when circumstances make it impossible to - * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). - * - * @param paths String representation of directories (each separated by ';') - */ - public void setPropertiesFileDirectories(String paths) { - String[] pathArray = paths.split(";"); - setPropertiesFileDirectories(Arrays.asList(pathArray)); - } - - /** - * During import, the default behavior is to skip the import if a conflict occurs - * within the destination folder. This method can be used to force the original - * conflicting file to be removed prior to import. - * If the pre-existing file is versioned, the commit option must also be - * enabled to have the overwrite remove the versioned file. - * - * @param enabled if true conflicting domain files will be removed from the - * project prior to importing the new file. - */ - public void enableOverwriteOnConflict(boolean enabled) { - this.overwrite = enabled; - } - - /** - * This method can be used to enable recursive processing of files during - * -import or -process modes. In order for recursive processing of files to - * occur, the user must have specified a directory (and not a specific file) - * for the Headless Analyzer to import or process. - * - * @param enabled if true, enables recursive processing - */ - public void enableRecursiveProcessing(boolean enabled) { - this.recursive = enabled; - } - - /** - * When readOnly processing is enabled, any changes made by script or analyzers - * are discarded when the Headless Analyzer exits. When used with import mode, - * the imported program file will not be saved to the project or repository. - * - * @param enabled if true, enables readOnly processing or import - */ - public void enableReadOnlyProcessing(boolean enabled) { - this.readOnly = enabled; - } - - /** - * Set project delete flag which allows temporary projects created - * to be deleted upon completion. This option has no effect if a - * Ghidra URL or an existing project was specified. This option - * will be assumed when importing with the readOnly option enabled. - * - * @param enabled if true a created project will be deleted when - * processing is complete. - */ - public void setDeleteCreatedProjectOnClose(boolean enabled) { - this.deleteProject = enabled; - } - - /** - * Auto-analysis is enabled by default following import. This method can be - * used to change the enablement of auto-analysis. - * - * @param enabled True if auto-analysis should be enabled; otherwise, false. - */ - public void enableAnalysis(boolean enabled) { - this.analyze = enabled; - } - - /** - * Sets the language and compiler spec from the provided input. Any null value will attempt - * a "best-guess" if possible. - * - * @param languageId The language to set. - * @param compilerSpecId The compiler spec to set. - * @throws InvalidInputException if the language and compiler spec combination is not valid. - */ - public void setLanguageAndCompiler(String languageId, String compilerSpecId) - throws InvalidInputException { - if (languageId == null && compilerSpecId == null) { - return; - } - if (languageId == null) { - throw new InvalidInputException("Compiler spec specified without specifying language."); - } - try { - language = - DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(languageId)); - if (compilerSpecId == null) { - compilerSpec = language.getDefaultCompilerSpec(); - } - else { - compilerSpec = language.getCompilerSpecByID(new CompilerSpecID(compilerSpecId)); - } - } - catch (LanguageNotFoundException e) { - language = null; - compilerSpec = null; - throw new InvalidInputException("Unsupported language: " + languageId); - } - catch (CompilerSpecNotFoundException e) { - language = null; - compilerSpec = null; - throw new InvalidInputException("Compiler spec \"" + compilerSpecId + - "\" is not supported for language \"" + languageId + "\""); - } - } - - /** - * Set analyzer timeout on a per-file basis. - * - * @param stringInSecs timeout value in seconds (as a String) - * @throws InvalidInputException if the timeout value was not a valid value - */ - public void setPerFileAnalysisTimeout(String stringInSecs) throws InvalidInputException { - try { - perFileTimeout = Integer.parseInt(stringInSecs); - } - catch (NumberFormatException nfe) { - throw new InvalidInputException( - "'" + stringInSecs + "' is not a valid integer representation."); - } - } - - public void setPerFileAnalysisTimeout(int secs) { - perFileTimeout = secs; - } - - /** - * Set Ghidra Server client credentials to be used with "shared" projects. - * - * @param userID optional userId to use if server permits the user to use - * a userId which differs from the process owner name. - * @param keystorePath file path to keystore file containing users private key - * to be used with PKI or SSH based authentication. - * @param allowPasswordPrompt if true the user may be prompted for passwords - * via the console (stdin). Please note that the Java console will echo - * the password entry to the terminal which may be undesirable. - * @throws IOException if an error occurs while opening the specified keystorePath. - */ - public void setClientCredentials(String userID, String keystorePath, - boolean allowPasswordPrompt) throws IOException { - this.connectUserID = userID; - this.keystore = keystorePath; - this.allowPasswordPrompt = allowPasswordPrompt; - HeadlessClientAuthenticator.installHeadlessClientAuthenticator(userID, keystorePath, - allowPasswordPrompt); - } - - /** - * Enable committing of processed files to the repository which backs the specified - * project. - * - * @param commit if true imported files will be committed - * @param comment optional comment to use when committing - */ - public void setCommitFiles(boolean commit, String comment) { - this.commit = commit; - this.commitComment = comment; - } - - public void setOkToDelete(boolean deleteOk) { - okToDelete = deleteOk; - } - - /** - * Sets the maximum number of cpu cores to use during headless processing. - * - * @param cpu The maximum number of cpu cores to use during headless processing. - * Setting it to 0 or a negative integer is equivalent to setting it to 1. - */ - public void setMaxCpu(int cpu) { - this.maxcpu = cpu; - System.setProperty("cpu.core.limit", Integer.toString(cpu)); - - } - - /** - * Sets the loader to use for imports, as well as any loader-specific arguments. A null loader - * will attempt "best-guess" if possible. Loader arguments are not supported if a "best-guess" - * is made. - * - * @param loaderName The name (simple class name) of the loader to use. - * @param loaderArgs A list of loader-specific arguments. Could be null if there are none. - * @throws InvalidInputException if an invalid loader name was specified, or if loader arguments - * were specified but a loader was not. - */ - public void setLoader(String loaderName, List> loaderArgs) - throws InvalidInputException { - if (loaderName != null) { - this.loaderClass = LoaderService.getLoaderClassByName(loaderName); - if (this.loaderClass == null) { - throw new InvalidInputException("Invalid loader name specified: " + loaderName); - } - this.loaderArgs = loaderArgs; - } - else { - if (loaderArgs != null && loaderArgs.size() > 0) { - throw new InvalidInputException( - "Loader arguments defined without a loader being specified."); - } - this.loaderClass = null; - this.loaderArgs = null; - } - } -} diff --git a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessScript.java b/Sample2/src/com/nosecurecode/libghidra/LibHeadlessScript.java deleted file mode 100644 index 7708ab7..0000000 --- a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessScript.java +++ /dev/null @@ -1,508 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.io.IOException; - -import generic.jar.ResourceFile; -import ghidra.app.script.*; -import ghidra.framework.model.DomainFolder; -import ghidra.util.InvalidNameException; - -/** - * This class is analogous to GhidraScript, except that is only meant to be used with - * the HeadlessAnalyzer. That is, if a user writes a script that extends HeadlessScript, - * it should only be run in the Headless environment. - */ -public abstract class LibHeadlessScript extends GhidraScript { - - /** - * Options for controlling disposition of program after the current script completes. - */ - public enum LibHeadlessContinuationOption { - /** - * Continue running scripts and/or analysis; -import and -process - * modes complete normally. - */ - CONTINUE, - - /** - * Continue running scripts and/or analysis; - * -import mode does not save program, - * -process mode deletes program. - */ - CONTINUE_THEN_DELETE, - - /** - * Abort any scripts or analysis that come after this script; - * -import mode does not save program, -process mode deletes program. - */ - ABORT_AND_DELETE, - - /** - * Abort any scripts or analysis that come after this script; -import mode does - * save program (but it may not be processed completely), - * -process mode completes normally, minus scripts or analysis that - * runs after the ABORT request. - */ - ABORT - } - - private LibHeadlessAnalyzer headless = null; - - private LibHeadlessContinuationOption currentOption = LibHeadlessContinuationOption.CONTINUE; - private LibHeadlessContinuationOption scriptSetOption = null; - - private boolean runningInnerScript = false; - - // This is necessary because it determine when we nullify the 'scriptSetOption' variable - private void setRunningInnerScript(boolean b) { - runningInnerScript = b; - } - - /** - * Sets the current headless instance -- doing so gives the user the ability to manipulate - * headless analyzer-specific parameters. - *

- * This method is declared with no access modifier to only allow package-level (no subclass) - * access. This method is meant to only be used by the HeadlessAnalyzer class. - * - * @param ha HeadlessAnalyzer instance - */ - void setHeadlessInstance(LibHeadlessAnalyzer ha) { - headless = ha; - } - - /** - * Sets the "beginning-of-script" continuation status. - *

- * This method is declare with no access modifier to only allow package-level (no - * subclass) access. This method is meant to only be used by the HeadlessAnalyzer class. - * - * @param option initial continuation option for this script - */ - void setInitialContinuationOption(LibHeadlessContinuationOption option) { - currentOption = option; - } - - /** - * Returns the final resolved continuation option (after script processing is done). - *

- * The continuation option specifies whether to continue or abort follow-on processing, - * and whether to delete or keep the current program. - *

- * This method is declared with no access modifier to only allow package-level (no - * subclass) access. This method is meant to only be used by the HeadlessAnalyzer class. - * - * @return the script's final HeadlessContinuationOption - */ - LibHeadlessContinuationOption getContinuationOption() { - return currentOption; - } - - /** - * Checks to see if this script is running in headless mode (it should be!). - *

- * This method should be called at the beginning of every public method in HeadlessScript - * that accesses HeadlessAnalyzer methods (for instance, 'headless.isAnalysisEnabled()'). - * The call to this method can not be placed in the constructor, because 'setHeadlessInstance', - * which connects the script with the current headless instance, is not called until after the - * call to the constructor. - * - * @throws ImproperUseException if not in headless mode or headless instance not set - */ - private void checkHeadlessStatus() throws ImproperUseException { - if (headless == null || !isRunningHeadless()) { - throw new ImproperUseException("This method can only be used in the headless case!"); - } - } - - /** - * Stores a key/value pair in the HeadlessAnalyzer instance for later use. - *

- * This method, along with the 'getStoredHeadlessValue' method, is useful for debugging and - * testing the Headless Analyzer (when the user has directly instantiated the HeadlessAnalyzer - * instead of running it from analyzeHeadless.sh or analyzeHeadless.bat). This method is - * intended to allow a HeadlessScript to store variables that reflect the current state of - * processing (at the time the script is being run). Storing variables in the HeadlessAnalyzer - * instance may be the only way to access the state of processing during cases when the user - * is forced to run in -readOnly mode, or if there is a value that is only accessible at the - * scripts stage. - * - * @param key storage key in String form - * @param value value to store - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #getStoredHeadlessValue(String) - * @see #headlessStorageContainsKey(String) - */ - public void storeHeadlessValue(String key, Object value) throws ImproperUseException { - checkHeadlessStatus(); - headless.addVariableToStorage(key, value); - } - - /** - * Get stored value by key from the HeadlessAnalyzer instance. - *

- * This method, along with the 'storedHeadlessValue' method, is useful for debugging and - * testing the Headless Analyzer (when the user has directly instantiated the HeadlessAnalyzer - * instead of running it from analyzeHeadless.sh or analyzeHeadless.bat). This method is - * intended to allow a HeadlessScript to store variables that reflect the current state of - * processing (at the time the script is being run). Storing variables in the HeadlessAnalyzer - * instance may be the only way to access the state of processing during cases when the user - * is forced to run in -readOnly mode, or if there is a value that is only accessible at the - * scripts stage. - * - * @param key key to retrieve the desired stored value - * @return stored Object, or null if none exists for that key - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #storeHeadlessValue(String, Object) - * @see #headlessStorageContainsKey(String) - */ - public Object getStoredHeadlessValue(String key) throws ImproperUseException { - checkHeadlessStatus(); - return headless.getVariableFromStorage(key); - } - - /** - * Returns whether the specified key was stored in the HeadlessAnalyzer instance. - * - * @param key value of key to check for in Headless Analyzer instance - * @return true if the specified key exists - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #storeHeadlessValue(String, Object) - * @see #getStoredHeadlessValue(String) - */ - public boolean headlessStorageContainsKey(String key) throws ImproperUseException { - checkHeadlessStatus(); - return headless.storageContainsKey(key); - } - - /** - * Sets the continuation option for this script - *

- * The continuation option specifies whether to continue or abort follow-on processing, - * and whether to delete or keep the current program. - * - * @param option HeadlessContinuationOption set by this script - * @see #getHeadlessContinuationOption() - */ - public void setHeadlessContinuationOption(LibHeadlessContinuationOption option) { - scriptSetOption = option; - } - - /** - * Returns the continuation option for the current script (if one has not been set in this - * script, the option defaults to CONTINUE). - *

- * The continuation option specifies whether to continue or abort follow-on processing, - * and whether to delete or keep the current program. - * - * @return the current HeadlessContinuationOption - * @see #setHeadlessContinuationOption(LibHeadlessContinuationOption) - */ - public LibHeadlessContinuationOption getHeadlessContinuationOption() { - if (scriptSetOption == null) { - return LibHeadlessContinuationOption.CONTINUE; - } - - return scriptSetOption; - } - - /** - * Enables or disables analysis according to the passed-in boolean value. - *

- * A script that calls this method should run as a 'preScript', since preScripts - * execute before analysis would typically run. Running the script as a 'postScript' - * is ineffective, since the stage at which analysis would have happened has already - * passed. - *

- * This change will persist throughout the current HeadlessAnalyzer session, unless - * changed again (in other words, once analysis is enabled via script for one program, - * it will also be enabled for future programs in the current session, unless changed). - * - * @param b true to enable analysis, false to disable analysis - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #isHeadlessAnalysisEnabled() - */ - public void enableHeadlessAnalysis(boolean b) throws ImproperUseException { - checkHeadlessStatus(); - - headless.getOptions().enableAnalysis(b); - } - - /** - * Returns whether analysis is currently enabled or disabled in the HeadlessAnalyzer. - * - * @return whether analysis has been enabled or not - * @throws ImproperUseException if not in headless mode or headless instance not set - * @see #enableHeadlessAnalysis(boolean) - */ - public boolean isHeadlessAnalysisEnabled() throws ImproperUseException { - checkHeadlessStatus(); - - return headless.getOptions().analyze; - } - - /** - * Returns whether the headless analyzer is currently set to -import mode or not (if not, - * it is in -process mode). The use of -import mode implies that binaries are actively being - * imported into the project (with optional scripts/analysis). The use of -process mode implies - * that existing project files are being processed (using scripts and/or analysis). - * - * @return whether we are in -import mode or not - * @throws ImproperUseException if not in headless mode or headless instance not set - */ - public boolean isImporting() throws ImproperUseException { - checkHeadlessStatus(); - - return !headless.getOptions().runScriptsNoImport; - } - - /** - * Changes the path in the Ghidra project where imported files are saved. - * The passed-in path is assumed to be relative to the project root. For example, - * if the directory structure for the Ghidra project looks like this: - * - *

-	 * 		MyGhidraProject:
-	 * 		  /dir1
-	 * 		    /innerDir1
-	 * 		    /innerDir2
-	 * 
- * - * Then the following usage would ensure that any files imported after this call would - * be saved in the MyGhidraProject:/dir1/innerDir2 folder. - *
-	 * 		setHeadlessImportDirectory("dir1/innerDir2");
-	 * 
- * In contrast, the following usages would add new folders to the Ghidra project and save - * the imported files into the newly-created path: - *
-	 * 		setHeadlessImportDirectory("innerDir2/my/folder");
-	 * 
- * changes the directory structure to: - *
-	 * 		MyGhidraProject:
-	 * 		  /dir1
-	 * 		    /innerDir1
-	 * 		    /innerDir2
-	 * 		      /my
-	 * 		        /folder
-	 * 
- * and: - *
-	 * 		setHeadlessImportDirectory("newDir/saveHere");
-	 * 
- * changes the directory structure to: - *
-	 * 		MyGhidraProject:
-	 * 		  /dir1
-	 * 		    /innerDir1
-	 * 			/innerDir2
-	 *		  /newDir
-	 * 		    /saveHere
-	 * 
- * As in the examples above, if the desired folder does not already exist, it is created. - *

- * A change in the import save folder will persist throughout the current HeadlessAnalyzer - * session, unless changed again (in other words, once the import directory has been changed, - * it will remain the 'save' directory for import files in the current session, unless changed). - *

- * To revert back to the default import location (that which was specified via command line), - * pass the null object as the argument to this method, as below: - *

-	 * 		setHeadlessImportDirectory(null);	// Sets import save directory to default
-	 * 
- * If a file with the same name already exists in the desired location, it will only be - * overwritten if "-overwrite" is true. - *

- * This method is only applicable when using the HeadlessAnalyzer -import mode and - * is ineffective in -process mode. - * - * @param importDir the absolute path (relative to root) where inputs will be saved - * @throws ImproperUseException if not in headless mode or headless instance not set - * @throws IOException if there are issues creating the folder - * @throws InvalidNameException if folder name is invalid - */ - public void setHeadlessImportDirectory(String importDir) - throws ImproperUseException, IOException, InvalidNameException { - checkHeadlessStatus(); - - // Do nothing if not importing -- we don't want to have arbitrary folders - // created when not being used! - - if (!headless.getOptions().runScriptsNoImport) { - DomainFolder saveFolder = null; - - if (importDir != null) { - - if (!importDir.startsWith("/")) { - importDir = "/" + importDir; - } - - // Add ending slash so the dir gets created for server projects - if (!importDir.endsWith("/")) { - importDir += "/"; - } - - // Gets folder -- creates path if it doesn't already exist - saveFolder = headless.getDomainFolder(importDir, true); - } - - headless.setSaveFolder(saveFolder); - } - } - - /** - * Returns whether analysis for the current program has timed out. - *

- * Analysis will time out only in the case where: - *

    - *
  1. the users has set an analysis timeout period using the -analysisTimeoutPerFile - * parameter
  2. - *
  3. analysis is enabled and has completed
  4. - *
  5. the current script is being run as a postScript (since postScripts run after - * analysis)
  6. - *
- * - * @return whether analysis timeout occurred - * @throws ImproperUseException if not in headless mode or headless instance not set - */ - public boolean analysisTimeoutOccurred() throws ImproperUseException { - checkHeadlessStatus(); - return headless.checkAnalysisTimedOut(); - } - - @Override - public void runScript(String scriptName, String[] scriptArguments, GhidraState scriptState) - throws Exception { - - boolean isHeadlessScript = false; - - if (scriptSetOption != null) { - resolveContinuationOptionWith(scriptSetOption); - scriptSetOption = null; - } - ResourceFile scriptSource = GhidraScriptUtil.findScriptByName(scriptName); - if (scriptSource != null) { - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource); - - if (provider == null) { - throw new IOException("Attempting to run subscript '" + scriptName + - "': unable to run this script type."); - } - - GhidraScript script = provider.getScriptInstance(scriptSource, writer); - isHeadlessScript = script instanceof LibHeadlessScript ? true : false; - - if (potentialPropertiesFileLocs.size() > 0) { - script.setPotentialPropertiesFileLocations(potentialPropertiesFileLocs); - } - - if (scriptState == state) { - updateStateFromVariables(); - } - - if (isHeadlessScript) { - ((LibHeadlessScript) script).setHeadlessInstance(headless); - ((LibHeadlessScript) script).setRunningInnerScript(true); - } - - script.setScriptArgs(scriptArguments); - - script.execute(scriptState, monitor, writer); - - if (scriptState == state) { - loadVariablesFromState(); - } - - // Resolve continuations options, if they have changed - if (isHeadlessScript) { - LibHeadlessContinuationOption innerScriptOpt = - ((LibHeadlessScript) script).getHeadlessContinuationOption(); - - if (innerScriptOpt != null) { - resolveContinuationOptionWith(innerScriptOpt); - } - - ((LibHeadlessScript) script).setRunningInnerScript(false); - } - - return; - } - - throw new IllegalArgumentException("Script does not exist: " + scriptName); - } - - @Override - public void cleanup(boolean success) { - resolveContinuationOption(); - - if (!runningInnerScript) { - scriptSetOption = null; - } - } - - private void resolveContinuationOption() { - resolveContinuationOptionWith(scriptSetOption); - } - - /** - * Resolve continuation options according to the table in 'analyzeHeadlessREADME.html'. - * (See "Multiple Scripts" section). - * - * @param opt continuation option to combine with current continuation option - */ - private void resolveContinuationOptionWith(LibHeadlessContinuationOption opt) { - - if (opt == null) { - return; - } - - switch (currentOption) { - - case CONTINUE: - currentOption = opt; - break; - - case CONTINUE_THEN_DELETE: - switch (opt) { - case ABORT: - - case ABORT_AND_DELETE: - currentOption = LibHeadlessContinuationOption.ABORT_AND_DELETE; - break; - - default: - break; - } - break; - - case ABORT_AND_DELETE: - // nothing changes - break; - - case ABORT: - // nothing changes - break; - } - } -} diff --git a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java b/Sample2/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java deleted file mode 100644 index 42e2fad..0000000 --- a/Sample2/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java +++ /dev/null @@ -1,147 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - -package com.nosecurecode.libghidra; - -import java.util.Timer; -import java.util.TimerTask; - -import ghidra.util.exception.CancelledException; -import ghidra.util.task.CancelledListener; -import ghidra.util.task.TaskMonitor; - -/** - * Monitor used by Headless Analyzer for "timeout" functionality - */ -public class LibHeadlessTimedTaskMonitor implements TaskMonitor { - - private Timer timer = new Timer(); - private volatile boolean isCancelled; - - LibHeadlessTimedTaskMonitor(int timeoutSecs) { - isCancelled = false; - timer.schedule(new TimeOutTask(), timeoutSecs * 1000); - } - - private class TimeOutTask extends TimerTask { - @Override - public void run() { - LibHeadlessTimedTaskMonitor.this.cancel(); - } - } - - @Override - public boolean isCancelled() { - return isCancelled; - } - - @Override - public void setShowProgressValue(boolean showProgressValue) { - // stub - } - - @Override - public void setMessage(String message) { - // stub - } - - @Override - public String getMessage() { - return null; - } - - @Override - public void setProgress(long value) { - // stub - } - - @Override - public void initialize(long max) { - // stub - } - - @Override - public void setMaximum(long max) { - // stub - } - - @Override - public long getMaximum() { - return 0; - } - - @Override - public void setIndeterminate(boolean indeterminate) { - // stub - } - - @Override - public boolean isIndeterminate() { - return false; - } - - @Override - public void checkCanceled() throws CancelledException { - if (isCancelled()) { - throw new CancelledException(); - } - } - - @Override - public void incrementProgress(long incrementAmount) { - // stub - } - - @Override - public long getProgress() { - return 0; - } - - @Override - public void cancel() { - timer.cancel(); // Terminate the timer thread - isCancelled = true; - } - - @Override - public void addCancelledListener(CancelledListener listener) { - // stub - } - - @Override - public void removeCancelledListener(CancelledListener listener) { - // stub - } - - @Override - public void setCancelEnabled(boolean enable) { - // stub - } - - @Override - public boolean isCancelEnabled() { - return true; - } - - @Override - public void clearCanceled() { - isCancelled = false; - } -} diff --git a/Sample2/src/com/nosecurecode/libghidra/LibProgramHandler.java b/Sample2/src/com/nosecurecode/libghidra/LibProgramHandler.java deleted file mode 100644 index fcae24e..0000000 --- a/Sample2/src/com/nosecurecode/libghidra/LibProgramHandler.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - This interface implements the callback functionality, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com -*/ - - -package com.nosecurecode.libghidra; - -import ghidra.program.model.listing.Program; - -/** - * Implement this interface in the class that need to have a callback after Ghidra processing - */ -public interface LibProgramHandler { - public void PostProcessHandler(Program program); -} From 87cfe00a11e3c0c53045ade640207606d8f692d8 Mon Sep 17 00:00:00 2001 From: Jiayang Li Date: Wed, 16 Jul 2025 01:47:21 -0400 Subject: [PATCH 2/2] Updated to Ghidra 11.4 --- GhidraLib/LibGhidra.java | 7 ++-- GhidraLib/LibHeadlessAnalyzer.java | 51 ++++++++++++++---------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/GhidraLib/LibGhidra.java b/GhidraLib/LibGhidra.java index 6fa6efa..5450f82 100644 --- a/GhidraLib/LibGhidra.java +++ b/GhidraLib/LibGhidra.java @@ -22,7 +22,8 @@ import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.*; @@ -88,9 +89,9 @@ private LibGhidra(String args[], LibProgramHandler handler) throws Exception { if (args[0].startsWith("ghidra:")) { optionStartIndex = 1; try { - ghidraURL = new URL(args[0]); + ghidraURL = new URI(args[0]).toURL(); } - catch (MalformedURLException e) { + catch (URISyntaxException e) { System.err.println("Invalid Ghidra URL: " + args[0]); usage(); } diff --git a/GhidraLib/LibHeadlessAnalyzer.java b/GhidraLib/LibHeadlessAnalyzer.java index 4c638cd..8e0ecf0 100644 --- a/GhidraLib/LibHeadlessAnalyzer.java +++ b/GhidraLib/LibHeadlessAnalyzer.java @@ -89,7 +89,7 @@ public class LibHeadlessAnalyzer { * Gets a headless analyzer, initializing the application if necessary with the specified * logging parameters. An {@link IllegalStateException} will be thrown if the application has * already been initialized or a headless analyzer has already been retrieved. In these cases, - * the headless analyzer should be gotten with {@link LibHeadlessAnalyzer#getInstance()}. + * the headless analyzer should be gotten with {@link LibHeadlessAnalyzer#getInstance(LibProgramHandler)}. * * @param logFile The desired application log file. If null, no application logging will take place. * @param scriptLogFile The desired scripting log file. If null, no script logging will take place. @@ -214,9 +214,6 @@ private LibHeadlessAnalyzer() { System.setProperty("java.awt.headless", "true"); System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.TRUE.toString()); - // Allows handling of old content which did not have a content type property - DomainObjectAdapter.setDefaultContentClass(ProgramDB.class); - // Put analyzer in its default state reset(); } @@ -257,10 +254,10 @@ public LibHeadlessOptions getOptions() { * @param filesToImport directories and files to be imported (null or empty * is acceptable if we are in -process mode) * @throws IOException if there was an IO-related problem - * @throws MalformedURLException specified URL is invalid + * @throws URISyntaxException specified URL is invalid */ public void processURL(URL ghidraURL, List filesToImport) - throws IOException, MalformedURLException { + throws IOException, URISyntaxException { if (options.readOnly && options.commit) { Msg.error(this, @@ -270,7 +267,7 @@ public void processURL(URL ghidraURL, List filesToImport) } if (!"ghidra".equals(ghidraURL.getProtocol())) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); + throw new URISyntaxException(ghidraURL.toString(), "Unsupported repository URL"); } if (GhidraURL.isLocalProjectURL(ghidraURL)) { @@ -281,12 +278,12 @@ public void processURL(URL ghidraURL, List filesToImport) String path = ghidraURL.getPath(); if (path == null) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); + throw new URISyntaxException(ghidraURL.toString(), "Unsupported repository URL"); } path = path.trim(); if (path.length() == 0) { - throw new MalformedURLException("Unsupported repository URL: " + ghidraURL); + throw new URISyntaxException(ghidraURL.toString(), "Unsupported repository URL"); } if (!options.runScriptsNoImport) { // Running in -import mode @@ -298,13 +295,13 @@ public void processURL(URL ghidraURL, List filesToImport) if (!path.endsWith("/")) { // force explicit folder path so that non-existent folders are created on import - ghidraURL = new URL("ghidra", ghidraURL.getHost(), ghidraURL.getPort(), path + "/"); + ghidraURL = new URI("ghidra", null, ghidraURL.getHost(), ghidraURL.getPort(), path + "/", null, null).toURL(); } } else { // Running in -process mode if (path.endsWith("/") && path.length() > 1) { - ghidraURL = new URL("ghidra", ghidraURL.getHost(), ghidraURL.getPort(), - path.substring(0, path.length() - 1)); + ghidraURL = new URI("ghidra", null, ghidraURL.getHost(), ghidraURL.getPort(), + path.substring(0, path.length() - 1), null, null).toURL(); } } @@ -327,7 +324,7 @@ public void processURL(URL ghidraURL, List filesToImport) Object obj = c.getContent(); if (!(obj instanceof GhidraURLWrappedContent)) { throw new IOException( - "Connect to repository folder failed. Response code: " + c.getResponseCode()); + "Connect to repository folder failed. Status code: " + c.getStatusCode()); } GhidraURLWrappedContent wrappedContent = (GhidraURLWrappedContent) obj; Object content = null; @@ -351,9 +348,6 @@ public void processURL(URL ghidraURL, List filesToImport) processWithImport(folder.getPathname(), filesToImport); } } - catch (NotFoundException e) { - throw new IOException("Connect to repository folder failed"); - } finally { if (content != null) { wrappedContent.release(content, this); @@ -1031,7 +1025,8 @@ private boolean analyzeProgram(String fileAbsolutePath, Program program) { mgr.startAnalysis(TaskMonitor.DUMMY); // kick start Msg.info(this, "REPORT: Analysis succeeded for file: " + fileAbsolutePath); - GhidraProgramUtilities.setAnalyzedFlag(program, true); + + GhidraProgramUtilities.markProgramAnalyzed(program); } else { LibHeadlessTimedTaskMonitor timerMonitor = @@ -1054,7 +1049,7 @@ private boolean analyzeProgram(String fileAbsolutePath, Program program) { timerMonitor.cancel(); Msg.info(this, "REPORT: Analysis succeeded for file: " + fileAbsolutePath); - GhidraProgramUtilities.setAnalyzedFlag(program, true); + GhidraProgramUtilities.markProgramAnalyzed(program); } } } @@ -1486,7 +1481,7 @@ public String getComment() throws CancelledException { public boolean createKeepFile() throws CancelledException { return false; } - }, true, TaskMonitor.DUMMY); + }, TaskMonitor.DUMMY); Msg.info(this, "REPORT: Committed file changes to repository: " + df.getPathname()); } catch (IOException e) { @@ -1657,24 +1652,24 @@ private Program loadProgram(File file) throws VersionException, InvalidNameExcep if (options.loaderClass == null) { // User did not specify a loader if (options.language == null) { - program = AutoImporter.importByUsingBestGuess(file, null, this, messageLog, - TaskMonitor.DUMMY); + program = AutoImporter.importByUsingBestGuess(file, null, null, this, messageLog, + TaskMonitor.DUMMY).getPrimaryDomainObject(); } else { - program = AutoImporter.importByLookingForLcs(file, null, options.language, - options.compilerSpec, this, messageLog, TaskMonitor.DUMMY); + program = AutoImporter.importByLookingForLcs(file, null, null, options.language, + options.compilerSpec, this, messageLog, TaskMonitor.DUMMY).getPrimaryDomainObject(); } } else { // User specified a loader if (options.language == null) { - program = AutoImporter.importByUsingSpecificLoaderClass(file, null, - options.loaderClass, options.loaderArgs, this, messageLog, TaskMonitor.DUMMY); + program = AutoImporter.importByUsingSpecificLoaderClass(file, null, null, + options.loaderClass, options.loaderArgs, this, messageLog, TaskMonitor.DUMMY).getPrimaryDomainObject(); } else { - program = AutoImporter.importByUsingSpecificLoaderClassAndLcs(file, null, + program = AutoImporter.importByUsingSpecificLoaderClassAndLcs(file, null, null, options.loaderClass, options.loaderArgs, options.language, options.compilerSpec, - this, messageLog, TaskMonitor.DUMMY); + this, messageLog, TaskMonitor.DUMMY).getPrimaryDomainObject(); } } @@ -1866,7 +1861,7 @@ private static class HeadlessProject extends DefaultProject { HeadlessProject(HeadlessGhidraProjectManager projectManager, GhidraURLConnection connection) throws IOException { - super(projectManager, connection); + super(projectManager, (DefaultProjectData) connection.getProjectData()); } HeadlessProject(HeadlessGhidraProjectManager projectManager, ProjectLocator projectLocator)