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>
The main loop cleared the whole PacketCommandNG payload before every call to
receive_ng(), so a device sitting idle memset PM3_CMD_DATA_SIZE bytes on each
pass of the loop whether anything was arriving or not. The zeroing belongs
inside receive_ng_internal(), once the preamble has been read and a packet is
known to be coming in, and that is where it is now.
It has to be before either branch fills the payload, because neither fills all
of it: an NG command writes only its own length, and an old style one only
PM3_CMD_DATA_SIZE_OLD. Whatever is not written has to start at zero.
Moving it also covers two callers that never had it. appmain.c has a second
receive_ng() that drains the buffer on a mode switch, and iclass.c has one that
picks up EML_MEMSET commands while waiting for RF. Both declare their
PacketCommandNG without initialising it, and the iclass one then reads a struct
out of the payload -- so a command shorter than that struct was reading stack
garbage beyond its length. That is fixed as a side effect.
Builds for RDV4 and PM5.
Thanks to @Msprg et al
memcpy was taken off its byte-at-a-time loop in b66bb2659 and memset was left on
one, though the device memsets constantly and the loop is the same shape. This
gives it the same treatment, and it is the simpler of the two: with no source
buffer there is no alignment to match, so once the destination is word aligned
the word fill is always available.
Align the destination, build the fill word once, then store it four at a time
and singly for the remainder, with at most three bytes left over. Unrolled four
ways for the same reason memcpy is: at -Os the loop bookkeeping otherwise costs
more than the stores.
The bulk path goes from six instructions per byte to nine per sixteen bytes,
read off the disassembly:
old subs / cmp / bgt / strb / adds / b per byte
new str x4 / adds / b, plus subs / cmp / bgt per 16 bytes
Wall clock is not measured here. The closest anchor is memcpy's own figure from
b66bb2659, which measured 221us to 34us for a 624 byte aligned frame, and memset
has a store where memcpy has a load and a store.
Verified against libc memset on the host before flashing: 7224 cases, every
alignment from 0 to 7, every length from 0 to 300, and fill bytes 0x00, 0xFF and
0xA5, with no mismatches. Then on hardware, where a wrong memset would show up
everywhere rather than in one place: the DESFire simulation harness passes, and
hw status, hw tearoff, mem info, lf search, hf 14a info and hf mf info all
behave.
Builds for RDV4 and PM5.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things you asked about after a scan printed a lone "d" between applications.
The "d" and "p" were a progress marker saying how a key had been found, a
dictionary or a pattern, printed without a newline after every hit in
non-verbose mode. The find itself is already reported in full and
unconditionally on its own line -- "AID 0x010203, Found AES Key 00... <key>" --
so the marker added a stray character to the output and nothing else. Removed.
Each found key also repeated the AID that the "Checking aid" line above it had
just announced, so that is gone too. The only path to that print is the scan
loop, which prints the AID for every application before checking it.
The crypto algorithm is highlighted now as well, so the algorithm column can be
scanned down rather than read. `hf mfdes detect` already prints its channel, its
algorithm and its key that way, and chk was highlighting only the key.
And the command needed a dictionary spelled out. `hf mfdes detect` already falls
back to the bundled `mfdes_default_keys` when none is given, and there is no
reason for chk to differ, so it does the same now when neither a dictionary nor
a pattern was asked for. That also lets it run with no arguments at all, which
it previously refused with a help screen because its parser was told not to
allow an empty command line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four fixes, three of them from auditing the simulation against M134034 rather
than from a failing test, and one found by the audit's own new test.
An error status now ends the authenticated session. "The CMAC is not calculated
and attached, if an error code is returned. Then the application of the PICC
leaves the authenticated state and needs to be authenticated again" (7.3.4).
The simulation had 127 error returns and eight of them cleared the session, so a
reader saw an authenticated session survive errors no real card survives. It is
applied once where a command's answer leaves the dispatcher, rather than at every
error return, so a new error path cannot forget it. 0x00, 0x0C, 0x90 and 0xAF are
the four statuses that are not errors.
Answers are sized from the reader's frame size. The reader states it in its RATS
-- FSDI in the high nibble of the parameter byte -- and the simulation ignored it
in favour of a fixed 96 byte chunk. That is safe against a reader asking for 256
and wrong against anything below 128, which would be sent frames it cannot
receive. The chunk now comes from FSDI, rounded to a whole cipher block. A reader
asking for less than 32 bytes still cannot be served, because the status byte, a
CMAC and the framing cost twelve before any payload, but nothing in practice asks
for that and this card's own ATS advertises 64.
The length of a write is taken from the command instead of guessed. WriteData,
WriteRecord and UpdateRecord each carry a 3 byte length in their header, and the
header is in the clear in every communication mode, enciphered included, so the
total is known from the first frame. The simulation had been treating "this frame
looked full" as "more is coming", which is guesswork about something the protocol
states outright.
Writing that test found that a chained write never worked at all. The dispatcher
refused an 0xAF continuation unless a read was being chained or an authentication
was half done, so the second frame of any write longer than one frame came back
91 1C. Every write tested until now fitted a single frame. It is one more
condition on that check.
Tested against the simulation: 100 byte writes in plain, MACed and enciphered
mode all complete over two frames and read back byte for byte, `hf mfdes dump`
walks three applications and six files with no errors, and the regression harness
passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An enciphered read longer than one frame was answered with 91 7E, because the
CRC32 covers the whole read and the code could only build that in one piece.
It is one CBC run over the file data, a CRC32 behind it and zero padding, split
across frames with the init vector carried from one to the next -- "if the
commands are queued due to a very long data stream, the init vector for the
decipherment is always updated" (M134034 7.3.7). So each frame only has to carry
whole blocks, which the 96 byte chunk and the padded total both are, and the
reader joins the ciphertext and deciphers the lot in one go.
The read now walks a stream rather than a file extent: bytes come from the file
while they last, then the CRC32, then padding. The CRC is taken up front, over
the data and the status byte the last frame will carry, so it has to be built a
piece at a time -- the status byte is not in the card image and the data can be
a whole file, which rules out crc32_ex() and its single buffer. common/crc.h
already has an incremental CRC, the one legicrf.c uses, so that is what this
uses: crc_update() shifts LSB first, so the polynomial goes in already reflected
with neither reflect flag set. Those parameters were checked against crc32_ex()
over every length from 0 to 200 before being used.
Tested against the simulation with a 128 byte file created through the reader in
Full communication mode with all rights on key 0, so the file's mode actually
applies rather than being forced plain by a free access right. Reads of 64, 96,
112 and 128 bytes all verify, the last three over two frames, and the content
comes back as written. A wrong init vector or a wrong CRC would have failed the
client's CRC32 check rather than passed quietly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A realtime `lf read` streams samples straight to the host at the LF sample rate,
around 125 kB/s. If the host stopped draining for even a moment,
async_usb_write_requestWrite() returned false, and ReadLF_realtime() treated that
as fatal: it returned PM3_EIO through a goto that also skipped
async_usb_write_stop(). The IN endpoint was left busy, every later usb_write()
then returned PM3_EIO, and the device went silent until it was physically
replugged. A USB bus reset does not clear it -- EP0 keeps working, descriptors
read fine, only bulk IN is dead. That is what a long `lf read` looked like from
the client: a transfer that stopped early, and then a device that would not
answer the next command.
A busy endpoint is back pressure, not an error. The sampling loop now waits for
the host, up to 100ms, and only gives up if it never comes back. Every exit path
closes the async write. The spins inside the USB helpers are bounded and drop the
stuck packet instead of turning into an infinite loop, which is the other half of
why the device never recovered. Measured against a reader deliberately held at
41 kB/s: before, the stream died at 212238 of 300000 bytes and the unit needed a
replug; after, 300000 of 300000 and it still answers.
The last packet of a stream was being lost as well. The host's CDC read buffer is
two max sized packets, and a bulk IN transfer only completes on a short packet or
a full buffer, so a stream ending on a full 64 byte packet was left sitting in a
half filled buffer. It showed as exact parity on the packet count:
513 packets requested -> 512 delivered 514 -> 514
515 packets requested -> 514 delivered 516 -> 516
async_usb_write_stop() now always sends a closing packet, the leftover partial
bytes when there are any and a zero length packet otherwise, the same way
usb_write() already did for its own transfers.
On the client side a short transfer is reported instead of being presented as a
complete read, the device is told to stop streaming on that path too, and the in
place byte counter is repeated with its final value, since the loop only samples
it every 10ms and the last line printed was stale.
Lastly `lf read` and `lf sniff` cap the sample count to the graph buffer size.
Anything past it was streamed and then discarded by getSamplesFromBufEx(), so
`-s 1500000` spent about two extra seconds collecting 220000 samples that were
thrown away, and printed "Received 1500000 / 1500000 bytes" followed by "Got
1280000 samples" with nothing to explain the gap.
Co-Authored-By: Claude Opus 5 (1M context) <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 simulation answers ReadSignature, handing back the originality signature the
card image carries, with the 0x90 status this command uses in place of the usual
0x00 and the response CMAC taken over that byte.
It is gated on the image's generation rather than on whether bytes happen to be
stored, because an originality signature is not an EV1 feature. M134034 has no
mention of command 0x3C and no mention of signatures at all, `hf mfdes info`
only asks EV2, EV2_XL, Light, EV3, NTAG413DNA and DuoX for one, and a genuine
MF3ICD81 answers 91 1C. Measured on UID 04 26 85 12 A2 56 80, which answered
GetVersion normally in the same session:
90 3C 00 00 01 00 00 -> 91 1C
90 60 00 00 00 -> 04010101001A05 91 AF
So a D40 or EV1 image answers 91 1C whatever it holds, and an EV2 or later image
serves its signature. Generation comes from versionhw[3], which is 0x01 for EV1
and 0x12, 0x22 or 0x42 for EV2.
A later card whose dump never read a signature still answers 91 1C rather than
56 zero bytes, which a reader could not tell from a real answer.
Tested against the simulation both ways: an EV1 image refuses the command with
the same status the real card gives, and an image with the hardware version
bytes of an EV2 returns its 56 bytes followed by 91 90.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The simulation answers GetKeyVersion, returning the version of the named key in
the selected application, or of the PICC master key at card level where only key
number 0 is valid (M134034 9.3.7).
No authentication is required, which is the point of the command: a key version
is readable without knowing the key. The card image has always tracked the two
apart for that reason -- a version that was read and a key value that was
recovered are different facts -- so a version the dump never read comes back as
0, the same as a default key carries.
Tested against the simulation by changing a key with a version of 0x5A and
reading it back, which also confirms ChangeKey stores the AES key version byte
it is handed. A key number the application does not have is refused.
Co-Authored-By: Claude Opus 5
A reader can rekey the simulated card now, at PICC level and inside an
application, and the new keys are in what `hf mfdes esave` writes out.
The reader enciphers the key data under the session key, and its shape depends
on whether the key being changed is the one the session was opened with. Change
that key and the frame is just the new key; change any other and the new key
arrives XORed with the current one, with a second CRC32 over the new key alone
so the card can tell the XOR came apart correctly. The card holds both keys in
that case, so it can undo the XOR -- and a key the image only knows the version
of is refused rather than guessed at. The shape being fixed means the plaintext
length is known rather than searched for: the key, an AES version byte, the
CRC32 over command, key number and that lot, and the second CRC32 when it
applies.
Which key has to be authenticated comes from the key settings, not from the
frame. The master key changes only with the master key and only while bit 0
still says it is changeable; a change-key nibble of 0x0F freezes every other
key; 0x0E lets a key be changed by whoever authenticated with it; any other
value names the one key that may change the others (M134034 9.3.4, 9.3.6).
At PICC level the top two bits of the key number choose the algorithm the new
master key is to be, since there is no application creation to fix it. Inside an
application the key type cannot change, so those bits are ignored there.
"After a successful change of the key used to reach the current authentication
status, this authentication is invalidated" -- so the session is dropped after
changing the key it was opened with, but only after the MACed answer has been
built with it, since the reader still has to verify that.
Tested against the simulation on an AES application and on the PICC master key:
changing the authenticated key leaves the old key unable to authenticate and the
new one able to, changing a different key does the same to that key while the
session carries on, and changing a key while authenticated with one that the key
settings do not name is refused with the key left alone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChangeFileSettings is answered by the simulation now. Which form it takes is
decided by the file's own "change access rights" right, not by the reader:
never refuses the command outright, free means the settings arrive as plain text
with no security mechanism at all, and anything else names the key that has to
be authenticated and the settings arrive enciphered under it (M134034 9.5.4).
That enciphered form is the first command whose parameters the reader secured
rather than the card, so it shares the unwrapping the writes use: the file
number stays in the clear, the rest is one CBC run under the session key, and
the CRC32 behind the plaintext is checked before anything is changed. A command
that unwraps its own parameters also has to be kept away from the blanket
command CMAC, or the IV moves twice.
Tested against the simulation: a file whose change right is free goes from
rights eeee to 1234 with no session at all, a file whose change right is key 0
goes from 1200 to 3210 authenticated and enciphered, and a file whose change
right has been set to F refuses every later change and keeps its settings, which
is the point of that value.
Separately, `hf mfdes esave` grew a `--keep` flag. A deleted application or file
stays in the card image as a tombstone, because the memory it held stays spent
until a FormatPICC, and esave was writing those out as though they still
existed. They are now left out by default, so a dump is the card as a reader
sees it, and `--keep` puts them back for when what a reader did is the
interesting part rather than the result it left.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A reader can add and remove applications on the simulated card now.
The three tables sit head to tail -- applications, then files, then keys -- so a
new application entry goes in where the file table currently starts and
everything above it moves up by one entry, with the application's keys added to
the end of the key table. They come up all zero, which is what a card gives a
new application. KeySettings2 is decoded as the spec defines it: the key count
in bits 0-3, the ISO file id flag in bit 5, and the cipher for the whole
application in bits 6-7.
CreateApplication requires that AID 0x000000 is selected (M134034 9.4.1) and,
when bit 2 of the PICC key settings is clear, a PICC master key session.
DeleteApplication takes either the PICC master key, or -- when that bit leaves
create and delete free -- the application's own master key, which means the
application being deleted has to be the selected and authenticated one.
Delete leaves a tombstone rather than compacting. That is not a shortcut: a
genuine card does not hand the memory back either, and only FormatPICC reclaims
it. Measured against the simulation, starting from 6400 bytes free: two
applications of five AES keys each cost 160 bytes apiece and leave 6080,
deleting one of them still leaves 6080, and FormatPICC then returns the card to
8000.
The refusals were checked by sending the raw APDUs with no session, since the
client always authenticates first: a duplicate AID answers 91 DE, creating while
an application is selected answers 91 9D, deleting with no session answers
91 AE, and AID 0x000000 answers 91 9E because it is reserved as the reference to
the PICC itself.
Note that AIDs travel little endian, so AID 010203 is 03 02 01 on the wire.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FormatPICC is answered now. It releases every application and every file and
hands the memory back, which is what makes it different from DeleteFile and
DeleteApplication: those leave a tombstone because a real card does not give the
memory back either, and this is the one command that reclaims. Here that means
rebuilding the tables around the surviving PICC entry and putting both frontiers
back where an empty card has them.
The PICC master key, its settings and the card identity are explicitly untouched
(M134034 9.4.6), so what is left is the same card with nothing on it. The same
section says the command always requires a preceding authentication with the
PICC master key, and unlike create and delete that is not relaxed by the free
create/delete key setting, so the check is on the session rather than on the
application's key settings. Measured against the simulation: 3 applications and
6400 bytes free become 0 applications and 8000 bytes free, and a bare
`90 FC 00 00 00` with no session is refused with 91 AE.
Separately, `hf mfdes esave` wrote the ATS as zeros. desfire_em_unpack() bounded
the copy with
MIN(hdr->atslen, (uint8_t)sizeof(dump->card_info.ats))
and card_info.ats is 256 bytes, so the cast wrapped to 0 and the memcpy copied
nothing -- while ats_len was still set from the image, so the saved file carried
a correct length and eight zero bytes. Both copies here are now bounded by the
image's own array, which is the smaller of the two and the one a corrupt length
could run past.
This was invisible in simulation because SimulateIso14443aInit() falls back to
the tag type's built-in DESFire ATS when the image supplies none, and for this
card the two are identical. A reader saw a correct ATS from a card image that no
longer had one, and only a load-save round trip showed it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The DESFire simulation now plays the EV1 protocol rather than just the
activation and a handful of unauthenticated queries. A reader authenticates,
enumerates, reads and writes files, and commits or aborts transactions against
the card image `hf mfdes eload` put in emulator memory.
Authentication covers 0x0A, 0x1A and 0xAA with DES, 2TDEA, 3K3DES and AES keys
taken from the image. A key the image only holds a version for still refuses to
authenticate rather than authenticating with zeros.
Secure messaging follows the file's communication mode, with the rule that the
mode only applies when a key right matching the authenticated key granted the
operation -- access granted by the free-access right runs plain whatever the
file settings say. Responses are MACed or enciphered accordingly, and the
session CMAC is taken over every command and response in order so the two sides'
IVs stay together even where neither puts a MAC on the wire.
Reads cover ReadData, ReadRecords, GetValue and GetCardUID. Writes cover
WriteData, WriteRecord, UpdateRecord, Credit, Debit and LimitedCredit, plus
CommitTransaction, AbortTransaction and ClearRecordFile. Backup data, value and
record files write into their shadow region and only move across on commit, so
an abort really does discard.
Three fixes were needed to interoperate with the client, and all three share a
shape worth naming: the authentication handshake still succeeded, because it
runs on the original key, and only the traffic afterwards was wrong.
1. A DES or 2TDEA key is stored as 16 bytes and the key itself decides which
cipher the PICC uses -- if the second half equals the first it is a single
DES key, and that governs session key generation too (M134034 8.1). The
all-zero default key is the common case. Deriving a 2K3DES session key from
it left the card MACing under a key the reader did not have. Confirmed by
decrypting a captured GetCardUID response offline: under the session key the
reader derives it yields the UID and a valid CRC32, under the other it does
not.
2. Session keys are built here rather than through Desfire_session_key_new(),
whose 3K3DES branch clears the low bit of the first eight bytes. Those bits
are key version, which a session key does not have, and the reader keeps them.
3. The reader's 0xAF continuation is not a command, it continues one. Giving it
its own CMAC restarted the running calculation halfway through a chained
answer, so every chained response longer than one frame carried a wrong MAC
while single-frame answers verified fine.
The sample card in traces/mifare is rekeyed from all zeros to the sequence
01 02 .. 10, extended to .. 18 for 3TDEA. An all-zero key has matching halves,
so it exercises only the degenerate path of fix 1 above and hides the bug;
distinct halves surface a wrong derivation on the first MACed frame.
tools/desfire_sim_test.sh loads an image, simulates it, drives the second
Proxmark3 at it and reports. It stops the simulation the way the client does, a
newline on its stdin, through a fifo held open for the run rather than a fixed
timer -- a run that outlives its timer leaves the device simulating. A command
counts as passing only if it prints something that says it worked: card errors,
client side argument rejections and MAC or CRC complaints all fail, because
several of those print a plausible result line as well and matching on the
result alone reports passes that never happened.
Tested on two RDV4s, one simulating and one reading: activation, info,
application and file enumeration, all three key types, enciphered GetCardUID,
plain and MACed reads up to 256 bytes over chained frames, and writes verified
by reading the image back out with `hf mfdes esave`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PacketResponseNGPreamble is 10 bytes, so data[] sat two past a word
boundary while its source is word aligned. The residues differ, so the
memcpy word path could never trigger for a reply.
Offset the whole frame by two in the buffer and data[] lands aligned.
Costs two bytes of stack. The preamble, the frame length and every byte
on the wire are unchanged, so no client change is needed.
hw status transfer speed on RDV4: 630240 -> 788736 bytes/s. That is
about 1us/packet off the link floor; what remains is usb_write waiting
for the host, not device work.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The device ships its own string.c because there is no libc linked, and
memcpy was a byte at a time loop: six instructions per byte, 17 cycles
per byte measured, 221us for a 624 byte frame. Every copy on the device
paid that.
Take words when source and destination share their offset within a word,
which is the only case ARM7TDMI can do at all, and unroll the byte tail
four ways since at -Os the loop bookkeeping otherwise costs more than the
copy. 624 bytes aligned goes 221us -> 34us, misaligned 221 -> 125.
192 bytes, was 24. The bootrom does not link string.c.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AT91F_USB_SendStall spun on STALLSENT with no exit. That bit only
arrives once the host polls the endpoint and takes the handshake, so a
host that vanishes in between wedges the device. It was the last
unbounded wait in the file without an escape.
Exit on RXSETUP (host abandoned the request and sent a new SETUP), on
ENDBUSRES, or on a spin count, reusing the 0x1FFF that usb_read already
uses. usb_check() is not usable here: it calls back into
AT91F_CDC_Enumerate, which is what calls this.
Do not test RXSUSP. Nothing writes it back to UDP_ICR, so it latches on
the first bus idle and stays set, which makes the guard fire on every
stall and stops the STALL from ever being delivered.
The second wait is bounded too, since leaving the first one early can
let the host set STALLSENT after the clear.
Also ISOERROR -> STALLSENT. Same bit 3, but only one of those names is
true on a control endpoint.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
uint32_t plus three uint8_t pads to 8, so sizeof(line) was 8 where CDC
defines 7. Hosts asking for more than 7 got a byte of uninitialised
static padding. The commented out SET_LINE_CODING loop already hardcodes
i < 7, so the wire format was understood; the struct just did not match.
No observable change for cdc_acm or usbser.sys, which both ask for
exactly 7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AT91F_USB_SendData only took length, and every caller passed
MIN(sizeof(x), wLength), so the function could not tell it had
truncated. A control IN transfer ends on wLength bytes or on a short
packet; returning fewer than wLength in whole 8 byte packets is neither,
and the host polls until it times out.
Take wLength too, do the MIN inside, and send a ZLP when the reply is
shorter than asked and lands on a packet boundary. The length > 0 guard
avoids a second termination, since the send loop already emits an empty
packet for an empty payload.
This is what the serial number descriptor's '(size % 8) == 0 OS bug
workaround' padding was dodging. Not an OS bug.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
StrMS_OSDescriptor advertises MSFT100 and vendor code 0x1C, but the
handler for that request was commented out, so it fell through to the
standard switch and stalled. Windows caches that under
usbflags\<VID><PID><bcdDevice> and stops asking, and bcdDevice never
changes, so no reflash could recover it.
Enable both feature descriptors and answer 0x1C in AT91F_CDC_Enumerate.
The compatible ID is left empty so usbser.sys keeps binding; WINUSB
there would take interface 0 away from CDC and kill the COM port.
DeviceInterfaceGUID was GUID_DEVCLASS_PORTS, a setup class GUID in an
interface GUID field - replaced with a fixed project GUID.
AT32 is unchanged; both descriptors are behind #ifndef PM5.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
detectindala() reports the bit count it gave up on through
PrintAndLogEx(INFO, ...), so every failed Indala demodulation prints
[=] DEBUG: detectindala | 56
in normal output. It is the failure path: found_size < 64 means the
demodulator did not find a valid Indala (64 or 224 bits) and is returning
-5. Any noisy LF read hits it, so `lf search` with no card on the antenna
shows a developer's debug line to every user.
Every sibling in the same function already uses DEBUG, including the one
nine lines above it at "DEBUG: Warning - Indala had to invert bits", so
this is an oversight rather than a deliberate level. ui.c drops DEBUG when
g_debugMode is 0, which is the default, so the line stays available under
`data setdebugmode -1` and is simply no longer in the way.
Checked the whole client for the same class: the only other "DEBUG"
strings printed above DEBUG level are cmddata.c FSKToNRZ, and both of
those are already wrapped in `if (g_debugMode > 1)`, so they are correct
and are left alone.
Before, `lf search` over a reader field with no card:
[=] Checking for known tags...
[=] DEBUG: detectindala | 56
[-] No known 125/134 kHz tags found!
After:
[=] Checking for known tags...
[-] No known 125/134 kHz tags found!
and with `data setdebugmode -1` the detectindala lines come back, now
carrying the [#] debug prefix like the rest of them.
Passes `make style` unmodified.
data_available() and data_available_fast() are how the device notices that
the host has asked a running command to stop. They branch on USB and on
WITH_FPC_USART_HOST, and never learned about the Proxmark5 BWM.
A PM5 with the wireless module builds PLATFORM=PM5 PLATFORM_EXTRAS=BWM,
which defines WITH_BWM_FORWARD. BTADDON errors out on PM5 and BWM errors
out on anything else (common_arm/Makefile.hal), so WITH_FPC_USART_HOST is
never defined on that build and both functions compile down to a USB-only
check.
Commands still arrive, because receive_ng() does poll the module
(armsrc/cmd.c). What was blind was the abort path: CMD_BREAK_LOOP is never
observed over BLE or WiFi, so every loop whose only exit is this check runs
until the button is pressed. That is 75 call sites across 26 files in
armsrc/, including the sniff, simulate and reader loops, so in practice a
wireless session cannot be interrupted at all. ReaderHitag() is the worst
case: with no tag on the antenna it has no other exit, so `lf search` alone
leaves the device answering nothing.
This is reachable with upstream tooling only: client/src/uart/ble_posix.c
is the native BLE transport, selected with `-p ble:<address-or-name>`, and
doc/md/PM5_Start_Here/PM5-BWM-USAGE.md documents both that and
`-p tcp:<host>:<port>` over the module's WiFi. Both share the same inbound
de-framer, so both are affected.
Folded into the existing preprocessor chain as an #elif, mirroring
receive_ng(), since the two transports are mutually exclusive by build.
USB is still tested first, so the module is only polled when USB has
nothing, exactly as the FPC branch behaves.
bwm_fwd_rxdata_available() is the same call receive_ng() already uses. When
the de-frame FIFO is empty it costs a flag test and one DMA counter read
(bwm_uart_rx_available()); it only walks bytes when bytes have actually
arrived, and those bytes are the abort. That mirrors the FPC branch, where
usart_rxdata_available() already calls usart_rx_poll() from these same two
functions. data_available_fast()'s only caller is the ReadLF_realtime()
sampling loop, which reaches it once per 64 saved samples; it is included
here so the two functions cannot drift apart again, which is how this bug
arose.
Object code is byte-identical on every platform that does not define
WITH_BWM_FORWARD. Built PLATFORM=PM3RDV4, PM3RDV4 with BTADDON, PM5 without
BWM and PM5 with BWM; only the last one changes:
PM3RDV4 74f7f7a1... -> 74f7f7a1... identical
PM3RDV4 BTADDON f94c31de... -> f94c31de... identical
PM5 e3f0a203... -> e3f0a203... identical
PM5 BWM e3f0a203... -> efbba2f0... changed
Passes `make style` unmodified.
Verified on a Proxmark5 with the BWM fitted, over BLE, with the stock
client. Before, one command left the device deaf for good:
[fpc|tcp] pm5 --> hw ping
[+] Ping response received in 111 ms and content ( ok )
[fpc|tcp] pm5 --> lf hitag read --ht2 --pwd -k 4D494B52
[!] timeout while waiting for reply
[fpc|tcp] pm5 --> hw ping
[!] Ping response timeout
[fpc|tcp] pm5 --> hw ping
[!] Ping response timeout
After, the same sequence on the same device:
[fpc|tcp] pm5 --> hw ping
[+] Ping response received in 107 ms and content ( ok )
[fpc|tcp] pm5 --> lf hitag read --ht2 --pwd -k 4D494B52
[!] timeout while waiting for reply
[fpc|tcp] pm5 --> hw ping
[+] Ping response received in 102 ms and content ( ok )
[fpc|tcp] pm5 --> hw ping
[+] Ping response received in 85 ms and content ( ok )
A USB command recovers a device stuck this way, which is why the bug is
easy to miss on a bench with a cable attached: after the failing sequence
above, `hw ping` over USB answered in 1 ms and the wireless link resumed.
Both were unimplemented, so they fell through to ILLEGAL_COMMAND_CODE and a
reader walking the simulated card saw every ISO file id and DF name blank.
GetDFNames (0x6D) is PICC level and chains, one application per frame: AID
LSB first, ISO file id little endian, then the DF name. Applications without a
DF name are not sent back at all, which the spec is explicit about (M134034
9.4.4, "If the DC [sic, DF] has no DF name it is not sent back within this
command"), so the walk steps past them and only the last record carries
OPERATION_OK. Verified on the wire against a reader, and the frames come back
the same shape a genuine EV1 8K produces:
90 6D -> 03 02 01 | 34 12 | "test1" 91 AF
90 AF -> 33 22 11 | 45 23 | "test2" 91 AF
90 AF -> CC BB AA | 56 34 | "test3" 91 00
GetISOFileIDs (0x61) is application level and lists the ISO file ids of the
files that have one. Value files and transaction MAC files never carry one, so
they are skipped -- that matters because the client maps the returned ids onto
files positionally, and including them would shift every id after onto the
wrong file. When no file in the application has an ISO file id the answer is
FILE_NOT_FOUND, which is what 9.5.2 means by "If there is no ISO File EF, only
an error code can be returned", and is already what the client reads as an
application created without ISO file ids.
With these in place `hf mfdes lsapp` and `hf mfdes lsfiles` report the
simulated card completely: ISO ids 0x1234 / 0x2345 / 0x3456, DF names test1 /
test2 / test3, and per file ids 0001, 0002, 0011, 0033, 0055 with the value
file correctly showing n/a.
Not done: 0x61 is answered in a single frame. The spec allows it to chain, 27
file ids in the first frame and up to 5 more after, so an application holding
more ISO file ids than fit one frame would need the chained path that 0x6D
already has.
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>