
Ghidra Headless
- 284 installs
- 475 repo stars
- Updated July 14, 2026
- trailofbits/skills-curated
Run Ghidra headless analysis on binaries: import artifacts, execute scripts, decompile functions, and extract findings for vulnerability research or release security review.
About
Guides automated Ghidra headless reverse-engineering sessions for security teams and researchers, covering batch imports, analysis scripts, decompilation exports, and repeatable audit workflows over native binaries and firmware images.
- Ghidra headless CLI automation
- Binary import and project setup
- Scripted decompilation workflows
- Function and string analysis
- Security review artifact extraction
Ghidra Headless by the numbers
- 284 all-time installs (skills.sh)
- +25 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #646 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/trailofbits/skills-curated --skill ghidra-headlessAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 284 |
|---|---|
| repo stars | ★ 475 |
| Last updated | July 14, 2026 |
| Repository | trailofbits/skills-curated ↗ |
What it does
Run Ghidra headless analysis on binaries: import artifacts, execute scripts, decompile functions, and extract findings for vulnerability research or release security review.
Files
Ghidra Headless Analysis
Perform automated reverse engineering using Ghidra's analyzeHeadless tool. Import binaries, run analysis, decompile to C code, and extract useful information.
When to Use
- Decompiling a binary to C pseudocode for review
- Extracting function signatures, strings, or symbols from executables
- Analyzing call graphs to understand binary control flow
- Triaging unknown binaries or firmware images
- Batch-analyzing multiple binaries for comparison
- Security auditing compiled code without source access
When NOT to Use
- Source code is available — read it directly instead
- Interactive debugging is needed — use GDB, LLDB, or Ghidra GUI
- The binary is a .NET assembly — use dnSpy or ILSpy
- The binary is Java bytecode — use jadx or cfr
- Dynamic analysis is required — use a debugger or sandbox
Quick Reference
| Task | Command |
|---|---|
| Full analysis with all exports | {baseDir}/scripts/ghidra-analyze.sh -s ExportAll.java -o ./output binary |
| Decompile to C code | {baseDir}/scripts/ghidra-analyze.sh -s ExportDecompiled.java -o ./output binary |
| List functions | {baseDir}/scripts/ghidra-analyze.sh -s ExportFunctions.java -o ./output binary |
| Extract strings | {baseDir}/scripts/ghidra-analyze.sh -s ExportStrings.java -o ./output binary |
| Get call graph | {baseDir}/scripts/ghidra-analyze.sh -s ExportCalls.java -o ./output binary |
| Export symbols | {baseDir}/scripts/ghidra-analyze.sh -s ExportSymbols.java -o ./output binary |
| Find Ghidra path | {baseDir}/scripts/find-ghidra.sh |
Prerequisites
- Ghidra must be installed. On macOS:
brew install --cask ghidra - Java (OpenJDK 17+) must be available
The skill automatically locates Ghidra in common installation paths. Set GHIDRA_HOME environment variable if Ghidra is installed in a non-standard location.
Main Wrapper Script
{baseDir}/scripts/ghidra-analyze.sh [options] <binary>Wrapper that handles project creation/cleanup and provides a simpler interface to analyzeHeadless.
Options:
-o, --output <dir>— Output directory for results (default: current dir)-s, --script <name>— Post-analysis script to run (can be repeated)-a, --script-args <args>— Arguments for the last specified script--script-path <path>— Additional script search path-p, --processor <id>— Processor/architecture (e.g.,x86:LE:32:default)-c, --cspec <id>— Compiler spec (e.g.,gcc,windows)--no-analysis— Skip auto-analysis (faster, but less info)--timeout <seconds>— Analysis timeout per file--keep-project— Keep the Ghidra project after analysis--project-dir <dir>— Directory for Ghidra project (default: /tmp)--project-name <name>— Project name (default: auto-generated)-v, --verbose— Verbose output
Built-in Export Scripts
ExportAll.java
Runs summary, decompilation, function list, strings, and interesting-pattern exports. Does not include call graph or symbols — run ExportCalls.java and ExportSymbols.java separately if needed. Best for initial analysis.
Output files:
{name}_summary.txt— Overview: architecture, memory sections, function counts{name}_decompiled.c— All functions decompiled to C{name}_functions.json— Function list with signatures and calls{name}_strings.txt— All strings found (plain text; use ExportStrings.java for JSON){name}_interesting.txt— Functions matching security-relevant patterns
{baseDir}/scripts/ghidra-analyze.sh -s ExportAll.java -o ./analysis firmware.binExportDecompiled.java
Decompile all functions to C pseudocode.
Output: {name}_decompiled.c
ExportFunctions.java
Export function list as JSON with addresses, signatures, parameters, and call relationships.
Output: {name}_functions.json
ExportStrings.java
Extract all strings (ASCII, Unicode) with addresses.
Output: {name}_strings.json
ExportCalls.java
Export function call graph showing caller/callee relationships. Includes full call graph, potential entry points, and most frequently called functions.
Output: {name}_calls.json
ExportSymbols.java
Export all symbols: imports, exports, and internal symbols.
Output: {name}_symbols.json
Common Workflows
Analyze an Unknown Binary
mkdir -p ./analysis
{baseDir}/scripts/ghidra-analyze.sh -s ExportAll.java -o ./analysis unknown_binary
cat ./analysis/unknown_binary_summary.txt
cat ./analysis/unknown_binary_interesting.txtAnalyze Firmware
{baseDir}/scripts/ghidra-analyze.sh \
-p "ARM:LE:32:v7" \
-s ExportAll.java \
-o ./firmware_analysis \
firmware.binQuick Function Listing
{baseDir}/scripts/ghidra-analyze.sh --no-analysis -s ExportFunctions.java -o . program
cat program_functions.json | jq '.functions[] | "\(.address): \(.name)"'Find Specific Patterns
# After running ExportDecompiled, search for patterns
grep -n "password\|secret\|key" output_decompiled.c
grep -n "strcpy\|sprintf\|gets" output_decompiled.cArchitecture/Processor IDs
Common processor IDs for the -p option:
| Architecture | Processor ID |
|---|---|
| x86 32-bit | x86:LE:32:default |
| x86 64-bit | x86:LE:64:default |
| ARM 32-bit | ARM:LE:32:v7 |
| ARM 64-bit | AARCH64:LE:64:v8A |
| MIPS 32-bit | MIPS:BE:32:default or MIPS:LE:32:default |
| PowerPC | PowerPC:BE:32:default |
Troubleshooting
Ghidra Not Found
{baseDir}/scripts/find-ghidra.sh
# Or set GHIDRA_HOME if in non-standard location
export GHIDRA_HOME=/path/to/ghidra_11.x_PUBLICAnalysis Takes Too Long
{baseDir}/scripts/ghidra-analyze.sh --timeout 300 -s ExportAll.java binary
# Or skip analysis for quick export
{baseDir}/scripts/ghidra-analyze.sh --no-analysis -s ExportSymbols.java binaryOut of Memory
Set before running:
export MAXMEM=4GWrong Architecture Detected
Explicitly specify the processor:
{baseDir}/scripts/ghidra-analyze.sh -p "ARM:LE:32:v7" -s ExportAll.java firmware.binTips
1. Start with ExportAll.java — gives everything; the summary helps orient 2. Check interesting.txt — highlights security-relevant functions automatically 3. Use jq for JSON parsing — JSON exports are designed to be machine-readable 4. Decompilation isn't perfect — use as a guide, cross-reference with disassembly 5. Large binaries take time — use --timeout and consider --no-analysis for quick scans
#!/bin/bash
# Locate Ghidra installation and analyzeHeadless script
# Searches common installation paths and outputs the path to analyzeHeadless
set -euo pipefail
# Common locations to search for Ghidra
SEARCH_PATHS=(
# Homebrew on Apple Silicon
"/opt/homebrew/Caskroom/ghidra"
# Homebrew on Intel
"/usr/local/Caskroom/ghidra"
# Manual installation locations
"/opt/ghidra"
"/usr/local/ghidra"
"$HOME/ghidra"
"$HOME/Applications/ghidra"
"/Applications/ghidra"
# Linux common paths
"/usr/share/ghidra"
"/usr/local/share/ghidra"
)
# Check GHIDRA_HOME environment variable first
if [[ -n "${GHIDRA_HOME:-}" ]]; then
HEADLESS="$GHIDRA_HOME/support/analyzeHeadless"
if [[ -x "$HEADLESS" ]]; then
echo "$HEADLESS"
exit 0
fi
fi
# Search through common paths
for base_path in "${SEARCH_PATHS[@]}"; do
if [[ -d "$base_path" ]]; then
# Find analyzeHeadless in the directory tree (handles versioned paths)
HEADLESS=$(find "$base_path" -name "analyzeHeadless" -type f 2>/dev/null | head -n 1)
if [[ -n "$HEADLESS" && -x "$HEADLESS" ]]; then
echo "$HEADLESS"
exit 0
fi
fi
done
# Try to find it anywhere on the system as a last resort
HEADLESS=$(find /opt /usr/local /Applications "$HOME" -maxdepth 5 -name "analyzeHeadless" -type f 2>/dev/null | head -n 1)
if [[ -n "$HEADLESS" && -x "$HEADLESS" ]]; then
echo "$HEADLESS"
exit 0
fi
echo "ERROR: Could not find Ghidra's analyzeHeadless script." >&2
echo "Please set GHIDRA_HOME environment variable or install Ghidra." >&2
exit 1
/* ###
* Export comprehensive analysis: decompiled code, functions, strings, calls, and symbols
* @category Export
*/
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.*;
public class ExportAll extends GhidraScript {
private String outputDir;
private String programName;
@Override
public void run() throws Exception {
outputDir = System.getenv("GHIDRA_OUTPUT_DIR");
if (outputDir == null || outputDir.isEmpty()) {
outputDir = ".";
}
programName = currentProgram.getName().replaceAll("[^a-zA-Z0-9._-]", "_");
println("=== Starting comprehensive export ===");
println("Output directory: " + outputDir);
println("Program: " + currentProgram.getName());
println("Architecture: " + currentProgram.getLanguage().getProcessor());
println("");
// Export summary first
exportSummary();
// Export decompiled code
exportDecompiled();
// Export functions
exportFunctions();
// Export strings
exportStrings();
// Export interesting patterns
exportInteresting();
println("");
println("=== Export complete ===");
}
private void exportSummary() throws Exception {
File outputFile = new File(outputDir, programName + "_summary.txt");
println("Exporting summary to: " + outputFile.getName());
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("Binary Analysis Summary");
writer.println("=======================");
writer.println("");
writer.println("File: " + currentProgram.getName());
writer.println("Architecture: " + currentProgram.getLanguage().getProcessor());
writer.println("Address Size: " + currentProgram.getLanguage().getLanguageDescription().getSize() + " bit");
writer.println("Endianness: " + currentProgram.getLanguage().isBigEndian() + " (big endian)");
writer.println("Compiler: " + currentProgram.getCompilerSpec().getCompilerSpecID());
writer.println("");
// Count functions
int totalFuncs = 0, externalFuncs = 0, thunkFuncs = 0;
FunctionIterator funcs = currentProgram.getFunctionManager().getFunctions(true);
while (funcs.hasNext()) {
Function f = funcs.next();
totalFuncs++;
if (f.isExternal()) externalFuncs++;
if (f.isThunk()) thunkFuncs++;
}
writer.println("Functions:");
writer.println(" Total: " + totalFuncs);
writer.println(" External: " + externalFuncs);
writer.println(" Thunks: " + thunkFuncs);
writer.println(" User-defined: " + (totalFuncs - externalFuncs - thunkFuncs));
writer.println("");
// Memory sections
writer.println("Memory Sections:");
for (var block : currentProgram.getMemory().getBlocks()) {
writer.println(" " + block.getName() + ": " + block.getStart() + " - " + block.getEnd() +
" (" + block.getSize() + " bytes)" +
(block.isExecute() ? " [X]" : "") +
(block.isWrite() ? " [W]" : "") +
(block.isRead() ? " [R]" : ""));
}
}
}
private void exportDecompiled() throws Exception {
File outputFile = new File(outputDir, programName + "_decompiled.c");
println("Exporting decompiled code to: " + outputFile.getName());
DecompInterface decompiler = new DecompInterface();
DecompileOptions options = new DecompileOptions();
decompiler.setOptions(options);
if (!decompiler.openProgram(currentProgram)) {
printerr("Failed to initialize decompiler");
return;
}
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("/* Decompiled from: " + currentProgram.getName() + " */");
writer.println("");
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
int count = 0;
while (functions.hasNext() && !monitor.isCancelled()) {
Function func = functions.next();
if (func.isExternal() || func.isThunk()) continue;
DecompileResults results = decompiler.decompileFunction(func, 30, monitor);
if (results.decompileCompleted()) {
writer.println("/* " + func.getName() + " @ " + func.getEntryPoint() + " */");
writer.println(results.getDecompiledFunction().getC());
writer.println("");
count++;
}
}
println(" Decompiled " + count + " functions");
} finally {
decompiler.dispose();
}
}
private void exportFunctions() throws Exception {
File outputFile = new File(outputDir, programName + "_functions.json");
println("Exporting functions to: " + outputFile.getName());
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("[");
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
boolean first = true;
int count = 0;
while (functions.hasNext() && !monitor.isCancelled()) {
Function func = functions.next();
if (!first) writer.println(",");
first = false;
writer.println(" {");
writer.println(" \"name\": \"" + escapeJson(func.getName()) + "\",");
writer.println(" \"address\": \"" + func.getEntryPoint() + "\",");
writer.println(" \"signature\": \"" + escapeJson(func.getPrototypeString(false, false)) + "\",");
writer.println(" \"external\": " + func.isExternal() + ",");
// Get calls
java.util.Set<Function> calls = func.getCalledFunctions(monitor);
writer.print(" \"calls\": [");
int callIdx = 0;
for (Function calledFunc : calls) {
if (callIdx >= 20) break;
if (callIdx > 0) writer.print(", ");
writer.print("\"" + escapeJson(calledFunc.getName()) + "\"");
callIdx++;
}
writer.println("]");
writer.print(" }");
count++;
}
writer.println();
writer.println("]");
println(" Exported " + count + " functions");
}
}
private void exportStrings() throws Exception {
File outputFile = new File(outputDir, programName + "_strings.txt");
println("Exporting strings to: " + outputFile.getName());
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
DataIterator dataIterator = currentProgram.getListing().getDefinedData(true);
int count = 0;
while (dataIterator.hasNext() && !monitor.isCancelled()) {
Data data = dataIterator.next();
DataType dt = data.getBaseDataType();
String typeName = dt.getName().toLowerCase();
if (typeName.contains("string") || typeName.contains("unicode")) {
Object value = data.getValue();
if (value instanceof String) {
String str = (String) value;
if (str.length() >= 4) {
writer.println(data.getAddress() + ": " + str);
count++;
}
}
}
}
println(" Exported " + count + " strings");
}
}
private void exportInteresting() throws Exception {
File outputFile = new File(outputDir, programName + "_interesting.txt");
println("Analyzing interesting patterns...");
// Interesting function name patterns
String[] interestingPatterns = {
"crypt", "encrypt", "decrypt", "aes", "des", "rsa", "md5", "sha",
"password", "passwd", "secret", "key", "token", "auth",
"socket", "connect", "send", "recv", "http", "url", "dns",
"file", "open", "read", "write", "exec", "system", "shell", "cmd",
"malloc", "free", "alloc", "memcpy", "strcpy", "sprintf",
"debug", "log", "print", "error", "fail"
};
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("Interesting Functions and Patterns");
writer.println("===================================");
writer.println("");
// Find functions matching patterns
Map<String, List<String>> categorized = new LinkedHashMap<>();
for (String pattern : interestingPatterns) {
categorized.put(pattern, new ArrayList<>());
}
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
while (functions.hasNext()) {
Function func = functions.next();
String name = func.getName().toLowerCase();
for (String pattern : interestingPatterns) {
if (name.contains(pattern)) {
categorized.get(pattern).add(func.getName() + " @ " + func.getEntryPoint());
}
}
}
for (Map.Entry<String, List<String>> entry : categorized.entrySet()) {
if (!entry.getValue().isEmpty()) {
writer.println("[" + entry.getKey().toUpperCase() + " related]");
for (String func : entry.getValue()) {
writer.println(" " + func);
}
writer.println("");
}
}
// Find potential vulnerabilities (dangerous function calls)
writer.println("[POTENTIALLY DANGEROUS FUNCTIONS]");
String[] dangerous = {"strcpy", "sprintf", "gets", "scanf", "strcat", "system", "exec"};
for (String pattern : dangerous) {
SymbolIterator symbols = currentProgram.getSymbolTable().getSymbols(pattern);
while (symbols.hasNext()) {
Symbol sym = symbols.next();
writer.println(" " + sym.getName() + " @ " + sym.getAddress());
}
}
}
}
private String escapeJson(String s) {
if (s == null) return "";
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
switch (c) {
case '\\': sb.append("\\\\"); break;
case '"': sb.append("\\\""); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
default:
if (c < 32 || c > 126) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
}
return sb.toString();
}
}
/* ###
* Export function call graph
* @category Export
*/
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.*;
public class ExportCalls extends GhidraScript {
@Override
public void run() throws Exception {
String outputDir = System.getenv("GHIDRA_OUTPUT_DIR");
if (outputDir == null || outputDir.isEmpty()) {
outputDir = ".";
}
String programName = currentProgram.getName().replaceAll("[^a-zA-Z0-9._-]", "_");
File outputFile = new File(outputDir, programName + "_calls.json");
println("Exporting call graph to: " + outputFile.getAbsolutePath());
// Build call graph
Map<String, Set<String>> callGraph = new LinkedHashMap<>();
Map<String, String> functionAddresses = new LinkedHashMap<>();
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
while (functions.hasNext() && !monitor.isCancelled()) {
Function func = functions.next();
String funcName = func.getName();
functionAddresses.put(funcName, func.getEntryPoint().toString());
Set<String> calls = new TreeSet<>();
Set<Function> calledFunctions = func.getCalledFunctions(monitor);
for (Function called : calledFunctions) {
calls.add(called.getName());
}
callGraph.put(funcName, calls);
}
// Write output
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("{");
writer.println(" \"program\": \"" + escapeJson(currentProgram.getName()) + "\",");
writer.println(" \"totalFunctions\": " + callGraph.size() + ",");
writer.println(" \"callGraph\": {");
boolean firstFunc = true;
for (Map.Entry<String, Set<String>> entry : callGraph.entrySet()) {
if (!firstFunc) {
writer.println(",");
}
firstFunc = false;
String funcName = entry.getKey();
Set<String> calls = entry.getValue();
writer.print(" \"" + escapeJson(funcName) + "\": {");
writer.print("\"address\": \"" + functionAddresses.get(funcName) + "\", ");
writer.print("\"calls\": [");
boolean firstCall = true;
for (String call : calls) {
if (!firstCall) {
writer.print(", ");
}
firstCall = false;
writer.print("\"" + escapeJson(call) + "\"");
}
writer.print("]}");
}
writer.println();
writer.println(" },");
// Also export interesting functions (potential entry points, etc.)
writer.println(" \"interestingFunctions\": {");
// Find functions with no callers (potential entry points)
Set<String> noCaller = new TreeSet<>();
Set<String> allCalled = new TreeSet<>();
for (Set<String> calls : callGraph.values()) {
allCalled.addAll(calls);
}
for (String func : callGraph.keySet()) {
if (!allCalled.contains(func)) {
noCaller.add(func);
}
}
writer.print(" \"potentialEntryPoints\": [");
boolean first = true;
for (String func : noCaller) {
if (!first) writer.print(", ");
first = false;
writer.print("\"" + escapeJson(func) + "\"");
}
writer.println("],");
// Find functions with many callers (commonly used)
Map<String, Integer> callerCount = new HashMap<>();
for (Set<String> calls : callGraph.values()) {
for (String call : calls) {
callerCount.merge(call, 1, Integer::sum);
}
}
List<Map.Entry<String, Integer>> sorted = new ArrayList<>(callerCount.entrySet());
sorted.sort((a, b) -> b.getValue().compareTo(a.getValue()));
writer.print(" \"mostCalled\": [");
first = true;
for (int i = 0; i < Math.min(20, sorted.size()); i++) {
if (!first) writer.print(", ");
first = false;
Map.Entry<String, Integer> e = sorted.get(i);
writer.print("{\"name\": \"" + escapeJson(e.getKey()) + "\", \"count\": " + e.getValue() + "}");
}
writer.println("]");
writer.println(" }");
writer.println("}");
println("Exported call graph with " + callGraph.size() + " functions");
}
}
private String escapeJson(String s) {
if (s == null) return "";
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
switch (c) {
case '\\': sb.append("\\\\"); break;
case '"': sb.append("\\\""); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
default:
if (c < 32 || c > 126) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
}
return sb.toString();
}
}
/* ###
* Export decompiled C code for all functions
* @category Export
*/
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
public class ExportDecompiled extends GhidraScript {
@Override
public void run() throws Exception {
String outputDir = System.getenv("GHIDRA_OUTPUT_DIR");
if (outputDir == null || outputDir.isEmpty()) {
outputDir = ".";
}
String programName = currentProgram.getName().replaceAll("[^a-zA-Z0-9._-]", "_");
File outputFile = new File(outputDir, programName + "_decompiled.c");
println("Decompiling all functions to: " + outputFile.getAbsolutePath());
DecompInterface decompiler = new DecompInterface();
DecompileOptions options = new DecompileOptions();
decompiler.setOptions(options);
if (!decompiler.openProgram(currentProgram)) {
printerr("Failed to initialize decompiler: " + decompiler.getLastMessage());
return;
}
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
// Write header
writer.println("/*");
writer.println(" * Decompiled from: " + currentProgram.getName());
writer.println(" * Architecture: " + currentProgram.getLanguage().getProcessor());
writer.println(" * Compiler: " + currentProgram.getCompilerSpec().getCompilerSpecID());
writer.println(" */");
writer.println();
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
int count = 0;
int failed = 0;
while (functions.hasNext() && !monitor.isCancelled()) {
Function func = functions.next();
// Skip external/thunk functions
if (func.isExternal() || func.isThunk()) {
continue;
}
monitor.setMessage("Decompiling: " + func.getName());
DecompileResults results = decompiler.decompileFunction(func, 30, monitor);
if (results.decompileCompleted()) {
String decompiledCode = results.getDecompiledFunction().getC();
writer.println("/* Function: " + func.getName() + " @ " + func.getEntryPoint() + " */");
writer.println(decompiledCode);
writer.println();
count++;
} else {
writer.println("/* FAILED TO DECOMPILE: " + func.getName() + " @ " + func.getEntryPoint() + " */");
writer.println("/* Error: " + results.getErrorMessage() + " */");
writer.println();
failed++;
}
}
println("Decompiled " + count + " functions (" + failed + " failed)");
} finally {
decompiler.dispose();
}
}
}
/* ###
* Export function list with addresses, signatures, and metadata as JSON
* @category Export
*/
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
import ghidra.program.model.listing.Parameter;
import ghidra.program.model.symbol.SourceType;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
public class ExportFunctions extends GhidraScript {
@Override
public void run() throws Exception {
String outputDir = System.getenv("GHIDRA_OUTPUT_DIR");
if (outputDir == null || outputDir.isEmpty()) {
outputDir = ".";
}
String programName = currentProgram.getName().replaceAll("[^a-zA-Z0-9._-]", "_");
File outputFile = new File(outputDir, programName + "_functions.json");
println("Exporting functions to: " + outputFile.getAbsolutePath());
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("{");
writer.println(" \"program\": \"" + escapeJson(currentProgram.getName()) + "\",");
writer.println(" \"architecture\": \"" + currentProgram.getLanguage().getProcessor() + "\",");
writer.println(" \"functions\": [");
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
boolean first = true;
int count = 0;
while (functions.hasNext() && !monitor.isCancelled()) {
Function func = functions.next();
if (!first) {
writer.println(",");
}
first = false;
writer.println(" {");
writer.println(" \"name\": \"" + escapeJson(func.getName()) + "\",");
writer.println(" \"address\": \"" + func.getEntryPoint() + "\",");
writer.println(" \"size\": " + func.getBody().getNumAddresses() + ",");
writer.println(" \"signature\": \"" + escapeJson(func.getPrototypeString(false, false)) + "\",");
writer.println(" \"returnType\": \"" + escapeJson(func.getReturnType().getDisplayName()) + "\",");
writer.println(" \"callingConvention\": \"" + escapeJson(func.getCallingConventionName()) + "\",");
writer.println(" \"isExternal\": " + func.isExternal() + ",");
writer.println(" \"isThunk\": " + func.isThunk() + ",");
writer.println(" \"hasVarArgs\": " + func.hasVarArgs() + ",");
writer.println(" \"sourceType\": \"" + func.getSymbol().getSource() + "\",");
// Parameters
writer.print(" \"parameters\": [");
Parameter[] params = func.getParameters();
for (int i = 0; i < params.length; i++) {
if (i > 0) writer.print(", ");
writer.print("{\"name\": \"" + escapeJson(params[i].getName()) + "\", ");
writer.print("\"type\": \"" + escapeJson(params[i].getDataType().getDisplayName()) + "\"}");
}
writer.println("],");
// Called functions
writer.print(" \"calls\": [");
java.util.Set<Function> called = func.getCalledFunctions(monitor);
int callIdx = 0;
for (Function calledFunc : called) {
if (callIdx >= 50) break; // Limit to 50 calls
if (callIdx > 0) writer.print(", ");
writer.print("\"" + escapeJson(calledFunc.getName()) + "\"");
callIdx++;
}
writer.println("],");
// Calling functions
writer.print(" \"calledBy\": [");
java.util.Set<Function> callers = func.getCallingFunctions(monitor);
int callerIdx = 0;
for (Function caller : callers) {
if (callerIdx >= 50) break; // Limit to 50 callers
if (callerIdx > 0) writer.print(", ");
writer.print("\"" + escapeJson(caller.getName()) + "\"");
callerIdx++;
}
writer.println("]");
writer.print(" }");
count++;
}
writer.println();
writer.println(" ]");
writer.println("}");
println("Exported " + count + " functions");
}
}
private String escapeJson(String s) {
if (s == null) return "";
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
switch (c) {
case '\\': sb.append("\\\\"); break;
case '"': sb.append("\\\""); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
default:
if (c < 32 || c > 126) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
}
return sb.toString();
}
}
/* ###
* Export all strings found in the binary
* @category Export
*/
import ghidra.app.script.GhidraScript;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.StringDataType;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.DataIterator;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.DefinedDataIterator;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
public class ExportStrings extends GhidraScript {
@Override
public void run() throws Exception {
String outputDir = System.getenv("GHIDRA_OUTPUT_DIR");
if (outputDir == null || outputDir.isEmpty()) {
outputDir = ".";
}
String programName = currentProgram.getName().replaceAll("[^a-zA-Z0-9._-]", "_");
File outputFile = new File(outputDir, programName + "_strings.json");
println("Exporting strings to: " + outputFile.getAbsolutePath());
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("{");
writer.println(" \"program\": \"" + escapeJson(currentProgram.getName()) + "\",");
writer.println(" \"strings\": [");
int count = 0;
boolean first = true;
// Iterate through all defined data looking for strings
DataIterator dataIterator = currentProgram.getListing().getDefinedData(true);
while (dataIterator.hasNext() && !monitor.isCancelled()) {
Data data = dataIterator.next();
if (isStringData(data)) {
String value = getStringValue(data);
if (value != null && !value.isEmpty() && value.length() >= 4) { // Skip very short strings
if (!first) {
writer.println(",");
}
first = false;
writer.println(" {");
writer.println(" \"address\": \"" + data.getAddress() + "\",");
writer.println(" \"type\": \"" + data.getDataType().getName() + "\",");
writer.println(" \"length\": " + value.length() + ",");
writer.println(" \"value\": \"" + escapeJson(truncate(value, 1000)) + "\"");
writer.print(" }");
count++;
}
}
}
writer.println();
writer.println(" ]");
writer.println("}");
println("Exported " + count + " strings");
}
}
private boolean isStringData(Data data) {
DataType dt = data.getBaseDataType();
String typeName = dt.getName().toLowerCase();
return typeName.contains("string") ||
typeName.equals("char") ||
typeName.contains("unicode");
}
private String getStringValue(Data data) {
Object value = data.getValue();
if (value instanceof String) {
return (String) value;
}
// Try to get the string representation
String repr = data.getDefaultValueRepresentation();
if (repr != null && repr.startsWith("\"") && repr.endsWith("\"")) {
return repr.substring(1, repr.length() - 1);
}
return repr;
}
private String truncate(String s, int maxLen) {
if (s == null) return "";
if (s.length() <= maxLen) return s;
return s.substring(0, maxLen) + "...";
}
private String escapeJson(String s) {
if (s == null) return "";
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
switch (c) {
case '\\': sb.append("\\\\"); break;
case '"': sb.append("\\\""); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
default:
if (c < 32 || c > 126) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
}
return sb.toString();
}
}
/* ###
* Export all symbols and their addresses
* @category Export
*/
import ghidra.app.script.GhidraScript;
import ghidra.program.model.symbol.*;
import ghidra.program.model.listing.*;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
public class ExportSymbols extends GhidraScript {
@Override
public void run() throws Exception {
String outputDir = System.getenv("GHIDRA_OUTPUT_DIR");
if (outputDir == null || outputDir.isEmpty()) {
outputDir = ".";
}
String programName = currentProgram.getName().replaceAll("[^a-zA-Z0-9._-]", "_");
File outputFile = new File(outputDir, programName + "_symbols.json");
println("Exporting symbols to: " + outputFile.getAbsolutePath());
SymbolTable symbolTable = currentProgram.getSymbolTable();
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {
writer.println("{");
writer.println(" \"program\": \"" + escapeJson(currentProgram.getName()) + "\",");
// Export imports (external functions)
writer.println(" \"imports\": [");
boolean first = true;
int importCount = 0;
SymbolIterator externalSymbols = symbolTable.getExternalSymbols();
while (externalSymbols.hasNext() && !monitor.isCancelled()) {
Symbol sym = externalSymbols.next();
if (!first) writer.println(",");
first = false;
writer.println(" {");
writer.println(" \"name\": \"" + escapeJson(sym.getName()) + "\",");
writer.println(" \"address\": \"" + sym.getAddress() + "\",");
writer.println(" \"namespace\": \"" + escapeJson(sym.getParentNamespace().getName()) + "\",");
writer.println(" \"type\": \"" + sym.getSymbolType() + "\"");
writer.print(" }");
importCount++;
}
writer.println();
writer.println(" ],");
// Export exports (if any)
writer.println(" \"exports\": [");
first = true;
int exportCount = 0;
// Functions that are potential exports (entry points or exported)
FunctionIterator functions = currentProgram.getFunctionManager().getExternalFunctions();
// Look for functions marked as entry points
FunctionIterator allFunctions = currentProgram.getFunctionManager().getFunctions(true);
while (allFunctions.hasNext() && !monitor.isCancelled()) {
Function func = allFunctions.next();
// Check if function is at an entry point or has export symbol
Symbol sym = func.getSymbol();
if (sym.isExternalEntryPoint() || sym.getSource() == SourceType.IMPORTED) {
if (!first) writer.println(",");
first = false;
writer.println(" {");
writer.println(" \"name\": \"" + escapeJson(func.getName()) + "\",");
writer.println(" \"address\": \"" + func.getEntryPoint() + "\",");
writer.println(" \"signature\": \"" + escapeJson(func.getPrototypeString(false, false)) + "\"");
writer.print(" }");
exportCount++;
}
}
writer.println();
writer.println(" ],");
// Export all labels/symbols
writer.println(" \"symbols\": [");
first = true;
int symbolCount = 0;
SymbolIterator allSymbols = symbolTable.getAllSymbols(true);
while (allSymbols.hasNext() && !monitor.isCancelled()) {
Symbol sym = allSymbols.next();
// Skip default/dynamic symbols to reduce noise
if (sym.getSource() == SourceType.DEFAULT) {
continue;
}
if (!first) writer.println(",");
first = false;
writer.println(" {");
writer.println(" \"name\": \"" + escapeJson(sym.getName()) + "\",");
writer.println(" \"address\": \"" + sym.getAddress() + "\",");
writer.println(" \"type\": \"" + sym.getSymbolType() + "\",");
writer.println(" \"source\": \"" + sym.getSource() + "\",");
writer.println(" \"namespace\": \"" + escapeJson(sym.getParentNamespace().getName()) + "\",");
writer.println(" \"primary\": " + sym.isPrimary());
writer.print(" }");
symbolCount++;
// Limit to prevent huge outputs
if (symbolCount >= 10000) {
writer.println(",");
writer.println(" {\"_truncated\": true, \"_message\": \"Output truncated at 10000 symbols\"}");
break;
}
}
writer.println();
writer.println(" ],");
// Summary
writer.println(" \"summary\": {");
writer.println(" \"imports\": " + importCount + ",");
writer.println(" \"exports\": " + exportCount + ",");
writer.println(" \"symbols\": " + symbolCount);
writer.println(" }");
writer.println("}");
println("Exported " + importCount + " imports, " + exportCount + " exports, " + symbolCount + " symbols");
}
}
private String escapeJson(String s) {
if (s == null) return "";
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
switch (c) {
case '\\': sb.append("\\\\"); break;
case '"': sb.append("\\\""); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
default:
if (c < 32 || c > 126) {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
}
return sb.toString();
}
}
#!/bin/bash
# Wrapper script for Ghidra headless analysis
# Handles project creation/cleanup and provides a simpler interface
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Find analyzeHeadless
ANALYZE_HEADLESS=$("$SCRIPT_DIR/find-ghidra.sh")
export GHIDRA_HOME
GHIDRA_HOME=$(dirname "$(dirname "$ANALYZE_HEADLESS")")
show_help() {
cat <<'EOF'
Usage: ghidra-analyze.sh [options] <binary>
Analyze a binary file using Ghidra's headless analyzer.
Options:
-o, --output <dir> Output directory for results (default: current dir)
-s, --script <name> Post-analysis script to run (can be repeated)
-a, --script-args <args> Arguments for the last specified script
--script-path <path> Additional script search path
-p, --processor <id> Processor/architecture (e.g., x86:LE:32:default)
-c, --cspec <id> Compiler spec (e.g., gcc, windows)
--no-analysis Skip auto-analysis
--timeout <seconds> Analysis timeout per file
--keep-project Keep the Ghidra project after analysis
--project-dir <dir> Directory for Ghidra project (default: /tmp)
--project-name <name> Project name (default: auto-generated)
-v, --verbose Verbose output
-h, --help Show this help
Built-in Scripts (use with -s):
ExportDecompiled.java Export all functions as decompiled C code
ExportFunctions.java Export function list with addresses and signatures
ExportStrings.java Export all strings found in the binary
ExportCalls.java Export function call graph
ExportSymbols.java Export all symbols and their addresses
Examples:
# Basic analysis with decompilation output
ghidra-analyze.sh -s ExportDecompiled.java -o ./output myprogram
# Analyze with specific architecture
ghidra-analyze.sh -p ARM:LE:32:v7 firmware.bin
# Run multiple scripts
ghidra-analyze.sh -s ExportFunctions.java -s ExportStrings.java binary
# Keep project for later use
ghidra-analyze.sh --keep-project --project-name MyProject binary
EOF
}
# Default values
OUTPUT_DIR="."
SCRIPTS=()
SCRIPT_ARGS=()
SCRIPT_PATH=""
PROCESSOR=""
CSPEC=""
NO_ANALYSIS=""
TIMEOUT=""
KEEP_PROJECT=false
PROJECT_DIR="/tmp"
PROJECT_NAME=""
VERBOSE=false
BINARY=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-o | --output)
OUTPUT_DIR="$2"
shift 2
;;
-s | --script)
SCRIPTS+=("$2")
shift 2
;;
-a | --script-args)
# Associate args with the last script
if [[ ${#SCRIPTS[@]} -gt 0 ]]; then
SCRIPT_ARGS+=("${#SCRIPTS[@]}:$2")
fi
shift 2
;;
--script-path)
SCRIPT_PATH="$2"
shift 2
;;
-p | --processor)
PROCESSOR="$2"
shift 2
;;
-c | --cspec)
CSPEC="$2"
shift 2
;;
--no-analysis)
NO_ANALYSIS="-noanalysis"
shift
;;
--timeout)
TIMEOUT="$2"
shift 2
;;
--keep-project)
KEEP_PROJECT=true
shift
;;
--project-dir)
PROJECT_DIR="$2"
shift 2
;;
--project-name)
PROJECT_NAME="$2"
shift 2
;;
-v | --verbose)
VERBOSE=true
shift
;;
-h | --help)
show_help
exit 0
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
BINARY="$1"
shift
;;
esac
done
if [[ -z "$BINARY" ]]; then
echo "Error: No binary file specified" >&2
show_help
exit 1
fi
if [[ ! -f "$BINARY" ]]; then
echo "Error: Binary file not found: $BINARY" >&2
exit 1
fi
# Create output directory if needed
mkdir -p "$OUTPUT_DIR"
# Generate project name if not specified
if [[ -z "$PROJECT_NAME" ]]; then
PROJECT_NAME="ghidra_$(basename "$BINARY" | tr '.' '_')_$$"
fi
# Build script path including our built-in scripts
BUILTIN_SCRIPTS="$SCRIPT_DIR/ghidra_scripts"
if [[ -n "$SCRIPT_PATH" ]]; then
FULL_SCRIPT_PATH="$BUILTIN_SCRIPTS;$SCRIPT_PATH"
else
FULL_SCRIPT_PATH="$BUILTIN_SCRIPTS"
fi
# Build command
CMD=("$ANALYZE_HEADLESS" "$PROJECT_DIR" "$PROJECT_NAME" -import "$BINARY")
# Add script path
CMD+=(-scriptPath "$FULL_SCRIPT_PATH")
# Add scripts
for i in "${!SCRIPTS[@]}"; do
script="${SCRIPTS[$i]}"
CMD+=(-postScript "$script")
# Check if there are args for this script
for arg_entry in ${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}; do
idx="${arg_entry%%:*}"
args="${arg_entry#*:}"
if [[ "$idx" -eq $((i + 1)) ]]; then
# Append script arguments
# Intentional word splitting for script args
# shellcheck disable=SC2206
CMD+=($args)
fi
done
done
# Add output directory as environment variable for scripts
export GHIDRA_OUTPUT_DIR="$OUTPUT_DIR"
# Add processor if specified
if [[ -n "$PROCESSOR" ]]; then
CMD+=(-processor "$PROCESSOR")
fi
# Add compiler spec if specified
if [[ -n "$CSPEC" ]]; then
CMD+=(-cspec "$CSPEC")
fi
# Add no-analysis flag if specified
if [[ -n "$NO_ANALYSIS" ]]; then
CMD+=("$NO_ANALYSIS")
fi
# Add timeout if specified
if [[ -n "$TIMEOUT" ]]; then
CMD+=(-analysisTimeoutPerFile "$TIMEOUT")
fi
# Delete project after analysis unless keeping it
if [[ "$KEEP_PROJECT" != true ]]; then
CMD+=(-deleteProject)
fi
# Add log file
LOG_FILE="$OUTPUT_DIR/ghidra_analysis.log"
CMD+=(-log "$LOG_FILE")
# Run the analysis
if [[ "$VERBOSE" == true ]]; then
echo "Running: ${CMD[*]}"
fi
"${CMD[@]}" 2>&1 | tee "$OUTPUT_DIR/ghidra_output.log"
exit_code=${PIPESTATUS[0]}
if [[ $exit_code -eq 0 ]]; then
echo ""
echo "Analysis complete. Output files in: $OUTPUT_DIR"
ls -la "$OUTPUT_DIR"
else
echo "Analysis failed with exit code: $exit_code" >&2
echo "Check log file: $LOG_FILE" >&2
fi
exit "$exit_code"