b82f60362 landed parseproac.c without them, so a clean checkout does not
build.
- mad.c / mad.h: mad_find_aid() and mad_count_aid(); DetectHID() delegates
to the first, it was already generic
- mad.json: 0x4982 PROAC, sorted in after NORALSY's 0x4980
Co-Authored-By: Claude Opus 5 (1M context)
Sectors 9 and 11 were printed raw and marked 'not decoded'.
- remove the XOR keystream and print the ten eight byte records
- check the eighteen record bytes that are XOR combinations of sector 0,
sector 15 and the UID, plus three record to record ties
- 'hf mf view --selftest' now covers the decoder, using a synthetic card
- sector 15 marker compare ignores case, factory blanks were not matched
- an all FF or all 00 payload is named, not decrypted
- sector 15 block 2 is text on a blank, so only read a serial there when
the first four bytes are not printable
Research. The remaining 58 payload bytes are issuer data and are printed
without interpretation.
Co-Authored-By: Claude Opus 5 (1M context)
'hf mfdes dump' walked one application and printed it. It now walks
every application on the PICC, keeps what it reads, and saves a
'hf-mfdes-<UID>-dump.json' card image. '--aid' / '--isoid' / '--dfname'
still narrow it to one application, '--ns' skips the save.
The format is 'mfdes v1', written and read in fileutils.c and documented
in doc/mfdes_dump_format.md. Two decisions worth stating:
- The PICC level is application 000000, so every key in the file says
which AID it opens. Key version and key value are separate: a version
with no key is the normal shape for a key that was found but never
recovered, and a missing key never means the key is zero.
- Every file carries a 'Read' flag. A file whose contents could not be
fetched is recorded as unread with no data at all, rather than as a
run of zeros. A simulator built on this must not confuse '8 bytes of
00' with 'we could not read 8 bytes'.
'hf mfdes view -f <fn>' prints such a file with no device attached.
With no '--keys', the dump looks for 'hf-mfdes-<UID>-keys.json' by
itself, so a 'hf mfdes chk -j' run is picked up on the next dump without
naming the file again.
Two fixes fell out of testing against a DESFire EV2:
- DesfireSetKey() calls DesfireClearContext(), which wipes command set,
comm mode, KDF and UID, not just the key. Swapping in a per-application
key that way left the context at 'Communication mode: n/a' and
DesfireFillFileList() then returned junk file ids. Use
DesfireSetKeyNoClear().
- GetVersion and the originality signature are answered unauthenticated.
Asking for them from inside the authenticated session produced a
'Wrong communication mode' warning and a run of MAC mismatches.
hex_to_buffer() treats hex_max_len as a byte count while every sprint_hex*
caller passes sizeof(buf) - 1, a character count, so it writes two or
three times the buffer size. Measured, sprint_hex_inrow overflowed at
4098 input bytes. Doubling UTIL_BUFFER_SIZE_SPRINT to 16384 moves that to
8192; the mixed semantics still need auditing across ~30 call sites.
Co-Authored-By: Claude Opus 5 (1M context)
Refactor BLE error handling to return standard error codes instead of PM3_* constants. Update time-related functions to use a consistent method for obtaining the current time.
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
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)
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)
Added a function to check if a string is a valid Bluetooth address and updated the BLE connection logic to handle both addresses and names.
Signed-off-by: Niel Nielsen <nieldk@gmail.com>
'-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)
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)
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)
zlib_decompress() walks its output in whole FPGA_INTERLEAVE_SIZE chunks:
for (long k = 0; k < *outsize / (FPGA_INTERLEAVE_SIZE * num_outfiles); k++)
so a stream that is not a whole number of chunks loses its trailing partial one.
With two or more inputs the read loop zero-pads each stream past EOF and the
total lands on a boundary, but the padding was guarded by 'num_infiles > 1', so
the single input case was left ragged. 42172 bytes of fpga_pm3_hf.bit is 146.43
chunks, and -d handed back 39788 - a clean looking prefix, short by 2384 bytes,
22 of them real bitstream data.
Gate the padding on single_block instead. It must not be 'always pad': -s is the
.data section, and start.c's uncompress_data_section() sizes the decompression
with __data_end__ - __data_start__. Rounding .data from 14944 up to 14976 makes
LZ4_decompress_safe() return an error, and that path is the LED panic loop, so
the firmware would never reach AppMain().
1 bitstream 42172 in -> 42336 packed, -d round trip byte identical,
archive 28730 -> 28731
4 bitstreams archive byte identical to 43fe6c3eb, round trip exact
-s .data byte identical to 43fe6c3eb on the same input, unpadded
The 164 padding bytes never reach the FPGA: DownloadFPGA() shifts out only
bitstream_length bytes, taken from the .bit 'e' section header.
Also simulated the ARM decoder over the new archive - LZ4_decompress_safe_continue()
block by block into a FPGA_RING_BUFFER_BYTES buffer - 3 blocks of 16384/16384/9568,
none over the ring buffer.
Co-Authored-By: Claude Opus 5 (1M context)