# mtk-wifi-fw (working name) — Project Plan Goal: cut teeth on real firmware RE while producing community-useful tooling, docs, and update tracking for MediaTek Connac2 WiFi firmware. The product is the tooling/knowledge and the skills built making it — not a modified router. ## Target facts (verified, keep cited) - Hardware: GL-MT3000 (Beryl AX), MT7981B (Filogic 820), OpenWrt 24.10.5. Radios on-SoC at `platform/soc/18000000.wifi`, driver `mt7915e` (mt76). - "The blob" = 4 images, 3 NDS32LE cores inside the SoC: - `mt7981_wm.bin` 2.05MB — WM, main MAC/PHY MCU ("neptune", build 20240823) - `mt7981_wa.bin` 494KB — WA, offload/stats MCU - `mt7981_wo.bin` 2.45MB — WED packet offload core (FreeRTOS v10, loaded by `mtk_wed`, not mt76 — separate container format, TODO) - `mt7981_rom_patch.bin` 9.8KB — boot-ROM patch ("ALPS") - NDS32LE evidence: `GCC: (2018-10-23_nds32le-elf-mculib-v3) 4.9.4` in wa; `portable/GCC/NDS32/port.c` in wo. - No signature/encryption wall: loader is the kernel driver (`mt7915_load_firmware` → `mt76_connac2_load_patch` + `load_ram`); protocol supports per-region encrypt (`FW_FEATURE_SET_ENCRYPT`) but blobs ship plaintext. Source-verified; empirical proof = M3 milestone. - Container formats (from `mt76_connac_mcu.h`): - RAM images: region data from offset 0, n×40B region table, 36B trailer at EOF. `fw_ver` stripped (`____000000`); build_date is the only versioning. - Patch: 92B BE header + 64B BE section table at start. - Family coverage: mt7915/7916/7981/7986 all parse. mt7915 blobs = 2022 build, others = 2024 — free pilot for version diffing. - Tooling: Ghidra has an official NDS32 module (`NDS32:LE:32:default`, some V3 instr gaps); Andes GNU toolchain public (exact 2018 GCC vintage exists). - Prior art: cyrozap/mediatek-wifi-re (older chip gen; CC-BY-SA notes, compatible). No public RE of Connac2-generation firmware exists. ## Strategy shape RE first, fork later. Nobody can price replacement firmware before the boot and core-boundary picture exists, so the plan front-loads cheap-certain RE and gates expensive authoring behind a data-informed go/no-go. - Phase 1 static RE: carve → ELF → Ghidra → boot/ABI map (no decision needed) - Phase 2 dynamic RE: patch bytes, reload, observe (patching = verification instrument, no decision needed) - Fork point (after boot/handshake + WM↔WA boundary mapped): choose between a) audit & document track, and/or b) authoring ladder, easiest-first: custom WO (self-contained, no radio) → WM handshake-only → beacon TX → association → data path. Each rung independently valuable/stoppable. - Version diffing runs parallel throughout (pure static, high community value). ## MVP v0.1 — "toolkit + changelog" (no hardware required, ~8–12 evenings) Scope: extract (family-wide, correct) + to-elf + diff report over linux-firmware git history + format/boot docs + CI cron that tracks upstream blob drops. The repo becomes the public changelog for silently-updated firmware. Worklist in order: 1. Repo scaffolding: git init, dirs, dual LICENSE (MIT code / CC-BY-SA docs), Makefile; commit existing extractor. 2. Parser correctness: resolve or document-as-unknown the 100/36B gap, region `type` semantics, feature bit 0x80. 3. Test fixtures: golden manifests per blob (metadata only, no blob bytes). 4. ELF emitter at true load addresses (Ghidra/objdump acceptance test). 5. Dataset: walk linux-firmware git history for mediatek WiFi files. 6. Diff engine + pilot report (mt7915 2022 vs 2024; string-table add/remove is the human-readable signal). 7. Docs: format.md (every field cited), boot.md, README quickstart. 8. CI: fixtures on commit; weekly cron vs linux-firmware HEAD regenerating reports. 9. Polish, tag v0.1.0, undersold announcement (OpenWrt forum + r/re). Not in v0.1: Ghidra annotation projects, symbol DBs, patched blobs, anything router-dependent. ## Quality gates ("not noise") - Every claim cites evidence (kernel line, blob offset) or is marked unknown. No invented semantics. - Family coverage verified in CI, not asserted in README. - Reproducible in ~3 commands, plain Linux, no blobs committed (`make fetch-fw` pulls from kernel.org). - Undersell announcements. ## Status log - 2026-08-20: Extractor `tools/mtk_fw_extract.py` written and run on mt7981/7915/7916/7986 wm+wa+patch (7 files, 4 chips) — all parse clean. Output in `extracted*/`, `extracted/manifest.json`. Solved same day: the "gap" is a second trailer family-wide (wm 100B, wa 36B) sitting between region data and the kernel-parsed trailer; WM's carries the full build string MTK strips from the kernel-visible trailer (branch, MP tag, build timestamp) — recovered version metadata for the changelog. Region type: all parsed blobs are FW_TYPE_DEFAULT(0); loader does use `type` elsewhere (mt7921 FW_TYPE_CLC). Remaining unknowns: feature bit 0x80 (wm 0xf0xxxxxx regions), hidden-trailer non-string fields. Verified claims + citations: docs/findings.md. - Router unreachable (thinkpad jump host down). Not blocking: v0.1 is pure static. Hardware options when needed: fix thinkpad / wire devuan-hpz to router LAN / join `zach` WiFi from devuan-hpz (key + likely MAC-whitelisted, but changes this machine's network path mid-session). - 2026-08-20 (later): MVP v0.1 complete. All 9 worklist steps: scaffold, parser correctness (hidden trailer = recovered version metadata), dataset-driven golden tests, ELF emitter (readelf-validated; Ghidra open-check deferred to Ghidra bring-up — deviation noted), 54-revision dataset, diff engine + 3 pilot reports (2026 build: 0xe003b000 region ~95% changed, new RA/DPD/thermal strings), docs (format/boot), CI (push + weekly track), v0.1.0 tagged. Announcement drafted, not posted — hosting decision still open. - 2026-08-20 (P1-B started): Ghidra 12.1.3 + JDK21 + PyGhidra pipeline working (venv at ~/data/tools). Patch blob reversed to structure level: 10-entry ROM function replacement table + 49 NDS32 functions decompiling cleanly, assert anchors (2 source files w/ line numbers), N9 core name confirmed, CCIF/mailbox/WDT host-comm hooks mapped (findings F5). Full decomp kept out of repo (blob-derived); notes only. WA import running. - Repo name + hosting: RESOLVED — mtk-wifi-fw at https://zachery.lol/code/zacheryasc/mtk-wifi-fw (self-hosted Forgejo 9, public, pushed 2026-08-20 with v0.1.0). GitHub mirror optional later. Forgejo Actions may pick up .github/workflows if a runner is registered (ci-token exists on docean — unverified). - Router access path (needed from M3/dynamic phase). - Fork decision (audit vs authoring ladder mix) — after boot/boundary map. - WO container format (from `mtk_wed_mcu.c`, different loader) — M2-ish. ## Decision function & finish criteria Project has no natural completion point; finish = satisficing set + per-node gates. All checks binary. **Finish conditions (both required):** - F1 public artifact: v0.1 shipped passing quality gates, then one of community uptake / cron keeps it alive / documented sunset ("last verified against linux-firmware X"). Silent rot is noise; sunset is finish. - F2 teeth cut, each backed by an artifact: container parsed (done); one *verified cited* claim from disassembly; changed firmware behavior and observed it on hardware (P2); optionally self-written code on target. **Universal gate (every checkpoint):** advance iff next gate is reachable with known techniques AND has a stated cost ceiling AND its failure would not invalidate shipped artifacts. Slipped >2x twice → stop-and-replan. Outputs ship as verified or documented-unknown, never "probably"; unknowns are valid P1 exits but never authoring foundations. **Node exits:** MVP = worklist 1–9 + quality gates. P1 = boot chain writable step-by-step with citations; ≥30 functions named via dispatch anchors, 3 traced end-to-end; memory map complete; WM↔WA characterized; fork memo. Authoring rung N+1 opens iff rung N demonstrated on hardware AND N+1 interface mapped AND unbrick path proven. **Kill criteria:** 3 consecutive flash cycles with no hypothesis advance → rung closed, findings published. ROM-verified lockout discovered → authoring dead, audit track unaffected. Router unreachable >4 weeks → P2 paused only. **Zombie rule:** no commits and no CI activity for 2 quarters → archive with last-verified statement.