diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 0e9a34bc8..7951bfc1b 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -101,6 +101,8 @@ typedef struct { uint8_t nested_phase; uint8_t prng_type; uint8_t backdoor; + uint16_t nested_target_key; + uint16_t msb_count; } NfcMfClassicDictAttackContext; struct NfcApp { 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 39bf4cef1..1104cafa7 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 @@ -5,7 +5,8 @@ #define TAG "NfcMfClassicDictAttack" -// TODO: Update progress bar with nested attacks +// TODO: Fix lag when leaving the dictionary attack view after Hardnested +// TODO: Re-enters backdoor detection between user and system dictionary if no backdoor is found typedef enum { DictAttackStateUserDictInProgress, @@ -63,6 +64,8 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) instance->nfc_dict_context.nested_phase = data_update->nested_phase; instance->nfc_dict_context.prng_type = data_update->prng_type; instance->nfc_dict_context.backdoor = data_update->backdoor; + instance->nfc_dict_context.nested_target_key = data_update->nested_target_key; + instance->nfc_dict_context.msb_count = data_update->msb_count; view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { @@ -125,6 +128,8 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { dict_attack_set_nested_phase(instance->dict_attack, mfc_dict->nested_phase); dict_attack_set_prng_type(instance->dict_attack, mfc_dict->prng_type); dict_attack_set_backdoor(instance->dict_attack, mfc_dict->backdoor); + dict_attack_set_nested_target_key(instance->dict_attack, mfc_dict->nested_target_key); + dict_attack_set_msb_count(instance->dict_attack, mfc_dict->msb_count); } } @@ -214,7 +219,9 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventDictAttackComplete) { - if(state == DictAttackStateUserDictInProgress) { + bool ran_nested_dict = instance->nfc_dict_context.nested_phase != + MfClassicNestedPhaseNone; + if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->nfc_dict_context.dict); @@ -243,7 +250,9 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent } else if(event.event == NfcCustomEventDictAttackSkip) { const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); - if(state == DictAttackStateUserDictInProgress) { + bool ran_nested_dict = instance->nfc_dict_context.nested_phase != + MfClassicNestedPhaseNone; + if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { if(instance->nfc_dict_context.is_card_present) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); @@ -261,7 +270,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent dolphin_deed(DolphinDeedNfcReadSuccess); } consumed = true; - } else if(state == DictAttackStateSystemDictInProgress) { + } else { nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); @@ -299,6 +308,8 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.nested_phase = MfClassicNestedPhaseNone; instance->nfc_dict_context.prng_type = MfClassicPrngTypeUnknown; instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown; + instance->nfc_dict_context.nested_target_key = 0; + instance->nfc_dict_context.msb_count = 0; nfc_blink_stop(instance); } diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index c0cc3802b..5138dd912 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -24,6 +24,8 @@ typedef struct { MfClassicNestedPhase nested_phase; MfClassicPrngType prng_type; MfClassicBackdoor backdoor; + uint16_t nested_target_key; + uint16_t msb_count; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { @@ -71,7 +73,12 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { canvas_draw_str_aligned( canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); - if(m->is_key_attack) { + if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + uint8_t nonce_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); + snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + } else if(m->is_key_attack) { snprintf( draw_str, sizeof(draw_str), @@ -81,21 +88,47 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); } canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - float dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); - float progress = m->sectors_total == 0 ? 0 : - ((float)(m->current_sector) + dict_progress) / - (float)(m->sectors_total); - if(progress > 1.0f) { - progress = 1.0f; - } - if(m->dict_keys_current == 0) { - // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + float dict_progress = 0; + if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || + m->nested_phase == MfClassicNestedPhaseDictAttack || + m->nested_phase == MfClassicNestedPhaseDictAttackResume) { + // Phase: Nested dictionary attack + uint8_t target_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else if( + m->nested_phase == MfClassicNestedPhaseCalibrate || + m->nested_phase == MfClassicNestedPhaseRecalibrate || + m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + // Phase: Nonce collection + if(m->prng_type == MfClassicPrngTypeWeak) { + uint8_t target_sector = m->nested_target_key / 4; + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else { + uint16_t max_msb = UINT8_MAX + 1; + dict_progress = (float)(m->msb_count) / (float)(max_msb); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); + } } else { - snprintf( - draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, + sizeof(draw_str), + "%zu/%zu", + m->dict_keys_current, + m->dict_keys_total); + } + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; } elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); canvas_set_font(canvas, FontSecondary); @@ -170,6 +203,8 @@ void dict_attack_reset(DictAttack* instance) { model->nested_phase = MfClassicNestedPhaseNone; model->prng_type = MfClassicPrngTypeUnknown; model->backdoor = MfClassicBackdoorUnknown; + model->nested_target_key = 0; + model->msb_count = 0; furi_string_reset(model->header); }, false); @@ -301,3 +336,20 @@ void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor) { with_view_model( instance->view, DictAttackViewModel * model, { model->backdoor = backdoor; }, true); } + +void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t nested_target_key) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->nested_target_key = nested_target_key; }, + true); +} + +void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index d28188fbc..8dc9b9708 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -77,6 +77,10 @@ void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type); void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor); +void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key); + +void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index e59657a40..0f2b48e4e 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -82,7 +82,7 @@ uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { return out; } -uint32_t prng_successor(uint32_t x, uint32_t n) { +uint32_t crypto1_prng_successor(uint32_t x, uint32_t n) { SWAPENDIAN(x); while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; @@ -169,9 +169,9 @@ void crypto1_encrypt_reader_nonce( nr[i] = byte; } - nt_num = prng_successor(nt_num, 32); + nt_num = crypto1_prng_successor(nt_num, 32); for(size_t i = 4; i < 8; i++) { - nt_num = prng_successor(nt_num, 8); + nt_num = crypto1_prng_successor(nt_num, 8); uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num); bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01); bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); @@ -198,7 +198,7 @@ static uint8_t lfsr_rollback_bit(Crypto1* crypto1, uint32_t in, int fb) { return ret; } -uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { +uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { uint32_t ret = 0; for(int i = 31; i >= 0; i--) { ret |= lfsr_rollback_bit(crypto1, BEBIT(in, i), fb) << (24 ^ i); @@ -206,7 +206,7 @@ uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { return ret; } -bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { +bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { return (nfc_util_even_parity8((nt >> 24) & 0xFF) == (((nt_par_enc >> 3) & 1) ^ FURI_BIT(ks, 16))) && (nfc_util_even_parity8((nt >> 16) & 0xFF) == @@ -215,7 +215,7 @@ bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_pa (((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0))); } -bool is_weak_prng_nonce(uint32_t nonce) { +bool crypto1_is_weak_prng_nonce(uint32_t nonce) { if(nonce == 0) return false; uint16_t x = nonce >> 16; x = (x & 0xff) << 8 | x >> 8; @@ -226,11 +226,12 @@ bool is_weak_prng_nonce(uint32_t nonce) { return x == (nonce & 0xFFFF); } -uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { +uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { uint64_t known_key_int = bit_lib_bytes_to_num_be(known_key.data, 6); Crypto1 crypto_temp; crypto1_init(&crypto_temp, known_key_int); crypto1_word(&crypto_temp, nt_enc ^ cuid, 1); - uint32_t decrypted_nt_enc = (nt_enc ^ lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); + uint32_t decrypted_nt_enc = + (nt_enc ^ crypto1_lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); return decrypted_nt_enc; } diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index 26862ab49..0e358581a 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -1,6 +1,6 @@ #pragma once -#include "protocols/mf_classic/mf_classic.h" +#include #include #ifdef __cplusplus @@ -39,15 +39,15 @@ void crypto1_encrypt_reader_nonce( BitBuffer* out, bool is_nested); -uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); +uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); -bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); +bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); -bool is_weak_prng_nonce(uint32_t nonce); +bool crypto1_is_weak_prng_nonce(uint32_t nonce); -uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); +uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); -uint32_t prng_successor(uint32_t x, uint32_t n); +uint32_t crypto1_prng_successor(uint32_t x, uint32_t n); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index 7e4f4725b..ef571117a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -157,14 +157,17 @@ static MfClassicListenerCommand uint32_t nt_num = bit_lib_bytes_to_num_be(instance->auth_context.nt.data, sizeof(MfClassicNt)); uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); - if(secret_poller != prng_successor(nt_num, 64)) { + if(secret_poller != crypto1_prng_successor(nt_num, 64)) { FURI_LOG_T( - TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64)); + TAG, + "Wrong reader key: %08lX != %08lX", + secret_poller, + crypto1_prng_successor(nt_num, 64)); command = MfClassicListenerCommandSleep; break; } - uint32_t at_num = prng_successor(nt_num, 96); + uint32_t at_num = crypto1_prng_successor(nt_num, 96); bit_lib_num_to_bytes_be(at_num, sizeof(uint32_t), instance->auth_context.at.data); bit_buffer_copy_bytes( instance->tx_plain_buffer, instance->auth_context.at.data, sizeof(MfClassicAr)); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index aac9f228b..be08b0698 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 (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary // TODO: Fix rare nested_target_key 64 bug +// TODO: Dead code for malloc returning NULL? #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -97,6 +97,8 @@ static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance data_update->nested_phase = instance->mode_ctx.dict_attack_ctx.nested_phase; data_update->prng_type = instance->mode_ctx.dict_attack_ctx.prng_type; data_update->backdoor = instance->mode_ctx.dict_attack_ctx.backdoor; + data_update->nested_target_key = instance->mode_ctx.dict_attack_ctx.nested_target_key; + data_update->msb_count = instance->mode_ctx.dict_attack_ctx.msb_count; instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate; return instance->callback(instance->general_event, instance->context); } @@ -1059,7 +1061,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan for(uint8_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; - if(!is_weak_prng_nonce(nonce->nt)) hard_nt_count++; + if(!crypto1_is_weak_prng_nonce(nonce->nt)) hard_nt_count++; } if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) { @@ -1108,10 +1110,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance } NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { - // TODO: Discard outliers (e.g. greater than 3 standard deviations) NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; + uint16_t distances[MF_CLASSIC_NESTED_CALIBRATION_COUNT - 1] = {0}; dict_attack_ctx->d_min = UINT16_MAX; dict_attack_ctx->d_max = 0; @@ -1172,7 +1174,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); // Store the decrypted static encrypted nonce dict_attack_ctx->static_encrypted_nonce = - decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); + crypto1_decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); dict_attack_ctx->calibrated = true; @@ -1228,25 +1230,22 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); - for(uint32_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; + uint8_t valid_distances = 0; + for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { bool found = false; - uint32_t decrypted_nt_enc = decrypt_nt_enc( + uint32_t decrypted_nt_enc = crypto1_decrypt_nt_enc( cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); for(int i = 0; i < 65535; i++) { - uint32_t nth_successor = prng_successor(nt_prev, i); - if(nth_successor != decrypted_nt_enc) { - continue; - } - if(collection_cycle > 0) { + uint32_t nth_successor = crypto1_prng_successor(nt_prev, i); + if(nth_successor == decrypted_nt_enc) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); - if(i < dict_attack_ctx->d_min) dict_attack_ctx->d_min = i; - if(i > dict_attack_ctx->d_max) dict_attack_ctx->d_max = i; + distances[valid_distances++] = i; + nt_prev = nth_successor; + found = true; + break; } - nt_prev = nth_successor; - found = true; - break; } if(!found) { FURI_LOG_E( @@ -1258,21 +1257,62 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } } - // Some breathing room, doesn't account for overflows or static nested (FIXME) - dict_attack_ctx->d_min -= 3; - dict_attack_ctx->d_max += 3; + // Calculate median and standard deviation + if(valid_distances > 0) { + // Sort the distances array (bubble sort) + for(uint8_t i = 0; i < valid_distances - 1; i++) { + for(uint8_t j = 0; j < valid_distances - i - 1; j++) { + if(distances[j] > distances[j + 1]) { + uint16_t temp = distances[j]; + distances[j] = distances[j + 1]; + distances[j + 1] = temp; + } + } + } + + // Calculate median + uint16_t median = + (valid_distances % 2 == 0) ? + (distances[valid_distances / 2 - 1] + distances[valid_distances / 2]) / 2 : + distances[valid_distances / 2]; + + // Calculate standard deviation + float sum = 0, sum_sq = 0; + for(uint8_t i = 0; i < valid_distances; i++) { + sum += distances[i]; + sum_sq += (float)distances[i] * distances[i]; + } + float mean = sum / valid_distances; + float variance = (sum_sq / valid_distances) - (mean * mean); + float std_dev = sqrtf(variance); + + // Filter out values over 3 standard deviations away from the median + dict_attack_ctx->d_min = UINT16_MAX; + dict_attack_ctx->d_max = 0; + for(uint8_t i = 0; i < valid_distances; i++) { + if(fabsf((float)distances[i] - median) <= 3 * std_dev) { + if(distances[i] < dict_attack_ctx->d_min) dict_attack_ctx->d_min = distances[i]; + if(distances[i] > dict_attack_ctx->d_max) dict_attack_ctx->d_max = distances[i]; + } + } + + // Some breathing room + dict_attack_ctx->d_min = (dict_attack_ctx->d_min > 3) ? dict_attack_ctx->d_min - 3 : 0; + dict_attack_ctx->d_max += 3; + } furi_assert(dict_attack_ctx->d_min <= dict_attack_ctx->d_max); dict_attack_ctx->calibrated = true; instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); + uint16_t d_dist = dict_attack_ctx->d_max - dict_attack_ctx->d_min; FURI_LOG_E( TAG, "Calibration completed: min=%u max=%u static=%s", dict_attack_ctx->d_min, dict_attack_ctx->d_max, - (dict_attack_ctx->d_min == dict_attack_ctx->d_max) ? "true" : "false"); + ((d_dist >= 3) && (d_dist <= 6)) ? "true" : "false"); return command; } @@ -1381,17 +1421,25 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst uint32_t nt_prev = 0, decrypted_nt_prev = 0, found_nt = 0; uint16_t dist = 0; if(is_weak && !(dict_attack_ctx->static_encrypted)) { + // Ensure this isn't the same nonce as the previous collection + if((dict_attack_ctx->nested_nonce.count == 1) && + (dict_attack_ctx->nested_nonce.nonces[0].nt_enc == nt_enc)) { + FURI_LOG_E(TAG, "Duplicate nonce, dismissing collection attempt"); + break; + } + // Decrypt the previous nonce nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); + decrypted_nt_prev = + crypto1_decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); // Find matching nt_enc plain at expected distance found_nt = 0; uint8_t found_nt_cnt = 0; uint16_t current_dist = dict_attack_ctx->d_min; while(current_dist <= dict_attack_ctx->d_max) { - uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); - if(nonce_matches_encrypted_parity_bits( + uint32_t nth_successor = crypto1_prng_successor(decrypted_nt_prev, current_dist); + if(crypto1_nonce_matches_encrypted_parity_bits( nth_successor, nth_successor ^ nt_enc, parity)) { found_nt_cnt++; if(found_nt_cnt > 1) { @@ -1415,6 +1463,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Hardnested if(!is_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF)) { set_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF); + dict_attack_ctx->msb_count++; // Add unique parity to sum dict_attack_ctx->msb_par_sum += nfc_util_even_parity32(parity & 0x08); } @@ -1487,13 +1536,13 @@ static MfClassicKey* search_dicts_for_nonce_key( bool full_match = true; for(uint8_t j = 0; j < nonce_array->count; j++) { // Verify nonce matches encrypted parity bits for all nonces - uint32_t nt_enc_plain = decrypt_nt_enc( + uint32_t nt_enc_plain = crypto1_decrypt_nt_enc( nonce_array->nonces[j].cuid, nonce_array->nonces[j].nt_enc, stack_key); if(is_weak) { - full_match &= is_weak_prng_nonce(nt_enc_plain); + full_match &= crypto1_is_weak_prng_nonce(nt_enc_plain); if(!full_match) break; } - full_match &= nonce_matches_encrypted_parity_bits( + full_match &= crypto1_nonce_matches_encrypted_parity_bits( nt_enc_plain, nt_enc_plain ^ nonce_array->nonces[j].nt_enc, nonce_array->nonces[j].par); @@ -1751,15 +1800,6 @@ bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_di return mf_classic_is_key_found(instance->data, target_sector, target_key_type); } -bool found_all_nt_enc_msb(const MfClassicPollerDictAttackContext* dict_attack_ctx) { - for(int i = 0; i < 32; i++) { - if(dict_attack_ctx->nt_enc_msb[i] != 0xFF) { - return false; - } - } - return true; -} - bool is_valid_sum(uint16_t sum) { for(size_t i = 0; i < 19; i++) { if(sum == valid_sums[i]) { @@ -1964,7 +2004,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Target all remaining sectors, key A and B if(dict_attack_ctx->nested_target_key < nonce_collect_key_max) { - if((!(is_weak)) && found_all_nt_enc_msb(dict_attack_ctx)) { + if((!(is_weak)) && (dict_attack_ctx->msb_count == (UINT8_MAX + 1))) { if(is_valid_sum(dict_attack_ctx->msb_par_sum)) { // All Hardnested nonces collected dict_attack_ctx->nested_target_key++; @@ -1975,6 +2015,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count++; instance->state = MfClassicPollerStateNestedCollectNtEnc; } + dict_attack_ctx->msb_count = 0; dict_attack_ctx->msb_par_sum = 0; memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); return command; @@ -2038,6 +2079,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_target_key += 2; dict_attack_ctx->current_key_checked = false; } else { + dict_attack_ctx->msb_count = 0; dict_attack_ctx->msb_par_sum = 0; memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); dict_attack_ctx->nested_target_key++; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 0501de21e..818d19d0a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -80,6 +80,9 @@ typedef struct { uint8_t nested_phase; /**< Nested attack phase. */ uint8_t prng_type; /**< PRNG (weak or hard). */ uint8_t backdoor; /**< Backdoor type. */ + uint16_t nested_target_key; /**< Target key for nested attack. */ + uint16_t + msb_count; /**< Number of unique most significant bytes seen during Hardnested attack. */ } MfClassicPollerEventDataUpdate; /** 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 71c972c1a..f0175b25b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,12 +3,12 @@ #include "mf_classic_poller.h" #include #include -#include "nfc/helpers/iso14443_crc.h" +#include #include #include #include #include "keys_dict.h" -#include "helpers/nfc_util.h" +#include #ifdef __cplusplus extern "C" { @@ -179,6 +179,7 @@ typedef struct { uint8_t nt_enc_msb [32]; // Bit-packed array to track which unique most significant bytes have been seen (256 bits = 32 bytes) uint16_t msb_par_sum; // Sum of parity bits for each unique most significant byte + uint16_t msb_count; // Number of unique most significant bytes seen } MfClassicPollerDictAttackContext; typedef struct { diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 8a3476e94..a48fa06e9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -908,10 +908,15 @@ Function,+,crypto1_alloc,Crypto1*, Function,+,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" Function,+,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" Function,+,crypto1_decrypt,void,"Crypto1*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,crypto1_encrypt,void,"Crypto1*, uint8_t*, const BitBuffer*, BitBuffer*" Function,+,crypto1_encrypt_reader_nonce,void,"Crypto1*, uint64_t, uint32_t, uint8_t*, uint8_t*, BitBuffer*, _Bool" Function,+,crypto1_free,void,Crypto1* Function,+,crypto1_init,void,"Crypto1*, uint64_t" +Function,+,crypto1_is_weak_prng_nonce,_Bool,uint32_t +Function,+,crypto1_lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" +Function,+,crypto1_nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" +Function,+,crypto1_prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,crypto1_reset,void,Crypto1* Function,+,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* @@ -922,7 +927,6 @@ Function,+,datetime_get_days_per_year,uint16_t,uint16_t Function,+,datetime_is_leap_year,_Bool,uint16_t Function,+,datetime_timestamp_to_datetime,void,"uint32_t, DateTime*" Function,+,datetime_validate_datetime,_Bool,DateTime* -Function,+,decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -2112,7 +2116,6 @@ Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType Function,-,iprintf,int,"const char*, ..." -Function,+,is_weak_prng_nonce,_Bool,uint32_t Function,-,isalnum,int,int Function,-,isalnum_l,int,"int, locale_t" Function,-,isalpha,int,int @@ -2298,7 +2301,6 @@ Function,+,lfrfid_worker_stop,void,LFRFIDWorker* Function,+,lfrfid_worker_stop_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_write_and_set_pass_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" Function,+,lfrfid_worker_write_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" -Function,+,lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,lgamma,double,double Function,-,lgamma_r,double,"double, int*" Function,-,lgammaf,float,float @@ -2905,7 +2907,6 @@ Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_even_parity8,uint8_t,uint8_t Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t -Function,+,nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -3019,7 +3020,6 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." -Function,+,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,process_favorite_launch,_Bool,char** Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t"