Commit Graph
7257 Commits
Author SHA1 Message Date
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
Matthew CarrollandClaude Opus 5 782d28932c lf: restore the graph buffer length along with its contents
restore_bufferS32() returns the length it put back and every graph-buffer
caller dropped it, so g_GraphTraceLen kept whatever value the code between
save and restore had left.

In try_detect_modulation() that is the 160 sample psk antenna-settle trim:
`data modulation` gave the buffer's samples back but left it 160 short, and
a following demod lost round(160 / clk) bits off the end. check_chiptype()
loses the same way through the acquisitions its detections make, on the
`lf search` path, and for g_DemodBuffer too. demodTI() loses convLen + 16
on its error path.

save_graphbuffer() / restore_graphbuffer() pair the length and grid offset
with the contents so no call site has to remember them. The visa2000 and
zx8211 sites do not change the length, so converting them is a no-op today
and keeps the next one from being a bug.

Regression test in tools/pm3_tests.sh: `data modulation` on a saved psk1
trace must leave all 20000 samples.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 10:33:17 -07:00
iceman1001 78b4d4ef00 fix psk1 detection. now scores 24/24 tests 2026-09-10 05:45:13 +02:00
iceman1001 e121e3f0a8 fixing FSK / t5577 2026-09-10 05:28:06 +02:00
iceman1001 d54ae662ce fix lua scripts grabbing return text. mimics the python binding way now 2026-09-10 04:31:47 +02:00
iceman1001andClaude Opus 5 (1M context) ea2909702f lf t55xx: anchor the block read offset in samples, not demod bits
GetT55xxBlockData extracted every block at config.offset, a bit index into a
demod buffer cached by the last detect - a different capture. Demodulators do
not all start on the same bit, so a later read could return a rotation of the
block with nothing marking it as wrong: psk1 by four or five bits, fsk2a and
nrz by one, manchester and biphase never.

Record the boundary where it is stable instead. Every demodulator reports the
graph sample its first bit sits on, and turn_read_lf_on(137 * 8) makes the
firmware open each read acquisition at the same point, so the word boundary
lands on the same sample every time. Convert back per demodulation.

Block 0 re-anchors from its known value, and a write is now verified by the
value being anywhere in the repeating stream rather than at one cached offset -
which also fixes correct writes reporting as validation failures. A block 0
verify re-detects on a mismatch, not only on a decode failure: the pre-write
config can decode the post-write signal into garbage rather than failing.

Also fixes a -1 password sentinel truncated to 32 bits by a ternary, which made
that detect report a password that was never supplied.

Measured on a T5577 across psk1, fsk2a and direct/nrz: three consecutive dumps
agree field for field, against a baseline where psk1 corrupted 22 of 24.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-10 03:58:14 +02:00
iceman1001 253e0d148a hf emrtd info - did not identify and mapped OID for algorithms in PACE. Now we keep a list and pretty print it 2026-09-10 02:55:16 +02:00
Niel Nielsen 6e84d4ca8b Refactor chip ID and app signature checks
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-08 13:35:48 +02:00
Niel Nielsen b0aa484eba Fix comment for app descriptor in cmdhw.c
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-08 12:17:34 +02:00
Niel Nielsen c9fe1dd891 Merge branch 'RfidResearchGroup:master' into esp32-c2 2026-09-08 12:11:52 +02:00
Niel Nielsen 14ab313ca7 Implement firmware image validation for ESP32-C2
Add checks for firmware image validity before flashing.

Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-08 12:11:32 +02:00
Sophiel Zhou 7cfc6fdea2 fix hf mf sen wrong payload frame size 2026-09-08 16:47:07 +08:00
Iceman 5fc30b1a6e Merge pull request #3597 from kormax/iso1443_3a_timeslot_support
Add ISO14443-3 Type A timeslot support to 'hf 14a info' and 'hf 14a reader'
2026-09-08 15:28:30 +07:00
iceman1001 764783cbfc annotation for topaz had an issue where it used 9 bytes for reader frames when it could be up to 16 bytes. 2026-09-08 08:04:09 +02:00
kormaxandmxcdoam 1ecbcb74be Add ISO14443-3 Type A timeslot support to 'hf 14a info' and 'hf 14a reader'
Co-authored-by: mxcdoam <72457810+mxcdoam@users.noreply.github.com>
2026-09-07 22:55:24 +03:00
Iceman 9f641c3ced Merge pull request #3427 from Sanduuz/feature/st25ta_ndef_sim
Added support for emulating ST25TA tag (IKEA Rothult) with custom NDEF response
2026-09-07 15:38:46 +07:00
Iceman 07209fc358 Merge pull request #3493 from 0x6r1an0y/20260819-gdmfix
Improve `hf mf gdm*` commands
2026-09-07 15:28:56 +07:00
Iceman 23f8bfbc46 Merge pull request #3473 from YoungJules/feature/add_kgh_tools
Feature/add kgh tools
2026-09-06 21:01:40 +07:00
Iceman 45c1d79282 Merge branch 'master' into 20260823-mfuformat
Signed-off-by: Iceman <iceman@iuse.se>
2026-09-06 21:00:13 +07:00
dxl fc355df050 Added IO test capabilities to the factory QC for PM5. 2026-09-05 18:09:46 +02:00
dxl ea5485b4b8 Rename CMD_PM5_QC_TEST to CMD_PM5_QC_TEST_HW
and delete repeated def: CMD_PM5_BWM_SET_CAP
2026-09-05 18:09:46 +02:00
Niel Nielsen ea4d90ebe4 Refactor BWM command handling for clarity and structure
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
2026-09-05 10:09:23 +02:00
歐歪 6960a6e788 fix: oops 2026-09-05 05:40:45 +08:00
歐歪 7e2ecc0ba6 rename: hf mfu format to ndefformat 2026-09-05 05:18:36 +08:00
Julian Isaac 1d5d09b073 Merge branch 'master' into feature/add_kgh_tools 2026-09-04 22:23:13 +02:00
Niel Nielsen 93ea5600f8 Merge branch 'RfidResearchGroup:master' into BWM-work 2026-09-04 17:10:15 +02:00
iceman1001 0aef94c667 fix 'lf hitag reader -@' mode with sending a break afterwards. 2026-09-04 15:18:16 +02:00
Niel Nielsen 7b37171986 Merge pull request #1 from nieldk/bwm-ota-begin-timeout
Give BWM OTA begin enough time to erase, and stop retuning UART for t…
2026-09-04 14:55:00 +02:00
Niel Nielsen cf3730e1b4 Merge branch 'RfidResearchGroup:master' into BWM-work 2026-09-04 14:29:26 +02:00
WillandCursor 22969ec79a Give BWM OTA begin enough time to erase, and stop retuning UART for the transfer
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 08:24:16 -04:00
iceman1001 ace5d63ff9 hitag2: fix simulation against genuine readers, add restore, fix info
Simulation now completes the full exchange with a genuine Paxton reader in
password mode, and crypto mode read/write passes Proxmark-to-Proxmark.

Firmware:
- SOF was one bit period short. The lead-in that compensated for the lost
  head half bit was removed and nothing replaced it, so readers rejected
  every answer with a second START_AUTH. Default is now 6.
- The edge-detect threshold was latched before being measured, so the value
  chosen depended on whether the Proxmark was in a field when sim started.
  It is now measured on field entry and re-armed when the reader leaves.
- The percentile walk latched on run-scoped variables, so one attempt made
  outside a field poisoned every later one.
- Field loss was detected from TIMESTAMP, which is free-running MCU time and
  never stalls. Detect it from receive silence instead.
- Frames of a length the protocol does not have no longer reach the state
  machine; our own modulation tail was resetting the session and breaking
  every write.
- A dropped edge merges two or three reader bit periods into one gap. Those
  bits were discarded; they are now recovered by decomposition, which is what
  made crypto mode work (AUTH decode 15% -> 100%).
- Threshold selection is limited to 20 and 32 and settles in under 25 ms.

Client:
- lf hitag info printed a hardcoded 0x06 and reported 'Password mode' for
  every tag. It now reads page 3, takes -k (4 bytes password, 6 bytes
  crypto), and says so when the config cannot be read.
- lf hitag restore: writes a dump back in dependency order - user pages,
  then key material, then config last - validates the config byte, and
  prints the credential the tag will require afterwards.
- lf hitag crack2 now reports why it failed instead of a bare 'fail'.
- trace list: bit count moved to its own column, relative mode shows a
  Frame Delay Time row rather than renaming Start/End, --frame and -r
  rejected together.
2026-09-04 13:20:29 +02:00