Skip to content

fw-diff

CI License: BSD-3-Clause

Explain what changed between two firmware images — in plain English.

fw-diff takes two builds of almost anything that executes — Linux/Windows/Mac binaries (ELF/PE/Mach-O), iOS .ipa, Android .apk, Switch NRO/NSO, UEFI firmware volumes, .deb/.rpm/MSI/CAB/DMG packages, raw flash dumps, or extracted directory trees — lifts both through Ghidra headless, matches functions across builds, computes a deterministic structural diff, and then annotates every change with a security-aware, human-readable explanation — using a local LLM by default. No cloud required.

$ fw-diff explain fw-1.4.2.bin fw-1.4.3.bin --arch armv7 --base 0x40000000

  lifting    ██████████ fw-1.4.2  (1,910 functions, 3m12s)
  lifting    ██████████ fw-1.4.3  (1,912 functions, 3m05s)
  matching   ██████████ exact 1,681 · structural 118 · embedding 43 · unmatched 180

┌─ HIGH · parse_header 0x4001a3c0 → 0x4001a4f0 ─────────────────────────────────┐
│ New bound check inserted before memcpy (dst size 0x40 vs len).                │
│ Classifier: bound_change (evidence: cmp #0x40 added; caller of memcpy)        │
│ Hypothesis: CWE-190 fix — integer/bounds hardening. Confidence: medium.       │
└───────────────────────────────────────────────────────────────────────────────┘

  Changed 89 · Added 121 · Removed 28 · new crypto constants in 2 functions
  Reports: out/report.html · out/report.md · out/facts.json

fw-diff HTML report — ranked changes with evidence tables

Why this exists

Every existing binary-diff workflow stops at “these functions changed.” Nobody tells you what changed, why it matters, or whether it is a security fix or a regression — and none of them work headless on symbol-less, multi-arch firmware inside CI.

fw-diff BinDiff Diaphora Ghidra Version Tracking
Headless / CI-friendly yes (policy gates) no partial partial (GUI-bound)
Raw firmware, no symbols first-class weak weak manual effort
Multi-arch (ARM/MIPS/RISC-V/…) yes x86-centric varies yes
Natural-language explanations yes, local LLM no no no
Deterministic, reproducible output yes yes partial no
Offline / air-gapped default n/a n/a n/a
License BSD-3-Clause proprietary GPL Apache-2.0

What you get

  • facts.json — machine-readable diff facts. Deterministic: same inputs → byte-identical output. Safe to gate a release pipeline on.
  • report.html — interactive side-by-side decompilation with per-change explanations, function graph, and evidence links.
  • report.md — the changelog your patch notes were missing.
  • sarif.json — SARIF 2.1 for GitHub code scanning / IDEs (high → warning, medium → note, low → omitted).
  • CLI policy engine — fail CI on classes of change (--fail-on bound_change,new_crypto).

How it works

flowchart LR
    A[old image] --> I[ingest<br/>unpack · arch detect]
    B[new image] --> I
    I --> W1[ghidra-worker<br/>old]
    I --> W2[ghidra-worker<br/>new]
    W1 --> M[matcher<br/>S1 exact → S2 structural → S3 embedding]
    W2 --> M
    M --> D[delta engine<br/>AST diff → ChangeFacts]
    D --> E[explainer<br/>local LLM annotation]
    D --> R[renderers]
    E --> R
    R --> O[facts.json · report.md · report.html]
Loading

The core design rule: facts before narrative. The deterministic diff engine is the source of truth. The LLM only annotates facts it is handed and must cite evidence for every claim — it can never invent a finding. See ADR-0003.

Quickstart

Zero-setup demo (bundled fixture IR — no Ghidra needed):

pipx install fw-diff
fw-diff demo --out out/          # full pipeline on a synthetic firmware pair

Real firmware (Ghidra 11.3+ required — tested against 11.3.2, Java 21):

fw-diff explain old.bin new.bin --embed   # adds the S3 embedding stage (fastembed)
pipx install 'fw-diff[ghidra]'
export GHIDRA_INSTALL_DIR=/opt/ghidra   # or the bundled wheel: pip install <ghidra>/Ghidra/Features/PyGhidra/pypkg/dist/pyghidra-*.whl
fw-diff doctor                          # verifies the environment

fw-diff explain fw-1.4.2.bin fw-1.4.3.bin --base 0x40000000
fw-diff ci fw-1.4.2.bin fw-1.4.3.bin --policy policy.yaml
fw-diff timeline fw-1.4.1.bin fw-1.4.2.bin fw-1.4.3.bin   # release-train diff
fw-diff mcp                                               # agent integration

Runs fully offline with any Ollama-served model; zero API keys needed. Remote OpenAI-compatible endpoints are opt-in via --llm-url. All numeric gates: exit 0/1 (policy), 2 (config/ingest).

Install

Method Command Notes
pip / pipx pipx install fw-diff recommended for analysts
Docker docker run ghcr.io/dilates/fw-diff explain … worker sandboxing built in
from source uv sync && uv run fw-diff --help see CONTRIBUTING

Agent integration (MCP)

fw-diff mcp runs a read-only MCP server on stdio — sessions, facts, and change lists exposed to MCP-compatible agents (editors, pipelines). The analysis pipeline stays CLI-side; the server only reads the local session store.

Status

v0.2.0a1 — implemented. Deterministic core end-to-end (ingest → Ghidra lift → normalize → multi-stage match → delta → classifiers → facts/reports), local-LLM explain layer with evidence-validated claims, SARIF 2.1 output, docker worker sandboxing (ADR-0008), blob-level lift cache, S3 embedding stage (optional), and a nightly eval harness. CI gates: lint/types ×3 Python versions, corpus, byte-reproducibility, docs, real-Ghidra integration, docker-worker sandbox. See docs/ROADMAP.md and docs/PRODUCT_SPEC.md.

Documentation

Doc What's in it
Product spec users, scope, success metrics
Architecture components, data model, process model
Pipeline spec normalization, matching, classifiers — the deep dive
Report format facts.json schema v1
ADRs 10 recorded design decisions
Threat model untrusted-input posture, LLM guardrails
Testing strategy synthetic firmware corpus, eval harness
Getting started walkthrough with real session output

Contributing

We treat this like a product: specs first, ADRs for every decision, corpus-driven tests. Start with CONTRIBUTING.md. Found a vulnerability in fw-diff itself? See SECURITY.md.

License

BSD-3-Clause — see LICENSE.

About

Explain what changed between two firmware images — deterministic Ghidra diff with local-LLM, evidence-cited explanations (BSD-3)

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages