fix(14a): NG struct for sim (+ tagtype, enables MFU/NTAG sim), NG for sniff
14a.sim sent a MIX frame but the handler is NG casting a packed {tagtype,u16 flags,uid[10],exitAfter,rats[20],ulauth...} struct -> whole struct garbled, flags never arrived. Rewrite to send_ng with the exact struct and expose tagtype (2=Ultralight,7=NTAG) so Ultralight/NTAG sim works. 14a.sniff sent MIX (args stripped -> empty asBytes); send NG with the 1-byte param. Pre-existing, independent of rebase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,26 +71,58 @@ class HF14ACommands:
|
|||||||
|
|
||||||
async def sniff(self, param: int = 0) -> dict:
|
async def sniff(self, param: int = 0) -> dict:
|
||||||
"""Sniff ISO14443A traffic."""
|
"""Sniff ISO14443A traffic."""
|
||||||
resp = await self._t.send_mix(
|
resp = await self._t.send_ng(
|
||||||
Cmd.HF_ISO14443A_SNIFF, arg0=param, timeout=30.0,
|
Cmd.HF_ISO14443A_SNIFF, payload=bytes([param]), timeout=30.0,
|
||||||
)
|
)
|
||||||
return {"status": resp.status}
|
return {"status": resp.status}
|
||||||
|
|
||||||
async def sim(self, uid: bytes, sak: int = 0x08, atqa: bytes = b"\x04\x00",
|
async def sim(self, tagtype: int = 1, uid: bytes = b"", exit_after: int = 0,
|
||||||
flags: int = 0) -> dict:
|
flags: int = 0, rats: bytes = b"",
|
||||||
"""Simulate ISO14443A card."""
|
ulauth_1a1: bytes = b"", ulauth_1a2: bytes = b"",
|
||||||
|
ulauth_1a2_mirror: bool = False) -> dict:
|
||||||
|
"""Simulate an ISO14443A tag.
|
||||||
|
|
||||||
|
tagtype selects the emulated tag: 1=MIFARE Classic 1k, 2=Ultralight,
|
||||||
|
3=DESFire, 4=ISO14443-4, 5=Tnp3xxx, 6=MF Mini, 7=MFU EV1/NTAG215,
|
||||||
|
8=MFC 4k, 9=FM11RF005SH, 10=ST25TA, 11=JCOP, 12=Seos, 13=ULC,
|
||||||
|
14=ULAES, 15=MF Plus.
|
||||||
|
|
||||||
|
uid: 4, 7, or 10 raw bytes. Empty = take UID from emulator memory.
|
||||||
|
exit_after: stop after N reader reads (0 = infinite).
|
||||||
|
ulauth_1a1/ulauth_1a2: ULC (8 byte) / ULAES (16 byte) auth replies.
|
||||||
|
"""
|
||||||
|
# Fold the UID length into flags per FLAG_SET_UID_IN_DATA (pm3_cmd.h).
|
||||||
|
if uid:
|
||||||
if len(uid) == 4:
|
if len(uid) == 4:
|
||||||
uid_flag = 0x0010
|
uid_flag = 0x0010 # FLAG_4B_UID_IN_DATA
|
||||||
elif len(uid) == 7:
|
elif len(uid) == 7:
|
||||||
uid_flag = 0x0020
|
uid_flag = 0x0020 # FLAG_7B_UID_IN_DATA
|
||||||
elif len(uid) == 10:
|
elif len(uid) == 10:
|
||||||
uid_flag = 0x0030
|
uid_flag = 0x0030 # FLAG_10B_UID_IN_DATA
|
||||||
else:
|
else:
|
||||||
raise ValueError("UID must be 4, 7, or 10 bytes")
|
raise ValueError("UID must be 4, 7, or 10 bytes")
|
||||||
|
else:
|
||||||
|
uid_flag = 0x0000 # FLAG_UID_IN_EMUL
|
||||||
|
|
||||||
flag_val = flags | uid_flag | 0x0001 # FLAG_INTERACTIVE
|
flag_val = (flags & ~0x0030) | uid_flag # FLAG_MASK_UID = 0x0030
|
||||||
payload = atqa + bytes([sak]) + uid
|
|
||||||
resp = await self._t.send_mix(
|
# struct { u8 tagtype; u16 flags; u8 uid[10]; u8 exitAfter;
|
||||||
Cmd.HF_ISO14443A_SIMULATE, arg0=flag_val, timeout=60.0, payload=payload,
|
# u8 rats[20]; u8 ulauth_1a1_len; u8 ulauth_1a2_len;
|
||||||
|
# u8 ulauth_1a1[16]; u8 ulauth_1a2[16]; bool mirror; } PACKED
|
||||||
|
payload = struct.pack(
|
||||||
|
"<BH10sB20sBB16s16sB",
|
||||||
|
tagtype & 0xFF,
|
||||||
|
flag_val & 0xFFFF,
|
||||||
|
uid.ljust(10, b"\x00")[:10],
|
||||||
|
exit_after & 0xFF,
|
||||||
|
rats.ljust(20, b"\x00")[:20],
|
||||||
|
len(ulauth_1a1),
|
||||||
|
len(ulauth_1a2),
|
||||||
|
ulauth_1a1.ljust(16, b"\x00")[:16],
|
||||||
|
ulauth_1a2.ljust(16, b"\x00")[:16],
|
||||||
|
1 if ulauth_1a2_mirror else 0,
|
||||||
|
)
|
||||||
|
resp = await self._t.send_ng(
|
||||||
|
Cmd.HF_ISO14443A_SIMULATE, payload=payload, timeout=60.0,
|
||||||
)
|
)
|
||||||
return {"status": resp.status}
|
return {"status": resp.status}
|
||||||
|
|||||||
@@ -23,3 +23,46 @@ def test_14a_scan():
|
|||||||
assert result["uid"] == "04010203040506"
|
assert result["uid"] == "04010203040506"
|
||||||
assert result["uid_len"] == 7
|
assert result["uid_len"] == 7
|
||||||
assert result["sak"] == 0x08
|
assert result["sak"] == 0x08
|
||||||
|
|
||||||
|
|
||||||
|
def test_14a_sim_packs_ng_struct():
|
||||||
|
t = AsyncMock()
|
||||||
|
hf14a = HF14ACommands(t)
|
||||||
|
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO14443A_SIMULATE, status=0,
|
||||||
|
reason=0, ng=True, data=b"", oldarg=[])
|
||||||
|
uid = b"\x04\x01\x02\x03"
|
||||||
|
result = asyncio.get_event_loop().run_until_complete(
|
||||||
|
hf14a.sim(tagtype=7, uid=uid, exit_after=3))
|
||||||
|
assert result["status"] == 0
|
||||||
|
args, kwargs = t.send_ng.call_args
|
||||||
|
assert args[0] == Cmd.HF_ISO14443A_SIMULATE
|
||||||
|
payload = kwargs["payload"]
|
||||||
|
assert len(payload) == 69 # PACKED firmware struct size
|
||||||
|
tagtype, flags = struct.unpack_from("<BH", payload, 0)
|
||||||
|
assert tagtype == 7
|
||||||
|
assert flags & 0x0030 == 0x0010 # FLAG_4B_UID_IN_DATA
|
||||||
|
assert payload[3:7] == uid # uid[10] field, first 4 bytes
|
||||||
|
assert payload[13] == 3 # exitAfter
|
||||||
|
|
||||||
|
|
||||||
|
def test_14a_sim_no_uid_uses_emul_flag():
|
||||||
|
t = AsyncMock()
|
||||||
|
hf14a = HF14ACommands(t)
|
||||||
|
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO14443A_SIMULATE, status=0,
|
||||||
|
reason=0, ng=True, data=b"", oldarg=[])
|
||||||
|
asyncio.get_event_loop().run_until_complete(hf14a.sim(tagtype=1))
|
||||||
|
payload = t.send_ng.call_args.kwargs["payload"]
|
||||||
|
_, flags = struct.unpack_from("<BH", payload, 0)
|
||||||
|
assert flags & 0x0030 == 0x0000 # FLAG_UID_IN_EMUL
|
||||||
|
|
||||||
|
|
||||||
|
def test_14a_sniff_sends_ng_param_byte():
|
||||||
|
t = AsyncMock()
|
||||||
|
hf14a = HF14ACommands(t)
|
||||||
|
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO14443A_SNIFF, status=0,
|
||||||
|
reason=0, ng=True, data=b"", oldarg=[])
|
||||||
|
result = asyncio.get_event_loop().run_until_complete(hf14a.sniff(2))
|
||||||
|
assert result["status"] == 0
|
||||||
|
args, kwargs = t.send_ng.call_args
|
||||||
|
assert args[0] == Cmd.HF_ISO14443A_SNIFF
|
||||||
|
assert kwargs["payload"] == bytes([2])
|
||||||
|
|||||||
Reference in New Issue
Block a user