The DESFire simulation could only be reached over the air. A conformance
suite that has recorded a genuine card's session needs to replay it byte
for byte, which takes two things the simulation did not have: a way in
over USB, and control of the card's random draws.
CMD_HF_DESFIRE_SIM_TEST carries one operation per packet into the same
state machine the RF loop drives: BEGIN, SCAN, APDU, RANDOM, FIELDOFF,
STATE, END. SCAN runs the RF-reset path (init, reset, random id, FSD) and
APDU goes through desfire_sim_apdu(), so native and ISO 7816 wrapped
commands answer exactly as they do on air. No FPGA involvement.
Randomness goes through a small host-fed byte queue: RndB and the random
id draw from it first and fall back to the existing fixed value or tick
counter when it is short, raising a sticky underflow flag. With nothing
queued the RF simulation is byte-identical to before; SimulateDesfireTag()
clears the queue so a host session cannot leak into a reader session.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
The simulation answers SetConfiguration. The option byte travels in the clear
and the data behind it is enciphered with a CRC32, the same shape ChangeKey uses,
and card level master key authentication is required (M134034 9.4.9).
Option 0x00, the configuration byte, is kept in the card image header. Both bits
it defines are one way on a card -- the spec says "cannot be reset" of each --
so they are only ever set, never cleared, and a reader that sends a zero byte
afterwards gets an OK and no change, which is what the silicon does.
bit 0 disables FormatPICC, and that command now refuses from then on.
bit 1 switches anticollision to a random id: a 4 byte id whose first byte is
the 0x08 random tag and whose other three are the random number, with a single
cascade level (M134034 6.5). The real UID is then only reachable through
GetCardUID, which is the reason that command exists. A card draws the number
at RF reset; the nearest thing here is the start of a simulation, since that is
when the anticollision answers are built, so it is stable across activations
within one run and different across runs -- measured 08 01 9A C9 four times in
a row, then 08 01 BB 96 and 08 01 DC 26 and 08 01 FC 8E on three restarts.
Simulating that needed the 4 byte case adding to the UID length the simulation
accepts, which previously took only 7 and 10 and refused to start otherwise.
Option 0x02 replaces the ATS. It lands in the image, so it takes effect the next
time the simulation starts rather than mid-run, because the ATS is one of the
answers precompiled before the reader is listened to.
Option 0x01, the default key new applications are created with, is refused with
91 9E rather than accepted and ignored. Storing it needs two fields the card
image does not have, and adding them moves every table in it. A reader that sets
a default key and then finds new applications keyed with zeros is worse off than
one told the option is not there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The DESFire simulation gets its own ISO 14443-A loop in armsrc/desfiresim.c
rather than hooking into SimulateIso14443aTag(). That function is complicated
enough without a DESFire state machine threaded through it, and the hook had
already got the ATQA wrong once. It still borrows the library helpers from
iso14443a.c -- the precompiled activation answers, the receive call, the send
calls -- so there is no duplicated state machine, and iso14443a.c goes back to
knowing nothing about DESFire. desfiresim.h exports one symbol.
Three fixes were needed to make it actually answer a reader.
1. ISO 7816 wrapping. A reader sends `02 90 60 00 00 00`, not `02 60`. The
simulation read in[0] as the DESFire command and so saw 0x90, the class
byte, answering ILLEGAL_COMMAND_CODE to everything -- and in native form
(status || data) when the reader wanted the wrapped form (data || 91 SW).
Both framings are handled now, including the Lc/Le distinction: five bytes
means no data and in[4] is Le, longer means in[4] is Lc with data following.
2. A bitstream download frees and clears BigBuf to get scratch space for the
decompressor. SimulateIso14443aTagEx() and Mifare1ksim() both guard against
this by calling FpgaDownloadAndGo_keep_EM() before they allocate anything;
the new loop did not, so iso14443a_setup() wiped the emulator memory holding
the card image and the precompiled answers after they had been filled. The
pointers survive, the bytes do not, and the tag then clocks out zeros.
3. iso14443a_setup() itself used the plain FpgaDownloadAndGo(), which does
BigBuf_free() and BigBuf_Clear_ext() -- taking the emulator memory with it.
Every 14a command comes through there, so a card image that `eload` had just
put in place was destroyed by the next `hf 14a` command whenever the HF
bitstream was not already resident. Demonstrated before and after:
`eload` then `hf 14a reader` then `eview` used to report "No DESFire card
image in emulator memory" and now returns the image intact. This affected
every emulator memory user, not only DESFire.
Verified against a second Proxmark3 acting as reader, simulating a real
DESFire EV1 8K dump: activation (UID 04268512A25680, ATQA 03 44, SAK 20,
ATS 06 75 77 81 02 80), the three frame GetVersion chain over 0xAF, GetFreeMem,
GetApplicationIDs, SelectApplication, GetKeySettings and GetFileSettings.
`hf mfdes info`, `getaids`, `freemem`, `lsapp` and `lsfiles` all read correctly,
including per application key types (AES, 2TDEA, 3TDEA) and all five EV1 file
types with their real settings -- a value file holding 1000 with limits
[0..10000], a linear record 2/8 of 16 bytes, a cyclic record 1/4 of 24 bytes,
standard files of 256 and 64 bytes, and a backup file of 128 bytes in MAC mode
with keyed rights 1200.
Not implemented yet: GetDFNames (0x6D) and GetISOFileIDs (0x61), so a reader
sees empty ISO IDs and DF names. Everything else answers
ILLEGAL_COMMAND_CODE, which is what a PICC says to a command it does not have.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the on-device card image a DESFire simulation will read, the client
side that packs a dump into it, and eload / esave / eview.
The layout is in include/desfire_em.h. Tables grow up from the header,
file data grows down from the end, and an allocation only has to leave
the two frontiers apart -- so a three-file card spends a few hundred
bytes rather than a worst case, and the space between them is what the
card has left. The PICC is application 000000 and uses the same struct
as any other application, so key settings and keys are always stated
against an AID. Delete sets a tombstone rather than compacting, which is
not a shortcut: a real card does not reclaim on delete either, measured
as 2080 bytes free with zero applications before an experiment and 2560
after FormatPICC.
Two size limits, and a reader only ever sees the first. cardsize is what
the emulated card claims to hold, so GetFreeMem answers from that and
CreateFile will refuse with OUT_OF_EEPROM when it runs out. Without it a
card impersonating a 2K part would report 7434 bytes free, which no 2K
part does. The image size is what emulator memory physically holds, is
the harder limit, and is never visible.
cardsize is anchored to the free memory the real card reported when the
dump was taken -- observed free plus what we reserve for the same content
-- so the emulation answers what its original answered. That is also a
check on the reservation rule rather than only a convenience: for the
bench card it computed 576 bytes spent, and 1984 + 576 is 2560, exactly
what that card reports when formatted.
Reservation follows what CommitTransaction covers. Backup data, value and
record files each get a shadow region because writes to them are staged
until commit; a standard data file writes through and does not. Sizes
round to a 32 byte granule, which is the granule a real card allocates in.
It deliberately does not reproduce NXP's allocator -- that is
undocumented and does not fit a simple model, a declared 1024 byte record
file costs 1088 on silicon -- so what matters is that the figure is
self-consistent and shrinks as the reader writes.
No new device command. Emulator memory is one shared region, so
CMD_HF_MIFARE_EML_MEMSET and BIG_BUF_EML already reach it, and both
inherit the bounds checking those paths gained earlier.
Verified with the client only, no simulation yet: packing a dump taken
from a DESFire EV2 and walking it back reproduces every field including
file contents byte for byte, the same round trip through the device via
eload and esave is identical, and an image too large is refused by name
rather than truncated -- 'Out of emulator memory laying out AID 112233
file 00: needs 4066 more bytes'.
Co-Authored-By: Claude Opus 5 (1M context)
FpgaDownloadAndGoEx() with keep_em false does BigBuf_free() plus
BigBuf_Clear_ext(): the emulator memory pointer is nulled and all of BigBuf is
zeroed. iso14443a_setup() calls that variant, and Mifare1ksim(),
SimulateIso14443aTagEx() and SimulateIso14443aTagAID() build their precompiled
anticollision responses in BigBuf first. CMD_HF_MIFARE_EML_MEMGET had it the
other way round - it downloaded, then read the memory it had just wiped, so
'hf mf eview' and 'hf mf esave' returned their own zeros after any lf hitag,
hf iclass or hf 15 command. A download already cached early-returns, so which
image the FPGA held decided whether any of this happened.
All five now call FpgaDownloadAndGo_keep_EM() before any BigBuf allocation.
Free-BigBuf floor at download time goes 16384 -> 20480 (ring plus the 4096 byte
emulator block); BigBuf measures 29084..31420 across all standalone configs on
RDV4 and 40120 on a PM3 Easy.
The sim paths also clear the trace before taking their modulation buffer -
BigBuf_malloc() refuses memory a stale trace holds, and MifareSimInit() ignored
the NULL, leaving prepare_tag_modulation() to memcpy 571 bytes over the vector
table at address 0.
'hf mf eload' now zeroes emulator memory device side: CMD_HF_MIFARE_EML_MEMSET
takes a flags byte, set on the first chunk only, so 'hf mf esetblk' and every
other partial write still touch only their own blocks. That byte is why
CAPABILITIES_VERSION goes 9 -> 10; mismatched client and firmware refuse to
connect rather than write everything one byte offset.
Reported in #2836, whose USB drop is separately addressed by bc289cf43.
Builds clean for PM3RDV4, PM5 and the client; astyle clean. Not yet verified on
hardware.
Co-Authored-By: Claude Opus 5 (1M context)
Thanks @TheArchitect0880 for pointing it out and suggested a first fix.
MifareAcquireEncryptedNonces packs two 4 byte encrypted nonces plus one
byte holding both their encrypted parity nibbles into every entry, but
the reply declared num_nonces * 4 bytes. The client walks that buffer 9
bytes at a time, so on RDV4 it read 612 bytes out of a 544 byte payload
and handed roughly 15 of every 136 nonces to add_nonce() from stale
packet buffer content. Those fake nonces went into the .bin nonce file
too.
Count pairs instead of bytes. num_nonces now reports whole pairs only,
so a button abort or a static nonce bailout part way through a pair
drops the dangling nonce rather than shipping a half built entry whose
parity nibble was never filled in.
MFC_NONCE_PAIR_SIZE and MFC_MAX_NONCE_PAIRS document the layout next to
mf_nonces_resp_t so the 9 vs 4 confusion cannot come back, and the
client now refuses a reply too short for the nonce count it carries.
Both acquisition functions collect straight into the reply buffer rather
than a second PM3_CMD_DATA_SIZE stack array, which halves the stack used
per call. MifareAcquireNonces also returned isOK = 2 on button press,
which is not a PM3_* status; that is PM3_EOPABORTED now.
Co-Authored-By: Claude Opus 5 (1M context)
foundKeys was indexed [keytype][keyno] with no AID dimension and was
never reset between applications, so a key number recovered on one AID
was skipped without a single auth attempt on every later AID. On a card
with AES key 00 set on two apps, only the first one was ever reported.
Give every application its own desfire_app_keys_t and hand that to the
checker. Saving to json now uses a new 'mfdes v2' format keyed by AID,
with a loader that round-trips it; v1 is kept for existing files.
Also in the same path:
- one auth error below 7 broke out of the key number loop, abandoning
every remaining key number for the AID. Only an algo mismatch (4, 50,
51) skips the key type now; an invalid key number (3) skips just that
key number, and a transmit error retries after a reselect
- 3TDEA keys were stored 16 bytes wide and written out as 24
- -k with a 24 byte key was rejected by the parser, making the 24 byte
branch unreachable
- DesfireGetAIDList() wrote unbounded into a 78 byte app_ids buffer
Co-Authored-By: Claude Opus 5 (1M context)
Simulation now completes the full exchange with a genuine Paxton reader in
password mode, and crypto mode read/write passes Proxmark-to-Proxmark.
Firmware:
- SOF was one bit period short. The lead-in that compensated for the lost
head half bit was removed and nothing replaced it, so readers rejected
every answer with a second START_AUTH. Default is now 6.
- The edge-detect threshold was latched before being measured, so the value
chosen depended on whether the Proxmark was in a field when sim started.
It is now measured on field entry and re-armed when the reader leaves.
- The percentile walk latched on run-scoped variables, so one attempt made
outside a field poisoned every later one.
- Field loss was detected from TIMESTAMP, which is free-running MCU time and
never stalls. Detect it from receive silence instead.
- Frames of a length the protocol does not have no longer reach the state
machine; our own modulation tail was resetting the session and breaking
every write.
- A dropped edge merges two or three reader bit periods into one gap. Those
bits were discarded; they are now recovered by decomposition, which is what
made crypto mode work (AUTH decode 15% -> 100%).
- Threshold selection is limited to 20 and 32 and settles in under 25 ms.
Client:
- lf hitag info printed a hardcoded 0x06 and reported 'Password mode' for
every tag. It now reads page 3, takes -k (4 bytes password, 6 bytes
crypto), and says so when the config cannot be read.
- lf hitag restore: writes a dump back in dependency order - user pages,
then key material, then config last - validates the config byte, and
prints the credential the tag will require afterwards.
- lf hitag crack2 now reports why it failed instead of a bare 'fail'.
- trace list: bit count moved to its own column, relative mode shows a
Frame Delay Time row rather than renaming Start/End, --frame and -r
rejected together.
PM3_CMD_DATA_SIZE went 512 -> 624 without a capabilities bump, so a new
client connects to old firmware and every oversized command dies at the
device's length check with no message.
Append max_cmd_data_size, bump to v9. The client now accepts an older
capabilities struct - it only ever grows by appending, so an older layout
is a prefix - and defaults the frame size for pre-v9 firmware.
SendCommandNG bounds by the device value instead of the compile time one.
Also zero init capabilities_t on the device, it leaked stack bytes.