2026-08-20 17:09:43 +00:00
|
|
|
|
# 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 17:27:14 +00:00
|
|
|
|
- 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 17:51:36 +00:00
|
|
|
|
- 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.
|
2026-08-20 17:27:14 +00:00
|
|
|
|
|
2026-08-20 17:32:42 +00:00
|
|
|
|
- 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).
|
2026-08-20 17:09:43 +00:00
|
|
|
|
- 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.
|