diff --git a/Makefile b/Makefile index d9c24c1..5c2e0b1 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ BLOBS ?= $(wildcard /lib/firmware/mediatek/mt79*_wm.bin) \ $(wildcard /lib/firmware/mediatek/mt79*_wa.bin) \ $(wildcard /lib/firmware/mediatek/mt79*_rom_patch.bin) -.PHONY: help extract test +.PHONY: help extract test elf help: @echo "make extract - carve all MediaTek Connac2 blobs found on this host" @@ -13,3 +13,6 @@ extract: test: python3 -m unittest discover -s tests -v + +elf: + python3 tools/mtk_fw_elf.py $(BLOBS) -o extracted diff --git a/tests/test_elf.py b/tests/test_elf.py new file mode 100644 index 0000000..31569c7 --- /dev/null +++ b/tests/test_elf.py @@ -0,0 +1,78 @@ +"""ELF emitter tests: structural validity + segment fidelity. + +Validates with readelf where available and always with a programmatic +re-parse against the container's own region table. +""" +import os +import struct +import subprocess +import sys +import unittest +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(REPO / 'tools')) +import mtk_fw_elf as E # noqa: E402 + +FW = Path(os.environ.get('MTK_FW_DIR', '/lib/firmware/mediatek')) + + +def blobs(): + return sorted(FW.glob('mt79*_wm.bin')) + sorted(FW.glob('mt79*_wa.bin')) \ + + sorted(FW.glob('mt79*_rom_patch.bin')) + + +class Emitter(unittest.TestCase): + + def test_structure_and_fidelity(self): + found = blobs() + if not found: + self.skipTest('no mediatek blobs') + for p in found: + with self.subTest(blob=p.name): + d = p.read_bytes() + if 'patch' in p.name: + regions = list(E.patch_regions(d)) + else: + regions = list(E.ram_regions(d)) + regions = [(a, dat) for a, dat in regions if dat] + elf = E.build_elf(regions) + # ehdr sanity + self.assertEqual(elf[:4], b'\x7fELF') + etype, mach, _, entry, phoff = struct.unpack_from(' (chip_id, eco, n_region, hidden_trailer_string_or_None) GOLDEN_WM = { diff --git a/tools/mtk_fw_elf.py b/tools/mtk_fw_elf.py new file mode 100755 index 0000000..a00ed93 --- /dev/null +++ b/tools/mtk_fw_elf.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Emit a spec-correct 32-bit LE ELF (e_machine=EM_NDS32) from a MediaTek +Connac2 WiFi firmware container, one PT_LOAD segment per region at true +load addresses. + +Structure is validated by readelf; disassembly requires Ghidra (official +NDS32 module) or an Andes nds32le-elf toolchain — Debian binutils dropped +NDS32. Entry point is unknown pre-RE (e_entry=0); set it in your tool after +import. + +NON_DL regions are included as loadable segments by default; they are +host-side data per the driver, not downloaded to the MCU. +""" +import struct +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from mtk_fw_extract import PATCH_HDR, PATCH_SEC, REGION, TRAILER # noqa: E402 + +EM_NDS32 = 167 +EHDR = struct.Struct('<16sHHIIIIIHHHHHH') # 52 bytes +PHDR = struct.Struct(' bytes: + regions = [(a, dat) for a, dat in regions if dat] + n = len(regions) + phoff = EHDR.size + data_off = phoff + n * PHDR.size + eh = EHDR.pack( + b'\x7fELF' + bytes([1, 1, 1, 0]) + b'\x00' * 8, # ELFCLASS32/LSB/v1 + 2, EM_NDS32, 1, + regions[0][0] if regions else 0, # e_entry (first region; see doc) + phoff, 0, 0, + EHDR.size, PHDR.size, n, + 40, 0, 0) + out = bytearray(eh) + offset = data_off + for addr, dat in regions: + out += PHDR.pack(PT_LOAD, offset, addr, addr, len(dat), len(dat), + PF_RWX, 0x1000) + offset += len(dat) + for _, dat in regions: + out += dat + return bytes(out) + + +def main(): + if len(sys.argv) < 2: + sys.exit(f'usage: {sys.argv[0]} [...] [-o outdir]') + args, outdir = [], Path('extracted') + it = iter(sys.argv[1:]) + for a in it: + if a == '-o': + outdir = Path(next(it)) + else: + args.append(a) + for path in args: + p = Path(path) + d = p.read_bytes() + regions = patch_regions(d) if 'patch' in p.name else ram_regions(d) + elf = build_elf(regions) + out = outdir / p.stem / f'{p.stem}.elf' + out.parent.mkdir(parents=True, exist_ok=True) + out.write_bytes(elf) + n_seg = struct.unpack_from(' {out} ({len(elf)} bytes, {n_seg} PT_LOAD)') + + +if __name__ == '__main__': + main()