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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user