55 KiB
Headless Analyzer README
Table of Contents
- Introduction
- Usage
- Examples
- Scripting
- Wildcards
Introduction
The Headless Analyzer is a command-line-based (non-GUI) version of Ghidra that allows users to:
- Create and populate projects
- Perform analysis on imported or existing binaries
- Run non-GUI scripts in a project (scripts may be program-dependent or program-independent)
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:
- Import a single file or directory of executable(s) (recursively or non-recursively).
- Process a single file or directory of executable(s) already present in an existing project (recursively or non-recursively).
- Run any number of non-GUI Ghidra pre-processing scripts on each executable.
- Turn analysis on or off for each executable.
- Run any number of non-GUI Ghidra post-processing scripts on each executable.
- Write to a log with information about each file processed. Separated logging is available for scripts.
- Keep or delete a created project.
- Save any changes made to the project/file, or operate in a read-only manner in
-import
or-process
modes. - Use pre- and/or post-processing scripts to dictate program disposition. For example, scripts can dictate whether further processing (i.e., analysis or other scripts) should be aborted and whether the current file should be deleted after all processing is complete.
While running, be aware that:
- The Headless Analyzer may not run if the specified project is already open in Ghidra.
- In bulk import mode (i.e., specifying a directory,
-import dirOfExes
, or wildcard string,-import dir1/*
), any file beginning with the character.
is assumed to be a hidden file and ignored by default. However, when a file beginning with.
is named during import (for example,import /Users/user/.hidden.exe
), the Headless Analyzer will attempt to import it. - Log files can only be redirected if Log4J is used.
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] [-overwrite] [-recursive [<depth>]] [-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>] [-librarySearchPaths <path1>[;<path2>...]] [-loader <desired loader name>] [-loader-<loader argument name> <loader argument value>]
<project_location>
The directory that either contains an existing Ghidra project (in -import
or
-process
mode) or will contain a newly created project (in -import
mode for
a local project).
You must specify either a project location and project name, or a Ghidra Server repository URL.
Some parameters will have no effect, depending on which project_location
is specified. The
following table shows parameters that are specific to project_location
:
Parameter | Local Project | Server Repository |
---|---|---|
-p | X | |
-connect | X | |
-keystore | X | |
-commit | X | |
-delete | X |
<project_name>[/<folder_path>]
The name of either an existing project (in -import
or -process
mode) or new
project (in -import
mode) to be created in the above directory. If the optional folder
path is included, imports will be rooted under this project folder. In -import
mode with
-recursive
enabled, any folders in the folder path that do not already exist in the
project will be created (even if nested).
You must specify either a project location and project name, or a Ghidra Server repository URL.
ghidra://<server>[:<port>]/<repository_name>[/<folder_path>]
A Ghidra Server repository URL (shared Ghidra Server project) and folder path. Using the repository
URL eliminates the need for a local shared Ghidra project; however, the named repository must
already exist on the Ghidra Server. If the specified repository does not already exist, it will not
be created (see the GhidraProject
class for a simple API that allows shared project creation from
within a script).
If the optional folder path is included, imports will be rooted under this folder (in
-import
mode, folders will be created if they do not already exist).
-import [<directory>|<file>]+
Note: -import
and -process
can not both be present in the parameters list.
Specifies one or more executables (or directories of executables) to import. When importing a
directory or supported container format, a folder with the same name will be created in the Ghidra
project. When using the -recursive
parameter, each executable that is found in a
recursive search through the given directory or container file will be stored in the project in the
same relative location (i.e., any directories found under the import directory will also be created
in the project).
Operating system-specific wildcard characters can be used when importing files and/or directories. Please see the Wildcards section for more details.
When importing multiple executables/directories in the same session, use one of the following methods:
- List multiple directories and/or executables after the
-import
option, separated by a space.-import /Users/myDir/peFiles /Users/myDir/otherFiles/test.exe
- Repeat the
-import
option multiple times (each use of-import
may be separated by other parameters) to import from more than one directory or file source.-import /Users/myDir/peFiles -recursive -import /Users/myDir/otherFiles/test.exe
-process [<project_file>]
Note: -import
and -process
can not both be present in the parameters list.
Performs processing (running pre/post-scripts and/or analysis) on one or more program files
that already exist in the project or repository. Use the optional project_file
argument to specify
an existing file by name. Searching will be performed within the specified project folder
(specified by folder_path
, which was included with the project_name or
repository URL specification). Omit the project_file
argument to allow processing
over all files within the project folder.
You can also use the wildcard characters *
and ?
in the project_file
parameter to specify all
files within a folder which match the pattern. To prevent premature expansion (by the shell) of any
wildcard characters, use single quotes around the project_file
. For example:
-process '*.exe'
For further details on wildcard usage, please see the Wildcards section below.
Omitting the optional project_file
argument will cause all files to be processed within the
project folder (equivelent to *
).
Including the -recursive
parameter will cause the same project file name/pattern
search to be performed recursively within all sub-folders.
Unlike the -import
option, -process
may only be specified once.
-preScript <ScriptName.ext> [<arg>]*
Identifies the name of a script that will execute before analysis, and an optional list of arguments to pass to the script. The script name must include its file extension (i.e., MyScript.java).
This parameter expects the script name only; do not include the path to the script. The
Headless Analyzer searches specific default locations for the named script, but additional script
director(ies) may also be specified (see the -scriptPath
argument for more
information).
This option must be repeated to specify additional scripts. See the Scripting section for a description of advanced scripting capabilities.
-postScript <ScriptName.ext> [<arg>]*
Identifies the name of a script that will execute after analysis, and an optional list of arguments to pass to the script. The script name must include its file extension (i.e., MyScript.java).
This parameter expects the script name only; do not include the path to the script. The
Headless Analyzer searches specific default locations for the named script, but additional script
director(ies) may also be specified (see the -scriptPath
argument for more
information).
This option must be repeated to specify additional scripts. See the Scripting section for a description of advanced scripting capabilities.
-scriptPath "<path1>[;<path2>...]"
Specifies the search path(s) for scripts, including secondary scripts (a script invoked from
another script). A path may start with $GHIDRA_HOME
, which corresponds to the Ghidra installation
directory, or $USER_HOME
, which corresponds to the user's home directory. On Unix systems, these
home variables must be escaped using a \
(backslash) character.
Examples:
- Windows:
-scriptPath "$GHIDRA_HOME/Ghidra/Features/Base/ghidra_scripts;/myscripts"
- Unix:
-scriptPath "\$GHIDRA_HOME/Ghidra/Features/Base/ghidra_scripts;/myscripts"
The scriptPath
parameter is optional. If it is not present, the Headless Analyzer will search the
following paths for the specified script(s):
$USER_HOME/ghidra_scripts
- All
ghidra_script
subdirectories that exist in the Ghidra distribution
-propertiesPath "<path1>[;<path2>...]"
Specifies path(s) that contain .properties files used by scripts or secondary/subscripts. A path
may start with $GHIDRA_HOME
, which corresponds to the Ghidra installation directory, or
$USER_HOME
, which corresponds to the user's home directory. On Unix systems these home variables
must be escaped with a ;
character.
More information on the use of .properties files to pass parameters during Headless Analysis can be found here.
-scriptlog <path to script log file>
Sets the location of the file that stores logging information from pre- and post-scripts. If a
path to a script log file is not set, script logs are written to script.log
in the user directory,
by default.
Note: Only the built-in scripting print methods will print to the script log file (print
,
println
, printf
, printerr
).
Also note that in Python scripts, print
writes to stdout
. To write to the log from Python, use
println
instead.
-log <path to log file>
Sets the location of the file that stores logging information from analysis or other non-script
processing of the files. If a path to a log file is not set, logging information is written to
application.log
in the user directory, by default.
-overwrite
Applies to -import mode only and is ignored if the -readOnly
option is
present. If present, an existing project file that conflicts with an import file is overwritten.
If this parameter is not included, import files that conflict with existing project files will be
skipped (if not operating with the -readOnly
option). If a conflicting file is
contained within a version repository, and the -commit
option has not been specified,
the overwrite will fail. Removing a versioned file is also subject to other permission and in-use
restrictions which could also cause an overwrite failure.
-mirror
Applies to -import mode only. If present, the absolute filesystem path of each imported file is mirrored in the project, rooted at the specified folder path.
-recursive [<depth>]
If present, enables recursive descent into directories and project sub-folders when a directory/
folder has been specified in -import
or -process
modes.
Specifying a positive integer value for the optional <depth>
argument enables recursive descent
into supported container files (e.g., zip, tar, .a, etc). The depth value only applies to nested
container files. Intermediate directories found within each nested container file are not affected
by the specified depth value. If a depth value is not specified, it will default to 0 if importing
a directory, and 1 if importing a file. A depth of 0 will prevent recursing into any container
files.
-readOnly
If present in -import
mode, imported files will NOT be saved to the project. If present
in -process
mode, any changes made to existing files by scripts or analysis are
discarded. When processing a shared project or URL associated with a read-only repository, such
files will be skipped unless this option is specified. The -overwrite
option will be
ignored if this option is specified during import operations.
-deleteProject
If present, the Ghidra project will be deleted after scripts and/or analysis have completed
(only applies if the project has been created in the current session with
-import
; existing projects are never deleted). This project delete option is assumed
when the -readOnly
option is specified for import operations which create a new
project.
-noanalysis
If present, executables will not be analyzed (auto-analysis occurs by default).
-processor <languageID>
Specifies the processor information to be used in -import
mode (and subsequent analysis,
if analysis is enabled). Be sure to use quotes around the languageId
if it contains spaces. If
this parameter is not present, Ghidra uses header info (if available) to identify the processor.
The possible languageIDs can be found in the processor-specific .ldefs files (found here:
ghidra_x.x\Ghidra\Processors\proc_name\data\languages\*.ldefs
) in the id
attribute of the
language
element. The specified <languageID>
should match exactly, including case, as it appears
in the .ldefs file.
For example:
<language processor="x86"
endian="little"
size="32"
variant="default"
version="2.6"
slafile="x86.sla"
processorspec="x86.pspec"
manualindexfile="../manuals/x86.idx"
id="x86:LE:32:default">
Note: The -processor
parameter may be used without specifying the -cspec
parameter (if the given processor is valid, the Headless Analyzer chooses the default compiler
specification for that processor).
-cspec <compilerSpecID>
Specifies the compiler specification to be used in -import
mode (and subsequent
analysis, if analysis is enabled).
The possible compilerSpecIDs can be found in the processor-specific .ldefs files (found here:
ghidra_x.x\Ghidra\Processors\proc_name\data\languages\*.ldefs
) in the id
attribute of the
appropriate compiler
element. The specified <compilerSpecID>
should match exactly, including
case, as it appears in the .ldefs file.
For example:
<compiler name="Visual Studio" spec="x86win.cspec" id="windows"/>
<compiler name="gcc" spec="x86gcc.cspec" id="gcc"/>
<compiler name="Borland C++" spec="x86borland.cspec" id="borlandcpp"/>
Note: The -cspec
parameter may not be used without specifying the
-processor
parameter.
-analysisTimeoutPerFile <timeout in seconds>
Sets a timeout value (in seconds) for analysis. If analysis on a file exceeds the specified time,
analysis is interrupted and processing continues as scheduled (i.e., to the
-postScript
stage, if specified). Results from individual analyzers that have
completed processing prior to timeout will still be saved with the program. Post-scripts can be used
to detect that analysis has timed out (in Headless processing ONLY) by calling the
getHeadlessAnalysisTimeoutStatus()
method.
-keystore <KeystorePath>
When connecting to a Ghidra Server using PKI or SSH authentication, this option allows specification of a suitable private keystore file. The keystore file should always be properly protected with filesystem protections. Since SSH authentication is intended for batch operations, we do not support password protected SSH keys. However, we do support password prompting for PKI authentication.
See here for more information regarding which authentication method to use.
-connect <userID>
If used, allows the process owner's default userID to be overridden with the given userID
when
connecting to a Ghidra Server. In order to use this parameter, the server must be configured to
allow a non-default username (Ghidra server -u
option).
-p
This option may be specified to allow for interactive password prompting when either a specified PKI keystore is password protected or the Ghidra Server requires password authentication. This option should not be used during batch operations where a user will be unable to enter a password. If the terminal in use is unable to suppress echoing an entered password, a warning will be issued with the prompt, and the entered password will be echoed to the terminal. Use of this option is discouraged when such a warning occurs.
See here for more information regarding which authentication method to use.
-commit ["<comment>"]
When connected to a shared project, enables a commit of changes to the project's underlying
repository (residing on the Ghidra Server). Commits are enabled by default for shared projects;
however, the optional quoted comment
may be specified and will be saved with all commits. Commits
do not apply when the -readOnly parameter is present.
-okToDelete
When using Headless Scripts to control program disposition in -process
mode, it is possible to delete existing programs in a project. These deletions are permanent and can
not be undone (in a versioned project, all versions of a program are deleted). To ensure that
programs are not deleted irretrievably without the user's knowledge, Headless operation requires the
-okToDelete
parameter to be set if a program is to be deleted in
-process
mode. If a program is scheduled to be deleted and -okToDelete
has not been set, Headless will print a warning and the program will not be deleted.
The -okToDelete
parameter is not necessary when running in -import
mode. If a HeadlessScripts schedules deletion of one of the programs being imported, the program
will simply not be saved to the project.
-max-cpu <max cpu cores to use>
Sets the maximum number of CPU cores to use during headless processing (must be an integer).
Setting max-cpu
to 0 or a negative integer is equivalent to setting the maximum number of cores to
1.
-librarySearchPaths <path1>[;<path2>...]
Specifies an ordered list of library search paths to use during import instead of the default. Search paths may be either full system paths or "FSRLs".
-loader <desired loader name>
Forces the file to be imported using a specific loader.
Loaders can take additional arguments that they apply during the import process. Below is a list of the most commonly used loaders and their arguments.
Note: Full java package loader paths are no longer recognized.
-
-loader BinaryLoader
-
-loader ElfLoader
-loader-applyLabels <true|false>
-loader-anchorLabels <true|false>
-loader-linkExistingProjectLibraries <true|false>
-loader-projectLibrarySearchFolder <project path>
-loader-loadLibraries <true|false>
-loader-libraryLoadDepth <depth>
-loader-libraryDestinationFolder <project path>
-loader-applyRelocations <true|false>
-loader-applyUndefinedData <true|false>
-loader-imagebase <imagebase>
3-loader-dataImageBase <dataImageBase>
4-loader-includeOtherBlocks <true|false>
-loader-maxSegmentDiscardSize <0..255> (default: 255)
-
-loader PeLoader
-loader-applyLabels <true|false>
-loader-anchorLabels <true|false>
-loader-linkExistingProjectLibraries <true|false>
-loader-projectLibrarySearchFolder <project path>
-loader-loadLibraries <true|false>
-loader-libraryLoadDepth <depth>
-loader-libraryDestinationFolder <project path>
-loader-ordinalLookup <true|false>
-loader-parseCliHeaders <true|false>
-loader-showDebugLineNumbers <true|false>
-
-loader MachoLoader
-loader-applyLabels <true|false>
-loader-anchorLabels <true|false>
-loader-linkExistingProjectLibraries <true|false>
-loader-projectLibrarySearchFolder <project path>
-loader-loadLibraries <true|false>
-loader-libraryLoadDepth <depth>
-loader-libraryDestinationFolder <project path>
-loader-reexport <true|false>
Authentication
Use this table to figure out which authentication option to use with the Headless Analyzer, based on your Ghidra Server's method of authentication, and the type of analysis operation you are performing.
Type of Operation | SSH Without Password* | SSH With Password | PKI Without Password | PKI With Password | Username/Password |
---|---|---|---|---|---|
Interactive Command Line | -keystore | Not Supported | -keystore | -keystore and -p | -p |
Batch/Script Use | -keystore | Not Supported | -keystore | Not Supported | NotSupported |
*NOTE: The use of OpenSSH keys are not supported. The following command can be used to generate
a suitable SSH key while avoiding the unsupported OpenSSH format: ssh-keygen -b 2048 -t rsa -m pem
Examples
Example 1
Import a binary /binaries/binary1.exe
to a local Ghidra Project named Project1
. Analysis is on
by default.
analyzeHeadless /Users/user/ghidra/projects Project1 -import /binaries/binary1.exe
Example 2
Import all *.exe
binaries from a local folder to a local Ghidra project named Project1
,
suppressing analysis.
analyzeHeadless /Users/user/ghidra/projects Project1 -import /Users/user/sourceFiles/*.exe -noanalysis
Example 3
Import the binary /usr/local/binaries/binaryA.exe
to a subfolder of a local Ghidra Project,
running a prescript, but suppressing analysis.
analyzeHeadless /Users/user/ghidra/projects Project1/folderOne -scriptPath /usr/scripts -preScript RunThisScriptFirst.java -import /usr/local/binaries/binaryA.exe -noanalysis
Example 4
Import the binary /usr/local/binaries/binaryB.exe
to a local Ghidra Project, running a prescript
that depends on a .properties file in the location /propertiesLocation
. Analysis is on by
default.
analyzeHeadless /Users/user/ghidra/projects Project1 -scriptPath /usr/scripts -preScript RunThisScriptFirst.java -propertiesPath /propertiesLocation -import /usr/local/binaries/binaryB.exe
Example 5
Specify more than one import to a local project, running more than one script and performing analysis.
analyzeHeadless /Users/user/ghidra/Projects Project1/folderOne -scriptPath /usr/scripts -preScript RunThisScriptFirst.java -preScript RunThisScriptSecond.java -import /usr/local/binaries/binaryA.exe /user/local/morebinaries -postScript RunThisScriptLast.java
OR
analyzeHeadless /Users/user/ghidra/Projects Project1/folderOne -scriptPath /usr/scripts -preScript RunThisScriptFirst.java -preScript RunThisScriptSecond.java -import /usr/local/binaries/binaryA.exe -postScript RunThisScriptLast.java -import /user/local/morebinaries
Example 6
Run a script on an existing project binary importedBinA.exe
in the folder folderOne
of the
existing project named Project1
.
analyzeHeadless /Users/user/ghidra/Projects Project1/folderOne -scriptPath /user/scripts -postScript FixupScript.java -process importedBinA.exe -noanalysis
Example 7
Recursively run scripts and analysis over all the binaries in the folder folderTwo
of the existing
project named Project2
.
analyzeHeadless /Users/user/ghidra/Projects Project2/folderTwo -scriptPath /user/scripts -preScript FixupPreScript.java -process -recursive
Example 8
Run a script and analysis on binaries starting with the letter a
in the folder aFolder
(and any
of its subfolders) in the existing projected named Project1
.
analyzeHeadless /Users/user/ghidra/Projects Project1/aFolder -scriptPath /user/scripts -preScript ProcessAScript.java -process 'a*' -recursive
Example 9
Recursively import the directory /usr/local/binaries
to a Ghidra Server, running a prescript and
analysis. Commit changes with the specified comment. Server prompts for a password for the user
named userID
.
analyzeHeadless ghidra://example.server.org:13100/RepositoryName/RootFolder -scriptPath /usr/scripts/ -preScript RunThisScriptFirst.java -import /usr/local/binaries -recursive -connect userID -p -commit "Testing server imports."
Example 10
Change the default log location when importing and analyzing a file.
analyzeHeadless /Users/user/ghidra/projects Project1 -import /binaries/binary1.exe -log /new/log_location.txt
Example 11
Re-import and overwrite a file that already exists in the project.
analyzeHeadless /Users/user/ghidra/projects Project1 -import /binaries/IAlreadyExist.exe -overwrite
Example 12
Create a new project, import and analyze a file, then delete the project when done.
analyzeHeadless /Users/user/ghidra/projects ANewProject -import /binaries/binary2.exe -deleteProject
Example 13
Set a timeout value, in seconds, for analysis (analysis will abort if it takes longer than the set timeout value).
analyzeHeadless /Users/user/ghidra/projects MyProject -import /binaries/binary2.exe -analysisTimeoutPerFile 100
Example 14
Run a script without using -import
or -process
modes (Script must not be program-dependent!).
analyzeHeadless /Users/user/ghidra/projects MyProject -preScript HelloWorldScript.java -scriptPath /my/ghidra_scripts
Example 15
Specify a language and compiler to be used when importing with analysis.
analyzeHeadless /Users/user/ghidra/projects MyProject -import hello.exe -processor "x86:LE:32:System Management Mode" -cspec default
Example 16
Import, run a script, and analyze a file, but don't allow the file to be saved to the project.
analyzeHeadless /Users/user/ghidra/projects MyProject -import hello.exe -preScript GetInfoScript.java -readOnly
Example 17
Import and run scripts that take their own arguments.
analyzeHeadless /Users/user/ghidra/projects MyProject -import hello.exe -preScript Script.java arg1 arg2 arg3 -preScript AnotherScript.java "arg1 with spaces" arg2
Example 18
Import a PE file as a raw binary image with a specified base address and block name.
analyzeHeadless /Users/user/ghidra/projects MyProject -import hello.exe -loader BinaryLoader -loader-baseAddr 0x1000 -loader-blockName MyBlock -processor x86:LE:32:default
Scripting
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 Headless Scripts section for more detail.
Here are some general guidelines for running scripts headlessly.
- If neither
-import
mode nor-process
mode is specified in the Headless Analyzer command line arguments, only the specified pre/post-script(s) will be executed. In this case, all scripts must execute in a program-independent manner, or errors will occur. If you intend for scripts to be run against programs, please run in-process
mode. - For each pre-/post-script group, scripts are executed in the order specified on the command line.
- Any pre- or post-script may invoke the
setTemporary()
method oncurrentProgram
to prevent changes from being saved. In-import
mode, the method prevents the specific import from being saved. In-process
mode, the method prevents changes to the program from being saved. - Avoid using the script API method
setServerCredentials()
for shared projects.
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:
askFile
askDirectory
askLanguage
askProjectFolder
askInt
askLong
askAddress
askBytes
askProgram
askDomainFile
askDouble
askString
askChoice
askChoices
askYesNo
Further details for each specific askXxx()
method can be found in the method's JavaDoc.
When running headlessly, the askXxx()
methods allow users 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
.
Headless Scripts
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.
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();
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
- The following usage ensures that any files imported after the call to this method are saved in the
existing
MyGhidraProject:dir1/innerDir2
folder:setHeadlessImportDirectory("dir1/innerDir2");
- In contrast, the following usage adds new folders to the Ghidra project and saves the imported
files into the newly-created path.
changes the directory structure to:setHeadlessImportDirectory("dir1/innerDir2/my/folder");
MyGhidraProject: /dir1 /innerDir1 /innerDir2 /my /folder
- Another usage example where new folders are added to the Ghidra project.
This changes the directory structure to:setHeadlessImportDirectory("dir1/newDir/saveHere");
MyGhidraProject: /dir1 /innerDir1 /innerDir2 /newDir /saveHere
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).
Checking for Analysis Timeout
In the case where all of the following apply:
- the user set an analysis timeout period using the
-analysisTimeoutPerFile
parameter - analysis is enabled and has completed
- the current script is being run as a postScript
The user can check whether analysis timed out, using the following query method:
boolean didTimeout = analysisTimeoutOccurred();
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);
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:
HeadlessContinuationOption.ABORT
HeadlessContinuationOption.ABORT_AND_DELETE
HeadlessContinuationOption.CONTINUE_THEN_DELETE
HeadlessContinuationOption.CONTINUE
(default setting)
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 the next section to understand how this works.
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:
- when the user runs multiple pre-scripts and/or post-scripts that use
setHeadlessContinuationOption()
- when the user runs scripts that call sub-scripts (or secondary scripts) that use
setHeadlessContinuationOption()
- when the user runs a script that makes multiple calls to the
setHeadlessContinuationOption()
method
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 (left column), then is followed by Script2.java which also sets the continuation option (column headers), the resulting continuation status is shown in the following table:
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 |
*NOTE: 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:
- If Script2 does not change the continuation option, then the status from Script1 will carry over.
- An
ABORT
at the postScript stage is still meaningful in stopping further processing, since follow-on analysis may occur as a result of changes made by the postScript. - You can check the current continuation option by using the
getHeadlessContinuationOption()
method. For example:HeadlessContinuationOption currentOption = getHeadlessContinuationOption();
- When specifying deletion options such as
ABORT_AND_DELETE
orCONTINUE_THEN_DELETE
in-process
mode, be sure to include-okToDelete
in the command line parameters to verify that deletions are allowed. This is an extra safety step to ensure programs aren't deleted when the user didn't mean to delete them.
- When specifying deletion options such as
Wildcards
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:
-
-import
mode- During import, the rules for wildcard use depend on the operating system on which the Headless Analyzer is being run. The operating system will expand the wildcards to a list of matching files and pass the list to the Headless Analyzer.
- Unix-based Operating Systems allow the following wildcards:
- Use the
*
character to substitute for zero or more characters - Use the
?
character to substitute for exactly one character - Use ranges of characters enclosed in square brackets (for example,
[a-z]
) to substitute for any one of the characters in that range. Negation of the characters is also allowed by using a leading!
within the brackets (i.e.,[!a-z]
). - Wildcards can expand to either directories or files
- Use the
- Windows allows the following wildcards:
- Use the
*
character to substitute for zero or more characters - Use the
?
to substitute for one character or less - Wildcards can only expand to files (directories whose names conform to the wildcard string will not be returned)
- Use the
- When using a wildcard to specify files, be sure to use as specific of a string as possible.
There may be unintended consequences to using more generalized wildcard strings, such as:
When using a Unix-based operating system, this import specification results in not only all files in./analyzeHeadless /home/usr/ghidra/projects TestProj -import /home/files/n*
/home/files
starting with n to be imported, but also the contents of the all directories starting with n to be imported (contents of those directories' subdirectories would also be imported, if the-recursive
option was specified -- note that the contents of the directories starting with n are not also subject to the restriction that they start with n).
-
-process
mode- In process mode, the wildcard string applies only to files, not directories. The only accepted
wildcard characters are
*
and?
. - There are some cases where the wildcard string may be prematurely expanded by the operating
system. For example, in order to run in
-process
mode over all existing project files that start with the letter a, one might use the following command:./analyzeHeadless /home/usr/ghidra/projects TestProj -process a* -recursive
- Instead of the "a*" string being passed along to be used within the Ghidra project, the shell
will prematurely expand the wildcard string to match all a-prefixed files in the current
directory. The command ends up looking like this:
./analyzeHeadless /home/usr/ghidra/projects TestProj -process analyzeHeadless -recursive
- In order to prevent the system shell from doing this premature wildcard expansion, simply
surround the wildcard string with single-quote characters:
./analyzeHeadless /home/usr/ghidra/projects TestProj -process 'a*' -recursive
- In process mode, the wildcard string applies only to files, not directories. The only accepted
wildcard characters are
-
Address must be in the form
[space:]offset
. Space is optional, and offset is a hex valuewith no leading0x
. ↩︎ -
To specify hexadecimal, use a leading
0x
. ↩︎ -
Base address is in the default space and must be specified as a hexadecimal value without the leading
0x
. ↩︎ -
Base address is in the default data space and must be specified as a hexadecimal value without the leading
0x
. This option only applies to Harvard Architecture processors when loading relocatable ELF binaries (i.e., object modules). ↩︎