Static-analysis provider evaluation

July 23, 2026 ยท View on GitHub

Status: the Ghidra read-only analysis provider is shipped on Linux x64 and has an experimental Windows x64 P0 for approved native PE applications. It validates an exact bring-your-own Ghidra 12.1.2/JDK 21 environment, resolves a provider/version/profile commitment, runs one isolated read-only headless import, and publishes 19 operation-level capabilities after the authenticated post-analysis handshake.

The provider-neutral target, provider registry, deterministic target binding, analysis-profile commitment, Evidence provenance, snapshot v2, and bounded provider-process lifecycle foundations are implemented. The Ghidra launcher, packaged Java bridge, doctor/setup projection, bounded client lifecycle, and multi-target real Linux verifier, curated Windows CI, and controlled real Windows verifier are also implemented. Program identity, procedure/string/symbol inventory, memory blocks, address/name and containing-procedure resolution, bounded search, function metadata, decompilation, assembly, resolved calls, typed references, xrefs, CFG, and function dossiers are admitted. A provider is not a drop-in replacement for Hopper: every capability is mapped explicitly, with truthful unavailable, unknown, or degraded results where the engine cannot provide equivalent semantics.

ADR-0001 fixes the provider registry, deterministic selection, target binding, analysis profile, snapshot migration, and compatibility semantics that implementation must follow.

Approved Ghidra v1 boundary

  • Keep the existing provider-neutral CLI and MCP tool names.
  • Bind one deep-analysis provider to a target for the target's lifetime; never fail over silently between Hopper and Ghidra.
  • Start with bring-your-own Ghidra and a compatible Java runtime. Setup must not install or upgrade Java.
  • Run Ghidra headlessly in an owned process with a private temporary project, bounded startup/analysis/request deadlines, cancellation, and cleanup.
  • Prefer a packaged Java bridge loaded through Ghidra's script path. PyGhidra remains useful for prototypes but is not a mandatory production dependency.
  • Treat authenticated ping and shutdown as lifecycle proof only. Inventory, xref, CFG, and decompilation claims require separately admitted operation contracts and real-provider conformance.
  • Implement read-only inventories, assembly, decompilation, function metadata, calls, references, containment, and bounded search first.
  • Report GUI cursor/navigation and persistent mutation operations as unavailable until their semantics and project ownership are explicitly designed.
  • Verify real claims on Linux with at least two distinct source-owned binaries. Verify the Windows host claim with the deterministic source-owned x86-64 PE fixture and all admitted operations; compare normalized semantic facts rather than provider-specific pseudocode text.

Shipped foundation boundary

GHIDRA_INSTALL_DIR must identify an extracted official 12.1.2 release; optional JAVA_HOME must identify a 64-bit full JDK 21, otherwise doctor probes java/javac or java.exe/javac.exe from PATH. Linux accepts x86, x86-64, ARM, and ARM64 ELF, PE, and Mach-O executable targets. Windows P0 accepts only native x86-64 PE applications and rejects DLLs, managed images, unknown roles, other formats, and other architectures before launch.

The launcher creates one ephemeral runtime root with project, home/cache/config/data/temp, logs, descriptor, endpoint, target snapshot, and ownership manifest. It passes -readOnly, -deleteProject, a 300-second per-file analysis limit, two CPUs, and a 2 GiB heap; inherited Java option injection variables are cleared. On Linux, the mode-0600 descriptor carries the random token without exposing it in argv or environment and the Java bridge binds a mode-restricted Unix socket. Windows uses authenticated IPv4 loopback with a strict token-free endpoint record. Both transports report actual Ghidra/language/compiler/analysis/import-digest metadata, accept only the exact authenticated ping, shutdown, and ten inventory plus nine function-analysis methods, and deletes the socket. Close, cancellation, timeout, malformed protocol, or process exit stops the owned process resources and removes the entire runtime root. Windows P0 uses bounded taskkill tree termination and explicitly does not claim Job Object, private-DACL, or reparse-point authority; see Windows Ghidra P0.

The provider catalog lists only the 19 proved Ghidra operations. GUI cursor, navigation, and mutation operations remain absent; the router therefore reports them unavailable instead of borrowing Hopper semantics or inferring capability from a successful import.

Admitted inventory semantics

ConcernGhidra contract
Program identityOne analyzeHeadless import produces exactly one Program; list_documents therefore returns exactly one name.
AddressesDefault memory uses lowercase 0x hexadecimal. Non-default and external spaces use <percent-encoded-space>:0x<hex> and remain round-trippable. The handshake commits image base and default address-space name.
Symbolslist_names includes address-bearing memory and external symbols, including dynamic symbols, while excluding variable and no-address namespace records. Each item reports primary, dynamic, external, symbol type, and source facts.
ProceduresBoth non-external and external functions are listed. A local thunk remains distinct from its resolved target; exact and qualified name lookup fails on ambiguity rather than guessing.
StringsOnly Ghidra-defined string Data is observed. Items report charset, byte length, and whether a required null terminator is missing. The API cannot distinguish a present terminator from fixed/Pascal layouts when no terminator is missing, so that state is named present_or_not_required.
MemoryMemory-block end addresses are exclusive. Read/write/execute, initialization, overlay, address space, and image base are direct Ghidra observations.
PaginationList limits are 500 and search limits are 100. Pages commit exact totals and advancing offsets; an inventory above one million items fails rather than returning a partial result labeled exhaustive.
SearchLiteral search scans the immutable inventory with a 1,000,000-unit cumulative work budget. Regex uses a conservative finite Java-regex subset with 10,000 static paths, 4,096 UTF-16 code units per candidate, and the same cumulative budget. Exceeding a budget fails explicitly; returned values identify truncation.
Analysis stateThe socket is exposed only after default auto-analysis. A 300-second analysis timeout fails target opening; ordinary established requests have a 10-second client deadline and every response has a 1 MiB ceiling.

Admitted function-analysis semantics

ConcernGhidra contract
Decompiler lifetimeOne persistent DecompInterface is opened for the imported Program and disposed during bridge shutdown. Each native decompile has a 30-second deadline; the socket leaves a bounded 35-second projection window. External functions or functions without bodies return null; timeout, cancellation, and native failure remain distinct.
SerializationA bounded FIFO admits at most 32 active-plus-queued requests and sends one Program request at a time. Queue wait counts against the caller's deadline, and queued cancellation is prompt. This is an adapter safety commitment, not a claim that every Ghidra API is thread-safe.
Function identityEvery function result carries the entry address and Ghidra FunctionManager classification for external, thunk, and resolved thunk target. These are observations, not proof that unresolved targetless calls have been recovered.
Assembly and pseudocodeAssembly is bounded Ghidra Listing text; pseudocode is bounded Ghidra decompiler output. Neither is original source, and cross-provider comparison never treats Hopper and Ghidra text as equal or unequal semantic facts.
Instruction fast pathread_function_instructions returns one offset-paginated raw Listing window without invoking the decompiler or whole-program name/string inventories. It reports scan work, exact-or-unknown totals, truncation, and continuation metadata.
Calls and referencesCallers/callees contain only resolved functions. Reference edges preserve exact ReferenceManager type and call/jump/data/read/write/indirect/computed/conditional/terminal/external facts. Targetless computed flow remains unknown. Synthetic entry-point references without actionable memory sources are omitted explicitly.
CFGDossiers use BasicBlockModel and retain only non-call successors inside the function body. CFG topology is address-normalized for comparison; provider-specific block construction remains a declared difference.
BoundsDirect assembly fails above 100,000 instructions or the wire ceiling. Dossiers scan at most 5,000 requested instructions, return bounded independent pages, mark unknown totals after an incomplete scan, and paginate pseudocode by Unicode code points.

npm run verify:ghidra compiles the versioned C oracles into x86-64 debug and stripped ELF, AArch64 ELF, x86-64 PE, and x86-64 Mach-O targets. It proves all 19 operations, external functions, resolved thunks, exports, stripped-name behavior, direct and targetless indirect calls, typed references, strings/xrefs, multi-block CFG, semantic enhanced workflows, cancellation, deadlines, serialized concurrency, malformed-target rejection, profile identity, and process/project cleanup against real Ghidra 12.1.2. Unit fixtures separately cover analysis/decompile timeouts, process exit, queue saturation, and malformed wire output.

npm run verify:ghidra:windows generates a deterministic source-owned native x86-64 PE application, proves all 19 operations through the production Windows launcher and loopback transport, checks target/snapshot/import SHA-256 linkage, and requires project, endpoint, process, and runtime cleanup. Passing that lane proves only the documented P0 boundary, not the remaining Windows security gates.

Shared provider-process foundation

src/process/ now provides the mechanisms that a long-lived Hopper or Ghidra adapter genuinely shares: POSIX run-token-authenticated process-group ownership, ephemeral temporary runtime roots, one absolute startup deadline, correlated request timeout/cancellation cleanup, bounded stdout and stderr retention with exact byte counts, process-exit diagnostics, and bounded TERM-to-KILL shutdown. Reusable fixtures exercise exit, timeout, cancellation, graceful termination, forced termination, double-close, spawn failure, and resource release. Windows P0 adds bounded process-tree termination as an explicitly weaker host backend; Job Object ownership remains unimplemented.

The foundation does not define a bridge schema, socket framing, health payload, analysis model, or shutdown acknowledgement. Hopper keeps its authenticated NDJSON-over-Unix-socket protocol in src/hopper/; Ghidra has a separate strict NDJSON protocol implemented by the packaged Java HeadlessScript and reuses only the generic process mechanisms.

Shortlist

ProviderLicense / automation surfaceWhat it bringsREA fit and blockers
GhidraApache-2.0 source license; analyzeHeadless, Java APIs, and PyGhidraBroad static analysis, many processors and formats, scripting, and project/database workflowsRead-only analysis shipped: exact BYO checks, isolated state, bounded process and queue, packaged bridge, 19 admitted inventory/function operations, and real ELF/PE/Mach-O x86-64 plus AArch64 conformance. GUI and mutation semantics remain intentionally unavailable.
Rizin / rz-pipeRizin repository contains LGPL-3.0 and GPL-3.0 components; rizin, rz-bin, and language bridges through rzpipePortable CLI analysis, disassembly/debugging, many architectures and file formats, JSON command outputGood candidate for a process-backed Linux provider and fast metadata fallback. License/component inventory must be preserved; command output needs version-pinned parsers and semantic conformance before evidence is trusted.
LIEFApache-2.0; C++, Python, and other bindingsDeterministic parsing and modification of ELF, PE, Mach-O, COFF, and related executable formats; headers, sections, symbols, relocations, and functionsBest near-term complement, not a decompiler replacement. It can cover format metadata and artifact evidence without a long-lived analysis process; function semantics, pseudocode, CFG, and cross-reference parity remain out of scope unless separately demonstrated.
Binary NinjaAPI/documentation components are MIT, while the analysis product is licensed by edition; commercial, Ultimate, or Headless license is required for headless automationPython/Core/C++/Rust APIs, headless loading, IL layers, function analysis, plugins, and configurable analysisStrong technical fit for a native provider, especially function dossiers. Commercial licensing, license-secret handling, native runtime packaging, and multithreaded lifecycle rules are material deployment blockers.
  1. Use the implemented explicit provider registry and target binding without changing Hopper behavior.
  2. Maintain the admitted Ghidra function boundary through the shared conformance corpus and add formats or semantics only after real proof.
  3. Connect Electron/native-add-on application findings to the selected native analysis provider without introducing provider-prefixed tools.
  4. Evaluate LIEF or Rizin later as complementary metadata/disassembly providers, and Binary Ninja as an optional licensed provider, using the same admission gate.

Required admission gate

Before implementation, an adapter proposal must provide:

  • a capability matrix covering supported, unsupported, and degraded results;
  • provider identity, version, analysis profile, target digest, authority, limitations, and deterministic locations in every Evidence v2 record;
  • bounded subprocess or library lifetime, cancellation, timeouts, and cleanup;
  • actionable local diagnostics that retain paths, digests, mismatch locations, and provider metadata while redacting credentials, authorization headers, license secrets, and other genuine secrets;
  • the same source-owned fixture corpus across architectures and formats;
  • repeatable checks for function identity, addresses, strings, names, xrefs, CFG, and pseudocode wherever those capabilities are claimed.

These gates turn the accepted direction into verified capabilities rather than prematurely claiming equivalence or redistribution support.

Primary-source notes