diff --git a/pm3py/core/hf_mfc.py b/pm3py/core/hf_mfc.py index d4c3970..d97ff7d 100644 --- a/pm3py/core/hf_mfc.py +++ b/pm3py/core/hf_mfc.py @@ -49,8 +49,9 @@ class HFMFCommands: raise ValueError(f"Block data must be {MFBLOCK_SIZE} bytes") # MIX format: arg0=blockno, arg1=keytype - # payload = key(6) + reserved(10) + data(16) = 32 bytes - payload = key_bytes + b"\x00" * 10 + data + # firmware reads key at asBytes[0], block_data at asBytes[10] + # payload = key(6) + reserved(4) + data(16) = 26 bytes + payload = key_bytes + b"\x00" * 4 + data resp = await self._t.send_mix( Cmd.HF_MIFARE_WRITEBL, arg0=block, arg1=key_type, payload=payload, timeout=5.0, @@ -96,14 +97,24 @@ class HFMFCommands: continue return {"found": False} - async def sniff(self) -> dict: - """Sniff MIFARE traffic.""" - resp = await self._t.send_ng(Cmd.HF_MIFARE_SNIFF, timeout=30.0) + async def sniff(self, param: int = 0) -> dict: + """Sniff MIFARE (ISO14443-A) traffic. + + Firmware has no dedicated MIFARE sniff handler; the C client routes + ``hf mf sniff`` through the ISO14443-A sniffer. + """ + resp = await self._t.send_mix( + Cmd.HF_ISO14443A_SNIFF, arg0=param, timeout=30.0, + ) return {"status": resp.status} - async def sim(self, uid: str | bytes, size: str = "1k") -> dict: + async def sim(self, uid: str | bytes, size: str = "1k", + atqa: str | bytes | None = None, sak: int | None = None, + exit_after: int = 0, interactive: bool = False) -> dict: """Simulate MIFARE Classic card. - size: "mini", "1k", "2k", "4k" + + size: "mini", "1k", "2k", "4k". Optional atqa (2 bytes) / sak (1 byte) + override the defaults; exit_after N stops after N auth attempts (0=never). """ if isinstance(uid, str): uid = bytes.fromhex(uid) @@ -116,23 +127,70 @@ class HFMFCommands: if uid_flag is None: raise ValueError("UID must be 4, 7, or 10 bytes") - flags = 0x0001 | uid_flag | size_flags[size] # FLAG_INTERACTIVE - resp = await self._t.send_mix( - Cmd.HF_MIFARE_SIMULATE, arg0=flags, payload=uid, timeout=60.0, + flags = uid_flag | size_flags[size] + if interactive: + flags |= 0x0001 # FLAG_INTERACTIVE + + atqa_val = 0 + if atqa is not None: + if isinstance(atqa, str): + atqa = bytes.fromhex(atqa) + if len(atqa) != 2: + raise ValueError("ATQA must be 2 bytes") + atqa_val = (atqa[1] << 8) | atqa[0] + flags |= 0x0002 # FLAG_ATQA_IN_DATA + + sak_val = 0 + if sak is not None: + sak_val = sak & 0xFF + flags |= 0x0004 # FLAG_SAK_IN_DATA + + # struct { u16 flags; u8 exitAfter; u8 uid[10]; u16 atqa; u8 sak } + payload = struct.pack(" dict: + target_block: int = 0, target_key_type: int = MF_KEY_A, + calibrate: bool = True) -> dict: """Run nested attack to recover keys.""" key_bytes = self._parse_key(key) - payload = struct.pack(" dict: - """Identify magic (gen1/gen2) card type.""" - resp = await self._t.send_ng(Cmd.HF_MIFARE_CIDENT, timeout=5.0) - return {"status": resp.status, "data": resp.data.hex() if resp.data else None} + # response struct { int16 isOK; u8 block; u8 keytype; u8 cuid[4]; + # u8 nt_a[4]; u8 ks_a[4]; u8 nt_b[4]; u8 ks_b[4] } + if len(resp.data) < 24: + return {"success": False, "status": resp.status} + is_ok, r_block, r_keytype = struct.unpack_from(" dict: + """Identify magic (gen1/gen2/...) card type.""" + key_bytes = self._parse_key(key) + # struct { uint8 is_mfc; uint8 keytype; uint8 key[6] } + payload = bytes([1 if is_mfc else 0, key_type & 0xFF]) + key_bytes + resp = await self._t.send_ng(Cmd.HF_MIFARE_CIDENT, payload, timeout=5.0) + + magic = 0 + if resp.status == 0 and len(resp.data) >= 2: + magic = struct.unpack_from("