wre is a command-line reverse engineering toolkit for WebAssembly binaries.
It parses a module, lifts each function to an expression-tree IR, runs
several static analyses (xrefs, name recovery, struct-layout recovery,
type hints, forward and backward taint, constant propagation), and
produces human- and machine-readable output.
The CLI is designed primarily for consumption by LLM agents: JSON output is the default on the analysis commands, stdout is data, stderr is logs, and each query is bounded so it fits in a reasonable context window.
Usable for exploratory reverse engineering of real-world modules. Tested against large Emscripten-built binaries (several MB, thousands of functions). Not a general-purpose decompiler — the pseudo-C output is a reading aid, not a recompilable source.
Requires Rust 1.85+ (edition 2024).
git clone https://github.com/jlucaso1/ghidrs
cd ghidrs
cargo build --release
The wre binary will be at target/release/wre.
The workspace is split into six crates so analyses can be reused from other tools.
wre-core Module, Function, Address types, plus the parsed
Module struct used throughout.
wre-parse Streaming parser (wraps wasmparser) and a
name-section rewriter.
wre-ir Expression-tree IR, stack-to-tree lifter,
constant propagation pass.
wre-analyze Static analyses: call graph with direct and
resolved-indirect edges, name recovery, structural
patterns, struct recovery, struct merge across
the call graph, per-local type hints, import
classification catalog, forward and backward
taint analysis, data-segment classification,
shared analysis context.
wre-decompile Pseudo-C emitter consuming wre-ir.
wre-cli The `wre` binary.
Only wre-cli depends on clap/anyhow. Library crates can be pulled in
independently by downstream tooling.
Run wre <subcommand> --help for full argument lists.
wre info <wasm> Module summary.
wre funcs <wasm> Function listing.
wre strings <wasm> Printable strings in data segments.
wre rodata <wasm> [--elements] Data-segment classification; dump
element (dispatch) tables.
wre rename <wasm> Name recovery report.
wre refs <wasm> --value N Find functions that reference a
constant (useful for tracing
call_indirect table indices or
magic numbers).
wre disas <wasm> --func <sel> Disassembly.
wre decompile <wasm> --func <sel> Pseudo-C. Flags: --rename --hints
--const-prop --full.
wre api <wasm> [--callers] Classify imports and optionally
list callers (direct + resolved
indirect).
wre xrefs <wasm> --func <sel> Callers or callees.
wre slice <wasm> --from <sel> Forward/backward reachability in
the call graph.
wre struct <wasm> --func <sel> Recovered struct layouts per local.
wre struct <wasm> --merge Cross-function struct merge.
wre taint <wasm> --from <sel>:<param> Forward taint.
wre taint-back <wasm> --from-import <imp>:<arg>
Backward taint from a sink.
wre inspect <wasm> --func <sel> One-shot JSON dossier bundling
metadata, signature, xrefs,
struct layouts, taint summaries,
decompile text, and LLM hints.
--bundle a,b,c analyses multiple
functions with cached context.
wre export <wasm> Consolidated JSON export.
wre rewrite <wasm> --out <path> Write a new .wasm with recovered
names injected into the name
section.
Parsing. Section-by-section, streaming, via wasmparser. Passive data
segments commonly emitted by threaded Emscripten builds are resolved by
scanning every function for memory.init instructions to recover the
virtual address of each segment.
IR and lifter. Expression-tree IR with structured control flow preserved
(block/loop/if). Synthetic locals are introduced for block-valued
constructs so return values cross scope boundaries cleanly. local.tee
is modelled as an assignment-expression. A separate constant-propagation
pass folds provable constants; downstream analyses usually run against
the constant-propagated IR.
Call graph. Direct calls are collected on a raw-operator pass. A second
pass lifts the IR, runs constant propagation, and resolves any
call_indirect whose index is a concrete constant against the element
table. Emscripten invoke_* trampolines are recognised as
direct-dispatch shims: a call to invoke_foo(N, args...) where N is a
constant becomes a resolved-indirect edge to table[N]. Slicing and
reachability queries cross resolved-indirect edges automatically.
Name recovery. Three sources, ordered by confidence: the name custom
section (when present), the export section, and a string-xref heuristic
that names functions after strings they reference in rodata.
Structural-pattern recognition (thunks, getters, setters, constant
returns, identity functions, abort stubs) yields additional high-
confidence names with no text input.
Struct recovery. Per-local, collects every (offset, width, read/write, dominant-type) tuple from *(T*)(base + const) load/store patterns.
Cross-function merging uses a union-find over call-graph edges to
reconstruct shared struct shapes, bounded by a cluster cap to avoid
collapse through widely-shared helpers.
Type hints. Per-local pointer-vs-scalar inference driven by usage: any local that appears as a load/store base acquires a pointee-type hint derived from the access width. These annotate the decompile output as comments; they do not rewrite expressions.
Taint. Forward and backward, offset-sensitive. The forward driver is a bounded inter-procedural BFS with memoisation, iterating a small fixpoint so callee return values propagate to callers. The backward driver reverses the direction: given a sink (an import argument or a function parameter), it identifies every function along the reverse call chain whose writes land there, reporting stores, local assignments, and bulk memory writes.
Import classification. A built-in catalog assigns semantic categories to
the common Emscripten and WASI runtime imports (threading, time,
filesystem, memory, scheduler, crypto-adjacent C++ EH, embind, asm
const, etc.). Application-specific imports are reported as unknown
and left for the caller to classify.
Rewriter. wre rewrite produces a new .wasm with recovered function
names injected into a fresh name custom section, so downstream tools
that already consume the name section (Chrome DevTools, wasm2wat,
other RE tools) pick up the recovered labels automatically. Optional
--locals synthesises names for pointer-typed locals based on the
type hints.
Analysis commands that an LLM is likely to consume emit JSON by default
(inspect, taint, taint-back, refs --json, export). Text
output is available via --text where applicable. The schema is stable
within a minor version; breaking changes will be noted in the
release notes.
wre inspect --func X returns a single dossier. wre inspect --bundle a,b,c returns {reports: [...], errors: [...], count: N} and reuses
the analysis context across selectors for amortised cost.
- The tool treats type hints as advisory. Locals reused for different roles during a function body may be marked as pointer when they are scalar at a given point, or vice versa. Full per-SSA-value type inference is future work.
- Backward taint and call-graph slicing cross
call_indirectonly when the index is a constant (directly or after constant propagation). Dynamic dispatch through vtables stops the trail. - Forward taint propagates return values through up to three fixpoint iterations; deeper chains or transformations that widen to multi-source taint may lose precision.
- The pseudo-C output is not compilable. Goto-based lowering of
brtargets is used rather than reconstructing break/continue patterns. Type widths and signedness reflect the observed WASM ops and are not guaranteed to be C-correct. - SIMD, atomic, and reference-type operators fall back to a
pass-through
Unknownin the IR when not otherwise handled, and are rendered as comments in the decompile output. - The import catalog is Emscripten/WASI-focused. Applications with custom host imports need to extend it via their own wrapper.
Dual-licensed under MIT or Apache-2.0 at your option.