restore refused UL-AES outright. The generic page plan assumes the last
four pages are cfg0/cfg1/PWD/PACK, which on UL-AES are the AES keys, so
the data loop walked over the lock page, both config pages and the key
lock bits at 0x2D. Give UL-AES its own plan: user memory 0x04..0x27,
and lock/cfg0/cfg1 behind -s. Key pages 0x30..0x37 are left alone, the
dump only ever carries AESKey0 and 0x34..0x37 read back as zeros.
info never printed the UL-AES config. ulaes_print_configuration() is
called with 0x29 but its first branch tested for 0x2C, so cfg0 and cfg1
fell through to the bare return and only the CMAC page showed up.
info also read page 0x34 as if it were an EV1 config page. That is the
UIDRetrKey. Genuine silicon NAKs the read, but a magic or simulated
card would have had its key printed as cfg0/cfg1/PWD/PACK.
Tested on a UL-AES with AUTH0=4, PROT set and secure messaging on.
Restore of an unmodified dump comes back byte identical; a dump carrying
DEADBEEF at 0x27 and CAFEBABE at 0x2B writes 0x27 and leaves 0x2B alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Transcode the MinGW client's CP850 output before writing command documentation and force Python to read the converted stream as UTF-8. Normalize the remaining escaped Greek mu strings to the project's micro sign convention.
Co-Authored-By: OpenAI Codex <noreply@openai.com>
MSYS2 git fails with
C:\ProxSpace\msys2\usr\bin\git.exe rev-parse refs/tags/v4.23346^{commit}
Error: fatal: ambiguous argument 'refs/tags/v4.23346^commit': unknown revision or path not in the working tr
GitHub's windows-latest hosted runner images ship with Git, so let's use it
The warnings are false-positive: resp.data is a union of uint8_t asBytes[] and uint32_t asDwords[], so the buffer is actually 4-byte aligned — but casting uint8_t* directly to a stricter-aligned struct pointer trips -Wcast-align. Fix by routing through (const void *)
Add bidirectional conversion between DFC v4 credentials and mfdes v1 JSON, with documentation and round-trip coverage. Include H10301 FC69 CN420 factory and field fixtures and an external HID reader test target.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
Exclude the USB test dispatcher and queued-random support from normal firmware builds. Enable them with ENABLE_HFMFDESETEST=1 when running emulator vectors.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
Recognize interindustry SELECT APDUs by DF name or file identifier and return card-compatible status words.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
Constrain activation to supported bit rates, ignore invalid polling and frames, and follow independent ISO-DEP block sequences during recovery.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
A session opened with 0x0A kept the EV1 secure messaging: an 8 byte CMAC
on every answer, CRC32 in enciphered transfers and ChangeKey, one CBC
chain across commands. The legacy session is different in every one of
those: nothing on a plain answer, a 4 byte DES MAC over the data alone on
MACed transfers, CRC16 over the data in enciphered transfers and in the
ChangeKey cryptogram, the reader's cryptograms undone by enciphering, and
every cryptogram from a zero IV.
The session remembers which authentication opened it and the four places
that secure or check a transfer take the legacy path when it was 0x0A:
the write gatherer's expected length, the command unwrap, the answer
wrapping, and ChangeKey. Chained answers carry the transfer's mode so a
MACed read puts its MAC on the last frame only.
Measured against a genuine DESFire EV1 4K with the pm3 as reader, all
zero and non-zero DES keys, writes and reads in all three modes and a
ChangeKey of another key: the simulation answers every frame with the
card's bytes.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
The second frame of a 0x0A handshake was handled like 0x1A: the reader
token was deciphered in CBC with the IV chained from E(RndB), and RndA'
was enciphered with the IV chained from the token. A card starts each
legacy cryptogram from a zero IV and only ever enciphers, so it recovers
the token by enciphering each block and xoring the previous ciphertext
block.
The wrong IV only touches the token's first block, so RndB' still
verified and the simulation reported success with RndA off by E(RndB).
Session key and final frame were wrong from there, which is where the
D40 secure messaging captures started to disagree.
Measured against a genuine DESFire EV1 4K: the card's final frame is the
same across sessions with different RndB, which only a zero IV gives,
and the simulation's old answer matched the chained-IV arithmetic byte
for byte. Both handshake captures now replay through the simulation.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
The regenerated `hf mfdes etest` object had been spliced inside the
`hf mfdes esave` object in doc/commands.json, so catalog consumers did
not see it. Moved by hand to its place among the commands, matching what
`make commands` produces for that entry.
Help and how-to said a recorded session replays byte for byte. It does
only when the queue is filled in draw order: the random UID takes 3 bytes
at --scan on a random-id image, then each authentication takes its RndB.
Say so.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
One command with one action per call: --begin, --scan, --apdu <hex>,
--random <hex>, --fieldoff, --state, --end. Text output by default,
-j gives one line of JSON per call for a script to read back.
Distinct failures are reported as such: no image in emulator memory,
not activated (--scan first), random queue full, and a firmware that
does not answer.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
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>
A transfer whose size is an exact multiple of the endpoint size ends with
a zero-length packet. Nothing acknowledged it, so that receive bank stayed
selected and data queued in the other bank stalled. A command already
buffered by usb_read_ng was also not recognised until another packet came
in. Both AT91 and AT32 reuse their receive cleanup to acknowledge the empty
packet and rearm reception; frame parsing is unchanged.
Seen as a small packet lost now and then right behind a burst of
CMD_HF_MIFARE_EML_MEMSET chunks from `hf mfdes eload`.
(cherry picked from cindersocket/proxmark3 495bc201d, upstream PR #3628)
Co-Authored-By: gpt-daybreak-blue <noreply@openai.com>
Three things behind a scan that ran against one application, printed its check
line twice, and then said nothing at all.
The dictionary loader was truncating rather than rejecting. 'loadFileDICTIONARYEx'
null terminated each line at the requested key length and only skipped lines that
came out too short, so any line holding more hex than asked for was silently cut
down and taken as a key. Reading 'mfdes_default_keys' for DES keys turned all 47
of its 32-hex AES lines and all 4 of its 48-hex 3K3DES lines into 8-byte keys that
are on no card, reporting 58 DES keys where the file holds 7. It now skips a line
whose hex run is longer than the key being asked for, which is what its sibling
'loadFileDICTIONARY_safe_ex' has always done. A trailing comment or whitespace
after the key is still fine, so the bundled dictionaries keep working. Counts for
that file go from 58/51/4 to 7/47/4, and mfc, iclass, mfp and t55xx are unchanged.
The key numbers to check came only from file access rights. An application with
three keys whose files name just key 1 had key 2 left out of the sweep entirely,
even though the card had already said how many keys it holds. Key settings carry
that count in the low nibble, so chk reads it and checks every key number the
application declares, the same sweep 'hf mfdes detect' already does. On a test
card this immediately turned up an AES key 2 still at the factory default, which
no file pointed at and which the old code could never have found. The access
rights walk stays as the fallback for when key settings are not readable, which
also means the common path no longer spends a file list round trip.
And the last round of a chunked dictionary read comes back empty. That empty list
was still handed to AuthCheckDesfire, which selected the application, read its key
settings, probed for LRP and listed its files in order to then check no keys at
all, printing a second identical check line on the way. Skipped now.
Finally, a scan that found nothing printed nothing. In non verbose mode all that
was left was a blank line, itself a leftover from the progress markers removed in
d842c7e5. It says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hf mfu cchk and hf mfu aeschk were the same command twice. Ninety lines of
dictionary loading and chunked device calls were duplicated, and the only
real difference was which key slot they targeted: aeschk took --idx, cchk
hardcoded the Ultralight C slot. Both are replaced by hf mfu chk, which
reads the tag type off the card with GetHF14AMfU_Type() and picks the slot
itself. Ultralight AES still honours --idx, 0 DataProtKey, 1 UIDRetrKey,
2 OriginalityKey. Ultralight C holds a single key, so --idx is rejected
there rather than silently ignored. Any other tag is refused with a pointer
to hf mfu info.
Without -f the dictionary is mfulc_default_keys.dic for both tag types. A
segment check keeps needing an explicit -f, because the segment dictionaries
hold four byte keys and the default one does not. aeschk used to advertise
mfulaes_default_keys.dic in its help, which has never been shipped.
Collapsing the two bodies also fixes --retries. firstChunk and lastChunk
were declared outside the retry loop and never reset, so only the first pass
was correct. From the second pass on, every chunk went out with firstchunk
clear and lastchunk set, which on the device side means no select and a
field teardown after each chunk, against a field the previous pass had
already dropped. They are now scoped to the pass.
client/pyscripts/mfulaes_mask_recovery.py called aeschk and now calls chk.
doc/commands.md and doc/commands.json are regenerated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The SRIX4K block scrambler in cmdhf14b.c was a three entry lookup table with
its callers commented out, so `hf 14b valid` printed hard coded values and
nothing ever decoded. The scrambler is a 4x4 transpose of the block read as
sixteen crumbs. A transpose is its own inverse and it reproduces all three
entries of the old table, so the stub is replaced by a working parser.
client/src/parsers/parsemykey.c reads the application off a dump: key id,
production date, operations counter, vendor code, lock id, current and
previous credit, and the eight slot transaction ring. Every block carries a
checksum in its top byte and the parser reports how many hold up, which
catches a wrong UID or a torn write. The credit blocks are XORed with a
session key derived from the UID, the vendor code and the count down counter
in block 6, so the file name has to carry the UID.
`hf 14b valid` is removed. Checking that the maths holds is now
`hf 14b view --selftest`, following `hf mf view --selftest`, and it runs
against traces/hf-14b-D0021F673CB26556-dump.json, a dump of a real reset key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>