Commit Graph
7287 Commits
Author SHA1 Message Date
iceman1001 64b5db4ddd text 2026-09-13 18:22:27 +02:00
iceman1001andClaude Opus 5 (1M context) e19c6755fe hf mf: stop a refused nested auth from hanging autopwn and eating the keys
MifareNested collected nonces in 'while (target_nt[i] == 0)' with no
attempt counter. When the tag NAKs the nested authentication the loop
re-sent the identical frame forever, so the client's 2s wait expired and
autopwn returned PM3_ETIMEOUT — throwing away every key recovered up to
that point. Reported on a card whose sector 16 refuses the standard MFC
EV1 keys: 32 keys cracked, nothing saved.

A NAK is a refusal, not a glitch, so the device now gives up on it and
reports PM3_EWRONGANSWER. autopwn names the sector it skipped and carries
on, and a timeout falls through to the key table and the dump whenever
anything was recovered.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-13 15:56:41 +02:00
iceman1001 c615ef8930 adapting parser 2026-09-13 15:52:39 +02:00
iceman1001andClaude Opus 5 (1M context) 66f9a6b51f hf mfdes: report a select/auth failure once, not twice
DesfireSelectAndAuthenticate*() and its callers both printed the same
error at different severities, so every failure came out as two lines.
The core helper now owns the message and names the AID and the step;
the 23 restatements in cmdhfmfdes.c are gone. Same duplicate pair fixed
in cmdhfgallagher.c.

Also: DesfireAuthErrorToStr() falls back to DesfireGetErrorString() for
the PM3_E* codes the select paths pass through, which used to print an
empty reason, and auth error 7 no longer reuses the text of error 1.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-13 12:58:46 +02:00
iceman1001 7142ddc9bd added hexact parser 2026-09-13 12:55:43 +02:00
iceman1001andClaude Opus 5 (1M context) 72f48c8a02 fileutils: honor a filename extension the user supplied
'-f card.mfd' looked for card.mfd.bin and saved to card.mfd.bin,
because the caller's suffix was appended unless the name already
ended in that exact suffix.

searchFile() now tries the name as typed before falling back to the
suffixed one.  newfilenamemcopyEx() keeps an extension that denotes
the same kind of file it is about to write, and swaps any other for
its own, so 'hf mf dump -f card.mfd' gives card.mfd + card.json
rather than card.mfd + card.mfd.json.  Both classify with
get_filetype() so load and save cannot drift apart.

Also drops the size_t underflow in newfilenamemcopyEx(), where a long
path plus a configured save path made the snprintf bound wrap past
the 1000 byte buffer.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-13 12:19:53 +02:00
iceman1001andClaude Opus 5 (1M context) c77d6455f5 hf mf: identify VIGIK family systems by their static key sets
is_valid_vigik_card() only fired when the MAD advertised aid 0x4910. Plenty of
VIGIK based deployments ship no MAD at all - a Hexact card has HEXACT as key A
on every sector and nothing in sector 0 block 1 - so hf mf view and hf mf dump
--ns said nothing whatsoever about them.

These systems use the same keys on every card they issue, which makes them
identifiable outright. Take the schemas out of armsrc/Standalone/hf_colin.c,
where they sit commented out inside the standalone mode, and match a whole dump
against them:

  Infineon / Hexact / COGELEC / Intratone   key A HEXACT x16, 15 static key B
  Noralsy                                   ALARON / BLARON
  Urmet Captiv                              8829da9daf76 throughout
  VIGIK service badge                       MAD key, then 1KIGIV on sectors 1-4

Every slot a schema pins down has to match, VIGIK_KEY_ANY marks the ones it does
not, so there are 31 exact keys to hit for Hexact and no room for a coincidence.
A HID card carrying a MAD, and a card on default keys, both still match nothing.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-13 09:18:08 +02:00
iceman1001andClaude Opus 5 (1M context) a9105c5089 hf mf: split HID and VIGIK decoding into client/src/parsers/
The shared viewer had the VIGIK sector assembly inlined, and the HID PACS
decode sat in hf mf mad's file branch where hf mf view and hf mf dump --ns
could not reach it. Neither scheme had a home of its own.

Give each one a parser next to parsehrt.c, same shape as that one - an
is_valid_x_card() detector and an x_parser_parse() that prints:

  parsers/parsehid.c    MAD aid 0x484d, PACS sector, Wiegand decode
  parsers/parsevigik.c  MAD aid 0x4910/0x4916, sector assembly

parsevigik.c also takes vigik_get_service(), vigik_verify() and
vigik_annotate() out of mifare/mifarehost.c, 306 lines that were VIGIK only
with a single caller.

mf_view_dump() is now two detector calls, so hf mf view -f and hf mf dump --ns
both decode a HID credential off a live card for the first time, and adding a
scheme is a new file plus two lines. hf mf mad -f keeps its HID decode through
the same parser.

The sector copy in the VIGIK path gains a bounds check; a MAD entry pointing
past the end of a short dump used to read past the buffer.

All three source lists get the new files: client/Makefile,
client/CMakeLists.txt and client/experimental_lib/CMakeLists.txt. The library
one matters because vigik_annotate() moved; without it anything linking
libpm3rrg_rdv4 loses the symbol.

hf mf mad against a card still cannot decode PACS. It authenticates with the
MAD key alone and never reads the application sector, so it has no credential
bytes to work with - unchanged here.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-13 08:37:17 +02:00
iceman1001andClaude Opus 5 (1M context) 36b16b943f hf mf: share the view analysis between a file and a card read
'hf mf view -f' decoded VIGIK PACS and could extract keys with '--sk'. 'hf mf dump
--ns', which reads the same 1K off the card, printed only the blocks and the
verbose key/ACL tables. Same bytes, less analysis, purely because of where they
came from so getting a PACS decode off a live card meant dumping it to a file
and viewing that.

Move everything view does once the blocks are in hand into mf_view_dump() and
call it from both. 'hf mf dump --ns' and 'hf mf view -f' now print byte identical
output for the same card, and dump gains '--sk' to match. The old view leaked its
dump buffer on both VIGIK error returns; the shared version frees it in one place.

Drops a stale commented out convert_mfc_2_arr() call referencing a pdump
variable that no longer exists.

Rounds off #1942. mfc_read_tag() and '--ns' landed long ago, this was the piece
still missing.

hf mf eview, cview and the gen4 view share the same print block but omit
mf_analyse_acl() in verbose, so folding them in changes their output and is
left for a separate decision.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-13 08:22:52 +02:00
iceman1001andClaude Opus 5 (1M context) 971c318d78 smart raw: make --t1 switch the card to T=1 by itself
A card runs the protocol its TD1 names until a PPS changes it. --t1 only
picked the module's T=1 send opcode, so on a card whose ATR offers T=0 first
the APDU went out as T=1 blocks to a card still speaking T=0 and got no
answer at all:

  smart raw --t1 -s -d 00a4040007a000000004101000
  [!] smart card response failed

'smart pps --t1' in the same client session made it work, which is what the
help for 'smart pps' already promised was unnecessary:

  Note 'smart raw --t1' already switches a card to T=1 by itself when
  the ATR offers it; this is for negotiating Fi/Di explicitly.

Do it for real. SmartCardRaw() now runs the PPS when T=1 is asked for and
nothing has selected it yet, taking the ATR it needs first because PPS is
only legal in that window. It fires once per activation: a second --t1 apdu
sees the protocol already in force and sends straight away. A card that does
not offer T=1 is left alone and the apdu still goes out, as before.

Also fix the flag names in the docs and in the error path. 'smart raw's
first example and both 'Choose either' messages said -0 / -1, which have
never existed. tools/pm3_online_tests.sh had copied the example, so its T=0
checks failed with 'invalid option' on every run; its T=1 checks needed no
change beyond dropping the now unnecessary reset.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-12 23:54:39 +02:00
iceman1001andClaude Opus 5 (1M context) 3853540d31 hf mfdes dump: keep the session across the ISO file id probe
DesfireFillFileList() ends with GetISOFileIDs (0x61). An application created
without ISO file ids answers 0x91F0, and the PICC ends the authentication on a
command error. The client never noticed: dctx->secureChannel still said ev1, so
every later command was framed as MACed against a dead session.

For hf mfdes dump that meant the first file was read as if its response carried
a CMAC. An 8 byte plain free access file came back as exactly 8 bytes of data,
which were consumed as the MAC, so the file content was thrown away and reported
as

  Received MAC is not match with calculated
    received MAC:   A0 A1 A2 A3 A4 A5 A6 A7
  Read operation returned no data from file 0

The next key protected file then failed with 0x91AE, and only that failure
triggered the loop's blind re-authentication, after which the rest of the dump
was correct. Dumping an application that does have ISO file ids was never
affected, because 0x61 succeeds there.

Drop our side of the session when the probe fails so the context tells the
truth, and re-authenticate in CmdHF14ADesDump before the read loop. The two
warnings the probe printed are demoted to DEBUG: an application without ISO
file ids is the normal case, not something to warn about.

Key settings 2 bit 0x20 is not usable to predict this. On a DESFire EV2 both an
application with working ISO file ids and one without report key settings 0F 83,
so the bit reads back as 0 either way.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-12 23:28:17 +02:00
iceman1001andClaude Opus 5 (1M context) 2449bb2472 hf mfdes: derive the communication mode from the access rights
The PICC applies a file's communication mode only when access is granted by
a key right matching the authenticated key. When the operation is granted by
the free access right (0x0E) instead, it runs in plain, whatever the file
settings say. The client used the file comm mode unconditionally, so any file
created with the default EEEE rights failed with 0x917E on write, and a free
access Full file was printed as its data plus eight unstripped CMAC bytes
instead of being decrypted. Fixes #3393.

Which rights can grant an operation varies, and the rule above is evaluated
over that set. Measured on DESFire EV2, ev1 and ev2 secure channels:

  read / write data    w (or r), rw
  GetValue   0x6C      r, w, rw
  Credit     0x0C      rw only
  Debit      0xDC      r, w, rw
  LimCredit  0x1C      r, w, rw

Credit is the odd one out: a file with r=key0 w=key0 rw=free needs Credit in
plain but Debit in MAC, in the same session. The FreeValue option bit forces
GetValue to plain regardless of the rights.

hf mfdes value never read the file settings at all, which is #2712. e1598cd62
worked around that by dropping CREDIT/DEBIT/LIMITED_CREDIT from
EV1D40TransmitMAC and retrying in plain on a length error. That made MACed
value files impossible to credit by any route, and the retry resent a
byte-identical APDU after the failure had already desynced the CMAC chain,
turning a clear 0x7E into a misleading 0x91AE. Both are reverted and the
command now reads the file settings like read and write do. Fixes #2712.

hf mfdes write resolves the comm mode even when --type is given explicitly,
since that path skipped detection entirely.

The DACLRP branch in the read path is left alone: no LRP capable card was
available to test against.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-12 23:18:59 +02:00
Matthew CarrollandClaude Opus 5 36f689beaa lf idteck demod: report only what the tag actually holds
Three ways this command reported a credential that was not on the tag.

CmdIdteckDemod always passed its local raw[8] to demodIdteck, never NULL, so
without --raw the command decoded a zeroed stack array rather than the
graphbuffer and reported card id 0 every time. The help's own first example,
plain `lf idteck demod`, has been doing that since --raw was added. It now
passes NULL when no --raw is given, which is the signal path demodIdteck
already has and lf idteck reader already uses.

A raw frame whose first word is not 4944544B printed "No genuine IDTECK found"
and then fell through and announced a tag anyway, with a card id read out of
whatever was passed in. It now stops there. The signal path cannot reach this:
detectIdteck matches the full 32 bit preamble, so lf search and lf idteck
reader are unaffected.

Finally the Idteck card id was packed into a 26 bit wiegand_message_t and run
through HIDUnpack, which prints an HID H10301 line with a facility code and
card number. That is not a decode of anything - the id is 24 bits, byte
reversed off the wire, and Unpack_H10301 returns true for any 26 bit message,
so the parity flag is the only hint and it reads ok for 25% of card ids by
chance. Both committed Idteck traces show it: 4944544B351FBE4B prints
FC: 37 CN: 57103 parity ( ok ), AC40E069 prints FC: 52 CN: 61472 parity
( fail ). Dropped, with the wiegand_formats.h include that came in with it.

Added five offline regression tests. Three fail without this change, one per
defect; lf search over every committed lf trace is byte identical before and
after apart from the two removed H10301 lines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 12:52:48 -07:00
iceman1001andClaude Opus 5 (1M context) 344c2d6185 trace: stop trace save writing a stale client side buffer
`trace save` saved the client side trace buffer whenever it was non-empty
and only downloaded from device when it was empty. After `trace load` or
`trace list -1` a following sniff -> `trace save` silently wrote the old
trace, byte identical to the previous save, with no way to reset it.
See #3592, and #1252 / #1512 for earlier reports of the same thing.

- `trace save` now downloads from device by default and takes `-1` to save
  the client side buffer, mirroring `trace list`. Offline it falls back to
  the buffer so `trace load` -> `trace save` still works.
- split download_trace() into download_trace_ex(), which hands the caller
  its own buffer. `trace save` uses it and no longer mutates gs_trace, so
  `-1` means the client buffer regardless of what ran before.
- download_trace() freed gs_trace before it knew the download had worked,
  so a timeout threw away a loaded trace. It now swaps on success only.
- added `trace clear` to discard the client side buffer, and a shared
  ClearTraceBuffer() to replace the free/NULL/zero pattern that was
  open coded in ImportTraceBuffer() and CmdTraceLoad().

Help text for both new commands names the device vs client distinction.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-12 21:39:35 +02:00
iceman1001andClaude Opus 5 (1M context) 255576fa43 make hf 14a antifuzz actually reach a reader
Every answer was modulated after the reader's frame arrived, landing
~1812 carrier periods late against the 1172 the FDT allows, so readers
timed out before any of it mattered. All answers are now precomputed the
way hf 14a sim does it, and a phone answers at 1172 exactly.

Cascade mode sent a cascade tag at every level, which a conforming reader
rejects in the last level its ATQA announced - it dropped the card before
ever sending a SELECT. The cascade tag now appears only below that level;
it is the SAK that lies about the UID being incomplete, and that is what
walks a reader up to cascade level 7.

A bit oriented ANTICOLLISION was answered with the whole UID instead of
the bits the reader still lacked, handing it a frame of the wrong length.
One encoder now builds any tail, with or without collisions in it.

--coll leaves the first UID byte clean and collides every bit after it, so
a reader resolves 24 bits one round trip at a time, is then given a BCC
that checks out, and is cascaded into the same again - 183 answers per
poll against the 3 a real card needs. Colliding byte 0 as well only
produced a UID of all ones, which readers drop before asking for the BCC.

Reader frames that go unanswered are traced instead of silently dropped,
LED B marks an attempt in progress and LED C flips on each step deeper,
and the closing line reports rounds run and how far a reader got.

trace list -t 14a decodes SEL 0x99..0x9F, shows how many UID bits a bit
oriented ANTICOLL claims, and no longer flags those CRC-less frames as
bad CRC.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-12 21:18:26 +02:00
iceman1001 25b3c88acf fix #3487 2026-09-12 20:56:08 +02:00
iceman1001 11cac99913 fix cident 2026-09-12 20:39:19 +02:00
iceman1001 baea4014c1 fix cident overflows 2026-09-12 20:35:35 +02:00
iceman1001andClaude Opus 5 (1M context) 4d7d49e089 stop an FPGA bitstream download from eating the emulator memory
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)
2026-09-12 18:49:24 +02:00
iceman1001 054fae4ac1 fixed the spinner and download reporting 2026-09-12 18:30:45 +02:00
Niel Nielsen 2c779f337f Update cmdbwm.c
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-12 16:51:53 +02:00
Niel Nielsen 3bdef8b23b Add files via upload
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-12 16:48:51 +02:00
Niel Nielsen 0a0dd87224 Remove unused BWM command functions
Removed unused functions related to BWM commands and OTA process.

Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-12 16:48:08 +02:00
Niel Nielsen 6c34b6993c Refactor BWM firmware update and reset logic
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-12 16:33:14 +02:00
iceman1001 a878043061 fix missing appmain changes 2026-09-12 15:24:46 +02:00
iceman1001andClaude Opus 5 (1M context) 696b8357b6 add hf thinfilm sniff, and hold the tag's real frame period
hf thinfilm sniff passively records the frames a Kovio tag beams at a reader.
Kovio is tag-talks-first and the reader never sends a command, so only the tag
side is decoded - use 'hf 14a sniff' to watch a reader's poll loop.  Same DMA
loop, overrun recovery and nibble de-interleave as SniffIso14443a().

ManchesterDecoding_Thinfilm() is no longer static and takes non_real_time, the
same way ManchesterDecodingEx() does.  A DMA sniff loop cannot timestamp with
GetCountSspClk(); the clock has run on past the frame by the time the samples
are processed.

Only the decoder is RAMFUNC (724 bytes), not the sniff loop, since dropping the
Miller decode roughly halves the per sample work.  The loop counts overrun
recoveries and DMA stalls separately, so a lossy capture says so instead of
looking like a quiet tag.

With that, the sim's frame period could be measured instead of guessed.  A
genuine tag repeats every 65536 carrier periods - 2**16, it clocks a 16 bit
counter off the carrier - holding that to within 65520..65920.  So 25% duty
cycle, not the continuous beam assumed when the gap was shortened to 500us in
a96756033.  Revert to the tag's real rate.

SimulateThinFilm() now waits on the previous frame start rather than delaying a
fixed amount, so the field poll comes out of the gap instead of adding on top of
it, and the period stays right if the poll cost ever changes.  Measured back
with a second pm3 sniffing: 65561 carrier periods against the genuine tag's
65588, inside the tag's own jitter.

Rig: pm3 simulating, second pm3 sniffing, Pixel 9a reading, all three at once.
Builds clean for PM3RDV4 and PM5.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-12 15:09:53 +02:00
iceman1001andClaude Opus 5 (1M context) 9a8e179266 fix spiffs downloads >= 64K, and stop losing flash errors
CMD_SPIFFS_DOWNLOAD read the whole file into BigBuf first.  BigBuf_calloc()
takes a uint16_t while the size is a uint32_t, so a 64K file allocated zero
bytes and anything larger wrapped to a short buffer the file was then read
straight past the end of.  rdv40_spiffs_read_stream() opens the file once and
hands out one frame at a time, so the size never reaches the allocator, and the
SPIFFS path now honours start_index.  Verified with an 80K round trip: upload,
download, byte identical.

The same BigBuf_calloc(filesize) wrap was in copy_in_spiffs(); it now copies in
SPIFFS_WRITE_CHUNK_SIZE chunks.

Separately, every flash failure was invisible.  SPIFFS_CHECK_RES() only treats a
negative result as an error and the HAL hooks returned 128/129/130, so SPIFFS
saw success.  The erase path was worst: Flash_Erase4k() only reports that the
command was sent, both Flash_CheckBusy() results were discarded, and
'return (SPIFFS_OK == erased)' yields 1 on failure.  A flash dump from a device
that hit this showed one block written without being erased - its object index
header held '0x14000 & 0x10000', and the filename ANDed away to empty.  Erases
are now read back and retried, and the HAL returns real SPIFFS error codes.

Flash_Write() returned len unconditionally even after a rejected page, which
also defeated the 'res == payload->len' check in CMD_FLASHMEM_WRITE.

Feedback, so none of this is silent again: CMD_SPIFFS_WRITE answers with the
SPIFFS result, the client aborts on the first refusal and names the byte it
stopped at, dl_it() checks the terminator status, and both transfer directions
print inline progress.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-12 14:58:08 +02:00
iceman1001andClaude Opus 5 (1M context) a967560335 hf thinfilm sim - shorter frame gap
The inter frame delay was 3600us for a 128 bit payload and 2400us for 256 bit,
sized so that either one holds a constant 4.8ms repeat period.  That is the wrong
target.  A Kovio tag is never transacted with - the reader captures it during a
poll slot and hands the bytes up as an activation, see
nfa_dm_disc_handle_kovio_activation() in libnfc-nci, which reads the barcode
straight out of rf_tech_param.param.pk.uid - so the only thing that matters is how often a frame is on the air.

Measured repeat period before today was 8.66ms, ie 14% duty cycle.  A fixed 500us
gap takes a 128 bit payload to roughly 2.2ms.  The gap cannot go to zero, a reader
needs unmodulated carrier to find the frame start and our own demod wants three
quiet bytes, but 500us is an order of magnitude clear of that.

Also drop the 'not correct' caveat from 'hf thinfilm list', the sim side traces
properly now.

Tested on RDV4 against an Android reader.  Builds clean for PM3RDV4 and PM5.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-12 14:17:13 +02:00
Iceman a7c6116258 Merge pull request #3620 from nieldk/master
Add BWM set BLE name
2026-09-12 19:03:24 +07:00
iceman1001 f48250d51f text 2026-09-12 13:47:54 +02:00
Niel Nielsen 7046f43bfb Refactor BWM command handling and improve help messages
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-12 13:28:42 +02:00
Niel Nielsen 815d03baa3 Refactor BWM command handling with CLI parser
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-12 13:17:53 +02:00
iceman1001 812722ed81 fix overflow 2026-09-12 12:49:17 +02:00
iceman1001 3ea9d885cb fix realloc patterns 2026-09-12 12:42:02 +02:00
iceman1001 7285f1158d cleaner return value 2026-09-12 12:38:05 +02:00
iceman1001 47507a1d21 better string handling in the client in felica, hitagU, hitagS 2026-09-12 12:28:28 +02:00
iceman1001 58580c690f add a str_append fct. To handle some string manipulation issues in the client 2026-09-12 12:26:06 +02:00
iceman1001 10dafd8048 fix a hitagu overflow in cmdtrace.c 2026-09-12 11:56:10 +02:00
iceman1001 667b62f405 make codeql happy 2026-09-12 11:14:09 +02:00
iceman1001andClaude Opus 5 (1M context) 6341f40b4c hf mf hardnested: fix the nonce reply length, 9 bytes per pair not 4 per nonce
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)
2026-09-12 10:44:00 +02:00
Iceman 5ca15e4587 Merge pull request #3616 from mfcarroll/t55xx-psk-trim-graphbuffer
lf t55xx detect: save and restore the graph buffer through the shared helpers
2026-09-11 22:53:21 +07:00
iceman1001 7fb520ce18 text 2026-09-11 15:17:52 +02:00
iceman1001andClaude Opus 5 (1M context) 67c121f554 hf mfdes detect: sweep all key numbers, and count errors per key type
errcount was declared before the key type loop and only reset by a
non -11 result, so eleven scattered card errors during the DES pass left
the AES pass to break on its first error without trying a key.  Count
per key type instead.

With no -n, detect only ever looked at key 0.  Sweep the key numbers the
application declares (low nibble of the key settings) instead, falling
back to 0x00..0x0D when the settings are unreadable.  All keys in an
application share an algo, so the first key number that succeeds narrows
keytypes[] for the rest.  --save now stores the first key found rather
than whatever dctx was left holding, and a lost card aborts the sweep.

-n <num> behaves exactly as before.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-11 15:15:36 +02:00
iceman1001andClaude Opus 5 (1M context) e0543bd612 hf mfdes detect: probe for LRP, and use the DESFire dictionary
Two things stopped 'detect' finding keys it should have found.

An AES application and an LRP application report the same key settings
byte, so a card in LRP mode was only caught when the settings happened to
be unreadable and the auth-command fallback ran.  Probe with
AuthenticateLRPFirst whenever --schann was not given, then re-select.

With no -f, candidates came from g_mifare_plus_default_keys, which is 26
entries of 16 byte AES.  For DES that meant authenticating with the first
half of an AES key, and for 3K3DES it fabricated 24 bytes as
key[0..15] + key[0..7].  Default to the bundled mfdes_default_keys
dictionary instead: 7 DES, 47 2TDEA/AES and 4 real 3K3DES keys.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-11 12:38:16 +02:00
Matthew CarrollandClaude Opus 5 e810245caa lf t55xx detect: save and restore the graph buffer through the shared helpers
The psk antenna-settle trim open-coded the save/restore pair: save_bufferS32
plus g_GridOffset on the way in, and restore_bufferS32's return assigned to
g_GraphTraceLen on the way out. graph.c now has save_graphbuffer and
restore_graphbuffer, which hold contents, length and grid offset together, so
call those and drop the local copy of the length contract.

No behaviour change. `lf t55xx detect -1` on the in-tree psk traces leaves the
graph at 20000 samples before and after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 02:20:10 -07:00
iceman1001andClaude Opus 5 (1M context) cec0e800ab hf mfdes chk: detect the secure channel per application
secureChannel was the constant DACEV1, and there was no way to override
it, so a card in LRP mode could never be authenticated - chk reported no
keys on a card 'hf mfdes detect' handles fine.  When the key settings
were unreadable it gave up instead of probing.

Work the channel out per AID: the key settings give the algo, and for an
AES app one AuthenticateLRPFirst probe separates EV1/EV2 from LRP, which
the settings byte cannot.  When the settings are unreadable, fall back to
DesfireCheckAuthCommands() the way detect does.  --schann d40|ev1|ev2|lrp
pins it and skips detection.  Only AES is tried on an LRP channel.

Also clear the session after a found key, so the next key number starts a
first auth rather than an EV2/LRP non-first one.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-11 11:08:45 +02:00
iceman1001andClaude Opus 5 (1M context) 2cd5285cb2 hf mfdes chk: track found keys per application
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)
2026-09-11 10:38:57 +02:00
Iceman f5d50bb9d7 Merge pull request #3615 from mfcarroll/t55xx-nrz-startidx
lf: fix silently rotated and inverted blocks in the t55xx read path
2026-09-11 15:12:00 +07:00
Matthew CarrollandClaude Opus 5 b0d5178c92 lf t55xx detect: finish undoing the psk antenna-settle trim
test_scan and t55xx_fallback_try drop 160 samples off the head of the capture
before demodulating psk, then restore it. restore_bufferS32 copies the samples
back and returns the length it put back, but no caller assigned that return, so
g_GraphTraceLen stayed at the trimmed length for the rest of detect.

That left the sample-space anchoring unable to engage. t55xx_demod_offset only
resolves when config.anchor_tracelen equals g_GraphTraceLen; detect recorded
11840 while every block read acquires 12000, so the guard never matched and a
dump fell back to config.offset - a bit index into a demod buffer that no longer
exists, which is the case its own comment warns about. Over six instrumented
dumps of a PSK1 RF/128 tag, one resolved and five did not; the one that resolved
read block 0 correctly on the first try.

A candidate found inside the trim was also measured 160 samples left of where a
block read measures, so those anchors were out of frame even when the length
happened to match. 160 samples is round(160 / clk) bits - 5 at RF/32, 1 at
RF/128 - and a rotation of that size is what read-back was showing.

Pair the trim with its undo so a third call site cannot pick up half of it, hold
a bias while the capture is trimmed so a recorded anchor still names an
untrimmed sample, and give the 160 one home.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:12:43 -07:00
Matthew CarrollandClaude Opus 5 0586b8a1c3 lf t55xx detect: settle the block 0 rotation with the broadcast period
A block read repeats one 32 bit word, so every offset yields a rotation and more
than one can pass the structural checks. A T5577 in direct/nrz carried both
00080040, the word on the tag, and 00080001, its ror19 - both master key 0,
reserved 0, RF/32, direct. test() answers with the first hit from a fixed floor,
so which one detect reported was scan order rather than evidence.

No structural check separates them: every bit of block 0 is a real field, and
the three the scan skips are otp, fast write and inverse data, all settable. The
tag can, though - maxblock has to account for what regular read mode cycles
through. So measure the broadcast period and keep the rotation that agrees,
reusing the constraint printConfiguration already applies to the psk2/psk3
pre-images. Costs one acquisition, and only when the reading is ambiguous.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:57:15 -07:00