Skip to content

Commit 37fcf53

Browse files
authored
Merge pull request #3202 from Antiklesys/master
Fixed spacing & Fixed multithreading in hf iclass legbrute
2 parents 06c8f9a + e4b5388 commit 37fcf53

File tree

3 files changed

+150
-26
lines changed

3 files changed

+150
-26
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
33
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...
44

55
## [unreleased][unreleased]
6+
- 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)
67
- Added wildcard support to `hf secc sim` payloads (@antiklesys)
78
- Added `hf secc` to build a base for simulating basic function of iclass SE config cards (@antiklesys)
89
- Improved SIO parsing for `hf iclass view` based on Iceman's "Dismantling the SEOS Protocol" talk (@antiklesys)
@@ -29,7 +30,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
2930
- Added DESFire AID values related to LEAF (@kormax)
3031
- Added `dict`, `ascii`, `mad` presets for `hf mfdes bruteaid` (@kormax)
3132
- Added tag loss detection & recovery into `hf mfdes bruteaid` (@kormax)
32-
- Added --credit option for `hf iclass legrec` command to perform a credit key recovery. This is experimental and unfinished as it only partially works.(@antiklesys)
33+
- Added `--credit` option to `hf iclass legrec` for credit key recovery. Note: this option alone is experimental and only partially functional; the standard key recovery works normally.(@antiklesys)
3334
- Added hardening for all host binaries. Exact level of hardening depends on the OS (@doegox)
3435
- Added `hf aliro read` command (@kormax)
3536
- Added `hf aliro info` command (@kormax)

armsrc/secc.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,10 @@ bool hid_config_card_jam(const uint8_t *cmd, int len, uint8_t *dma_buf) {
237237
int off = (pcb & 0x08) ? 2 : 1; // skip CID if present
238238

239239
// Select active APDU pattern and response (custom or default)
240-
const uint8_t *match = (s_jam_apdu_len > 0) ? s_jam_apdu : s_jam_apdu_default;
241-
int match_len = (s_jam_apdu_len > 0) ? (int)s_jam_apdu_len : (int)sizeof(s_jam_apdu_default);
242-
const uint8_t *resp_data = (s_jam_resp_len > 0) ? s_jam_resp : s_jam_resp_default;
243-
int resp_data_len = (s_jam_resp_len > 0) ? (int)s_jam_resp_len : (int)sizeof(s_jam_resp_default);
240+
const uint8_t *match = (s_jam_apdu_len > 0) ? s_jam_apdu : s_jam_apdu_default;
241+
int match_len = (s_jam_apdu_len > 0) ? (int)s_jam_apdu_len : (int)sizeof(s_jam_apdu_default);
242+
const uint8_t *resp_data = (s_jam_resp_len > 0) ? s_jam_resp : s_jam_resp_default;
243+
int resp_data_len = (s_jam_resp_len > 0) ? (int)s_jam_resp_len : (int)sizeof(s_jam_resp_default);
244244

245245
if (len < off + match_len + 2) // off + APDU pattern + 2-byte CRC
246246
return false;

client/src/cmdhficlass.c

Lines changed: 144 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6063,13 +6063,18 @@ void generate_key_block_inverted(const uint8_t *startingKey, uint64_t index, uin
60636063
typedef struct {
60646064
uint8_t startingKey[8];
60656065
uint64_t index_start;
6066+
uint64_t index_end;
60666067
uint8_t CCNR1[12];
60676068
uint8_t MAC_TAG1[4];
60686069
uint8_t CCNR2[12];
60696070
uint8_t MAC_TAG2[4];
60706071
int thread_id;
60716072
int thread_count;
6072-
volatile bool *found;
6073+
uint64_t start_time;
6074+
_Atomic bool *found;
6075+
_Atomic bool *aborted;
6076+
_Atomic uint64_t *aborted_at;
6077+
bool debug;
60736078
pthread_mutex_t *log_lock;
60746079
} thread_args_t;
60756080

@@ -6082,7 +6087,31 @@ static void *brute_thread(void *args_void) {
60826087
uint8_t verification_mac[4];
60836088
uint64_t index = args->index_start;
60846089

6085-
while (!*(args->found)) {
6090+
if (args->debug) {
6091+
pthread_mutex_lock(args->log_lock);
6092+
6093+
PrintAndLogEx(INFO, "Thread[%2d] range [%" PRIu64 " - %" PRIu64 ") startingKey: %s"
6094+
, args->thread_id
6095+
, args->index_start
6096+
, args->index_end
6097+
, sprint_hex_inrow(args->startingKey, 8));
6098+
6099+
// Show first 2 candidates — different threads must start from different candidates
6100+
for (int d = 0; d < 2; d++) {
6101+
generate_key_block_inverted(args->startingKey, args->index_start + d, div_key);
6102+
PrintAndLogEx(INFO, " [index %" PRIu64 "]: %s", args->index_start + d, sprint_hex_inrow(div_key, 8));
6103+
}
6104+
6105+
// Show the midpoint of the slice — confirms byte-0 carry is reached inside this thread's range
6106+
uint64_t mid = args->index_start + (args->index_end - args->index_start) / 2;
6107+
generate_key_block_inverted(args->startingKey, mid, div_key);
6108+
PrintAndLogEx(INFO, " [index %" PRIu64 " (mid)]: %s", mid, sprint_hex_inrow(div_key, 8));
6109+
6110+
pthread_mutex_unlock(args->log_lock);
6111+
return NULL;
6112+
}
6113+
6114+
while (index < args->index_end && !*(args->found) && !*(args->aborted)) {
60866115

60876116
generate_key_block_inverted(args->startingKey, index, div_key);
60886117
doMAC(args->CCNR1, div_key, mac);
@@ -6104,15 +6133,45 @@ static void *brute_thread(void *args_void) {
61046133
}
61056134
}
61066135

6107-
if (index % 1000000 == 0 && !*(args->found)) {
6136+
uint64_t thread_progress = index - args->index_start;
6137+
if (thread_progress % 1000000 == 0 && !*(args->found)) {
61086138

61096139
if (args->thread_id == 0) {
6140+
uint64_t keyspace = (uint64_t)1 << 40;
6141+
uint64_t keyspace_m = keyspace / 1000000; // 1,099,511
6142+
uint64_t run_progress = thread_progress * (uint64_t)args->thread_count;
6143+
// Cumulative absolute position across all threads including any --index offset
6144+
uint64_t abs_done = args->index_start * (uint64_t)args->thread_count + run_progress;
6145+
uint64_t keys_left = (abs_done < keyspace) ? keyspace - abs_done : 0;
6146+
uint64_t elapsed_ms = msclock() - args->start_time;
6147+
61106148
pthread_mutex_lock(args->log_lock);
6111-
PrintAndLogEx(INPLACE, "Tested "_YELLOW_("%" PRIu64)" million keys, curr index: "_YELLOW_("%" PRIu64)", Thread[0]: %s"
6112-
, ((index / 1000000) * args->thread_count)
6113-
, (index / 1000000)
6114-
, sprint_hex_inrow(div_key, 8)
6115-
);
6149+
6150+
if (elapsed_ms > 0 && run_progress > 0) {
6151+
// speed based on keys tested in this run only
6152+
uint64_t kps = run_progress * 1000 / elapsed_ms;
6153+
uint64_t eta_s = (kps > 0) ? keys_left / kps : 0;
6154+
uint64_t eta_d = eta_s / 86400;
6155+
uint64_t eta_h = (eta_s % 86400) / 3600;
6156+
uint64_t eta_m = (eta_s % 3600) / 60;
6157+
uint64_t eta_r = eta_s % 60;
6158+
PrintAndLogEx(INPLACE, "Tested "_YELLOW_("%" PRIu64)"M / %" PRIu64 "M keys speed: "_YELLOW_("%" PRIu64)" k/s ETA: "_YELLOW_("%" PRIu64 "d %02" PRIu64 "h %02" PRIu64 "m %02" PRIu64 "s")
6159+
, abs_done / 1000000
6160+
, keyspace_m
6161+
, kps / 1000
6162+
, eta_d, eta_h, eta_m, eta_r
6163+
);
6164+
} else {
6165+
PrintAndLogEx(INPLACE, "Tested "_YELLOW_("%" PRIu64)"M / %" PRIu64 "M keys"
6166+
, abs_done / 1000000
6167+
, keyspace_m
6168+
);
6169+
}
6170+
6171+
if (kbd_enter_pressed()) {
6172+
*args->aborted_at = index;
6173+
*args->aborted = true;
6174+
}
61166175
pthread_mutex_unlock(args->log_lock);
61176176
}
61186177

@@ -6123,14 +6182,16 @@ static void *brute_thread(void *args_void) {
61236182
}
61246183

61256184
// HF iClass legbrute - Multithreaded brute-force function
6126-
static int CmdHFiClassLegBrute_MT(uint8_t epurse[8], uint8_t macs[8], uint8_t macs2[8], uint8_t startingKey[8], uint64_t index, int threads) {
6185+
static int CmdHFiClassLegBrute_MT(uint8_t epurse[8], uint8_t macs[8], uint8_t macs2[8], uint8_t startingKey[8], uint64_t index, int threads, bool debug) {
61276186

61286187
int thread_count = threads;
61296188
if (thread_count < 1) {
61306189
thread_count = 1;
61316190
}
6132-
if (thread_count > 16) {
6133-
thread_count = 16;
6191+
int max_threads = num_CPUs();
6192+
if (thread_count > max_threads) {
6193+
PrintAndLogEx(INFO, "Capping threads at available CPU count (%d)", max_threads);
6194+
thread_count = max_threads;
61346195
}
61356196
PrintAndLogEx(INFO, "Bruteforcing using " _YELLOW_("%u") " threads", thread_count);
61366197
PrintAndLogEx(NORMAL, "");
@@ -6146,42 +6207,101 @@ static int CmdHFiClassLegBrute_MT(uint8_t epurse[8], uint8_t macs[8], uint8_t ma
61466207

61476208
pthread_t tids[thread_count];
61486209
thread_args_t args[thread_count];
6149-
volatile bool found = false;
6210+
_Atomic bool found = false;
6211+
_Atomic bool aborted = false;
6212+
_Atomic uint64_t aborted_at = 0;
61506213
pthread_mutex_t log_lock;
61516214
pthread_mutex_init(&log_lock, NULL);
6215+
PrintAndLogEx(HINT, "Hint: Press " _YELLOW_("<Enter>") " to abort");
6216+
6217+
// Divide the full 40-bit keyspace into equal non-overlapping slices, one per thread.
6218+
// All threads use the same startingKey; only their index range differs.
6219+
uint64_t keyspace = (uint64_t)1 << 40;
6220+
uint64_t slice = keyspace / thread_count;
6221+
uint64_t start_time = msclock();
61526222

6153-
int nibble_range = 16 / thread_count;
61546223
for (int i = 0; i < thread_count; i++) {
61556224
memcpy(args[i].startingKey, startingKey, 8);
6156-
args[i].startingKey[0] = (startingKey[0] & 0x0F) | ((i * nibble_range) << 4);
6157-
args[i].index_start = index;
6225+
args[i].index_start = index + (uint64_t)i * slice;
6226+
args[i].index_end = (i == thread_count - 1)
6227+
? index + keyspace // last thread absorbs remainder
6228+
: index + (uint64_t)(i + 1) * slice;
61586229
memcpy(args[i].CCNR1, CCNR, 12);
61596230
memcpy(args[i].MAC_TAG1, MAC_TAG, 4);
61606231
memcpy(args[i].CCNR2, CCNR2, 12);
61616232
memcpy(args[i].MAC_TAG2, MAC_TAG2, 4);
61626233
args[i].thread_id = i;
61636234
args[i].thread_count = thread_count;
6235+
args[i].start_time = start_time;
61646236
args[i].found = &found;
6237+
args[i].aborted = &aborted;
6238+
args[i].aborted_at = &aborted_at;
6239+
args[i].debug = debug;
61656240
args[i].log_lock = &log_lock;
61666241

6167-
pthread_create(&tids[i], NULL, brute_thread, &args[i]);
6242+
if (pthread_create(&tids[i], NULL, brute_thread, &args[i]) != 0) {
6243+
PrintAndLogEx(WARNING, "Failed to create thread %d, running with %d thread(s)", i, i);
6244+
thread_count = i;
6245+
break;
6246+
}
6247+
}
6248+
6249+
if (thread_count == 0) {
6250+
pthread_mutex_destroy(&log_lock);
6251+
return PM3_ESOFT;
61686252
}
61696253

61706254
for (int i = 0; i < thread_count; i++) {
61716255
pthread_join(tids[i], NULL);
61726256
}
61736257
pthread_mutex_destroy(&log_lock);
61746258

6175-
return found ? PM3_SUCCESS : ERR;
6259+
if (debug) {
6260+
PrintAndLogEx(NORMAL, "");
6261+
PrintAndLogEx(INFO, "Index range summary (%d threads, keyspace 2^40 = %" PRIu64 "):", thread_count, (uint64_t)1 << 40);
6262+
PrintAndLogEx(INFO, " Thread start end slice size");
6263+
for (int i = 0; i < thread_count; i++) {
6264+
PrintAndLogEx(INFO, " [%2d] %-20" PRIu64 " %-20" PRIu64 " %" PRIu64
6265+
, i
6266+
, args[i].index_start
6267+
, args[i].index_end
6268+
, args[i].index_end - args[i].index_start);
6269+
}
6270+
// Verify ranges are contiguous and non-overlapping
6271+
bool ok = true;
6272+
for (int i = 1; i < thread_count; i++) {
6273+
if (args[i].index_start != args[i - 1].index_end) {
6274+
PrintAndLogEx(WARNING, _RED_(" Gap or overlap between thread %d and %d!"), i - 1, i);
6275+
ok = false;
6276+
}
6277+
}
6278+
if (ok) {
6279+
PrintAndLogEx(SUCCESS, _GREEN_(" Ranges are contiguous and non-overlapping"));
6280+
}
6281+
return PM3_SUCCESS;
6282+
}
6283+
6284+
if (aborted) {
6285+
uint64_t resume_millions = aborted_at / 1000000;
6286+
PrintAndLogEx(NORMAL, "");
6287+
PrintAndLogEx(WARNING, "aborted via keyboard!");
6288+
PrintAndLogEx(HINT, "Hint: resume with " _YELLOW_("--index %" PRIu64 " --threads %d"), resume_millions, thread_count);
6289+
return PM3_EOPABORTED;
6290+
}
6291+
6292+
if (found == false) {
6293+
PrintAndLogEx(WARNING, "Key not found in the given keyspace");
6294+
}
6295+
6296+
return found ? PM3_SUCCESS : PM3_ESOFT;
61766297
}
61776298

61786299
// CmdHFiClassLegBrute function with CLI and multithreading support
61796300
static int CmdHFiClassLegBrute(const char *Cmd) {
61806301
CLIParserContext *ctx;
61816302
CLIParserInit(&ctx, "hf iclass legbrute",
61826303
"This command takes sniffed trace data and a partial raw key and bruteforces the remaining 40 bits of the raw key.\n"
6183-
"Complete 40 bit keyspace is 1'099'511'627'776 and command is locked down to max 16 threads currently.\n"
6184-
"A possible worst case scenario on 16 threads estimates XXX days YYY hours MMM minutes.",
6304+
"Complete 40 bit keyspace is 1'099'511'627'776.",
61856305
"hf iclass legbrute --epurse feffffffffffffff --macs1 1306cad9b6c24466 --macs2 f0bf905e35f97923 --pk B4F12AADC5301225");
61866306

61876307
void *argtable[] = {
@@ -6191,7 +6311,8 @@ static int CmdHFiClassLegBrute(const char *Cmd) {
61916311
arg_str1(NULL, "macs2", "<hex>", "MACs captured from the reader, different than the first set (with the same csn and epurse value)"),
61926312
arg_str1(NULL, "pk", "<hex>", "Partial Key from legrec or starting key of keyblock from legbrute"),
61936313
arg_int0(NULL, "index", "<dec>", "Where to start from to retrieve the key, default 0 - value in millions e.g. 1 is 1 million"),
6194-
arg_int0(NULL, "threads", "<dec>", "Number of threads to use, by default it uses the cpu's max threads (max 16)."),
6314+
arg_int0(NULL, "threads", "<dec>", "Number of threads to use, by default it uses the cpu's max threads."),
6315+
arg_lit0(NULL, "dbg", "Print first 2 key candidates and midpoint per thread, then exit (use to verify thread partitioning)"),
61956316
arg_param_end
61966317
};
61976318
CLIExecWithReturn(ctx, Cmd, argtable, false);
@@ -6215,6 +6336,7 @@ static int CmdHFiClassLegBrute(const char *Cmd) {
62156336
uint64_t index = arg_get_int_def(ctx, 5, 0);
62166337
index *= 1000000;
62176338
int threads = arg_get_int_def(ctx, 6, num_CPUs());
6339+
bool debug = arg_get_lit(ctx, 7);
62186340
CLIParserFree(ctx);
62196341

62206342
if (epurse_len && epurse_len != PICOPASS_BLOCK_SIZE) {
@@ -6237,7 +6359,7 @@ static int CmdHFiClassLegBrute(const char *Cmd) {
62376359
return PM3_EINVARG;
62386360
}
62396361

6240-
return CmdHFiClassLegBrute_MT(epurse, macs, macs2, startingKey, index, threads);
6362+
return CmdHFiClassLegBrute_MT(epurse, macs, macs2, startingKey, index, threads, debug);
62416363
}
62426364

62436365
static void generate_single_key_block_inverted_opt(const uint8_t *startingKey, uint32_t index, uint8_t *keyBlock) {
@@ -7503,6 +7625,7 @@ static bool match_with_wildcard(const uint8_t *data, const uint8_t *pattern, con
75037625
return true;
75047626
}
75057627

7628+
75067629
static int CmdHFiClassSAM(const char *Cmd) {
75077630
CLIParserContext *ctx;
75087631
CLIParserInit(&ctx, "hf iclass sam",

0 commit comments

Comments
 (0)