diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 2fae91694..c0cc3802b 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -47,6 +47,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { furi_string_set(m->header, "Nested Dictionary"); break; case MfClassicNestedPhaseCalibrate: + case MfClassicNestedPhaseRecalibrate: furi_string_set(m->header, "Calibration"); break; case MfClassicNestedPhaseCollectNtEnc: diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index c2a7b1e68..d28188fbc 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -15,6 +15,7 @@ typedef enum { MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseRecalibrate, MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseFinished, } MfClassicNestedPhase; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 2b122679e..aac9f228b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,10 +6,10 @@ #define TAG "MfClassicPoller" -// TODO: Reflect status in NFC app +// TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches -// TODO: Test not resetting the tag during Hardnested (single initial auth, repeated failed nested) // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary +// TODO: Fix rare nested_target_key 64 bug #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -65,6 +65,26 @@ void mf_classic_poller_free(MfClassicPoller* instance) { bit_buffer_free(instance->tx_encrypted_buffer); bit_buffer_free(instance->rx_encrypted_buffer); + // Clean up resources in MfClassicPollerDictAttackContext + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + // Free the dictionaries + if(dict_attack_ctx->mf_classic_system_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + dict_attack_ctx->mf_classic_system_dict = NULL; + } + if(dict_attack_ctx->mf_classic_user_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + dict_attack_ctx->mf_classic_user_dict = NULL; + } + + // Free the nested nonce array if it exists + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + free(instance); } @@ -560,7 +580,8 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) // Attempt backdoor authentication MfClassicError error = mf_classic_poller_auth( instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); - if((next_key_index == 0) && (error == MfClassicErrorProtocol)) { + if((next_key_index == 0) && + (error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) { FURI_LOG_E(TAG, "No backdoor identified"); dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; @@ -837,9 +858,10 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; command = instance->callback(instance->general_event, instance->context); // Nested entrypoint - // TODO: Ensure nested attack isn't run if tag is fully read - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || - dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished) { + bool nested_active = dict_attack_ctx->nested_phase != MfClassicNestedPhaseNone; + if((nested_active && + (dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) || + (!(nested_active) && !(mf_classic_is_card_read(instance->data)))) { instance->state = MfClassicPollerStateNestedController; break; } @@ -1130,11 +1152,13 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) command = NfcCommandReset; uint8_t target_block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); + MfClassicKeyType target_key_type = + ((dict_attack_ctx->nested_target_key % 4) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; error = mf_classic_poller_auth_nested( instance, target_block, &dict_attack_ctx->nested_known_key, - dict_attack_ctx->nested_known_key_type, + target_key_type, &auth_ctx, use_backdoor, false); @@ -1209,7 +1233,6 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc( cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); - // TODO: Make sure we're not off-by-one here for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); if(nth_successor != decrypted_nt_enc) { @@ -1265,7 +1288,7 @@ static inline bool is_byte_found(uint8_t* found, uint8_t byte) { NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) // TODO: Look into using MfClassicNt more - NfcCommand command = NfcCommandReset; + NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { @@ -1283,11 +1306,12 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst (is_weak && !(dict_attack_ctx->static_encrypted)) ? ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : 1; - MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? Match calibrated? - uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; + uint8_t target_sector = dict_attack_ctx->nested_target_key / (is_weak ? 4 : 2); + MfClassicKeyType target_key_type = + (dict_attack_ctx->nested_target_key % (is_weak ? 4 : 2) < (is_weak ? 2 : 1)) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = mf_classic_get_sector_trailer_num_by_sector(target_sector); uint32_t nt_enc_temp_arr[nt_enc_per_collection]; uint8_t nt_enc_collected = 0; uint8_t parity = 0; @@ -1488,9 +1512,9 @@ static MfClassicKey* search_dicts_for_nonce_key( } NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { - // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) + // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) // TODO: Look into using MfClassicNt more - NfcCommand command = NfcCommandReset; + NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { @@ -1510,9 +1534,8 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? MfClassicKeyTypeA : MfClassicKeyTypeB; - // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? - uint8_t target_block = (is_weak) ? (4 * (dict_attack_ctx->nested_target_key / 2)) + 3 : - (4 * (dict_attack_ctx->nested_target_key / 16)) + 3; + uint8_t target_sector = dict_attack_ctx->nested_target_key / (is_weak ? 2 : 16); + uint8_t target_block = mf_classic_get_sector_trailer_num_by_sector(target_sector); uint8_t parity = 0; if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 0)) || @@ -1648,11 +1671,16 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { bool params_write_success = true; for(size_t i = 0; i < nonce_pair_count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; + // TODO: Avoid repeating logic here + uint8_t nonce_sector = nonce->key_idx / (weak_prng ? 4 : 2); + MfClassicKeyType nonce_key_type = + (nonce->key_idx % (weak_prng ? 4 : 2) < (weak_prng ? 2 : 1)) ? MfClassicKeyTypeA : + MfClassicKeyTypeB; furi_string_printf( temp_str, "Sec %d key %c cuid %08lx", - (nonce->key_idx / 4), - ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', + nonce_sector, + (nonce_key_type == MfClassicKeyTypeA) ? 'A' : 'B', nonce->cuid); for(uint8_t nt_idx = 0; nt_idx < ((weak_prng && (!(static_encrypted))) ? 2 : 1); nt_idx++) { @@ -1742,8 +1770,7 @@ bool is_valid_sum(uint16_t sum) { } NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { - // Iterate through keys - //NfcCommand command = NfcCommandContinue; + // This function guides the nested attack through its phases, and iterates over the target keys NfcCommand command = mf_classic_poller_handle_data_update(instance); MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool initial_dict_attack_iter = false; @@ -1773,6 +1800,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance initial_dict_attack_iter = true; } } + // Identify PRNG type if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { instance->state = MfClassicPollerStateNestedCollectNt; @@ -1860,9 +1888,11 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + dict_attack_ctx->mf_classic_system_dict = NULL; } if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + dict_attack_ctx->mf_classic_user_dict = NULL; } dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { @@ -1872,6 +1902,10 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateSuccess; return command; } + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { + // Skip initial calibration for static encrypted backdoored tags + dict_attack_ctx->calibrated = true; + } dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; instance->state = MfClassicPollerStateNestedController; return command; @@ -1895,24 +1929,23 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Calibration bool initial_collect_nt_enc_iter = false; + bool recalibrated = false; if(!(dict_attack_ctx->calibrated)) { if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedCalibrate; return command; } initial_collect_nt_enc_iter = true; - dict_attack_ctx->auth_passed = true; dict_attack_ctx->calibrated = true; - dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { initial_collect_nt_enc_iter = true; - dict_attack_ctx->auth_passed = true; - dict_attack_ctx->current_key_checked = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseRecalibrate) { + recalibrated = true; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces - // TODO: Calibrates too frequently for static encrypted backdoored tags if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && @@ -1946,10 +1979,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); return command; } - if(!(dict_attack_ctx->auth_passed)) { + if(initial_collect_nt_enc_iter) { + dict_attack_ctx->current_key_checked = false; + } + if(!(dict_attack_ctx->auth_passed) && !(initial_collect_nt_enc_iter)) { dict_attack_ctx->attempt_count++; } else { - if(is_weak && !(initial_collect_nt_enc_iter)) { + if(is_weak && !(initial_collect_nt_enc_iter) && !(recalibrated)) { if(!(dict_attack_ctx->static_encrypted)) { dict_attack_ctx->nested_target_key++; } else { @@ -1958,28 +1994,36 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; } - if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && - (dict_attack_ctx->nested_target_key % 4 == 0) && - (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { - dict_attack_ctx->calibrated = false; - } } dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; if(!(dict_attack_ctx->current_key_checked)) { + dict_attack_ctx->current_key_checked = true; + // Check if the nested target key is a known key if(mf_classic_nested_is_target_key_found(instance, false)) { // Continue to next key - if(!(is_weak)) { + if(!(dict_attack_ctx->static_encrypted)) { dict_attack_ctx->nested_target_key++; dict_attack_ctx->current_key_checked = false; } instance->state = MfClassicPollerStateNestedController; return command; } - dict_attack_ctx->current_key_checked = true; + + // If it is not a known key, we'll need to calibrate for static encrypted backdoored tags + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && + (dict_attack_ctx->nested_target_key < nonce_collect_key_max) && + !(recalibrated)) { + dict_attack_ctx->calibrated = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate; + instance->state = MfClassicPollerStateNestedController; + return command; + } } + + // If we have tried to collect this nonce too many times, skip if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) || (!(is_weak) && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) { @@ -2001,6 +2045,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->attempt_count = 0; } + + // Collect a nonce dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; 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 111ddf260..71c972c1a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -54,6 +54,7 @@ typedef enum { MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseRecalibrate, MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseFinished, } MfClassicNestedPhase;