Artifact for the ISSTA 2025 paper "Wemby's web: Hunting for Memory Corruption in WebAssembly" (10.1145/3728937).
It analyzes a live site's WebAssembly, flags the functions reachable from
attacker-controlled input, and fuzzes them with AFL++ under shadow-memory
corruption oracles. The stages are decoupled and communicate only through the
databases, keyed by page id (pid) — analyze once, fuzz a pid later.
@ARTICLE{Draissi2025-vc,
title = {Wemby’s web: Hunting for Memory Corruption in {WebAssembly}},
author = {Draissi, Oussama and Cloosters, Tobias and Klein, David and
Rodler, Michael and Musch, Marius and Johns, Martin and Davi,
Lucas},
journaltitle = {Proceedings of the ACM on Software Engineering},
publisher = {Association for Computing Machinery (ACM)},
date = {2025-06-22},
articleno = {ISSTA059},
doi = {10.1145/3728937},
url = {https://dl.acm.org/doi/10.1145/3728937},
}Podman + podman compose. Nothing else on the host — Node, Rust, wabt, AFL++
and Python all run inside the containers. make is optional (see the table
below for the raw commands).
For analyze only: fetch the Foxhound browser once.
make foxhound # or: ./browser/get-foxhound.shThe ~83 MB binary is a GitHub Release asset, not stored in git; the script
verifies its SHA-256, unpacks into browser/foxhound/, and is idempotent. See
browser/README.md for the FOXHOUND_URL / FOXHOUND_SHA256 overrides. To use
a build that lives elsewhere: FOXHOUND_PATH=/path/to/foxhound/foxhound make analyze url=…. (setup, fuzz and fuzz-example do not need Foxhound.)
cp .env.example .env # one-time; Compose reads it
make setup # 1. start backing services
make analyze url=https://example.com # 2. analyze a site's WASM
make fuzz pid=<pid> name=<label> # 3. fuzz a crawled page idsetup— MariaDB (_wemby), MongoDB (wemby-findings) and the findings collector; schema auto-seeded, all healthchecked.analyze— drives Foxhound, instruments every WebAssembly module the page loads, records calls/imports/snapshots into_wemby(taint findings go to MongoDB), then runs the taint-analysis stage that flags the fuzzable functions. Prints the page id (pid) you need for step 3.fuzz— rebuilds that page's modules into awasm2c+ AFL++ harness and fuzzes it.namelabels the output dir (defaults to thepid).
No database, just want to see fuzzing work:
make fuzz-example # fuzz example/twitch.tv (no DB)Tear everything down:
make clean # ⚠ removes containers + volumesmake |
equivalent |
|---|---|
make setup |
podman compose up -d |
make analyze url=… |
podman compose --profile analyze run --rm analyzer --url=… then … run --rm analysis |
make analyze-gui url=… |
podman compose --profile analyze-gui run --rm analyzer-gui --gui --url=… then … run --rm analysis |
make fuzz pid=… name=… |
podman compose --profile fuzz run --rm fuzzer <pid> <name> |
make fuzz-example |
podman compose --profile fuzz-example run --rm fuzzer-example |
make clean |
podman compose down -v |
Profile-gated services need the explicit --profile <name>: podman compose does
not auto-activate a service's profile from run the way Docker Compose v2
does.
For sites that need a human to drive them (log in, click around, trigger the
WASM-heavy flows), analyze headful instead of the headless ANALYZE_SECONDS
dwell — instrumentation continues until you close the window:
make analyze-gui url=https://example.comNeeds a host X server (X11 or Xwayland); make analyze-gui forwards DISPLAY,
mounts the X11 socket, and runs the container browser as your host user
(userns_mode: keep-id) so it can open the display under rootless podman. If
your X server still refuses, run xhost +local: once.
To skip the X11 plumbing, run the analyzer on the host against the setup
services on their published 127.0.0.1 ports:
cd analyzer && npm install
FOXHOUND_PATH=../browser/foxhound/foxhound node analyze.js --gui --url=https://example.com--gui implies wait-for-close; pass --waitClose (or WAIT_FOR_CLOSE=1) for the
same unlimited-time behavior headless.
make analyze prints the new page id as it works. If you lose it, list crawled
pages (the pages table stores host/path, not one URL column):
podman compose exec mariadb \
mariadb -uwemby -pwemby _wemby \
-e "SELECT pid, host, path, status, finished FROM pages ORDER BY pid DESC;"Output goes to ./fuzzer-output (FUZZ_OUTPUT_DIR); crashes for each fuzzed
function land in:
fuzzer-output/<name>/<name>_0/fuzz/<fn>_fuzz/default/crashes/
A saved crash is one of two kinds:
sig:11(SIGSEGV) — an out-of-bounds linear-memory access trapped by the wasm-rt guard page (a wild / off-the-end pointer).sig:06(SIGABRT) — a shadow-memory oracle detection: the access hit a poisoned byte. PrintsBUG FOUND THROUGH WAT ORACLES, aREASON:(BAD HEAP ACCESS,USE AFTER FREE,STACK BUFFER OVERFLOW (redzone), …) and a symbolized backtrace, then aborts.
Turn crashes into self-contained reproducible HTML (runs inside the fuzzer image, no host Python needed):
podman compose --profile fuzz-example run --rm --entrypoint python3 fuzzer-example \
generate_html.py src/<name>
cd fuzzer-output/<name> && python3 -m http.server # then open /htmlEach build-<fn>.sh also has a repro-<fn>.sh that rebuilds the harness as a
single-shot driver with the oracle logging on and re-drives the captured call:
podman compose --profile fuzz run --rm --entrypoint bash fuzzer -c \
'cd /fuzzer/src/twitch.tv/twitch.tv_0/fuzz && ./repro-f1414.sh'The bundled twitch.tv capture is a benign recorded call, so it ends in a wasm
trap and the driver exits cleanly. A captured input that writes into a redzone
stops with BUG FOUND THROUGH WAT ORACLES and aborts. See
fuzzer/README.md.
Each interesting function is built three ways so you can A/B the technique:
full (default — analysis-guided, snapshot-restored), no_analysis, and
no_snapshot. Set FUZZ_VARIANT (full | no_analysis | no_snapshot) and
AFL_V (seconds per target) in .env.
| Path | What it is |
|---|---|
Makefile, docker-compose.yml, .env.example |
The 3-step UX: thin make wrapper over the Compose pipeline. |
analyzer/ |
Single-URL WebAssembly analyzer (Node + Playwright + Foxhound) → MariaDB _wemby. |
analysis/ |
Taint-analysis stage (Rust + sqlx): correlates captured call args with the page's attacker-controllable inputs → wasm_parameters + is_interesting. |
fuzzer/ |
Harness synthesis from captured runtime data + AFL++ (via wasm2c). Ships a self-contained example/twitch.tv that runs without a database. |
instrumentation/ |
Binary-only WebAssembly instrumentation engine (Rust); provides the shadow-memory oracles. |
findings/ |
Findings collector — Express + MongoDB, ingests Foxhound's taint findings. |
seed/ |
MariaDB grants + _wemby schema applied on first setup. |
browser/ |
get-foxhound.sh — downloads + verifies the Foxhound build (make foxhound). |