[GH-ISSUE #2298] [Feature] Decompile WebAssembly functions into labeled pseudocode #760

Open
opened 2026-10-15 18:28:04 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2298

Before submitting

  • I searched the open and closed issues and pull requests for a related proposal.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This request does not include credentials, private samples, or sensitive target data.

Area

Native analysis or decompilation

Current limitation

REA's only WebAssembly route is inspect_wasm_artifact (docs/wasm-artifacts.md). It runs caller-supplied WABT 1.0.42 wasm-validate, wasm-objdump and wasm2wat and returns byte identity, validation, sections, import/export forms and the module's decoded WAT. There is no per-function pseudocode, procedure list, call graph or cross-reference operation for WASM.

open_binary recognizes the WASM v1 header and refuses it with wasm_requires_static_inspection before any provider is selected, and raw-binary admits only x86, x86_64, arm and arm64. Stock Ghidra 12.1.4 ships no WebAssembly processor.

The pinned WABT cannot help: WebAssembly/wabt#2769 removed wasm-decompile (merged 2026-06-22) as unmaintained. src/tools/ at tag 1.0.42 has no wasm-decompile.cc (1.0.41 does), and the official 1.0.42 macOS arm64 release asset, whose digest matches third_party/wabt/README.md, ships no wasm-decompile binary. #884 also scoped out an REA-owned WASM parser or decompiler.

Use cases

  • Read what one function in a .wasm compiled from C, C++ or Rust does, without hand-tracing stack-machine WAT.
  • Follow callers, callees and cross-references across a module, as REA already supports for native binaries.
  • Cite pseudocode with Evidence that ties it to the same module SHA-256 that inspect_wasm_artifact reports.

Desired outcome and success criteria

With a configured, compatible WebAssembly decompilation capability, an analyst opens a local .wasm with an explicitly selected provider and uses the existing function operations (procedure listing, pseudocode, assembly, callers/callees, xrefs) through CLI and MCP with equivalent results.

Success criteria:

  • A real-tool lane on a source-owned fixture compiled from C (one exported function, one internal function, one direct call) returns nonempty pseudocode for the export through CLI and MCP; the procedure list contains both functions and the call edge appears.
  • Evidence carries the module SHA-256 (equal to inspect_wasm_artifact's), the decompiler and extension identity, language and compiler spec, and a "decompiler output, not original source or runtime behavior" limitation.
  • Each function result identifies its WASM function index or export name, or states that the link is unavailable.
  • Engine interpretation choices, such as the inferred C stack pointer global or stack growth direction, are recorded in the profile or reported as limitations.
  • Missing, version-mismatched, malformed and content-changed extensions produce typed unavailable diagnostics in CLI, MCP and doctor; unconfigured hosts keep today's refusal and inspect_wasm_artifact guidance. No automatic provider fallback.
  • Invalid or truncated modules and cancellation leave no session, process or temporary project behind.
  • inspect_wasm_artifact and the WABT 1.0.42 pin stay unchanged; docs state supported Ghidra/extension versions and the verified host.

Expected interaction with targets

Read existing files or captured data

Target and scope details (optional)

Targets are explicitly selected local .wasm files. The decompiler would run as a local subprocess (for the Ghidra route, analyzeHeadless in REA's existing ephemeral, read-only project) that reads the module; the module is never executed, emulated or fetched. REA would not download, build, install, patch or vendor Ghidra or the extension; extension code runs inside Ghidra, so it needs the same caller-trusted identity and fingerprint handling as the existing PSP (ghidra-allegrex) and NativeAOT extensions. Out of scope: an REA-owned decompiler, original-source recovery, component-model binaries, and proposals the selected engine cannot decode.

Verification available so far

I can suggest a sample or manual check

Verification details (optional)

Verified from public sources: WABT 1.0.42 tool list and release asset contents, the plugin's releases, license, language definitions and open compatibility issues, and Ghidra 12.1.4's processor list. Not run: importing or decompiling a WASM module with the plugin under Ghidra 12.1.x. Suggested sample: a small C file compiled with clang --target=wasm32 (one exported function calling an internal one), checked with inspect_wasm_artifact for byte identity and with the new route for pseudocode and the call edge.

Alternatives considered

  • Ghidra with nneonneo/ghidra-wasm-plugin as a caller-installed extension (leading option). It reuses REA's Ghidra session and function operations and follows the PSP extension precedent (docs/ghidra-psp.md). GPL-3.0; language Wasm:LE:32:default with default and pos-stack compiler specs; README lists WebAssembly 1.0 opcodes, call/table xrefs and C stack recovery, with limits on inlining and multiple returns. The maintainer confirmed headless batch use with analyzeHeadless plus a decompile script in nneonneo/ghidra-wasm-plugin#20. Blocker: no release targets Ghidra 12.1. v2.4.0 (2025-12-15) ships only a Ghidra 12.0 build; nneonneo/ghidra-wasm-plugin#32 and nneonneo/ghidra-wasm-plugin#34 report that the source fails against 12.1 (checkAncestry moved), and the fix in nneonneo/ghidra-wasm-plugin#33 is unmerged. Users would need a self-built extension until upstream releases. REA would not distribute the plugin, so GPL-3.0 should not reach REA's MIT package; maintainers should confirm.
  • WABT wasm-decompile from 1.0.41 or earlier: removed upstream as unmaintained and fuzz-prone; needs a second WABT pin.
  • WABT wasm2c (still in 1.0.42): emits compilable C for the wasm2c runtime, not analyst pseudocode, and adds no call graph or xrefs.
  • wwwg/wasmdec: MIT, last commit 2018-11-29.
  • Built-in Ghidra support: NationalSecurityAgency/ghidra#4103 ("WebAssembly Support", by the plugin's author) has been open since 2022-03-26; if it lands, no extension is needed.

Possible direction

Treat WebAssembly as a Ghidra provider profile gated on a caller-installed extension under $GHIDRA_INSTALL_DIR/Ghidra/Extensions, mirroring the PSP profile: fingerprint the installed files, require the extension's declared Ghidra version to match the installation, commit identity, language and compiler spec to the analysis profile, and let open_binary admit WASM v1 headers only when that profile is available, otherwise keeping today's refusal. The contract above is engine-neutral, so built-in Ghidra support or another maintained decompiler could satisfy it later. A maintainer scope decision is needed before integration.

References

Originally created by @coygeek on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2298 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related proposal. - [x] I understand the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope) must be reviewed before integration; open questions are welcome here. - [x] This request does not include credentials, private samples, or sensitive target data. ### Area Native analysis or decompilation ### Current limitation REA's only WebAssembly route is `inspect_wasm_artifact` ([docs/wasm-artifacts.md](https://github.com/morluto/rea/blob/main/docs/wasm-artifacts.md)). It runs caller-supplied WABT 1.0.42 `wasm-validate`, `wasm-objdump` and `wasm2wat` and returns byte identity, validation, sections, import/export forms and the module's decoded WAT. There is no per-function pseudocode, procedure list, call graph or cross-reference operation for WASM. `open_binary` recognizes the WASM v1 header and refuses it with `wasm_requires_static_inspection` before any provider is selected, and `raw-binary` admits only x86, x86_64, arm and arm64. Stock Ghidra 12.1.4 ships no WebAssembly processor. The pinned WABT cannot help: [WebAssembly/wabt#2769](https://github.com/WebAssembly/wabt/pull/2769) removed `wasm-decompile` (merged 2026-06-22) as unmaintained. `src/tools/` at tag 1.0.42 has no `wasm-decompile.cc` (1.0.41 does), and the official 1.0.42 macOS arm64 release asset, whose digest matches `third_party/wabt/README.md`, ships no `wasm-decompile` binary. #884 also scoped out an REA-owned WASM parser or decompiler. ### Use cases - Read what one function in a `.wasm` compiled from C, C++ or Rust does, without hand-tracing stack-machine WAT. - Follow callers, callees and cross-references across a module, as REA already supports for native binaries. - Cite pseudocode with Evidence that ties it to the same module SHA-256 that `inspect_wasm_artifact` reports. ### Desired outcome and success criteria With a configured, compatible WebAssembly decompilation capability, an analyst opens a local `.wasm` with an explicitly selected provider and uses the existing function operations (procedure listing, pseudocode, assembly, callers/callees, xrefs) through CLI and MCP with equivalent results. Success criteria: - A real-tool lane on a source-owned fixture compiled from C (one exported function, one internal function, one direct call) returns nonempty pseudocode for the export through CLI and MCP; the procedure list contains both functions and the call edge appears. - Evidence carries the module SHA-256 (equal to `inspect_wasm_artifact`'s), the decompiler and extension identity, language and compiler spec, and a "decompiler output, not original source or runtime behavior" limitation. - Each function result identifies its WASM function index or export name, or states that the link is unavailable. - Engine interpretation choices, such as the inferred C stack pointer global or stack growth direction, are recorded in the profile or reported as limitations. - Missing, version-mismatched, malformed and content-changed extensions produce typed unavailable diagnostics in CLI, MCP and doctor; unconfigured hosts keep today's refusal and `inspect_wasm_artifact` guidance. No automatic provider fallback. - Invalid or truncated modules and cancellation leave no session, process or temporary project behind. - `inspect_wasm_artifact` and the WABT 1.0.42 pin stay unchanged; docs state supported Ghidra/extension versions and the verified host. ### Expected interaction with targets Read existing files or captured data ### Target and scope details (optional) Targets are explicitly selected local `.wasm` files. The decompiler would run as a local subprocess (for the Ghidra route, `analyzeHeadless` in REA's existing ephemeral, read-only project) that reads the module; the module is never executed, emulated or fetched. REA would not download, build, install, patch or vendor Ghidra or the extension; extension code runs inside Ghidra, so it needs the same caller-trusted identity and fingerprint handling as the existing PSP (`ghidra-allegrex`) and NativeAOT extensions. Out of scope: an REA-owned decompiler, original-source recovery, component-model binaries, and proposals the selected engine cannot decode. ### Verification available so far I can suggest a sample or manual check ### Verification details (optional) Verified from public sources: WABT 1.0.42 tool list and release asset contents, the plugin's releases, license, language definitions and open compatibility issues, and Ghidra 12.1.4's processor list. Not run: importing or decompiling a WASM module with the plugin under Ghidra 12.1.x. Suggested sample: a small C file compiled with `clang --target=wasm32` (one exported function calling an internal one), checked with `inspect_wasm_artifact` for byte identity and with the new route for pseudocode and the call edge. ### Alternatives considered - **Ghidra with [nneonneo/ghidra-wasm-plugin](https://github.com/nneonneo/ghidra-wasm-plugin) as a caller-installed extension (leading option).** It reuses REA's Ghidra session and function operations and follows the PSP extension precedent (`docs/ghidra-psp.md`). GPL-3.0; language `Wasm:LE:32:default` with `default` and `pos-stack` compiler specs; README lists WebAssembly 1.0 opcodes, call/table xrefs and C stack recovery, with limits on inlining and multiple returns. The maintainer confirmed headless batch use with `analyzeHeadless` plus a decompile script in nneonneo/ghidra-wasm-plugin#20. **Blocker:** no release targets Ghidra 12.1. v2.4.0 (2025-12-15) ships only a Ghidra 12.0 build; nneonneo/ghidra-wasm-plugin#32 and nneonneo/ghidra-wasm-plugin#34 report that the source fails against 12.1 (`checkAncestry` moved), and the fix in nneonneo/ghidra-wasm-plugin#33 is unmerged. Users would need a self-built extension until upstream releases. REA would not distribute the plugin, so GPL-3.0 should not reach REA's MIT package; maintainers should confirm. - **WABT `wasm-decompile` from 1.0.41 or earlier:** removed upstream as unmaintained and fuzz-prone; needs a second WABT pin. - **WABT `wasm2c` (still in 1.0.42):** emits compilable C for the wasm2c runtime, not analyst pseudocode, and adds no call graph or xrefs. - **[wwwg/wasmdec](https://github.com/wwwg/wasmdec):** MIT, last commit 2018-11-29. - **Built-in Ghidra support:** NationalSecurityAgency/ghidra#4103 ("WebAssembly Support", by the plugin's author) has been open since 2022-03-26; if it lands, no extension is needed. ### Possible direction Treat WebAssembly as a Ghidra provider profile gated on a caller-installed extension under `$GHIDRA_INSTALL_DIR/Ghidra/Extensions`, mirroring the PSP profile: fingerprint the installed files, require the extension's declared Ghidra version to match the installation, commit identity, language and compiler spec to the analysis profile, and let `open_binary` admit WASM v1 headers only when that profile is available, otherwise keeping today's refusal. The contract above is engine-neutral, so built-in Ghidra support or another maintained decompiler could satisfy it later. A maintainer scope decision is needed before integration. ### References - #2258 and the gap analysis in https://github.com/morluto/rea/issues/2258#issuecomment-6130226337 (gap 4) - #884 (WABT inspection lane, closed), #774 (website roadmap parent), #1852 and #1872 (typed WASM refusal and guidance) - WebAssembly/wabt#2769 (`wasm-decompile` removal); [WABT 1.0.42 release](https://github.com/WebAssembly/wabt/releases/tag/1.0.42) - [docs/ghidra-provider.md](https://github.com/morluto/rea/blob/main/docs/ghidra-provider.md), [docs/ghidra-psp.md](https://github.com/morluto/rea/blob/main/docs/ghidra-psp.md), [docs/installation.md#ghidra](https://github.com/morluto/rea/blob/main/docs/installation.md#ghidra) - nneonneo/ghidra-wasm-plugin [v2.4.0 release](https://github.com/nneonneo/ghidra-wasm-plugin/releases/tag/v2.4.0), nneonneo/ghidra-wasm-plugin#32, nneonneo/ghidra-wasm-plugin#33, nneonneo/ghidra-wasm-plugin#34, nneonneo/ghidra-wasm-plugin#20, nneonneo/ghidra-wasm-plugin#35 - NationalSecurityAgency/ghidra#4103
Author
Owner

@matbrgz commented on GitHub (Oct 12, 2026):

Design & Provider Integration Notes

Expanding WebAssembly inspection to function pseudocode aligns with REA's static analysis goals:

  • Provider Route: Ghidra has official WebAssembly modules (community/built-in WASM loader and decompiler). Routing .wasm binaries through the Ghidra lane with explicit wasm:LE:32 architecture allows reusing existing list_procedures, decompile, and call graph tooling.
  • WAT vs C Pseudocode: Raw instruction disassembly is already obtainable via wasm2wat; Ghidra's role should focus on structured high-level C-like pseudocode reconstruction and variable recovery.
  • Related to WASM component model inspection (#2350, PR #2406).
<!-- gh-comment-id:6134298202 --> @matbrgz commented on GitHub (Oct 12, 2026): ### Design & Provider Integration Notes Expanding WebAssembly inspection to function pseudocode aligns with REA's static analysis goals: - **Provider Route:** Ghidra has official WebAssembly modules (community/built-in WASM loader and decompiler). Routing `.wasm` binaries through the Ghidra lane with explicit `wasm:LE:32` architecture allows reusing existing `list_procedures`, `decompile`, and call graph tooling. - **WAT vs C Pseudocode:** Raw instruction disassembly is already obtainable via `wasm2wat`; Ghidra's role should focus on structured high-level C-like pseudocode reconstruction and variable recovery. - Related to WASM component model inspection (#2350, PR #2406).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#760
No description provided.