scripts/find-ghidra.sh
#!/bin/bash
# Locate Ghidra installation and analyzeHeadless script
# Searches common installation paths and outputs the path to analyzeHeadless
set -e
# 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" -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
scripts/ghidra_scripts/ExportAll.java
/* ###
* 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 "";
return s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
}
scripts/ghidra_scripts/ExportCalls.java
/* ###
* 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 "";
return s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
}
scripts/ghidra_scripts/ExportDecompiled.java
/* ###
* 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();
}
}
}
scripts/ghidra_scripts/ExportFunctions.java
/* ###
* 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 "";
return s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
}
scripts/ghidra_scripts/ExportStrings.java
/* ###
* 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();
}
}
scripts/ghidra_scripts/ExportSymbols.java
/* ###
* 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 "";
return s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
}
scripts/ghidra-analyze.sh
#!/bin/bash
# Wrapper script for Ghidra headless analysis
# Handles project creation/cleanup and provides a simpler interface
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Find analyzeHeadless
ANALYZE_HEADLESS=$("$SCRIPT_DIR/find-ghidra.sh")
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[@]}"; do
idx="${arg_entry%%:*}"
args="${arg_entry#*:}"
if [[ "$idx" -eq $((i + 1)) ]]; then
# Append script arguments
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
SKILL.md
---
name: ghidra
description: "Reverse engineer binaries using Ghidra's headless analyzer. Decompile executables, extract functions, strings, symbols, and analyze call graphs without GUI."
---
# Ghidra Headless Analysis Skill
Perform automated reverse engineering using Ghidra's `analyzeHeadless` tool. Import binaries, run analysis, decompile to C code, and extract useful information.
## Quick Reference
| Task | Command |
|------|---------|
| Full analysis with all exports | `ghidra-analyze.sh -s ExportAll.java -o ./output binary` |
| Decompile to C code | `ghidra-analyze.sh -s ExportDecompiled.java -o ./output binary` |
| List functions | `ghidra-analyze.sh -s ExportFunctions.java -o ./output binary` |
| Extract strings | `ghidra-analyze.sh -s ExportStrings.java -o ./output binary` |
| Get call graph | `ghidra-analyze.sh -s ExportCalls.java -o ./output binary` |
| Export symbols | `ghidra-analyze.sh -s ExportSymbols.java -o ./output binary` |
| Find Ghidra path | `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
```bash
./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
Comprehensive export - runs all other exports and creates a summary. 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
- `{name}_interesting.txt` - Functions matching security-relevant patterns
```bash
./scripts/ghidra-analyze.sh -s ExportAll.java -o ./analysis firmware.bin
```
### ExportDecompiled.java
Decompile all functions to C pseudocode.
**Output:** `{name}_decompiled.c`
```bash
./scripts/ghidra-analyze.sh -s ExportDecompiled.java -o ./output program.exe
```
### ExportFunctions.java
Export function list as JSON with addresses, signatures, parameters, and call relationships.
**Output:** `{name}_functions.json`
```json
{
"program": "example.exe",
"architecture": "x86",
"functions": [
{
"name": "main",
"address": "0x00401000",
"size": 256,
"signature": "int main(int argc, char **argv)",
"returnType": "int",
"callingConvention": "cdecl",
"isExternal": false,
"parameters": [{"name": "argc", "type": "int"}, ...],
"calls": ["printf", "malloc", "process_data"],
"calledBy": ["_start"]
}
]
}
```
### ExportStrings.java
Extract all strings (ASCII, Unicode) with addresses.
**Output:** `{name}_strings.json`
```bash
./scripts/ghidra-analyze.sh -s ExportStrings.java -o ./output malware.exe
```
### ExportCalls.java
Export function call graph showing caller/callee relationships.
**Output:** `{name}_calls.json`
Includes:
- Full call graph
- Potential entry points (functions with no callers)
- Most frequently called functions
### ExportSymbols.java
Export all symbols: imports, exports, and internal symbols.
**Output:** `{name}_symbols.json`
---
## Common Workflows
### Analyze an Unknown Binary
```bash
# Create output directory
mkdir -p ./analysis
# Run comprehensive analysis
./scripts/ghidra-analyze.sh -s ExportAll.java -o ./analysis unknown_binary
# Review the summary first
cat ./analysis/unknown_binary_summary.txt
# Look at interesting patterns (crypto, network, dangerous functions)
cat ./analysis/unknown_binary_interesting.txt
# Check specific decompiled functions
grep -A 50 "encrypt" ./analysis/unknown_binary_decompiled.c
```
### Analyze Firmware
```bash
# Specify ARM architecture for firmware
./scripts/ghidra-analyze.sh \
-p "ARM:LE:32:v7" \
-s ExportAll.java \
-o ./firmware_analysis \
firmware.bin
```
### Quick Function Listing
```bash
# Just get function names and addresses (faster)
./scripts/ghidra-analyze.sh --no-analysis -s ExportFunctions.java -o . program
# Parse with jq
cat program_functions.json | jq '.functions[] | "\(.address): \(.name)"'
```
### Find Specific Patterns
```bash
# After running ExportDecompiled, search for patterns
grep -n "password\|secret\|key" output_decompiled.c
grep -n "strcpy\|sprintf\|gets" output_decompiled.c
```
### Analyze Multiple Binaries
```bash
for bin in ./samples/*; do
name=$(basename "$bin")
./scripts/ghidra-analyze.sh -s ExportAll.java -o "./results/$name" "$bin"
done
```
---
## Architecture/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` |
Find all available processors:
```bash
ls "$(dirname $(./scripts/find-ghidra.sh))/../Ghidra/Processors/"
```
---
## Troubleshooting
### Ghidra Not Found
```bash
# Check if Ghidra is installed
./scripts/find-ghidra.sh
# Set GHIDRA_HOME if in non-standard location
export GHIDRA_HOME=/path/to/ghidra_11.x_PUBLIC
./scripts/ghidra-analyze.sh ...
```
### Analysis Takes Too Long
```bash
# Set a timeout (seconds)
./scripts/ghidra-analyze.sh --timeout 300 -s ExportAll.java binary
# Skip analysis for quick export
./scripts/ghidra-analyze.sh --no-analysis -s ExportSymbols.java binary
```
### Out of Memory
Edit the `analyzeHeadless` script or set:
```bash
export MAXMEM=4G
```
### Wrong Architecture Detected
Explicitly specify the processor:
```bash
./scripts/ghidra-analyze.sh -p "ARM:LE:32:v7" -s ExportAll.java firmware.bin
```
---
## Tips
1. **Start with ExportAll.java** - It gives you everything and the summary helps orient you
2. **Check the interesting.txt file** - It highlights security-relevant functions automatically
3. **Use jq for JSON parsing** - The JSON exports are designed to be machine-readable
4. **Decompilation isn't perfect** - Use it as a guide, cross-reference with disassembly
5. **Large binaries take time** - Use `--timeout` and consider `--no-analysis` for quick scans