diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b4ccee6..3d849c631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ * SubGHz: Add IL-100 Smart support for Add manually * SubGHz: Add **experimental counter overflow mode** (OFEX), replicates how some key duplicators work, DO NOT USE if you don't know what you are doing, it will reset your counter value! (accesible with debug on in radio settings - counter incr.) * SubGHz: **Return Honeywell Sec** with fixes and improvements (by htotoo & LiQuiDz & xMasterX) +* NFC: Keys found in key cache are now used in Nested attacks, deleting key cache is no longer required (by @noproto) +* NFC: MFKey 4.0, MIFARE Classic Static Encrypted Nested attacks run 10x faster (by @noproto) * NFC: **Add Saflok MFUL Parser Support** (by @aaronjamt) * NFC: **Add MFUL counters to Info page** (by @aaronjamt) * OFW: Fix Felica standard loading from nfc file diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index a142e47da..0e2c70942 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -113,6 +113,9 @@ typedef struct { uint16_t nested_target_key; uint16_t msb_count; bool enhanced_dict; + uint16_t current_key_idx; // Current key index for CUID dictionary mode + uint8_t* + cuid_key_indices_bitmap; // Bitmap of key indices present in CUID dictionary (256 bits = 32 bytes) } NfcMfClassicDictAttackContext; typedef struct { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 500dd759a..420f6a834 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -2,12 +2,22 @@ #include #include +#include -#define TAG "NfcMfClassicDictAttack" +#define TAG "NfcMfClassicDictAttack" +#define BIT(x, n) ((x) >> (n) & 1) // TODO FL-3926: Fix lag when leaving the dictionary attack view after Hardnested // TODO FL-3926: Re-enters backdoor detection between user and system dictionary if no backdoor is found +// KeysDict structure definition for inline CUID dictionary allocation +struct KeysDict { + Stream* stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; +}; + typedef enum { DictAttackStateCUIDDictInProgress, DictAttackStateUserDictInProgress, @@ -31,11 +41,22 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) instance->nfc_dict_context.is_card_present = false; view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); + bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress); + const MfClassicData* mfc_data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); - mfc_event->data->poller_mode.mode = (instance->nfc_dict_context.enhanced_dict) ? - MfClassicPollerModeDictAttackEnhanced : - MfClassicPollerModeDictAttackStandard; + + // Select mode based on dictionary type + if(is_cuid_dict) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackCUID; + } else if(instance->nfc_dict_context.enhanced_dict) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackEnhanced; + } else { + mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackStandard; + } + mfc_event->data->poller_mode.data = mfc_data; instance->nfc_dict_context.sectors_total = mf_classic_get_total_sectors_num(mfc_data->type); @@ -46,12 +67,57 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); + bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress); + MfClassicKey key = {}; - if(keys_dict_get_next_key( - instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) { + bool key_found = false; + + if(is_cuid_dict) { + // CUID dictionary: read 7 bytes (1 byte key_idx + 6 bytes key) and filter by exact key_idx + uint16_t target_key_idx = instance->nfc_dict_context.current_key_idx; + + // Check if this key index exists in the bitmap (only valid for 0-255) + if(target_key_idx < 256 && + BIT(instance->nfc_dict_context.cuid_key_indices_bitmap[target_key_idx / 8], + target_key_idx % 8)) { + uint8_t key_with_idx[sizeof(MfClassicKey) + 1]; + + while(keys_dict_get_next_key( + instance->nfc_dict_context.dict, key_with_idx, sizeof(MfClassicKey) + 1)) { + // Extract key_idx from first byte + uint8_t key_idx = key_with_idx[0]; + + instance->nfc_dict_context.dict_keys_current++; + + // Only use key if it matches the exact current key index + if(key_idx == (uint8_t)target_key_idx) { + // Copy the actual key (starts at byte 1) + memcpy(key.data, &key_with_idx[1], sizeof(MfClassicKey)); + key_found = true; + break; + } + } + } + } else { + // Standard dictionary: read 12 bytes + if(keys_dict_get_next_key( + instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) { + key_found = true; + instance->nfc_dict_context.dict_keys_current++; + } + } + + if(key_found) { mfc_event->data->key_request_data.key = key; + // In CUID mode, set key_type based on key_idx (odd = B, even = A) + if(is_cuid_dict) { + uint16_t target_key_idx = instance->nfc_dict_context.current_key_idx; + mfc_event->data->key_request_data.key_type = + (target_key_idx % 2 == 0) ? MfClassicKeyTypeA : MfClassicKeyTypeB; + } mfc_event->data->key_request_data.key_provided = true; - instance->nfc_dict_context.dict_keys_current++; if(instance->nfc_dict_context.dict_keys_current % 10 == 0) { view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); @@ -72,10 +138,27 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); + bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress); + keys_dict_rewind(instance->nfc_dict_context.dict); instance->nfc_dict_context.dict_keys_current = 0; - instance->nfc_dict_context.current_sector = - mfc_event->data->next_sector_data.current_sector; + + // In CUID mode, increment the key index and calculate sector from it + if(is_cuid_dict) { + instance->nfc_dict_context.current_key_idx++; + // Calculate sector from key_idx (each sector has 2 keys: A and B) + instance->nfc_dict_context.current_sector = + instance->nfc_dict_context.current_key_idx / 2; + // Write back to event data so poller can read it + mfc_event->data->next_sector_data.current_sector = + instance->nfc_dict_context.current_sector; + } else { + instance->nfc_dict_context.current_sector = + mfc_event->data->next_sector_data.current_sector; + } + view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) { @@ -153,18 +236,51 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { break; } - instance->nfc_dict_context.dict = keys_dict_alloc( - furi_string_get_cstr(cuid_dict_path), - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); + // Manually create KeysDict and scan once to count + populate bitmap + KeysDict* dict = malloc(sizeof(KeysDict)); + Storage* storage = furi_record_open(RECORD_STORAGE); + dict->stream = buffered_file_stream_alloc(storage); + dict->key_size = sizeof(MfClassicKey) + 1; + dict->key_size_symbols = dict->key_size * 2 + 1; + dict->total_keys = 0; - if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { - keys_dict_free(instance->nfc_dict_context.dict); + if(!buffered_file_stream_open( + dict->stream, + furi_string_get_cstr(cuid_dict_path), + FSAM_READ_WRITE, + FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(dict->stream); + free(dict); state = DictAttackStateUserDictInProgress; break; } + // Allocate and populate bitmap of key indices present in CUID dictionary + instance->nfc_dict_context.cuid_key_indices_bitmap = malloc(32); + memset(instance->nfc_dict_context.cuid_key_indices_bitmap, 0, 32); + + // Scan dictionary once to count keys and populate bitmap + uint8_t key_with_idx[dict->key_size]; + while(keys_dict_get_next_key(dict, key_with_idx, dict->key_size)) { + uint8_t key_idx = key_with_idx[0]; + // Set bit for this key index + instance->nfc_dict_context.cuid_key_indices_bitmap[key_idx / 8] |= + (1 << (key_idx % 8)); + dict->total_keys++; + } + keys_dict_rewind(dict); + + if(dict->total_keys == 0) { + keys_dict_free(dict); + free(instance->nfc_dict_context.cuid_key_indices_bitmap); + instance->nfc_dict_context.cuid_key_indices_bitmap = NULL; + state = DictAttackStateUserDictInProgress; + break; + } + + instance->nfc_dict_context.dict = dict; dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary"); + instance->nfc_dict_context.current_key_idx = 0; // Initialize key index for CUID mode } while(false); furi_string_free(cuid_dict_path); @@ -265,6 +381,10 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->nfc_dict_context.dict); + if(instance->nfc_dict_context.cuid_key_indices_bitmap) { + free(instance->nfc_dict_context.cuid_key_indices_bitmap); + instance->nfc_dict_context.cuid_key_indices_bitmap = NULL; + } scene_manager_set_scene_state( instance->scene_manager, NfcSceneMfClassicDictAttack, @@ -309,6 +429,10 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->nfc_dict_context.dict); + if(instance->nfc_dict_context.cuid_key_indices_bitmap) { + free(instance->nfc_dict_context.cuid_key_indices_bitmap); + instance->nfc_dict_context.cuid_key_indices_bitmap = NULL; + } scene_manager_set_scene_state( instance->scene_manager, NfcSceneMfClassicDictAttack, @@ -366,6 +490,12 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { keys_dict_free(instance->nfc_dict_context.dict); + // Free CUID bitmap if allocated + if(instance->nfc_dict_context.cuid_key_indices_bitmap) { + free(instance->nfc_dict_context.cuid_key_indices_bitmap); + instance->nfc_dict_context.cuid_key_indices_bitmap = NULL; + } + instance->nfc_dict_context.current_sector = 0; instance->nfc_dict_context.sectors_total = 0; instance->nfc_dict_context.sectors_read = 0; @@ -381,6 +511,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.nested_target_key = 0; instance->nfc_dict_context.msb_count = 0; instance->nfc_dict_context.enhanced_dict = false; + instance->nfc_dict_context.current_key_idx = 0; // Clean up temporary files used for nested dictionary attack if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam index 0d81c1050..5ece9e777 100644 --- a/applications/system/mfkey/application.fam +++ b/applications/system/mfkey/application.fam @@ -15,7 +15,7 @@ App( fap_icon_assets="images", fap_weburl="https://github.com/noproto/FlipperMfkey", fap_description="MIFARE Classic key recovery tool", - fap_version="3.1", + fap_version="4.0", ) App( diff --git a/applications/system/mfkey/init_plugin.c b/applications/system/mfkey/init_plugin.c index 8540a8f2d..af433cef4 100644 --- a/applications/system/mfkey/init_plugin.c +++ b/applications/system/mfkey/init_plugin.c @@ -251,10 +251,14 @@ bool load_nested_nonces( MfClassicNonce res = {0}; res.attack = static_encrypted; + int sector_num = 0; + char key_type = 'A'; int parsed = sscanf( line, - "Sec %*d key %*c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32 + "Sec %d key %c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32 " par0 %4[01] nt1 %" PRIx32 " ks1 %" PRIx32 " par1 %4[01]", + §or_num, + &key_type, &res.uid, &res.nt0, &res.ks1_1_enc, @@ -263,11 +267,14 @@ bool load_nested_nonces( &res.ks1_2_enc, res.par_2_str); - if(parsed >= 4) { // At least one nonce is present + // Calculate key_idx from sector and key type (for static encrypted: key_idx = sector * 2 + key_offset) + res.key_idx = (uint8_t)(sector_num * 2 + (key_type == 'B' ? 1 : 0)); + + if(parsed >= 6) { // At least one nonce is present (sector, key, uid, nt0, ks0, par0) res.par_1 = binaryStringToInt(res.par_1_str); res.uid_xor_nt0 = res.uid ^ res.nt0; - if(parsed == 7) { // Both nonces are present + if(parsed == 9) { // Both nonces are present res.attack = static_nested; res.par_2 = binaryStringToInt(res.par_2_str); res.uid_xor_nt1 = res.uid ^ res.nt1; diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index 9e3e71847..db700be32 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -67,8 +67,8 @@ static uint8_t MSB_LIMIT = 16; static inline void flush_key_buffer(ProgramState* program_state) { if(program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) { - // Pre-allocate exact size needed: 12 hex chars + 1 newline per key - size_t total_size = program_state->key_buffer_count * 13; + // Pre-allocate exact size needed: 2 hex chars (key_idx) + 12 hex chars (key) + 1 newline per key + size_t total_size = program_state->key_buffer_count * 15; //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); char* batch_buffer = malloc(total_size + 1); // +1 for null terminator @@ -77,6 +77,11 @@ static inline void flush_key_buffer(ProgramState* program_state) { const char hex_chars[] = "0123456789ABCDEF"; for(size_t i = 0; i < program_state->key_buffer_count; i++) { + // Write key_idx as 2 hex chars + uint8_t key_idx = program_state->key_idx_buffer[i]; + *ptr++ = hex_chars[key_idx >> 4]; + *ptr++ = hex_chars[key_idx & 0x0F]; + // Convert key to hex string directly into buffer for(size_t j = 0; j < sizeof(MfClassicKey); j++) { uint8_t byte = program_state->key_buffer[i].data[j]; @@ -144,6 +149,7 @@ static inline int // Use key buffer - buffer is guaranteed to be available for static_encrypted program_state->key_buffer[program_state->key_buffer_count] = n->key; + program_state->key_idx_buffer[program_state->key_buffer_count] = n->key_idx; program_state->key_buffer_count++; // Flush buffer when full @@ -659,17 +665,20 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_ // Allocate key buffer for static encrypted nonces if(n->attack == static_encrypted) { size_t available_ram = memmgr_heap_get_max_free_block(); - // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string - // Plus original 6 bytes in buffer = 19 bytes total per key + // Each key becomes 2 hex chars (key_idx) + 12 hex chars (key) + 1 newline = 15 bytes in the batch string + // Plus original 6 bytes (key) + 1 byte (key_idx) in buffer = 22 bytes total per key // Add extra safety margin for string overhead and other allocations const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; - const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation + const size_t bytes_per_key = + sizeof(MfClassicKey) + sizeof(uint8_t) + 15; // buffer + string representation if(available_ram > safety_threshold) { program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; program_state->key_buffer = malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); + program_state->key_idx_buffer = + malloc(program_state->key_buffer_size * sizeof(uint8_t)); program_state->key_buffer_count = 0; - if(!program_state->key_buffer) { + if(!program_state->key_buffer || !program_state->key_idx_buffer) { // Free the allocated blocks before returning for(int i = 0; i < num_blocks; i++) { free(block_pointers[i]); @@ -691,6 +700,7 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_ } } else { program_state->key_buffer = NULL; + program_state->key_idx_buffer = NULL; program_state->key_buffer_size = 0; program_state->key_buffer_count = 0; } @@ -736,7 +746,9 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_ if(n->attack == static_encrypted && program_state->key_buffer) { flush_key_buffer(program_state); free(program_state->key_buffer); + free(program_state->key_idx_buffer); program_state->key_buffer = NULL; + program_state->key_idx_buffer = NULL; program_state->key_buffer_size = 0; program_state->key_buffer_count = 0; } diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 43e558874..0e5ff01dd 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -54,6 +54,7 @@ typedef struct { FuriThread* mfkeythread; KeysDict* cuid_dict; MfClassicKey* key_buffer; + uint8_t* key_idx_buffer; size_t key_buffer_size; size_t key_buffer_count; } ProgramState; @@ -72,6 +73,7 @@ typedef struct { uint32_t nt1; // tag challenge second uint32_t uid_xor_nt0; // uid ^ nt0 uint32_t uid_xor_nt1; // uid ^ nt1 + uint8_t key_idx; // key index (for static encrypted nonces) union { // Mfkey32 struct { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index b2d9b114a..ae2f5467f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -163,11 +163,14 @@ NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { instance->mfc_event.type = MfClassicPollerEventTypeRequestMode; command = instance->callback(instance->general_event, instance->context); - if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackStandard) { + if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackStandard || + instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackCUID) { mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); + instance->mode_ctx.dict_attack_ctx.mode = instance->mfc_event_data.poller_mode.mode; instance->state = MfClassicPollerStateRequestKey; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackEnhanced) { mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); + instance->mode_ctx.dict_attack_ctx.mode = instance->mfc_event_data.poller_mode.mode; instance->state = MfClassicPollerStateAnalyzeBackdoor; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) { instance->state = MfClassicPollerStateRequestReadSector; @@ -590,7 +593,22 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) (error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) { FURI_LOG_D(TAG, "No backdoor identified"); dict_attack_ctx->backdoor = MfClassicBackdoorNone; - instance->state = MfClassicPollerStateRequestKey; + + // Check if any keys were cached - if so, go directly to nested attack + bool has_cached_keys = false; + for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { + if(mf_classic_is_key_found(instance->data, sector, MfClassicKeyTypeA) || + mf_classic_is_key_found(instance->data, sector, MfClassicKeyTypeB)) { + has_cached_keys = true; + break; + } + } + + if(has_cached_keys) { + instance->state = MfClassicPollerStateNestedController; + } else { + instance->state = MfClassicPollerStateRequestKey; + } } else if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Backdoor identified: v%d", backdoor_version); dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type; @@ -687,7 +705,15 @@ NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) { command = instance->callback(instance->general_event, instance->context); if(instance->mfc_event_data.key_request_data.key_provided) { dict_attack_ctx->current_key = instance->mfc_event_data.key_request_data.key; - instance->state = MfClassicPollerStateAuthKeyA; + dict_attack_ctx->requested_key_type = instance->mfc_event_data.key_request_data.key_type; + + // In CUID mode, go directly to the appropriate Auth state based on key_type + if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID && + dict_attack_ctx->requested_key_type == MfClassicKeyTypeB) { + instance->state = MfClassicPollerStateAuthKeyB; + } else { + instance->state = MfClassicPollerStateAuthKeyA; + } } else { instance->state = MfClassicPollerStateNextSector; } @@ -701,7 +727,12 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { if(mf_classic_is_key_found( instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) { - instance->state = MfClassicPollerStateAuthKeyB; + // In CUID mode, skip directly to RequestKey since we test keys by specific type + if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID) { + instance->state = MfClassicPollerStateRequestKey; + } else { + instance->state = MfClassicPollerStateAuthKeyB; + } } else { uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector); uint64_t key = @@ -722,7 +753,12 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { instance->state = MfClassicPollerStateReadSector; } else { mf_classic_poller_halt(instance); - instance->state = MfClassicPollerStateAuthKeyB; + // In CUID mode, skip directly to RequestKey since we test keys by specific type + if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID) { + instance->state = MfClassicPollerStateRequestKey; + } else { + instance->state = MfClassicPollerStateAuthKeyB; + } } } @@ -735,8 +771,11 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { if(mf_classic_is_key_found( instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB)) { - if(mf_classic_is_key_found( - instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) { + // In CUID mode, just request next key since we iterate by key_idx + if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID) { + instance->state = MfClassicPollerStateRequestKey; + } else if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) { instance->state = MfClassicPollerStateNextSector; } else { instance->state = MfClassicPollerStateRequestKey; @@ -774,12 +813,20 @@ NfcCommand mf_classic_poller_handler_next_sector(MfClassicPoller* instance) { MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; dict_attack_ctx->current_sector++; + if(dict_attack_ctx->current_sector == instance->sectors_total) { instance->state = MfClassicPollerStateSuccess; } else { instance->mfc_event.type = MfClassicPollerEventTypeNextSector; instance->mfc_event_data.next_sector_data.current_sector = dict_attack_ctx->current_sector; command = instance->callback(instance->general_event, instance->context); + + // In CUID mode, NFC app manages sector based on key_idx - read it back + if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID) { + dict_attack_ctx->current_sector = + instance->mfc_event_data.next_sector_data.current_sector; + } + instance->state = MfClassicPollerStateRequestKey; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 8efb931aa..5c853e21a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -45,6 +45,7 @@ typedef enum { MfClassicPollerModeRead, /**< Poller reading mode. */ MfClassicPollerModeWrite, /**< Poller writing mode. */ MfClassicPollerModeDictAttackStandard, /**< Poller dictionary attack mode. */ + MfClassicPollerModeDictAttackCUID, /**< Poller CUID dictionary attack mode. */ MfClassicPollerModeDictAttackEnhanced, /**< Poller enhanced dictionary attack mode. */ } MfClassicPollerMode; @@ -129,6 +130,7 @@ typedef struct { */ typedef struct { MfClassicKey key; /**< Key to be used by poller. */ + MfClassicKeyType key_type; /**< Key type (A or B) for CUID dict attack mode. */ bool key_provided; /**< Flag indicating if key is provided. */ } MfClassicPollerEventDataKeyRequest; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 915c899c3..607b126a0 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -128,10 +128,12 @@ typedef struct { uint8_t current_sector; MfClassicKey current_key; MfClassicKeyType current_key_type; + MfClassicKeyType requested_key_type; // Key type requested from app (for CUID mode) bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; MfClassicBackdoor backdoor; + MfClassicPollerMode mode; // Current attack mode // Enhanced dictionary attack and nested nonce collection bool enhanced_dict; MfClassicNestedPhase nested_phase;