Skip to content

jlucaso1/ghidrs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ghidrs

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.

Status

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.

Install

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.

Workspace layout

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.

Commands

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.

Analysis features

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.

JSON output

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.

Limitations

  • 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_indirect only 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 br targets 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 Unknown in 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.

License

Dual-licensed under MIT or Apache-2.0 at your option.

About

Reverse engineering toolkit for WebAssembly. Parser, IR, pseudo-C decompiler, call graph, struct recovery, forward/backward taint.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages