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)
PLATFORM=PM3ICOPYX has not compiled at all -- it dies on obj/start.o,
before anything else is built:
common_arm/gpio/gpio_hw_at91.h:77: error: 'GPIO_FPGA_ON' undeclared
(first use in this function); did you mean 'GPIO_FPGA_DONE'?
PA26 has two different jobs depending on the FPGA, per
config_gpio_proxmark3.h:
#if defined XC3
#define GPIO_FPGA_SWITCH AT91C_PIO_PA26
#else
#define GPIO_FPGA_ON AT91C_PIO_PA26
#endif
ICopyX has no FPGA power rail to switch; that pin selects the FPGA
instead. But Gpio_FPGA_ON_High(), Gpio_FPGA_ON_Low() and
gpio_fpga_on_setup() all referenced GPIO_FPGA_ON unconditionally, so the
symbol simply does not exist on XC3.
'This board has no FPGA power switch' is already a supported case: the
AT32 port stubs both accessors with a bare '// Unsupported' comment, and
PM5 depends on that, while Gpio_FPGA_SWITCH_High()/Low() two functions
further down in this same header are guarded with #ifdef for the mirror
case. The AT91 header just never grew the XC3 arm. All three now carry
the same guard.
fpga_loader.c calls Gpio_FPGA_ON_High() from shared code; on ICopyX that
compiles to nothing, which is correct -- the rail is always on.
Verified: fullimage builds for PM3ICOPYX (356832 bytes, with
PLATFORM_EXTRAS=FLASH), and for PM3RDV4, PM3GENERIC, PM3ULTIMATE and
PM5. For the boards that do have the pin the guard costs nothing -- the
.text section is byte-identical before and after, 289464 bytes on
PM3RDV4 and 243584 on PM3GENERIC; only the embedded build timestamp and
source hash move in the full ELF.
Co-Authored-By: Claude Opus 5 (1M context)
PLATFORM=PM3ULTIMATE has not built since c0ecb1c62 (2026-04-14), which
fixed a real heap overflow by changing one character:
- if (total_size > num_infiles * FPGA_CONFIG_SIZE) {
+ if (total_size >= num_infiles * FPGA_CONFIG_SIZE) {
The old '>' checked after the fact, so a round could start with the
buffer already full and write num_infiles * FPGA_INTERLEAVE_SIZE bytes
past the end. But '>=' answers the wrong question: a buffer that is
exactly full is not an error, it is the expected end state for a
platform whose bitstreams are sized to FPGA_CONFIG_SIZE.
Two things had to change.
all_feof() tested the EOF flag, and C only sets that once a read has
already run off the end -- consuming the last byte of a file leaves it
clear. A file whose length is an exact multiple of FPGA_INTERLEAVE_SIZE
therefore still looked unfinished after its final whole chunk, and the
interleave loop ran one more round of pure zero padding. It now peeks a
byte with fgetc/ungetc instead, so a file read to its last byte counts
as finished right away.
PM3ULTIMATE is the only platform this reaches:
fpga_pm3_ult_felica.bit 69984 = 243 * 288 exactly
fpga_pm3_ult_hf_15.bit 69983
fpga_pm3_ult_hf.bit 69980
fpga_pm3_ult_lf.bit 69980
243 rounds * 4 files * 288 = 279936, which is exactly
4 * FPGA_CONFIG_SIZE for 2s50vq144. Round 244 was padding only, and '>='
killed it there. Stock PM3's largest bitstream is 42172, not a chunk
multiple, so its last round trips feof naturally and it never gets that
far -- which is why this went unnoticed, nothing in CI builds ULTIMATE.
The guard now asks whether the next round fits, which is the condition
it was always meant to express and is strictly stronger than the
original '>':
if (total_size + (num_infiles * FPGA_INTERLEAVE_SIZE) > num_infiles * FPGA_CONFIG_SIZE)
Verified: PM3ULTIMATE compresses 279936 bytes to 40195 and all four
bitstreams decompress back byte-exact. Output is byte-identical to
before for stock 2s30vq100 (169344 -> 106628), for icopyx XC3
(72864 -> 27292) and for the '-s' .data section path. fullimage builds
for PM3ULTIMATE, PM3RDV4, PM3GENERIC and PM5.
Note FPGA_CONFIG_SIZE is now exactly the size of the largest ULTIMATE
bitstream. Regenerate that one byte larger and the guard fires again,
correctly; bump the constant by one interleave step rather than touching
the guard.
Co-Authored-By: Claude Opus 5 (1M context)
ReaderHitag() and WriterHitag() double-replied on the abort path: when a
new command arrives mid-loop, data_available() sets checked = -1 and breaks,
then the out: block calls reply_ng(PM3_ESOFT) *and* falls through to a second
reply_ng(PM3_SUCCESS/EFAILED) — two NG responses for one command.
USB tolerates the stray frame, but over the BLE/FPC bridge the extra
unsolicited response desyncs the pipeline and the device appears to stop
responding until reconnect (reproducible with `lf hitag dump --pwd` on empty
air, then any following command over BLE).
Gate the final reply_ng behind an else so each command yields exactly one
response on every path: abort -> PM3_ESOFT, otherwise -> PM3_SUCCESS/EFAILED.
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
'hf legic eload' writes emulator memory as a burst of back-to-back
packets. CMD_HF_LEGIC_ESET called FpgaDownloadAndGo(FPGA_BITSTREAM_HF)
on every one of them, and when that actually had a bitstream to download
the device stopped servicing USB for long enough that the packets already
in flight behind it were dropped.
Reproducible, and it only shows after a command that left a different
bitstream loaded, which is why it has gone unnoticed:
hf iclass eload ... # loads FPGA_BITSTREAM_HF_15
hf legic eload -f x.bin # first packet downloads FPGA_BITSTREAM_HF
hf legic esave --1024 # 404 bytes of 1024 come back as zeros
The boundary is always 619, which is max_cmd_data_size minus
sizeof(legic_packet_t) -- exactly one packet. Back to back a second time
it works, because the bitstream is then already loaded. The 'fast push
mode' in legic_seteml() is not involved: block_after_ACK only applies to
OLD frames and these are NG.
So the handler does no FPGA work at all now. The comment it used to carry
-- 'if it is called later, it might destroy the Emulator Memory' -- was
aiming at a real problem but solving it at the wrong end. init_tag() in
legicrfsim.c is where the bitstream is genuinely needed, and it was using
the plain variant twelve lines above 'legic_mem = BigBuf_get_EM_addr()',
so 'hf legic sim' was wiping the image it was about to serve. That one
becomes _keep_EM.
Five sibling handlers carry the same copy-pasted comment and the same
wipe-before-use pattern and are switched to _keep_EM as well:
CMD_LF_EM4X50_SIM (em4x50_sim reads its tag out of emulator memory),
CMD_LF_EM4X50_ESET, CMD_HF_ISO15693_EML_SETMEM, EML_GETMEM and
CMD_HF_MIFARE_EML_MEMCLR. Only the LEGIC path has a reproducer; the rest
is the same fix applied where the same mistake is visible.
Verified on an RDV4: the reproducer above now loses 0 bytes over three
runs, 'hf legic sim' reports the MCD/MSN of the uploaded image, and
eload/esave still round-trips byte-identical for MIFARE Classic 4K,
iso15693 and iCLASS.
Co-Authored-By: Claude Opus 5 (1M context)
A MIFARE Classic 4K fills 4096 of emulator memory exactly, so at that
size it was fully committed and nothing larger could be simulated at
all.
The 4096 comes out of BigBuf, which is 33272 bytes on AT91, so it is
4096 that traces and LF samples do not get. The largest allocation any
tag simulation makes alongside emulator memory is 10459 bytes, which
leaves 18.7 kB of trace at the new size. LF acquisition is unaffected
either way: lfops.c calls BigBuf_free(), which drops emulator memory
entirely, so the two never coexist.
The device already reports this size to the client in capabilities_t, so
no client change is needed and both the client and device side download
clamps pick up the new value on their own.
This increased reserved space for emulator memory is intended pave the way for MIFARE DESfire simulation better.
Verified on an RDV4: eload/esave round trips byte-identical for MIFARE
Classic 4K, Ultralight, iso15693, iCLASS and LEGIC MIM1024; ST25TA
simulation starts and runs; lf read still gets 33271 samples; both
clamps now truncate at 8192.
Co-Authored-By: Claude Opus 5 (1M context)
Three things that all cost memory or correctness once BigBuf is larger
than 64KB, which it already is on PM5.
BigBuf_max_traceLen() returned a uint16_t while s_bigbuf_hi is a
uint32_t. At 33272 on AT91 that is harmless; on AT32, where BigBuf runs
to several hundred kbyte, it truncates and LF sampling asks for a
fraction of the buffer that is there. Widened, along with the four
callers that assigned it straight back into a uint16_t.
BigBuf_malloc() and BigBuf_calloc() took a uint16_t chunksize, so a
request of exactly 65536 arrived as 0 and anything above wrapped. It
returned NULL for the 65536 case by accident, not by design. Both take a
uint32_t now and the guard tests the upper bound explicitly, so an
oversized request fails like any other allocation that does not fit.
The 14a tag simulation allocated its dynamic modulation buffer as a flat
512, or 4096 for ST25TA. prepare_tag_modulation() memcpy's the encoded
answer out of the ToSend buffer and tosend_stuffbit() hard caps that at
TOSEND_BUFFER_SIZE, so 1788 of ST25TA's 4096 could never be reached --
it was the largest single allocation of any tag simulation for nothing.
Meanwhile 512 is 68 bytes short of what a full 64 byte response encodes
to, at 9 bytes per byte plus 4, so those answers were refused at
modulation time.
Both are now derived from the response size and capped at what ToSend
can hold: 580 for the default tag types, 2308 for ST25TA. That also
fixes the reason the 4096 never helped -- all six call sites passed the
512 macro as max_buffer_size rather than the size actually allocated, so
ST25TA allocated 4096 and then bounds checked against 512.
Worst case for a simulation that also holds emulator memory drops from
12247 bytes of BigBuf to 10459.
Built for client, RDV4 and PM3GENERIC. Not yet run on hardware.
Co-Authored-By: Claude Opus 5 (1M context)
b82f60362 landed parseproac.c without them, so a clean checkout does not
build.
- mad.c / mad.h: mad_find_aid() and mad_count_aid(); DetectHID() delegates
to the first, it was already generic
- mad.json: 0x4982 PROAC, sorted in after NORALSY's 0x4980
Co-Authored-By: Claude Opus 5 (1M context)
Sectors 9 and 11 were printed raw and marked 'not decoded'.
- remove the XOR keystream and print the ten eight byte records
- check the eighteen record bytes that are XOR combinations of sector 0,
sector 15 and the UID, plus three record to record ties
- 'hf mf view --selftest' now covers the decoder, using a synthetic card
- sector 15 marker compare ignores case, factory blanks were not matched
- an all FF or all 00 payload is named, not decrypted
- sector 15 block 2 is text on a blank, so only read a serial there when
the first four bytes are not printable
Research. The remaining 58 payload bytes are issuer data and are printed
without interpretation.
Co-Authored-By: Claude Opus 5 (1M context)
'hf mfdes dump' walked one application and printed it. It now walks
every application on the PICC, keeps what it reads, and saves a
'hf-mfdes-<UID>-dump.json' card image. '--aid' / '--isoid' / '--dfname'
still narrow it to one application, '--ns' skips the save.
The format is 'mfdes v1', written and read in fileutils.c and documented
in doc/mfdes_dump_format.md. Two decisions worth stating:
- The PICC level is application 000000, so every key in the file says
which AID it opens. Key version and key value are separate: a version
with no key is the normal shape for a key that was found but never
recovered, and a missing key never means the key is zero.
- Every file carries a 'Read' flag. A file whose contents could not be
fetched is recorded as unread with no data at all, rather than as a
run of zeros. A simulator built on this must not confuse '8 bytes of
00' with 'we could not read 8 bytes'.
'hf mfdes view -f <fn>' prints such a file with no device attached.
With no '--keys', the dump looks for 'hf-mfdes-<UID>-keys.json' by
itself, so a 'hf mfdes chk -j' run is picked up on the next dump without
naming the file again.
Two fixes fell out of testing against a DESFire EV2:
- DesfireSetKey() calls DesfireClearContext(), which wipes command set,
comm mode, KDF and UID, not just the key. Swapping in a per-application
key that way left the context at 'Communication mode: n/a' and
DesfireFillFileList() then returned junk file ids. Use
DesfireSetKeyNoClear().
- GetVersion and the originality signature are answered unauthenticated.
Asking for them from inside the authenticated session produced a
'Wrong communication mode' warning and a run of MAC mismatches.
hex_to_buffer() treats hex_max_len as a byte count while every sprint_hex*
caller passes sizeof(buf) - 1, a character count, so it writes two or
three times the buffer size. Measured, sprint_hex_inrow overflowed at
4098 input bytes. Doubling UTIL_BUFFER_SIZE_SPRINT to 16384 moves that to
8192; the mixed semantics still need auditing across ~30 call sites.
Co-Authored-By: Claude Opus 5 (1M context)
Refactor BLE error handling to return standard error codes instead of PM3_* constants. Update time-related functions to use a consistent method for obtaining the current time.
Signed-off-by: Niel Nielsen <nieldk@gmail.com>