reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
Apache License 2.0 — see the repository root LICENSE file for the full text.
|
||||
|
||||
Copyright the AnalystAIPack contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
name: scripting-ghidra-headless-analysis
|
||||
description: 'Automates batch reverse engineering by generating Ghidra analyzeHeadless commands and
|
||||
post-analysis script skeletons to import, auto-analyze, and export results for many samples
|
||||
without the GUI. Activates for requests to script Ghidra headless analysis, batch-analyze samples
|
||||
with Ghidra, or generate an analyzeHeadless command and post-script.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- ghidra
|
||||
- automation
|
||||
- headless
|
||||
- batch-analysis
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1059
|
||||
- T1587.001
|
||||
d3fend:
|
||||
- D3-DA
|
||||
- D3-SDA
|
||||
references:
|
||||
- 'Ghidra analyzeHeadless documentation — https://ghidra-sre.org/'
|
||||
- 'Ghidra API (FlatProgramAPI) — https://ghidra.re/ghidra_docs/api/'
|
||||
---
|
||||
|
||||
# Scripting Ghidra Headless Analysis
|
||||
|
||||
## When to Use
|
||||
|
||||
- You need to auto-analyze many samples (or re-run a consistent analysis) without the Ghidra GUI.
|
||||
- You want to generate the `analyzeHeadless` command and a post-script that exports functions,
|
||||
strings, or decompiled output.
|
||||
|
||||
**Do not use** this to run untrusted samples — Ghidra analyzes binaries statically and never
|
||||
executes them, but keep the import directory isolated. This skill prepares automation, it does not
|
||||
run the binary.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Ghidra installation (`analyzeHeadless` in `support/`) and the samples to import.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Ghidra does not execute the sample; still store imports in an isolated, defanged directory.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Generate the headless command
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py command --project /proj --name Batch --import ./samples --script export.py
|
||||
```
|
||||
|
||||
Builds an `analyzeHeadless` invocation with project path, import directory, post-script, and common
|
||||
flags (`-recursive`, `-overwrite`, `-deleteProject` optional).
|
||||
|
||||
### Step 2: Generate a post-analysis script skeleton
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py postscript --out export.py --emit functions,strings
|
||||
```
|
||||
|
||||
Emits a Ghidra Python (Jython) script that iterates functions/strings and writes JSON next to the
|
||||
program.
|
||||
|
||||
### Step 3: Run and collect
|
||||
|
||||
Execute the generated command, then aggregate the per-sample JSON exports.
|
||||
|
||||
### Step 4: Review
|
||||
|
||||
Spot-check a few exports to confirm analysis completeness before scaling up.
|
||||
|
||||
## Validation
|
||||
|
||||
- The generated command references a valid project path, import dir, and existing post-script.
|
||||
- The post-script uses real Ghidra API calls (`currentProgram`, `getFunctionManager`).
|
||||
- Output JSON is produced per imported program.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Forgetting `-overwrite`/`-recursive`, causing skipped or duplicate imports.
|
||||
- Jython 2 syntax constraints in post-scripts (no f-strings).
|
||||
- Project lock conflicts when running multiple headless jobs on one project.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the generator.
|
||||
- Ghidra analyzeHeadless and API docs (linked in frontmatter).
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Reference: Scripting Ghidra Headless Analysis
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` generates a Ghidra `analyzeHeadless` command line and a Jython-compatible
|
||||
post-analysis script that exports functions and/or strings to JSON. It only generates text — it
|
||||
does not run Ghidra or any sample.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | shlex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `command(project, name, import_dir, script, recursive, overwrite)`
|
||||
Builds the analyzeHeadless invocation with common flags.
|
||||
|
||||
### `postscript(out, emit)`
|
||||
Writes a Jython post-script emitting `functions` and/or `strings` to a JSON file next to the
|
||||
program.
|
||||
|
||||
## Generated script notes
|
||||
|
||||
- Targets Jython 2 (no f-strings); uses `currentProgram`, `getFunctionManager`, `getListing`.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py command --project /proj --name Batch --import ./samples --script export.py
|
||||
python scripts/analyst.py postscript --out export.py --emit functions,strings
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Ghidra analyzeHeadless documentation — https://ghidra-sre.org/
|
||||
- Ghidra FlatProgramAPI / program model — https://ghidra.re/ghidra_docs/api/
|
||||
- Headless batch-analysis workflow from official Ghidra `support/analyzeHeadlessREADME.html`
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate Ghidra analyzeHeadless commands and post-analysis script skeletons.
|
||||
|
||||
`command` builds an analyzeHeadless invocation; `postscript` emits a Jython-compatible Ghidra
|
||||
script that exports functions and/or strings to JSON. This tool only generates text — it does not
|
||||
run Ghidra or any sample.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import shlex
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
FUNCTIONS_SNIPPET = '''
|
||||
def emit_functions():
|
||||
fm = currentProgram.getFunctionManager()
|
||||
out = []
|
||||
for f in fm.getFunctions(True):
|
||||
out.append({
|
||||
"name": f.getName(),
|
||||
"entry": str(f.getEntryPoint()),
|
||||
"size": f.getBody().getNumAddresses(),
|
||||
})
|
||||
return out
|
||||
'''
|
||||
|
||||
STRINGS_SNIPPET = '''
|
||||
def emit_strings():
|
||||
from ghidra.program.model.data import StringDataType
|
||||
out = []
|
||||
listing = currentProgram.getListing()
|
||||
data = listing.getDefinedData(True)
|
||||
for d in data:
|
||||
if d.getDataType() and "string" in d.getDataType().getName().lower():
|
||||
val = d.getValue()
|
||||
if val is not None:
|
||||
out.append(str(val))
|
||||
return out
|
||||
'''
|
||||
|
||||
|
||||
def command(project: str, name: str, import_dir: str, script: str | None,
|
||||
recursive: bool, overwrite: bool) -> dict:
|
||||
parts = ["analyzeHeadless", project, name, "-import", import_dir]
|
||||
if recursive:
|
||||
parts.append("-recursive")
|
||||
if overwrite:
|
||||
parts.append("-overwrite")
|
||||
if script:
|
||||
parts += ["-postScript", script]
|
||||
cmd = " ".join(shlex.quote(p) for p in parts)
|
||||
return {"command": cmd, "note": "run from Ghidra's support/ directory"}
|
||||
|
||||
|
||||
def postscript(out: str, emit: list[str]) -> dict:
|
||||
lines = ["# Ghidra post-analysis script (Jython 2). Generated skeleton.",
|
||||
"import json", "import os", ""]
|
||||
funcs = []
|
||||
if "functions" in emit:
|
||||
lines.append(FUNCTIONS_SNIPPET)
|
||||
funcs.append('"functions": emit_functions()')
|
||||
if "strings" in emit:
|
||||
lines.append(STRINGS_SNIPPET)
|
||||
funcs.append('"strings": emit_strings()')
|
||||
lines.append("result = {%s}" % ", ".join(funcs) if funcs else "result = {}")
|
||||
lines.append('out_path = currentProgram.getExecutablePath() + ".ghidra.json"')
|
||||
lines.append('f = open(os.path.basename(out_path), "w")')
|
||||
lines.append("f.write(json.dumps(result, indent=2))")
|
||||
lines.append("f.close()")
|
||||
Path(out).write_text("\n".join(lines), encoding="utf-8")
|
||||
return {"output": out, "emits": emit}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Generate Ghidra headless automation")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
pc = sub.add_parser("command")
|
||||
pc.add_argument("--project", required=True)
|
||||
pc.add_argument("--name", required=True)
|
||||
pc.add_argument("--import", dest="import_dir", required=True)
|
||||
pc.add_argument("--script")
|
||||
pc.add_argument("--recursive", action="store_true", default=True)
|
||||
pc.add_argument("--overwrite", action="store_true", default=True)
|
||||
pp = sub.add_parser("postscript")
|
||||
pp.add_argument("--out", required=True)
|
||||
pp.add_argument("--emit", default="functions,strings")
|
||||
args = parser.parse_args(argv)
|
||||
if args.cmd == "command":
|
||||
print(json.dumps(command(args.project, args.name, args.import_dir, args.script,
|
||||
args.recursive, args.overwrite), indent=2))
|
||||
else:
|
||||
print(json.dumps(postscript(args.out, [e.strip() for e in args.emit.split(",")]),
|
||||
indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user