@@ -6063,13 +6063,18 @@ void generate_key_block_inverted(const uint8_t *startingKey, uint64_t index, uin
60636063typedef 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
61796300static 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
62436365static 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+
75067629static int CmdHFiClassSAM (const char * Cmd ) {
75077630 CLIParserContext * ctx ;
75087631 CLIParserInit (& ctx , "hf iclass sam" ,
0 commit comments