Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,85 @@ jobs:
run: mypy src

- name: Pytest
run: pytest -q
run: pytest -q

ghidra-script-compile:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ghidra:
- version: "11.0"
tag: "Ghidra_11.0_build"
asset: "ghidra_11.0_PUBLIC_20231222.zip"
- version: "11.4.2"
tag: "Ghidra_11.4.2_build"
asset: "ghidra_11.4.2_PUBLIC_20250826.zip"
- version: "12.1"
tag: "Ghidra_12.1_build"
asset: "ghidra_12.1_PUBLIC_20260513.zip"

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Check Ghidra script changes
id: changes
shell: bash
run: |
set -euxo pipefail
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -E '^(src/dwarffi/ghidra_scripts/|tests/test_ghidra2isf.py|\.github/workflows/ci\.yml)'; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi

- uses: actions/setup-java@v4
if: steps.changes.outputs.changed == 'true'
with:
distribution: temurin
java-version: "21"

- name: Cache Ghidra ${{ matrix.ghidra.version }}
if: steps.changes.outputs.changed == 'true'
uses: actions/cache@v4
with:
path: .cache/ghidra/${{ matrix.ghidra.asset }}
key: ghidra-${{ matrix.ghidra.asset }}

- name: Download Ghidra ${{ matrix.ghidra.version }}
if: steps.changes.outputs.changed == 'true'
shell: bash
run: |
set -euxo pipefail
mkdir -p .cache/ghidra
if [ ! -f ".cache/ghidra/${{ matrix.ghidra.asset }}" ]; then
curl -L \
-o ".cache/ghidra/${{ matrix.ghidra.asset }}" \
"https://github.com/NationalSecurityAgency/ghidra/releases/download/${{ matrix.ghidra.tag }}/${{ matrix.ghidra.asset }}"
fi

- name: Compile Ghidra scripts against ${{ matrix.ghidra.version }}
if: steps.changes.outputs.changed == 'true'
shell: bash
run: |
set -euxo pipefail
rm -rf /tmp/ghidra-ci /tmp/ghidra-script-classes
mkdir -p /tmp/ghidra-ci /tmp/ghidra-script-classes
unzip -q ".cache/ghidra/${{ matrix.ghidra.asset }}" -d /tmp/ghidra-ci
GHIDRA_HOME="$(find /tmp/ghidra-ci -maxdepth 1 -type d -name 'ghidra_*' | head -n 1)"
test -n "$GHIDRA_HOME"
find "$GHIDRA_HOME" -name '*.jar' -print > /tmp/ghidra-jars.txt
test -s /tmp/ghidra-jars.txt
javac -proc:none \
-cp "$(paste -sd: /tmp/ghidra-jars.txt)" \
-d /tmp/ghidra-script-classes \
src/dwarffi/ghidra_scripts/Ghidra2ISF.java \
src/dwarffi/ghidra_scripts/ISF2Ghidra.java

- name: Skip Ghidra compile
if: steps.changes.outputs.changed != 'true'
run: echo "No Ghidra script or workflow changes detected; skipping Ghidra compile matrix."
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ as defined by the toolchain and target architecture.
- **Linux / embedded workflows:** ISF generated from **DWARF** in ELF binaries (e.g., via `dwarf2json`).
- **Windows workflows:** ISF generated from **PDB** symbols (e.g., Volatility3-style Windows ISFs generated from PDBs).
- **MacOS / Mach-O workflows:** ISF generated from DWARF in Mach-O binaries.
- **Ghidra workflows:** ISF generated from the active Ghidra program using the bundled `Ghidra2ISF.java` script.


Read more about `dwarf2json` and ISF in the [dwarf2json README](https://github.com/volatilityfoundation/dwarf2json).
Expand Down Expand Up @@ -138,6 +139,46 @@ ffi.inspect_layout("struct _UNICODE_STRING")

---

## Ghidra ISF scripts

`dwarffi` includes Ghidra scripts under `src/dwarffi/ghidra_scripts`.

Export the active Ghidra program to ISF:

```bash
analyzeHeadless /tmp/ghidra-project DffiExport \
-import ./firmware.elf \
-scriptPath ./src/dwarffi/ghidra_scripts \
-postScript Ghidra2ISF.java ./firmware.isf.json \
-deleteProject
```

The exporter writes `base_types`, `user_types`, `enums`, `typedefs`, `symbols`,
and `functions` in the same ISF shape consumed by `DFFI`.

Import an ISF into the active Ghidra program:

```bash
analyzeHeadless /tmp/ghidra-project DffiImport \
-import ./firmware.elf \
-scriptPath ./src/dwarffi/ghidra_scripts \
-postScript ISF2Ghidra.java ./firmware.isf.json
```

The importer creates a `/ISF` data type category, imports base types, structs,
unions, enums, typedefs, arrays, pointers, and bitfields, then applies labels,
data types, and function signatures when addresses are present.

Optional script arguments for both scripts:

```bash
--types-only
--no-symbols
--no-functions
```

---

## CFFI-style `cdef`

We do support inline C definitions that compile down to DWARF and ISF on the fly. This is ideal for quick prototyping or when you have a small struct definition that isn't already in your ISF.
Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ version-file = "src/dwarffi/_version.py"
[tool.hatch.build]
artifacts = [
"src/dwarffi/bin/dwarf2json",
"src/dwarffi/bin/dwarf2json.exe"
"src/dwarffi/bin/dwarf2json.exe",
"src/dwarffi/ghidra_scripts/Ghidra2ISF.java",
"src/dwarffi/ghidra_scripts/ISF2Ghidra.java"
]

[tool.hatch.version.raw-options]
Expand All @@ -88,4 +90,4 @@ module = "dwarffi._version"
ignore_missing_imports = true

[project.scripts]
dwarf2json = "dwarffi.cli:main"
dwarf2json = "dwarffi.cli:main"
Loading
Loading