mtk-wifi-fw/PLAN.md

8.3 KiB
Raw Permalink Blame History

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.