fix(mfc): correct WRITEBL offset, NESTED struct order, CIDENT/SIM payloads; route sniff to 14a

wrbl padded key to 16 (firmware reads block at asBytes+10) -> corrupt writes; pad to 10. nested sent {block,keytype,key,target...} but firmware struct is {block,keytype,target_block,target_keytype,calibrate,key[6]} -> attacked wrong target; reorder + add calibrate + decode reply. cident/sim sent empty/MIX payloads for NG handlers -> send the packed structs. mf.sniff (no firmware handler) reroutes to CMD_HF_ISO14443A_SNIFF. Pre-existing, independent of rebase.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-05 00:08:40 -07:00
parent 01357e4e78
commit 625104ea03
2 changed files with 161 additions and 19 deletions

View File

@@ -22,5 +22,89 @@ def test_mf_wrbl():
t.send_mix.return_value = PM3Response(cmd=Cmd.ACK, status=0, reason=0,
ng=False, data=b"", oldarg=[1, 0, 0])
result = asyncio.get_event_loop().run_until_complete(
mf.wrbl(block=4, key="FFFFFFFFFFFF", data="00" * 16))
mf.wrbl(block=4, key="FFFFFFFFFFFF", data="11" * 16))
assert result["success"] is True
# firmware: key at asBytes[0], block_data at asBytes[10]
kwargs = t.send_mix.call_args.kwargs
payload = kwargs["payload"]
assert len(payload) == 26
assert payload[:6] == bytes.fromhex("FFFFFFFFFFFF")
assert payload[6:10] == b"\x00" * 4
assert payload[10:26] == bytes.fromhex("11" * 16)
assert kwargs["arg0"] == 4
assert kwargs["arg1"] == 0
def test_mf_nested():
t = AsyncMock()
mf = HFMFCommands(t)
# reply: isOK=0, block=8, keytype=1, cuid, nt_a, ks_a, nt_b, ks_b
reply = struct.pack("<hBB", 0, 8, 1)
reply += bytes.fromhex("01020304") # cuid
reply += bytes.fromhex("11111111") # nt_a
reply += bytes.fromhex("22222222") # ks_a
reply += bytes.fromhex("33333333") # nt_b
reply += bytes.fromhex("44444444") # ks_b
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_MIFARE_NESTED, status=0,
reason=0, ng=True, data=reply)
result = asyncio.get_event_loop().run_until_complete(
mf.nested(block=0, key="FFFFFFFFFFFF", key_type=0,
target_block=8, target_key_type=1, calibrate=True))
# firmware struct order: block, keytype, target_block, target_keytype, calibrate, key[6]
payload = t.send_ng.call_args.args[1]
assert payload == struct.pack("<BBBBB", 0, 0, 8, 1, 1) + bytes.fromhex("FFFFFFFFFFFF")
assert result["success"] is True
assert result["block"] == 8
assert result["key_type"] == 1
assert result["cuid"] == "01020304"
assert result["ks_a"] == "22222222"
assert result["nt_b"] == "33333333"
def test_mf_cident():
t = AsyncMock()
mf = HFMFCommands(t)
# MAGIC_FLAG value returned as little-endian uint16
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_MIFARE_CIDENT, status=0,
reason=0, ng=True,
data=struct.pack("<H", 0x0001))
result = asyncio.get_event_loop().run_until_complete(mf.cident())
payload = t.send_ng.call_args.args[1]
assert payload == bytes([1, 0]) + bytes.fromhex("FFFFFFFFFFFF")
assert result["magic"] == 0x0001
assert result["is_magic"] is True
def test_mf_sim():
t = AsyncMock()
mf = HFMFCommands(t)
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_MIFARE_SIMULATE, status=0,
reason=0, ng=True, data=b"")
result = asyncio.get_event_loop().run_until_complete(
mf.sim(uid="11223344", size="1k", atqa="0400", sak=0x08))
assert result["status"] == 0
payload = t.send_ng.call_args.args[1]
# struct { u16 flags; u8 exitAfter; u8 uid[10]; u16 atqa; u8 sak }
assert len(payload) == 16
flags, exit_after = struct.unpack_from("<HB", payload, 0)
# 4B_UID_IN_DATA | FLAG_MF_1K | ATQA_IN_DATA | SAK_IN_DATA (interactive off by default)
assert flags == (0x0010 | 0x0040 | 0x0002 | 0x0004)
assert exit_after == 0
assert payload[3:7] == bytes.fromhex("11223344")
assert payload[7:13] == b"\x00" * 6
atqa_val, sak_val = struct.unpack_from("<HB", payload, 13)
assert atqa_val == 0x0004 # (0x00<<8)|0x04
assert sak_val == 0x08
def test_mf_sniff_routes_to_14a():
t = AsyncMock()
mf = HFMFCommands(t)
t.send_mix.return_value = PM3Response(cmd=Cmd.HF_ISO14443A_SNIFF, status=0,
reason=0, ng=False, data=b"")
result = asyncio.get_event_loop().run_until_complete(mf.sniff())
assert result["status"] == 0
# must target the ISO14443-A sniffer, not a (nonexistent) MF sniff handler
assert t.send_mix.call_args.args[0] == Cmd.HF_ISO14443A_SNIFF
assert t.send_mix.call_args.kwargs["arg0"] == 0
t.send_ng.assert_not_called()