|
| 1 | +import ast |
| 2 | +import subprocess |
| 3 | +import sys |
| 4 | +from binascii import crc_hqx |
| 5 | +from itertools import cycle |
| 6 | +from pathlib import Path |
| 7 | + |
| 8 | + |
| 9 | +def _load_obfuscation(): |
| 10 | + tree = ast.parse(Path("fw-pack.py").read_text()) |
| 11 | + for node in tree.body: |
| 12 | + if isinstance(node, ast.Assign): |
| 13 | + target = node.targets[0] |
| 14 | + if isinstance(target, ast.Name) and target.id == "OBFUSCATION": |
| 15 | + values = [] |
| 16 | + for elt in node.value.elts: |
| 17 | + if isinstance(elt, ast.Constant): |
| 18 | + values.append(elt.value) |
| 19 | + else: |
| 20 | + raise ValueError("Unexpected AST element in OBFUSCATION table") |
| 21 | + return values |
| 22 | + raise RuntimeError("OBFUSCATION table not found") |
| 23 | + |
| 24 | + |
| 25 | +OBFUSCATION = _load_obfuscation() |
| 26 | + |
| 27 | + |
| 28 | +def deobfuscate(payload: bytes) -> bytes: |
| 29 | + return bytes(a ^ b for a, b in zip(payload, cycle(OBFUSCATION))) |
| 30 | + |
| 31 | + |
| 32 | +def run_fw_pack(input_path, version, output_path): |
| 33 | + subprocess.run( |
| 34 | + [sys.executable, "fw-pack.py", str(input_path), version, str(output_path)], |
| 35 | + check=True, |
| 36 | + ) |
| 37 | + |
| 38 | + |
| 39 | +def test_fw_pack_injects_version(tmp_path): |
| 40 | + plain = (b"\x01\x02\x03\x04" * 4096) # 16 KiB test payload |
| 41 | + input_bin = tmp_path / "input.bin" |
| 42 | + packed_bin = tmp_path / "output.bin" |
| 43 | + input_bin.write_bytes(plain) |
| 44 | + |
| 45 | + run_fw_pack(input_bin, "LOANER01", packed_bin) |
| 46 | + |
| 47 | + data = packed_bin.read_bytes() |
| 48 | + assert len(data) == len(plain) + 16 + 2 # Version block inserted + CRC |
| 49 | + |
| 50 | + deobfuscated = deobfuscate(data[:-2]) |
| 51 | + version_block = deobfuscated[0x2000:0x2010] |
| 52 | + assert version_block.startswith(b"*OEFW-") |
| 53 | + assert version_block[6:6 + len("LOANER01")] == b"LOANER01" |
| 54 | + assert version_block.endswith(b"\x00" * (16 - len("*OEFW-") - len("LOANER01"))) |
| 55 | + |
| 56 | + # Verify CRC matches (xmodem polynomial) |
| 57 | + payload, crc_bytes = data[:-2], data[-2:] |
| 58 | + crc = crc_hqx(payload, 0x0000) |
| 59 | + assert crc_bytes == bytes([crc & 0xFF, (crc >> 8) & 0xFF]) |
| 60 | + |
| 61 | + |
| 62 | +def test_fw_pack_rejects_long_version(tmp_path): |
| 63 | + input_bin = tmp_path / "input.bin" |
| 64 | + input_bin.write_bytes(b"\x00" * 0x2100) |
| 65 | + packed_bin = tmp_path / "output.bin" |
| 66 | + |
| 67 | + result = subprocess.run( |
| 68 | + [sys.executable, "fw-pack.py", str(input_bin), "THIS_IS_TOO_LONG", str(packed_bin)], |
| 69 | + capture_output=True, |
| 70 | + text=True, |
| 71 | + ) |
| 72 | + |
| 73 | + assert result.returncode != 0 |
| 74 | + assert "Version suffix is too big" in result.stdout |
| 75 | + assert not packed_bin.exists() |
0 commit comments