NORTIC (Norwegian Ticketing Interoperable Concept) is the Norwegian
national public transport card, on DESFire since 2008 and specified by
Statens vegvesen, now Jernbanedirektoratet, in Handbok V821.
Decodes the card issuer header, file 0C of AID 578000. That file is
free to read, so the parser produces output on any Norwegian card
without key material: country code, format, choice bitmap, card serial
number, validity end date, application owner company, retailer
organisation and card key version.
The transport application, AID 578001, is listed by file with its role
and the key it needs, and any file that was read is dumped as hex. Its
field layouts are in Handbok V821 Del 18, which has no download link,
and every file needs read key 7, which is not public. Nothing there is
guessed at.
NORTIC is EN 1545 and packs fields most significant bit first, which is
the opposite of the RKF Type CL-1 layout in parserkf.c where RKF-0022
7.4.1 numbers bits from the least significant end. Reading a NORTIC
header the RKF way gives country code 144 instead of 578, so the self
test asserts that as a negative control.
Verified field by field against the published card issuer header, every
field matching its documented value, including the validity date 6938
decoding to 2015-12-31. 'hf mfdes view --selftest' runs the checks,
including a round trip through an encoder.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The RKF travel card (Resekortsföreningen i Norden) is the Nordic public
transport card, specified 2001-2002 on MIFARE Classic and deployed by
Västtrafik, SL Stockholm, Länstrafiken and the Danish Rejsekort.
Decodes the card issuer and support layers (CMI, TCCI, TCAS, TCDI) and
every application object the specification pins down: TCPU purse, TCEL
event log, TCDB, TCCP, TCST, and TCTI/TCCO through an identifier chain
walker covering all 18 data element groups. AID owners are named from
RKF-0019.
Three things the specification does not state, determined from 21 cards:
- RKF-0022 7.7.1 calls the block checksum 'standard CRC-8' without
naming the parameters. It is CRC-8/MIFARE-MAD, poly 0x1D init 0xC7,
which the tree already ships as CRC8Mad().
- The MAC trio is the final 24 bits of a MACed object. The RKF-0023
tables list Free (bits) as a trailing summary row, which reads as if
the authenticator follows a gap after the key identifier; it does
not. Measured on the fact that MACAlgorithmIdentifier must be 0:
TCST 42/42 against 15/42, TCCP 20/20 against 13/20.
- TCPU version 4 drops EndDate and moves Value to bit 16.
Cards reporting a version other than 2 are flagged, and a MAC algorithm
the specification does not define is reported as a layout failure rather
than printed as data.
MAC values are not verified. The master key is in RKF-0018, which was
never published. des_mac() is added to libpcrypto for when that changes,
with the FIPS 113 worked example as its test.
Verified against a Danish Rejsekort whose provenance notes give the
purchase timestamp, card number and balance; the parser reproduces all
three. 48 self test checks on 'hf mf view --selftest', no false
positives across 460 local dumps.
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>
Adds the on-device card image a DESFire simulation will read, the client
side that packs a dump into it, and eload / esave / eview.
The layout is in include/desfire_em.h. Tables grow up from the header,
file data grows down from the end, and an allocation only has to leave
the two frontiers apart -- so a three-file card spends a few hundred
bytes rather than a worst case, and the space between them is what the
card has left. The PICC is application 000000 and uses the same struct
as any other application, so key settings and keys are always stated
against an AID. Delete sets a tombstone rather than compacting, which is
not a shortcut: a real card does not reclaim on delete either, measured
as 2080 bytes free with zero applications before an experiment and 2560
after FormatPICC.
Two size limits, and a reader only ever sees the first. cardsize is what
the emulated card claims to hold, so GetFreeMem answers from that and
CreateFile will refuse with OUT_OF_EEPROM when it runs out. Without it a
card impersonating a 2K part would report 7434 bytes free, which no 2K
part does. The image size is what emulator memory physically holds, is
the harder limit, and is never visible.
cardsize is anchored to the free memory the real card reported when the
dump was taken -- observed free plus what we reserve for the same content
-- so the emulation answers what its original answered. That is also a
check on the reservation rule rather than only a convenience: for the
bench card it computed 576 bytes spent, and 1984 + 576 is 2560, exactly
what that card reports when formatted.
Reservation follows what CommitTransaction covers. Backup data, value and
record files each get a shadow region because writes to them are staged
until commit; a standard data file writes through and does not. Sizes
round to a 32 byte granule, which is the granule a real card allocates in.
It deliberately does not reproduce NXP's allocator -- that is
undocumented and does not fit a simple model, a declared 1024 byte record
file costs 1088 on silicon -- so what matters is that the figure is
self-consistent and shrinks as the reader writes.
No new device command. Emulator memory is one shared region, so
CMD_HF_MIFARE_EML_MEMSET and BIG_BUF_EML already reach it, and both
inherit the bounds checking those paths gained earlier.
Verified with the client only, no simulation yet: packing a dump taken
from a DESFire EV2 and walking it back reproduces every field including
file contents byte for byte, the same round trip through the device via
eload and esave is identical, and an image too large is refused by name
rather than truncated -- 'Out of emulator memory laying out AID 112233
file 00: needs 4066 more bytes'.
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)
Tab completion used a generated table (pm3line_vocabulary.h, refreshed by
hand via `make commands`) that drifted from the real command tables: new
commands were missing (e.g. `hw bwm*`), removed ones lingered, and the
"offline" flag depended on the platform of whoever regenerated it
(IfPm5() returns true offline on PM5 builds).
Build the vocabulary at startup instead:
- cmdparser: add walkCommandsRecursive(), a tree walk using a fourth
internal sentinel (XX_internal_command_walk_XX) next to the dump ones.
It hands each leaf to a visitor as its command_t chain (ancestors +
leaf). A dispatch counter detects entries shown like a category but
with their own parser (reveng) and reports them as leaves.
- pm3line_vocabulary: dynamic vocabulary holding the IsAvailable()
predicates of every command and its ancestor categories, so completion
applies exactly the rule CmdsHelp() uses, live, for both offline and
connected devices. Script entries ("script run <relpath>") come from
the same directories `script list` scans, sorted, including
subdirectories with the path `script run` needs.
- pm3line: readline and linenoise completers consume the live vocabulary.
The walk runs with output disabled so category handlers stay silent.
- Drop pm3_help2list.py and the header regeneration from `make commands`.
Behaviour change: entries whose category is hidden by `help` (e.g. `mem`,
`usart` offline) are no longer offered, matching `help`; when connected,
commands the device does not support are no longer offered either.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With the linker pin in place the proxspace job gets past the bootrom and
dies later instead, in the cmake build:
client/deps/jansson/load.c:193:35: error: writing 1 byte into a region
of size 0 [-Werror=stringop-overflow=]
client/CMakeLists.txt already turns that false positive off for GCC 10
and newer, but it does it at line 876 while add_subdirectory(deps) runs
at line 295. CMake copies the calling scope into a subdirectory at the
add_subdirectory call, so anything set afterwards never reaches the
bundled deps, and jansson is built without the suppression that the rest
of the client gets.
Move the block above the add_subdirectory call in both client and
experimental_lib. Checked by configuring before and after: 0 of 17 deps
targets carried -Wno-error=stringop-overflow in their flags.make before,
17 of 17 after.
ubuntu-cmake and macos-cmake stay green either way; their compilers do
not emit this particular false positive, which is why the gap went
unnoticed while the bootrom link was failing first.
Signed-off-by: Cole Munz <Munzzyy1@proton.me>
Imported updates from legbrute hashcat modules to speed up hf iclass legbrute
1. Bitslicing — biggest win (~32×)
Kernel stores each cipher register (l, r, b, t) as 32 parallel 1-bit lanes in u32s (m64000_a3-pure.cl:147-201, bs_iclass_tick) and computes 32 MACs per tick. The CPU doMAC_brute does 1 at a time. A 64-bit bitslice port would give ~64× per core; AVX2 gets 256×. This is the single largest speedup lever.
2. Early-reject after 8 output ticks
In m64000_sxx (m64000_a3-pure.cl:534-535) the kernel breaks out as soon as the first MAC byte can't match. doMAC_brute always produces the full 32 output bits before memcmp. Comparing byte-by-byte as bits are produced saves ~3× on the output phase since 255/256 keys fail after byte 0.
3. Pre-expanded y_ccnr bit array
Kernel expands the 96 input bits into a flat array once (m64000_a3-pure.cl:239-247) and reuses it for every candidate. suc_bytes in cipher.c:181 re-does b >>= 1 shifts for every key. Pre-expanding lets the inner loop be branch-free and vectorizable.
4. Widen lanes to 256/512 via AVX2/AVX-512. The bitslice code is written against a single uint64_t lane type — swapping for __m256i/__m512i (or an abstracted bs_word_t) gives 4×/8× throughput on hosts that support it, with scalar u64 fallback on ARM/older x86. NEON gives 2× for ARM.