Describe the bug
On Proxmark5 (AT32F437), CMD_HF_MIFARE_CIDENT hard-hangs the firmware when it is
executed after a RATS has been sent. The device stops answering on USB entirely — it
does not even reply to a raw CMD_PING — while still being enumerated by the host.
The watchdog does not recover it: a physical reset is required.
Because ul_magic_test() is called unconditionally by GetHF14AMfU_Type(), this makes
hf mfu info, hf search and auto kill the device on any tag, including a plain
non-magic NTAG 213.
To Reproduce
With a NTAG 213 (SAK=00) on the antenna:
pm3 --> hf mfu info
[=] --- Tag Information --------------------------
[+] TYPE: NTAG 213 144bytes (NT2H1311G0DU)
[#] iso14443a card select timeout
The device is dead from this point on; every later command returns
ERROR: cannot communicate with the Proxmark3 until it is physically reset.
Minimal reproducer — three commands, straight over the serial port, no client involved:
CMD_HF_ISO14443A_READER with ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECT
CMD_HF_ISO14443A_READER with ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, payload E0 80 (RATS)
CMD_HF_MIFARE_CIDENT, payload 8 zero bytes
Step 3 returns 0 bytes and the firmware is gone. A standalone python3 script (no
dependencies) that performs these three steps and probes liveness with a raw CMD_PING
after each one is attached below.
Expected behavior
MifareCIdent() should complete and reply, or fail gracefully. It should never leave the
firmware unresponsive.
Analysis
Neither command kills the device on its own — CIDENT alone is fine, RATS alone is fine.
Only the combination does, which matches the sensitivity the code already documents in
MifareCIdent():
// check for super card gen2
// not available after RATS, reset card before executing
The call chain from the client is:
ul_magic_test() -> ul_select_rats() (sends RATS E0 80) -> SendCommandNG(CMD_HF_MIFARE_CIDENT)
Ruled out during the investigation, each with a measurement rather than a guess:
- cabling / power / USB hub (device is plugged straight into the host;
hw version 5/5 OK)
- antenna (
hw tune: HF 35.59 V, LF 17.98 V, both reported ok)
- the tag itself (
hf 14a reader 5/5, UID stable, BCC0/BCC1 and CC all verify)
- corrupted flash image (bootrom and fullimage reflashed, behaviour unchanged)
ISO14A_CLEARTRACE, field OFF->ON cycling, GET_VERSION
- BigBuf exhaustion — measured 495740 bytes free out of 498048 right before the call,
against the ~1 KB the function allocates
Unrelated but worth fixing while in there: the three BigBuf_calloc() results at the top
of MifareCIdent() are used as receive buffers without ever being NULL-checked, even
though BigBuf_malloc() can return NULL.
Minimal reproducer script (python3, no dependencies)
#!/usr/bin/env python3
# Minimal reproducer: MifareCIdent() hard-hangs Proxmark5 firmware when called after RATS.
# Put any ISO14443-A tag on the antenna and run. No dependencies.
import os, select, struct, time
PORT = "/dev/cu.usbmodem101"
CMD_PING, CMD_READER, CMD_DROPFIELD, CMD_CIDENT = 0x0109, 0x0385, 0x0430, 0x0607
CONNECT, NO_DISCONNECT, RAW, APPEND_CRC, CLEARTRACE = 1, 2, 8, 32, 1 << 17
fd = os.open(PORT, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
def drain(t=0.4):
out = b""; end = time.time() + t
while time.time() < end:
r, _, _ = select.select([fd], [], [], 0.1)
if r:
try: c = os.read(fd, 4096)
except OSError: break
if c: out += c
return out
def pkt_ng(cmd, data=b""):
return b"PM3a" + struct.pack("<HH", (len(data) & 0x7FFF) | (1 << 15), cmd) + data + b"a3"
def pkt_mix(cmd, a0, a1=0, a2=0, payload=b""):
d = struct.pack("<QQQ", a0, a1, a2) + payload
return b"PM3a" + struct.pack("<HH", len(d) & 0x7FFF, cmd) + d + b"a3"
def alive():
drain(0.1); os.write(fd, pkt_ng(CMD_PING)); return len(drain(0.8)) > 0
print("initial state:", "ALIVE" if alive() else "DEAD")
os.write(fd, pkt_mix(CMD_READER, CONNECT | CLEARTRACE | NO_DISCONNECT)); drain(0.5)
print("1. select -> %s" % ("ALIVE" if alive() else "DEAD"))
os.write(fd, pkt_mix(CMD_READER, RAW | APPEND_CRC | NO_DISCONNECT, 2, 0, b"\xE0\x80")); drain(0.5)
print("2. RAW RATS -> %s" % ("ALIVE" if alive() else "DEAD"))
os.write(fd, pkt_ng(CMD_CIDENT, bytes(8))); r = drain(2.5)
a = alive()
print("3. CIDENT (%d bytes) -> %s" % (len(r), "ALIVE" if a else "DEAD"))
if not a:
print("\n>>> MINIMAL REPRODUCER: select -> RATS -> CIDENT (3 commands)")
os.close(fd)
Expected output on a device that still has the bug:
initial state: ALIVE
1. select -> ALIVE
2. RAW RATS -> ALIVE
3. CIDENT (0 bytes) -> DEAD
Desktop
- OS: macOS (Darwin 25.5.0, arm64)
- Client:
Iceman/master/v4.21611-843-g32e2e414c
- ARM OS + bootrom: same build, both
2026-08-25 11:14
- Model: PM5, uC AT32F437, external flash present
Describe the bug
On Proxmark5 (AT32F437),
CMD_HF_MIFARE_CIDENThard-hangs the firmware when it isexecuted after a RATS has been sent. The device stops answering on USB entirely — it
does not even reply to a raw
CMD_PING— while still being enumerated by the host.The watchdog does not recover it: a physical reset is required.
Because
ul_magic_test()is called unconditionally byGetHF14AMfU_Type(), this makeshf mfu info,hf searchandautokill the device on any tag, including a plainnon-magic NTAG 213.
To Reproduce
With a NTAG 213 (SAK=00) on the antenna:
The device is dead from this point on; every later command returns
ERROR: cannot communicate with the Proxmark3until it is physically reset.Minimal reproducer — three commands, straight over the serial port, no client involved:
CMD_HF_ISO14443A_READERwithISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECTCMD_HF_ISO14443A_READERwithISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, payloadE0 80(RATS)CMD_HF_MIFARE_CIDENT, payload 8 zero bytesStep 3 returns 0 bytes and the firmware is gone. A standalone python3 script (no
dependencies) that performs these three steps and probes liveness with a raw
CMD_PINGafter each one is attached below.
Expected behavior
MifareCIdent()should complete and reply, or fail gracefully. It should never leave thefirmware unresponsive.
Analysis
Neither command kills the device on its own —
CIDENTalone is fine, RATS alone is fine.Only the combination does, which matches the sensitivity the code already documents in
MifareCIdent():The call chain from the client is:
ul_magic_test()->ul_select_rats()(sends RATSE0 80) ->SendCommandNG(CMD_HF_MIFARE_CIDENT)Ruled out during the investigation, each with a measurement rather than a guess:
hw version5/5 OK)hw tune: HF 35.59 V, LF 17.98 V, both reported ok)hf 14a reader5/5, UID stable, BCC0/BCC1 and CC all verify)ISO14A_CLEARTRACE, field OFF->ON cycling,GET_VERSIONagainst the ~1 KB the function allocates
Unrelated but worth fixing while in there: the three
BigBuf_calloc()results at the topof
MifareCIdent()are used as receive buffers without ever being NULL-checked, eventhough
BigBuf_malloc()can return NULL.Minimal reproducer script (python3, no dependencies)
Expected output on a device that still has the bug:
Desktop
Iceman/master/v4.21611-843-g32e2e414c2026-08-25 11:14