Headless Analyzer README

Table of Contents

Headless Analyzer Introduction and Guidelines

The Headless Analyzer is a command-line-based (non-GUI) version of Ghidra that allows users to:

The Headless Analyzer can be useful when performing repetitive tasks on a project (i.e., importing and analyzing a directory of files or running a script over all the binaries in a project).

Users initiate Headless operation using the analyzeHeadless shell script. The shell script takes, at a minimum, the path and name of an existing project (or one to be created). When other parameters are specified, the following types of actions may be performed: While running, be aware that:

(Back to Top)

Headless Analyzer Usage

The Headless Analyzer uses the command-line parameters discussed below. See Examples for common use cases.

    analyzeHeadless <project_location> <project_name>[/<folder_path>] | ghidra://<server>[:<port>]/<repository_name>[/<folder_path>]
        [[-import [<directory>|<file>]+] | [-process [<project_file>]]]
        [-preScript <ScriptName> [<arg>]*]
        [-postScript <ScriptName> [<arg>]*]
        [-scriptPath "<path1>[;<path2>...]"]
        [-propertiesPath "<path1>[;<path2>...]"]
        [-scriptlog <path to script log file>]
        [-log <path to log file>]
        [-overwrite]
        [-recursive]
        [-readOnly]
        [-deleteProject]
        [-noanalysis]
        [-processor <languageID>]
        [-cspec <compilerSpecID>]
        [-analysisTimeoutPerFile <timeout in seconds>]
        [-keystore <KeystorePath>]
        [-connect [<userID>]]
        [-p]
        [-commit ["<comment>"]]
        [-okToDelete]
        [-max-cpu <max cpu cores to use>]
        [-loader <desired loader name>]

(Back to Top)

Headless Analyzer Examples

(Back to Top)

Writing Scripts for the Headless Analyzer

Many scripts that extend the GhidraScript class, and written for use with the headed (GUI) version of Ghidra, can also be used during Headless operation. However, there are certain GUI-specific methods that do not make sense when called during Headless operation. When a GhidraScript containing one or more GUI-specific methods is run headlessly, the script will throw an ImproperUseException.

A script that extends the HeadlessScript class may be used to write scripts that refer to Headless-only methods. See the HeadlessScripts section for more detail.

Here are some general guidelines for running scripts headlessly.

(Back to Top)

Passing Parameters using arguments

As of Ghidra 7.2, it is possible to pass script-specific arguments directly to scripts. The arguments are stored in a String array and can be accessed with the following method:

String[] args = getScriptArgs();
If running in headless mode, this array will contain the ordered list of arguments passed to the script on the command line (specified with -preScript or -postScript).
For example, if a script was run with the following command:
analyzeHeadless /Users/user/ghidra/projects MyProject -import hello.exe -preScript Script.java arg1 arg2 arg3 -preScript AnotherScript.java "arg1 with spaces" arg2
Then the elements of the argument array for Script.java would look like this:
args = {"arg1", "arg2", "arg3"}
and the argument array for AnotherScript.java would look like this:
args = {"arg1 with spaces", "arg2"}

Passing Parameters using askXxx() methods

Many of the GhidraScript askXxx() methods can be run in both headless and headed (GUI) modes, allowing seamless script usage between headed and headless modes. As of Ghidra 6.1, the following methods can be run in both modes:

Further details for each specific askXxx() method can be found in the method's JavaDoc.

When running headlessly, the askXxx() methods allow users to to "pre-set" or "pass in" one or more values for use in scripts. Use the appropriate method to pass in values of certain types (i.e., file, directory, int, long).

To pass a value to a script, create a .properties file corresponding to each GhidraScript that uses an askXxx() method. For example, the .properties file that corresponds to a script named MyScript.java would share the script's basename and be called MyScript.properties. By default, the Headless Analyzer assumes that the script and its .properties file are both located in the same folder. If you would like the .properties file to be in a different location from the script, you can use the propertiesPath parameter to specify the location of the .properties file. Below is an example of a GhidraScript and its .properties file. Use it for reference to determine how the .properties file should be structured to communicate the necessary information to the GhidraScript:

Script1.java
public class Script1 extends GhidraScript {
 
    @Override
    public void run() throws Exception {
  	
        File userFile = askFile("Choose a file ", "Please choose a file: ");
        println("Chosen file: " + userFile.toString());

        double userDouble = askDouble("Double dialog", "Please enter a double: ");
        println("Entered double: " + userDouble);
	
        double userDouble2 = askDouble("Double dialog", "Please enter another double: ");
        println("Second entered double: " + userDouble2);

        Address userAddress = askAddress("Address", "Enter an address!");
        println("Entered address: " + userAddress.toString());

        byte[] userBytes = askBytes("Asking for bytes", "Put some bytes here --");
        StringBuilder byteStr = new StringBuilder();
        for (byte aByte : askedBytes) {
            byteStr.append(String.format("%02X ", aByte));
        }
        println("Bytes: " + byteStr.toString().trim());
	
        String userString = askString("Asking for a string", "Please type a string: ", "my default String");
        println("Entered String: " + userString);
   
    }
}
Script1.properties
	  
    # A comment line is indicated if the '#' or '!' character is the first non-whitespace character of that line.
    # 
    # Use a space-separated concatenation of the parameters to communicate which variable gets what value:
    #    Format:    <space-separated concatenation of parameters> = <value>
    #
    # Notice that spaces at the beginning and end of parameters are removed prior to concatenation.
    #
    # Note that if the askXxx() method contains a "defaultValue" parameter, that parameter should not be included
    # in the concatenation of parameters.

    Choose a file Please choose a file: = /Users/username/help.exe
    Double dialog Please enter a double: = 32.2
    Address Enter an address! = 0x10AB34D
    Double dialog Please enter another double: = 3.14159 
    Asking for bytes Put some bytes here -- = AA BB CC 11 02 24
    Asking for a string Please type a string: = STRING ABC

NOTE: If script-specific arguments have been passed into the script, the askXxx() methods will consume values found in the argument array rather than a .properties file. The first askXxx() method will use the first value in the array, the second askXxx() method will use the second value in the array, and so on. If all of the arguments in the array have been consumed, the next askXxx() will throw an IndexOutOfBoundsException exception.

(Back to Top)

HeadlessScripts

A script of type HeadlessScript (which extends GhidraScript) can be used by any user looking for more control over the Headless Analysis process than is offered using the more generic GhidraScript class. Using HeadlessScripts, users are able to store variables for use by later scripts, change the location of where an import will be saved, and change the disposition of a program depending on script-specific conditions (i.e., save it in a different folder, delete it, turn off analysis, abort further processing, etc.).

HeadlessScripts allow the user to access certain methods that are specific to the HeadlessAnalyzer. Otherwise, these types of scripts operate exactly like GhidraScripts. Users should only use HeadlessScript for headless operation. While HeadlessScripts could possibly run successfully in the Ghidra GUI, an exception will be thrown if a HeadlessScript-only method is called during GUI operation.


(Back to Top)

Headless Scripts: Enabling/Disabling Analysis

In order to enable or disable analysis using a HeadlessScript, simply include the following line in your script:

    enableHeadlessAnalysis(true);  // turn on analysis

             OR

    enableHeadlessAnalysis(false);  // turn off analysis

Note that a script that includes this line should be 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).

NOTE: To check whether analysis is currently enabled, use the following method:

    boolean analysisEnabled = isHeadlessAnalysisEnabled();

(Back to Top)

Headless Scripts: Setting the Import Directory

When using -import mode, a user can change the path in the Ghidra project where imported files are saved. This is done by using the following script method:

    setHeadlessImportDirectory("path/to/new/dir");
The new path does not have to exist (it will be created if it doesn't already exist). The path is also assumed to be relative to the project's root folder.

Here are some examples assuming the Ghidra project structure looks like this:

	  
    MyGhidraProject:
        /dir1
            /innerDir1
            /innerDir2

When using this method to set the save directory for imports, whether the save succeeds may depend on the state of the -overwrite parameter. For example, if the new import location already exists and contains a file of the same name as the current program, the current program will only be successfully saved if -overwrite is enabled.

This change in import directory will persist throughout the current HeadlessAnalyzer session, unless changed again (in other words, once the import location has been changed, it will continue to be the import save location for future imported programs in the current session, unless changed again).

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:

    setHeadlessImportDirectory(null);    // Sets import save directory to default
The setHeadlessImportDirectory method is ineffective in -process mode (the program will not be saved to a different location if this method is called when running in -process mode).

(Back to Top)

Headless Scripts: Checking for Analysis Timeout

In the case where all of the following apply:

the user can check whether analysis timed out, using the following query method:
    boolean didTimeout = analysisTimeoutOccurred();

(Back to Top)

Headless Scripts: Passing Values Between Scripts

If you are running multiple scripts in headless operation and would like to store a value in one script that is accessible by another script, use the HeadlessScript methods below. They facilitate the storage and retrieval of key-value pairs to/from a data structure that is available to any script of type HeadlessScript:

    storeHeadlessValue(String key, Object value);
    Object myObject = getStoredHeadlessValue(String key);
    boolean containsKey = headlessStorageContainsKey(String key);
(Back to Top)

Headless Scripts: Using Scripts to Control Program Disposition

HeadlessScripts can be used to control disposition of the program currently being imported/processed (note: if running in -process mode with -readOnly enabled, programs can not be deleted, even if directed by a script).

The available options to control program disposition are as follows:

To set the program disposition, use the setHeadlessContinuationOption method. For example, to dictate that further processing be aborted and the program deleted, the script should use the following method with the ABORT_AND_DELETE option:
    setHeadlessContinuationOption(HeadlessContinuationOption.ABORT_AND_DELETE);
At the start of processing for each program (immediately before the first script runs), the script's continuation option is set to CONTINUE by default. If the setHeadlessContinationOption method is not used, then operation continues as normal.

Note that when an option is set, it takes effect AFTER the current script completes. For example, setting the continuation option to ABORT does not immediately abort the current script; instead, it aborts any processing (analysis, other scripts) that immediately follow the current script.

In the case where a subscript or secondary script sets an ABORT or ABORT_AND_DELETE option, that option will go into effect once the primary (or outermost) script has completed execution.

For a very basic example script, see SetHeadlessContinuationOptionScript.java, which is included in the Ghidra distribution.

When multiple scripts set program disposition, they are combined. Continue on to Using Multiple Scripts to Control Program Disposition to understand how this works.

(Back to Top)

Headless Scripts: Using Multiple Scripts to Control Program Disposition

While running scripts that change the program disposition, there may be instances when the program disposition is changed more than once for the same program. Some cases where this could happen are: If there are multiple calls to setHeadlessContinuationOption within a single script, the last method call is used as the setting dictated by that script.

However, if multiple scripts make calls to setHeadlessContinuationOption, the options from each script are combined in a rational way (in the order the options were set) to potentially result in a new continuation option.

For example, if Script1.java sets the continuation option, then is followed by Script2.java which also sets the continuation option, the resulting continuation status is shown in the following diagram:

Script1 Continuation Option Script2 Continuation Option
ABORT ABORT_AND_DELETE CONTINUE_THEN_DELETE CONTINUE
ABORT ABORT* ABORT* ABORT* ABORT*
ABORT_AND_DELETE ABORT_AND_DELETE* ABORT_AND_DELETE* ABORT_AND_DELETE* ABORT_AND_DELETE*
CONTINUE_THEN_DELETE ABORT_AND_DELETE ABORT_AND_DELETE CONTINUE_THEN_DELETE CONTINUE_THEN_DELETE
CONTINUE ABORT ABORT_AND_DELETE CONTINUE_THEN_DELETE CONTINUE

* In cases where Script1 specifies ABORT or ABORT_AND_DELETE, Script2 will not run unless Script2 is a subscript or secondary script called by Script1.

Keep in mind:

(Back to Top)

Wildcards: Specifying Files to Import or Process

Wildcards can be used when specifying files and/or directories for -import mode, or when specifying one or more files for -process mode. Wildcards in -import mode are expanded by the underlying system shell before being passed on to headless Ghidra (consequently, any wildcard limitations will be dictated by the specific operating system you are using). Wildcards in -process mode are expanded by headless Ghidra and are limited to the use of '*' and '?' only.

Note that wildcarding is NOT supported for specifying the Ghidra project/repository location or folder path.

Below are some general guidelines for wildcard usage:


(Back to Top)
Last modified: Aug 31 2017