From 6b2e6ab1e827aff563d25ced580f7b3465f47b77 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 3 Apr 2026 01:18:55 +0800 Subject: [PATCH 1/8] Huge hf iclass legbrute performance improvements (4x) Optimized `hf iclass legbrute` throughput: replaced recursive `suc()`/`output()` cipher functions with iterative loops, added 256-entry LUT for the `select()` function eliminating redundant bit arithmetic and halving key lookups per state step, switched successor state to in-place pointer update removing per-call struct copies, added `doMAC_brute()` with byte-wise LSB-first processing and direct output bit packing eliminating all bitstream struct overhead and output reversal calls per key candidate, and replaced per-iteration 64-bit modulo progress check with a countdown counter --- CHANGELOG.md | 1 + client/src/cmdhficlass.c | 8 +- client/src/loclass/cipher.c | 229 +++++++++++++++--------------------- client/src/loclass/cipher.h | 1 + 4 files changed, 101 insertions(+), 138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eac043d6..dae36c4cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Optimized `hf iclass legbrute` throughput: replaced recursive `suc()`/`output()` cipher functions with iterative loops, added 256-entry LUT for the `select()` function eliminating redundant bit arithmetic and halving key lookups per state step, switched successor state to in-place pointer update removing per-call struct copies, added `doMAC_brute()` with byte-wise LSB-first processing and direct output bit packing eliminating all bitstream struct overhead and output reversal calls per key candidate, and replaced per-iteration 64-bit modulo progress check with a countdown counter (@antiklesys) - Improved `hf iclass legbrute`: fixed multithreaded key-range partitioning so threads cover non-overlapping slices of the 40-bit keyspace, added ETA display, keyboard abort with resume hint, `_Atomic` correctness for shared state, `pthread_create` error handling, and thread count capped at available CPUs (@antiklesys) - Added wildcard support to `hf secc sim` payloads (@antiklesys) - Added `hf secc` to build a base for simulating basic function of iclass SE config cards (@antiklesys) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index d610daaa5..944488015 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -6111,13 +6111,14 @@ static void *brute_thread(void *args_void) { return NULL; } + uint32_t progress_countdown = 1000000; while (index < args->index_end && !*(args->found) && !*(args->aborted)) { generate_key_block_inverted(args->startingKey, index, div_key); - doMAC(args->CCNR1, div_key, mac); + doMAC_brute(args->CCNR1, div_key, mac); if (memcmp(mac, args->MAC_TAG1, 4) == 0) { - doMAC(args->CCNR2, div_key, verification_mac); + doMAC_brute(args->CCNR2, div_key, verification_mac); if (memcmp(verification_mac, args->MAC_TAG2, 4) == 0) { pthread_mutex_lock(args->log_lock); if (!*(args->found)) { @@ -6134,7 +6135,8 @@ static void *brute_thread(void *args_void) { } uint64_t thread_progress = index - args->index_start; - if (thread_progress % 1000000 == 0 && !*(args->found)) { + if (--progress_countdown == 0 && !*(args->found)) { + progress_countdown = 1000000; if (args->thread_id == 0) { uint64_t keyspace = (uint64_t)1 << 40; diff --git a/client/src/loclass/cipher.c b/client/src/loclass/cipher.c index 5aed6c589..08fc5d2bf 100644 --- a/client/src/loclass/cipher.c +++ b/client/src/loclass/cipher.c @@ -59,132 +59,57 @@ typedef struct { uint16_t t; } State_t; -/** -* Definition 2. The feedback function for the top register T : F 16/2 → F 2 -* is defined as -* T (x 0 x 1 . . . . . . x 15 ) = x 0 ⊕ x 1 ⊕ x 5 ⊕ x 7 ⊕ x 10 ⊕ x 11 ⊕ x 14 ⊕ x 15 . -**/ -static bool T(State_t state) { - /* - bool x0 = state.t & 0x8000; - bool x1 = state.t & 0x4000; - bool x5 = state.t & 0x0400; - bool x7 = state.t & 0x0100; - bool x10 = state.t & 0x0020; - bool x11 = state.t & 0x0010; - bool x14 = state.t & 0x0002; - bool x15 = state.t & 0x0001; - return x0 ^ x1 ^ x5 ^ x7 ^ x10 ^ x11 ^ x14 ^ x15; - */ -#define _x0 ((state.t & 0x8000) >> 15 ) -#define _x1 ((state.t & 0x4000) >> 14 ) -#define _x5 ((state.t & 0x0400) >> 10 ) -#define _x7 ((state.t & 0x0100) >> 8 ) -#define _x10 ((state.t & 0x0020) >> 5 ) -#define _x11 ((state.t & 0x0010) >> 4 ) -#define _x14 ((state.t & 0x0002) >> 1 ) -#define _x15 (state.t & 0x0001) - return (_x0) ^ (_x1) ^ (_x5) ^ (_x7) ^ (_x10) ^ (_x11) ^ (_x14) ^ (_x15); -} -/** -* Similarly, the feedback function for the bottom register B : F 8/2 → F 2 is defined as -* B(x 0 x 1 . . . x 7 ) = x 1 ⊕ x 2 ⊕ x 3 ⊕ x 7 . -**/ -/*static bool B(State_t state) { - bool x1 = state.b & 0x40; - bool x2 = state.b & 0x20; - bool x3 = state.b & 0x10; - bool x7 = state.b & 0x01; - return x1 ^ x2 ^ x3 ^ x7; -} -*/ -#define B(x) (((x.b & 0x40) >> 6) ^ ((x.b & 0x20) >> 5) ^ ((x.b & 0x10) >> 4) ^ (x.b & 0x01)) - -// 12 3456 -// 0100 0000 +// Precomputed lookup table for the r-dependent part of select(x, y, r). +// z0 (bit2) depends only on r. z1 (bit1) = LUT_bit1 ^ x ^ y. z2 (bit0) = LUT_bit0 ^ x. +// Generated from the _select formula; x and y are folded in at call time. +static const uint8_t opt_select_LUT[256] = { + 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, + 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, + 07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, + 06, 05, 04, 07, 04, 05, 06, 07, 02, 01, 01, 02, 00, 01, 03, 02, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, + 05, 06, 07, 04, 06, 07, 04, 05, 05, 06, 06, 05, 06, 07, 05, 04, + 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 02, 01, 00, 03, 00, 01, 02, 03, 02, 01, 01, 02, 00, 01, 03, 02, + 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02, + 04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, + 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, + 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00, +}; /** -* Definition 3 (Selection function). The selection function select : F 2 × F 2 × -* F 8/2 → F 3/2 is defined as select(x, y, r) = z 0 z 1 z 2 where -* z 0 = (r 0 ∧ r 2 ) ⊕ (r 1 ∧ r 3 ) ⊕ (r 2 ∨ r 4 ) -* z 1 = (r 0 ∨ r 2 ) ⊕ (r 5 ∨ r 7 ) ⊕ r 1 ⊕ r 6 ⊕ x ⊕ y -* z 2 = (r 3 ∧ r 5 ) ⊕ (r 4 ∧ r 6 ) ⊕ r 7 ⊕ x +* Definition 4 (Successor state). Optimized in-place version. +* T(t) computed via parallel XOR reduction on the masked t register. +* B(b) computed via parallel XOR reduction on the b register. +* select(x,y,r) resolved via opt_select_LUT with a single key lookup. **/ -static uint8_t _select(bool x, bool y, uint8_t r) { -#define _r0 ((r >> 7) & 0x01) -#define _r1 ((r >> 6) & 0x01) -#define _r2 ((r >> 5) & 0x01) -#define _r3 ((r >> 4) & 0x01) -#define _r4 ((r >> 3) & 0x01) -#define _r5 ((r >> 2) & 0x01) -#define _r6 ((r >> 1) & 0x01) -#define _r7 (r & 0x01) +static void successor(const uint8_t *k, State_t *s, uint8_t y) { + // T(t) = x0^x1^x5^x7^x10^x11^x14^x15, mask selects those bits + uint16_t Tt = s->t & 0xc533; + Tt ^= Tt >> 1; + Tt ^= Tt >> 4; + Tt ^= Tt >> 10; + Tt ^= Tt >> 8; + // bit0 of Tt is now T(t) -#define _z0 ( (_r0 & _r2) ^ ( _r1 & (!_r3)) ^ (_r2 | _r4) ) -#define _z1 ( (_r0 | _r2) ^ ( _r5 | _r7) ^_r1 ^ _r6 ^ (x) ^ (y) ) -#define _z2 ( (_r3 & (!_r5)) ^ (_r4 & _r6) ^ _r7 ^ (x) ) + s->t = (s->t >> 1) | ((Tt ^ (s->r >> 7) ^ (s->r >> 3)) << 15); - /* - uint8_t r0 = r >> 7 & 0x1; - uint8_t r1 = r >> 6 & 0x1; - uint8_t r2 = r >> 5 & 0x1; - uint8_t r3 = r >> 4 & 0x1; - uint8_t r4 = r >> 3 & 0x1; - uint8_t r5 = r >> 2 & 0x1; - uint8_t r6 = r >> 1 & 0x1; - uint8_t r7 = r & 0x1; + // B(b) = b1^b2^b3^b7; bit0 of opt_B = B(b) after the XOR reduction + uint8_t opt_B = s->b ^ (s->b >> 6) ^ (s->b >> 5) ^ (s->b >> 4); + s->b = (s->b >> 1) | ((opt_B ^ s->r) << 7); - bool z0 = (r0 & r2) ^ (r1 & (!r3)) ^ (r2 | r4); - bool z1 = (r0 | r2) ^ (r5 | r7) ^ r1 ^ r6 ^ x ^ y; - bool z2 = (r3 & (!r5)) ^ (r4 & r6) ^ r7 ^ x; + // select via LUT: z0 from LUT directly, z1/z2 fold in Tt and y + uint8_t sel = opt_select_LUT[s->r] & 0x04; + sel |= (opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02; + sel |= (opt_select_LUT[s->r] ^ Tt) & 0x01; - // The three bitz z0.. z1 are packed into a uint8_t: - // 00000ZZZ - //Return value is a uint8_t - return ((z0 << 2) & 4) | ((z1 << 1) & 2) | (z2 & 1); - */ - return ((_z0 << 2) & 4) | ((_z1 << 1) & 2) | (_z2 & 1); - - /* - uint8_t retval = 0; - retval |= (z0 << 2) & 4; - retval |= (z1 << 1) & 2; - retval |= (z2) & 1; - - // Return value 0 <= retval <= 7 - return retval; - */ -} - -/** -* Definition 4 (Successor state). Let s = l, r, t, b be a cipher state, k ∈ (F 82 ) 8 -* be a key and y ∈ F 2 be the input bit. Then, the successor cipher state s ′ = -* l ′ , r ′ , t ′ , b ′ is defined as -* t ′ := (T (t) ⊕ r 0 ⊕ r 4 )t 0 . . . t 14 l ′ := (k [select(T (t),y,r)] ⊕ b ′ ) ⊞ l ⊞ r -* b ′ := (B(b) ⊕ r 7 )b 0 . . . b 6 r ′ := (k [select(T (t),y,r)] ⊕ b ′ ) ⊞ l -* -* @param s - state -* @param k - array containing 8 bytes -**/ -static State_t successor(const uint8_t *k, State_t s, bool y) { - bool r0 = s.r >> 7 & 0x1; - bool r4 = s.r >> 3 & 0x1; - bool r7 = s.r & 0x1; - - State_t successor = {0, 0, 0, 0}; - - successor.t = s.t >> 1; - successor.t |= ((T(s)) ^ (r0) ^ (r4)) << 15; - - successor.b = s.b >> 1; - successor.b |= ((B(s)) ^ (r7)) << 7; - - bool Tt = T(s); - - successor.l = ((k[_select(Tt, y, s.r)] ^ successor.b) + s.l + s.r) & 0xFF; - successor.r = ((k[_select(Tt, y, s.r)] ^ successor.b) + s.l) & 0xFF; - - return successor; + uint8_t r = s->r; + s->r = (k[sel] ^ s->b) + s->l; + s->l = s->r + r; } /** * We define the successor function suc which takes a key k ∈ (F 82 ) 8 , a state s and @@ -193,11 +118,9 @@ static State_t successor(const uint8_t *k, State_t s, bool y) { * @param k - array containing 8 bytes **/ static State_t suc(uint8_t *k, State_t s, BitstreamIn_t *bitstream) { - if (bitsLeft(bitstream) == 0) { - return s; - } - bool lastbit = tailBit(bitstream); - return successor(k, suc(k, s, bitstream), lastbit); + while (bitsLeft(bitstream) > 0) + successor(k, &s, headBit(bitstream)); + return s; } /** @@ -208,15 +131,11 @@ static State_t suc(uint8_t *k, State_t s, BitstreamIn_t *bitstream) { * output(k, s, x 0 . . . x n ) = output(s) · output(k, s ′ , x 1 . . . x n ) * where s ′ = suc(k, s, x 0 ). **/ -static void output(uint8_t *k, State_t s, BitstreamIn_t *in, BitstreamOut_t *out) { - if (bitsLeft(in) == 0) { - return; +static void output(uint8_t *k, State_t s, BitstreamIn_t *in, BitstreamOut_t *out) { + while (bitsLeft(in) > 0) { + pushBit(out, (s.r >> 2) & 1); + successor(k, &s, headBit(in)); } - pushBit(out, (s.r >> 2) & 1); - //Remove first bit - uint8_t x0 = headBit(in); - State_t ss = successor(k, s, x0); - output(k, ss, in, out); } /** @@ -243,21 +162,61 @@ static void MAC(uint8_t *k, BitstreamIn_t input, BitstreamOut_t out) { void doMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]) { uint8_t cc_nr[13] = { 0 }; - uint8_t div_key[8]; memcpy(cc_nr, cc_nr_p, 12); - memcpy(div_key, div_key_p, 8); reverse_arraybytes(cc_nr, 12); BitstreamIn_t bitstream = {cc_nr, 12 * 8, 0}; uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; BitstreamOut_t out = { dest, sizeof(dest) * 8, 0 }; - MAC(div_key, bitstream, out); + MAC(div_key_p, bitstream, out); //The output MAC must also be reversed reverse_arraybytes(dest, sizeof(dest)); memcpy(mac, dest, 4); } +// Feeds `length` raw bytes into cipher state s, one bit at a time LSB-first. +// Equivalent to reflect8-then-MSB-first used by doMAC, with no intermediate buffer. +static void suc_bytes(const uint8_t *k, State_t *s, const uint8_t *in, int length) { + for (int i = 0; i < length; i++) { + uint8_t b = in[i]; + successor(k, s, b); b >>= 1; + successor(k, s, b); b >>= 1; + successor(k, s, b); b >>= 1; + successor(k, s, b); b >>= 1; + successor(k, s, b); b >>= 1; + successor(k, s, b); b >>= 1; + successor(k, s, b); b >>= 1; + successor(k, s, b); + } +} + +// Collects nbytes of cipher output into `out`, packing bits LSB-first per byte. +// Equivalent to output()+reflect8 used by doMAC, with no intermediate buffer or reversal. +static void output_bytes(const uint8_t *k, State_t *s, uint8_t *out, int nbytes) { + for (int i = 0; i < nbytes; i++) { + uint8_t bout = 0; + bout |= (s->r & 0x4) >> 2; successor(k, s, 0); + bout |= (s->r & 0x4) >> 1; successor(k, s, 0); + bout |= (s->r & 0x4); successor(k, s, 0); + bout |= (s->r & 0x4) << 1; successor(k, s, 0); + bout |= (s->r & 0x4) << 2; successor(k, s, 0); + bout |= (s->r & 0x4) << 3; successor(k, s, 0); + bout |= (s->r & 0x4) << 4; successor(k, s, 0); + bout |= (s->r & 0x4) << 5; successor(k, s, 0); + out[i] = bout; + } +} + +// doMAC variant for the brute-force hot loop: takes raw (non-reflected) cc_nr and +// produces the same MAC as doMAC with no intermediate buffers, no reversal calls, +// and no bitstream overhead. +void doMAC_brute(const uint8_t *cc_nr, const uint8_t *div_key, uint8_t mac[4]) { + State_t s = init(div_key); + suc_bytes(div_key, &s, cc_nr, 12); + output_bytes(div_key, &s, mac, 4); +} + void doMAC_N(uint8_t *address_data_p, uint8_t address_data_size, uint8_t *div_key_p, uint8_t mac[4]) { uint8_t *address_data; uint8_t div_key[8]; diff --git a/client/src/loclass/cipher.h b/client/src/loclass/cipher.h index dd60d8eb5..923e09809 100644 --- a/client/src/loclass/cipher.h +++ b/client/src/loclass/cipher.h @@ -38,6 +38,7 @@ #include "pm3_cmd.h" void doMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]); +void doMAC_brute(const uint8_t *cc_nr, const uint8_t *div_key, uint8_t mac[4]); void doMAC_N(uint8_t *address_data_p, uint8_t address_data_size, uint8_t *div_key_p, uint8_t mac[4]); #ifndef ON_DEVICE From 8380c02a547d8d71856f43708c6a111ff042c197 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 3 Apr 2026 03:22:32 +0800 Subject: [PATCH 2/8] Fixed legrec grammar error and checks --- client/src/cmdhficlass.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 944488015..12efaac77 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -6483,7 +6483,7 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf iclass legrec", "Attempts to recover the diversified key of a specific iCLASS card. This may take several days.\n" - "The card must remain be on the PM3 antenna during the whole process.\n" + "The card must remain on the PM3 antenna during the whole process.\n" _RED_(" ! Warning ! ") _WHITE_(" This process may brick the card! ") _RED_(" ! Warning ! "), "hf iclass legrec --macs 0000000089cb984b\n" "hf iclass legrec --macs 0000000089cb984b --index 0 --loop 100 --notest" @@ -6491,7 +6491,7 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) { void *argtable[] = { arg_param_begin, - arg_str1(NULL, "macs", "", "AA1 Authentication MACs"), + arg_str0(NULL, "macs", "", "AA1 Authentication MACs"), arg_int0(NULL, "index", "", "Where to start from to retrieve the key (def: 0)"), arg_int0(NULL, "loop", "", "The number of key retrieval cycles to perform, max 10000 (def 100)"), arg_lit0(NULL, "debug", "Re-enables tracing for debugging. Limits cycles to 1"), @@ -6525,6 +6525,12 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) { return PM3_SUCCESS; } + if (macs_len == 0) { + PrintAndLogEx(ERR, "Missing required argument: --macs"); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (no_test) { test = false; } From e1e689e2664110a7fe45b1d1cb4bfd9656e4c05d Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 3 Apr 2026 03:32:00 +0800 Subject: [PATCH 3/8] Fixed bug causing retries loop to be skipped Fixed bug causing retries loop to be skipped --- armsrc/iclass.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 8a894ee5f..e8c2d7295 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -3187,12 +3187,13 @@ fast_restore: Dbhexdump(8, fast_restore_key, false); Dbprintf(_RED_("Attempted to restore original key for %3d times and failed. Stopping. Card is likely unusable."), revert_retries); } - if (recovered) { + if (recovered && reverted) { goto restore; - } else { + } else if (revert_retries >= 7) { goto out; } } + goto out; restore: ;// empty statement for compilation From f9474feec0bd1a066dadca9d2939d753c6723e44 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 3 Apr 2026 11:38:08 +0800 Subject: [PATCH 4/8] Update elite_crack.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leveraging doMAC_brute for speed gains. Precomputation (lines 330-346): Before the loop, sel_brute_idx[i] is set to the brute-byte index j if key_index[i] == bytes_to_recover[j], or 0xFF if that key_sel position is from a already-cracked constant entry. Fixed positions are filled into key_sel once here. Hot loop (lines 356-361): Replaces: numbytes_to_recover keytable writes + 8 keytable reads + 8 & 0xFF masks With: up to numbytes_to_recover (1-3) direct shifts from brute — no keytable touches at all. Success path (line 386): (brute >> (i * 8)) & 0xFF recovers the exact byte value that was just used for the winning iteration, equivalent to the old keytable[bytes_to_recover[i]] & 0xFF which was the same value that had just been written into the keytable from brute. --- client/src/loclass/elite_crack.c | 43 +++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/client/src/loclass/elite_crack.c b/client/src/loclass/elite_crack.c index ad7beb249..50499648a 100644 --- a/client/src/loclass/elite_crack.c +++ b/client/src/loclass/elite_crack.c @@ -327,6 +327,24 @@ static void *bf_thread(void *thread_arg) { memcpy(bytes_to_recover, targ->bytes_to_recover, sizeof(bytes_to_recover)); memcpy(keytable, targ->keytable, sizeof(keytable)); + // Precompute which key_sel positions are fixed (already-cracked) vs. brute-forced. + // sel_brute_idx[i] == 0xFF: key_sel[i] is constant for this entire brute-force run. + // sel_brute_idx[i] == j: key_sel[i] = (brute >> (j*8)) & 0xFF each iteration. + uint8_t sel_brute_idx[8]; + uint8_t key_sel[8]; + for (uint8_t i = 0; i < 8; i++) { + sel_brute_idx[i] = 0xFF; + for (uint8_t j = 0; j < numbytes_to_recover; j++) { + if (key_index[i] == bytes_to_recover[j]) { + sel_brute_idx[i] = j; + break; + } + } + if (sel_brute_idx[i] == 0xFF) { + key_sel[i] = keytable[key_index[i]] & 0xFF; + } + } + while (!(brute & endmask)) { int found = __atomic_load_n(&loclass_found, __ATOMIC_SEQ_CST); @@ -335,24 +353,13 @@ static void *bf_thread(void *thread_arg) { return NULL; } - //Update the keytable with the brute-values - for (uint8_t i = 0; i < numbytes_to_recover; i++) { - keytable[bytes_to_recover[i]] &= 0xFF00; - keytable[bytes_to_recover[i]] |= (brute >> (i * 8) & 0xFF); + // Update only the bruted positions of key_sel directly from brute + for (uint8_t i = 0; i < 8; i++) { + if (sel_brute_idx[i] != 0xFF) { + key_sel[i] = (brute >> (sel_brute_idx[i] * 8)) & 0xFF; + } } - uint8_t key_sel[8] = {0}; - - // Piece together the key - key_sel[0] = keytable[key_index[0]] & 0xFF; - key_sel[1] = keytable[key_index[1]] & 0xFF; - key_sel[2] = keytable[key_index[2]] & 0xFF; - key_sel[3] = keytable[key_index[3]] & 0xFF; - key_sel[4] = keytable[key_index[4]] & 0xFF; - key_sel[5] = keytable[key_index[5]] & 0xFF; - key_sel[6] = keytable[key_index[6]] & 0xFF; - key_sel[7] = keytable[key_index[7]] & 0xFF; - // Permute from iclass format to standard format uint8_t key_sel_p[8] = {0}; @@ -364,7 +371,7 @@ static void *bf_thread(void *thread_arg) { // Calc mac uint8_t calculated_MAC[4] = {0}; - doMAC(cc_nr, div_key, calculated_MAC); + doMAC_brute(cc_nr, div_key, calculated_MAC); // success if (memcmp(calculated_MAC, mac, 4) == 0) { @@ -376,7 +383,7 @@ static void *bf_thread(void *thread_arg) { } for (uint8_t i = 0 ; i < numbytes_to_recover; i++) { - r->values[i] = keytable[bytes_to_recover[i]] & 0xFF; + r->values[i] = (brute >> (i * 8)) & 0xFF; } __atomic_store_n(&loclass_found, targ->thread_idx, __ATOMIC_SEQ_CST); pthread_exit((void *)r); From 10bd253eab21bf12ab92afcb3c7dfeb9bcbdc82b Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 3 Apr 2026 11:43:11 +0800 Subject: [PATCH 5/8] Update elite_crack.c --- client/src/loclass/elite_crack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/loclass/elite_crack.c b/client/src/loclass/elite_crack.c index 50499648a..1c1026f71 100644 --- a/client/src/loclass/elite_crack.c +++ b/client/src/loclass/elite_crack.c @@ -382,7 +382,7 @@ static void *bf_thread(void *thread_arg) { pthread_exit(NULL); } - for (uint8_t i = 0 ; i < numbytes_to_recover; i++) { + for (uint8_t i = 0 ; i < numbytes_to_recover && i < sizeof(r->values); i++) { r->values[i] = (brute >> (i * 8)) & 0xFF; } __atomic_store_n(&loclass_found, targ->thread_idx, __ATOMIC_SEQ_CST); From 51521fdad9990ddf3b76f687348fb7dac1c8ddc9 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 3 Apr 2026 12:00:56 +0800 Subject: [PATCH 6/8] Fixed blacktears bugs --- client/src/cmdhficlass.c | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 12efaac77..8f8382064 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -4338,7 +4338,7 @@ static int CmdHFiClass_BlackTears(const char *Cmd) { arg_int0(NULL, "ki", "", "Key index to select key from memory 'hf iclass managekeys'"), arg_lit0(NULL, "credit", "key is assumed to be the credit key"), arg_int0("s", NULL, "", "tearoff delay start (in us) must be between 1 and 43000 (43ms). Precision is about 1/3 us"), - arg_int0("i", NULL, "", "tearoff delay increment (in us) - default 10"), + arg_int0("i", NULL, "", "tearoff delay increment (in us) - default 5"), arg_int0("e", NULL, "", "tearoff delay end (in us) must be a higher value than the start delay"), arg_str0("o", "otp", "", "Custom OTP value as 2 hex bytes"), arg_lit0(NULL, "dns", "Do not stabilize the bits, and return the raw dump of the block after tearoff"), @@ -4510,27 +4510,16 @@ static int CmdHFiClass_BlackTears(const char *Cmd) { uint8_t data_read_orig[8] = {0}; uint8_t ff_data[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; bool first_read = false; - bool reread = false; bool erase_phase = false; read_auth = false; int blockno = 1; - + int res_orig = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read_orig, false, false); - while (reread) { - if (res_orig == PM3_SUCCESS && !reread) { - if (memcmp(data_read_orig, zeros, 8) == 0) { - reread = true; - } else { - reread = false; - } - } else if (res_orig == PM3_SUCCESS && reread) { - reread = false; - if (blockno == 2 && memcmp(data_read_orig, zeros, 8) == 0) { - reread = true; - } - } + if (res_orig == PM3_SUCCESS && memcmp(data_read_orig, zeros, 8) == 0) { + // zeros may be a transient read artifact - read once more to confirm + res_orig = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read_orig, false, false); } uint8_t data[8] = { 0 }; // tearoff payload @@ -4631,6 +4620,9 @@ static int CmdHFiClass_BlackTears(const char *Cmd) { reread = false; } else if (res != PM3_SUCCESS) { decrease = true; + if (readcount == 100) { + PrintAndLogEx(WARNING, "\nCard not responding after %d attempts, press " _GREEN_("") " to abort", readcount); + } } readcount++; @@ -4688,7 +4680,7 @@ static int CmdHFiClass_BlackTears(const char *Cmd) { bool goto_out = false; // App limit became SMALLER :( - if (data_read[0] > data_read_orig[0]) { + if (data_read[0] < data_read_orig[0]) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(SUCCESS, "Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]); @@ -4772,13 +4764,13 @@ static int CmdHFiClass_BlackTears(const char *Cmd) { msleep(tearoff_sleep); } } - + out: DropField(); - if (setDeviceDebugLevel(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) { + if (setDeviceDebugLevel(dbg_curr, false) != PM3_SUCCESS) { return PM3_EFAILED; } // disable tearoff in case of keyboard abort, or it'll trigger on next operation From 2145bc886977f5e8a01f5b99120a48f51158eef7 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 3 Apr 2026 12:03:59 +0800 Subject: [PATCH 7/8] Update cmdhficlass.c --- client/src/cmdhficlass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 8f8382064..acb2a31b4 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -4593,7 +4593,7 @@ static int CmdHFiClass_BlackTears(const char *Cmd) { // read the data back uint8_t data_read[8] = {0}; first_read = false; - reread = false; + bool reread = false; bool decrease = false; int readcount = 0; while (first_read == false) { From b5fe39cf3464b5748aaf3498762cf27bbdaf002a Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 3 Apr 2026 12:17:34 +0800 Subject: [PATCH 8/8] Update cmdhficlass.c Signed-off-by: Antiklesys --- client/src/cmdhficlass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index acb2a31b4..247103e24 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -4770,7 +4770,7 @@ out: DropField(); - if (setDeviceDebugLevel(dbg_curr, false) != PM3_SUCCESS) { + if (setDeviceDebugLevel(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) { return PM3_EFAILED; } // disable tearoff in case of keyboard abort, or it'll trigger on next operation