diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index 0898ac8ed..4ba934b6d 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -496,7 +496,7 @@ NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* MfClassicKey key = { .data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }; - error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL); + error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL, false); frame_test->state = (error == MfClassicErrorNone) ? NfcTestMfClassicSendFrameTestStateReadBlock : NfcTestMfClassicSendFrameTestStateFail; diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 295a75a4e..14e484622 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -74,8 +74,12 @@ #define NFC_APP_MFKEY32_LOGS_FILE_NAME ".mfkey32.log" #define NFC_APP_MFKEY32_LOGS_FILE_PATH (NFC_APP_FOLDER "/" NFC_APP_MFKEY32_LOGS_FILE_NAME) -#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH \ + (NFC_APP_FOLDER "/assets/mf_classic_dict_user_nested.nfc") #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") +#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \ + (NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc") typedef enum { NfcRpcStateIdle, @@ -93,6 +97,12 @@ typedef struct { bool is_key_attack; uint8_t key_attack_current_sector; bool is_card_present; + MfClassicNestedPhase nested_phase; + MfClassicPrngType prng_type; + MfClassicBackdoor backdoor; + uint16_t nested_target_key; + uint16_t msb_count; + bool enhanced_dict; } 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 328e39132..024bc5c1e 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 @@ -1,11 +1,15 @@ #include "../nfc_app_i.h" +#include #include -#include #define TAG "NfcMfClassicDictAttack" +// 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 + typedef enum { + DictAttackStateCUIDDictInProgress, DictAttackStateUserDictInProgress, DictAttackStateSystemDictInProgress, } DictAttackState; @@ -29,7 +33,9 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { const MfClassicData* mfc_data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); - mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack; + mfc_event->data->poller_mode.mode = (instance->nfc_dict_context.enhanced_dict) ? + MfClassicPollerModeDictAttackEnhanced : + MfClassicPollerModeDictAttackStandard; mfc_event->data->poller_mode.data = mfc_data; instance->nfc_dict_context.sectors_total = mf_classic_get_total_sectors_num(mfc_data->type); @@ -58,6 +64,11 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) instance->nfc_dict_context.sectors_read = data_update->sectors_read; instance->nfc_dict_context.keys_found = data_update->keys_found; instance->nfc_dict_context.current_sector = data_update->current_sector; + 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) { @@ -117,19 +128,75 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found); dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current); dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector); + 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); } } static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); + if(state == DictAttackStateCUIDDictInProgress) { + size_t cuid_len = 0; + const uint8_t* cuid = nfc_device_get_uid(instance->nfc_device, &cuid_len); + FuriString* cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", + EXT_PATH("nfc/assets"), + (uint32_t)bit_lib_bytes_to_num_be(cuid + (cuid_len - 4), 4)); + + do { + if(!keys_dict_check_presence(furi_string_get_cstr(cuid_dict_path))) { + state = DictAttackStateUserDictInProgress; + break; + } + + instance->nfc_dict_context.dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + + if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { + keys_dict_free(instance->nfc_dict_context.dict); + state = DictAttackStateUserDictInProgress; + break; + } + + dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary"); + } while(false); + + furi_string_free(cuid_dict_path); + } if(state == DictAttackStateUserDictInProgress) { do { + instance->nfc_dict_context.enhanced_dict = true; + + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) { + storage_common_remove( + instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH)) { + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } + if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { state = DictAttackStateSystemDictInProgress; break; } + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + } + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_USER_PATH, + NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + instance->nfc_dict_context.dict = keys_dict_alloc( NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { @@ -164,7 +231,7 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { NfcApp* instance = context; scene_manager_set_scene_state( - instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateCUIDDictInProgress); nfc_scene_mf_classic_dict_attack_prepare_view(instance); dict_attack_set_card_state(instance->dict_attack, true); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); @@ -193,7 +260,21 @@ 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 == DictAttackStateCUIDDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + consumed = true; + } else if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->nfc_dict_context.dict); @@ -222,7 +303,27 @@ 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 == DictAttackStateCUIDDictInProgress) { + if(instance->nfc_dict_context.is_card_present) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + } else { + nfc_scene_mf_classic_dict_attack_notify_read(instance); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } else if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { if(instance->nfc_dict_context.is_card_present) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); @@ -240,7 +341,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); @@ -262,7 +363,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { dict_attack_reset(instance->dict_attack); scene_manager_set_scene_state( - instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateCUIDDictInProgress); keys_dict_free(instance->nfc_dict_context.dict); @@ -275,6 +376,20 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.is_key_attack = false; instance->nfc_dict_context.key_attack_current_sector = 0; instance->nfc_dict_context.is_card_present = false; + 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; + instance->nfc_dict_context.enhanced_dict = false; + + // Clean up temporary files used for nested dictionary attack + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + } + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 14298a6aa..726076972 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -21,6 +21,11 @@ typedef struct { size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; + 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) { @@ -34,9 +39,47 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { } else { char draw_str[32] = {}; canvas_set_font(canvas, FontSecondary); + + switch(m->nested_phase) { + case MfClassicNestedPhaseAnalyzePRNG: + furi_string_set(m->header, "PRNG Analysis"); + break; + case MfClassicNestedPhaseDictAttack: + case MfClassicNestedPhaseDictAttackVerify: + case MfClassicNestedPhaseDictAttackResume: + furi_string_set(m->header, "Nested Dictionary"); + break; + case MfClassicNestedPhaseCalibrate: + case MfClassicNestedPhaseRecalibrate: + furi_string_set(m->header, "Calibration"); + break; + case MfClassicNestedPhaseCollectNtEnc: + furi_string_set(m->header, "Nonce Collection"); + break; + default: + break; + } + + if(m->prng_type == MfClassicPrngTypeHard) { + furi_string_cat(m->header, " (Hard)"); + } + + if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { + if(m->nested_phase != MfClassicNestedPhaseNone) { + furi_string_cat(m->header, " (Backdoor)"); + } else { + furi_string_set(m->header, "Backdoor Read"); + } + } + canvas_draw_str_aligned( - canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); - if(m->is_key_attack) { + canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + 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), @@ -46,21 +89,48 @@ 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 == MfClassicNestedPhaseDictAttackVerify || + 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); @@ -132,6 +202,11 @@ void dict_attack_reset(DictAttack* instance) { model->dict_keys_total = 0; model->dict_keys_current = 0; model->is_key_attack = false; + 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); @@ -242,3 +317,41 @@ void dict_attack_reset_key_attack(DictAttack* instance) { with_view_model( instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true); } + +void dict_attack_set_nested_phase(DictAttack* instance, MfClassicNestedPhase nested_phase) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->nested_phase = nested_phase; }, true); +} + +void dict_attack_set_prng_type(DictAttack* instance, MfClassicPrngType prng_type) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->prng_type = prng_type; }, true); +} + +void dict_attack_set_backdoor(DictAttack* instance, MfClassicBackdoor backdoor) { + furi_assert(instance); + + 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 30f3b3c44..b6c6fdbdc 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -45,6 +46,16 @@ void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector); void dict_attack_reset_key_attack(DictAttack* instance); +void dict_attack_set_nested_phase(DictAttack* instance, MfClassicNestedPhase nested_phase); + +void dict_attack_set_prng_type(DictAttack* instance, MfClassicPrngType prng_type); + +void dict_attack_set_backdoor(DictAttack* instance, MfClassicBackdoor 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 bd4fc8d61..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,11 +169,69 @@ 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); } } + +static uint8_t lfsr_rollback_bit(Crypto1* crypto1, uint32_t in, int fb) { + int out; + uint8_t ret; + uint32_t t; + + crypto1->odd &= 0xffffff; + t = crypto1->odd; + crypto1->odd = crypto1->even; + crypto1->even = t; + + out = crypto1->even & 1; + out ^= LF_POLY_EVEN & (crypto1->even >>= 1); + out ^= LF_POLY_ODD & crypto1->odd; + out ^= !!in; + out ^= (ret = crypto1_filter(crypto1->odd)) & (!!fb); + + crypto1->even |= (nfc_util_even_parity32(out)) << 23; + return ret; +} + +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); + } + return ret; +} + +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) == + (((nt_par_enc >> 2) & 1) ^ FURI_BIT(ks, 8))) && + (nfc_util_even_parity8((nt >> 8) & 0xFF) == + (((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0))); +} + +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; + for(uint8_t i = 0; i < 16; i++) { + x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; + } + x = (x & 0xff) << 8 | x >> 8; + return x == (nonce & 0xFFFF); +} + +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 ^ 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 e71ab9a40..0e358581a 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -1,5 +1,6 @@ #pragma once +#include #include #ifdef __cplusplus @@ -38,7 +39,15 @@ void crypto1_encrypt_reader_nonce( BitBuffer* out, bool is_nested); -uint32_t prng_successor(uint32_t x, uint32_t n); +uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); + +bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); + +bool crypto1_is_weak_prng_nonce(uint32_t nonce); + +uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); + +uint32_t crypto1_prng_successor(uint32_t x, uint32_t n); #ifdef __cplusplus } diff --git a/lib/nfc/helpers/nfc_util.c b/lib/nfc/helpers/nfc_util.c index f502b4bfb..80af5cf11 100644 --- a/lib/nfc/helpers/nfc_util.c +++ b/lib/nfc/helpers/nfc_util.c @@ -13,6 +13,10 @@ static const uint8_t nfc_util_odd_byte_parity[256] = { 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1}; +uint8_t nfc_util_even_parity8(uint8_t data) { + return !nfc_util_odd_byte_parity[data]; +} + uint8_t nfc_util_even_parity32(uint32_t data) { // data ^= data >> 16; // data ^= data >> 8; diff --git a/lib/nfc/helpers/nfc_util.h b/lib/nfc/helpers/nfc_util.h index f8e86d865..4abde4521 100644 --- a/lib/nfc/helpers/nfc_util.h +++ b/lib/nfc/helpers/nfc_util.h @@ -6,6 +6,8 @@ extern "C" { #endif +uint8_t nfc_util_even_parity8(uint8_t data); + uint8_t nfc_util_even_parity32(uint32_t data); uint8_t nfc_util_odd_parity8(uint8_t data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index 4f92201e3..b1c5c20c9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -543,6 +543,22 @@ void mf_classic_set_key_not_found( } } +MfClassicKey + mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type) { + furi_check(data); + furi_check(sector_num < mf_classic_get_total_sectors_num(data->type)); + furi_check(key_type == MfClassicKeyTypeA || key_type == MfClassicKeyTypeB); + + const MfClassicSectorTrailer* sector_trailer = + mf_classic_get_sector_trailer_by_sector(data, sector_num); + + if(key_type == MfClassicKeyTypeA) { + return sector_trailer->key_a; + } else { + return sector_trailer->key_b; + } +} + bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) { furi_check(data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 801ec1764..6ae7a623e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -6,14 +6,16 @@ extern "C" { #endif -#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) -#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) -#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) -#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) -#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) -#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) -#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) -#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) +#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) +#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) +#define MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A (0x64U) +#define MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B (0x65U) +#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) +#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) +#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) +#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) +#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) +#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) #define MF_CLASSIC_CMD_HALT_MSB (0x50) #define MF_CLASSIC_CMD_HALT_LSB (0x00) @@ -211,6 +213,9 @@ void mf_classic_set_key_not_found( uint8_t sector_num, MfClassicKeyType key_type); +MfClassicKey + mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type); + bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num); void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); 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 8c50230ca..ec37c8015 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,8 +6,24 @@ #define TAG "MfClassicPoller" +// TODO FL-3926: Buffer writes for Hardnested, set state to Log when finished and sum property matches +// TODO FL-3926: Store target key in CUID dictionary +// TODO FL-3926: Dead code for malloc returning NULL? +// TODO FL-3926: Auth1 static encrypted exists (rare) +// TODO FL-3926: Use keys found by NFC plugins, cached keys + #define MF_CLASSIC_MAX_BUFF_SIZE (64) +// Ordered by frequency, labeled chronologically +const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[] = { + {{{0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}, MfClassicBackdoorAuth3}, // Fudan (static encrypted) + {{{0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}, MfClassicBackdoorAuth1}, // Fudan, Infineon, NXP + {{{0x51, 0x8b, 0x33, 0x54, 0xe7, 0x60}}, MfClassicBackdoorAuth2}, // Fudan +}; +const size_t mf_classic_backdoor_keys_count = COUNT_OF(mf_classic_backdoor_keys); +const uint16_t valid_sums[] = + {0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256}; + typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance); MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { @@ -49,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); } @@ -58,6 +94,11 @@ static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance mf_classic_get_read_sectors_and_keys( instance->data, &data_update->sectors_read, &data_update->keys_found); data_update->current_sector = instance->mode_ctx.dict_attack_ctx.current_sector; + 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); } @@ -86,7 +127,8 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { iso14443_3a_copy( instance->data->iso14443_3a_data, iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); - MfClassicError error = mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL); + MfClassicError error = + mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType4k; instance->state = MfClassicPollerStateStart; @@ -96,7 +138,8 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { instance->current_type_check = MfClassicType1k; } } else if(instance->current_type_check == MfClassicType1k) { - MfClassicError error = mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL); + MfClassicError error = + mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType1k; FURI_LOG_D(TAG, "1K detected"); @@ -120,9 +163,12 @@ 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 == MfClassicPollerModeDictAttack) { + if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackStandard) { mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); 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->state = MfClassicPollerStateAnalyzeBackdoor; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) { instance->state = MfClassicPollerStateRequestReadSector; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeWrite) { @@ -236,7 +282,7 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { do { // Authenticate to sector error = mf_classic_poller_auth( - instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL); + instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL, false); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block); instance->state = MfClassicPollerStateFail; @@ -294,7 +340,12 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { // Reauth if necessary if(write_ctx->need_halt_before_write) { error = mf_classic_poller_auth( - instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL); + instance, + write_ctx->current_block, + auth_key, + write_ctx->key_type_write, + NULL, + false); if(error != MfClassicErrorNone) { FURI_LOG_D( TAG, "Failed to auth to block %d for writing", write_ctx->current_block); @@ -403,8 +454,8 @@ NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance MfClassicKey* key = (auth_key_type == MfClassicKeyTypeA) ? &write_ctx->sec_tr.key_a : &write_ctx->sec_tr.key_b; - MfClassicError error = - mf_classic_poller_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); + MfClassicError error = mf_classic_poller_auth( + instance, write_ctx->current_block, key, auth_key_type, NULL, false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_value_cmd(instance, write_ctx->current_block, value_cmd, diff); @@ -468,7 +519,8 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* sec_read_ctx->current_block, &sec_read_ctx->key, sec_read_ctx->key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; sec_read_ctx->auth_passed = true; @@ -505,6 +557,128 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* return command; } +NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + instance->mode_ctx.dict_attack_ctx.enhanced_dict = true; + + size_t current_key_index = + mf_classic_backdoor_keys_count - 1; // Default to the last valid index + + // Find the current key in the backdoor_keys array + for(size_t i = 0; i < mf_classic_backdoor_keys_count; i++) { + if(memcmp( + dict_attack_ctx->current_key.data, + mf_classic_backdoor_keys[i].key.data, + sizeof(MfClassicKey)) == 0) { + current_key_index = i; + break; + } + } + + // Choose the next key to try + size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count; + uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1; + + FURI_LOG_D(TAG, "Trying backdoor v%d", backdoor_version); + dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key; + + // 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 || error == MfClassicErrorTimeout)) { + FURI_LOG_D(TAG, "No backdoor identified"); + dict_attack_ctx->backdoor = MfClassicBackdoorNone; + 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; + instance->state = MfClassicPollerStateBackdoorReadSector; + } else if( + (error == MfClassicErrorAuth) && + (next_key_index == (mf_classic_backdoor_keys_count - 1))) { + // We've tried all backdoor keys, this is a unique key and an important research finding + furi_crash("New backdoor: please report!"); + } + + return command; +} + +NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* instance) { + // TODO FL-3926: Reauth not needed + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + MfClassicError error = MfClassicErrorNone; + MfClassicBlock block = {}; + + uint8_t current_sector = mf_classic_get_sector_by_block(dict_attack_ctx->current_block); + uint8_t blocks_in_sector = mf_classic_get_blocks_num_in_sector(current_sector); + uint8_t first_block_in_sector = mf_classic_get_first_block_num_of_sector(current_sector); + + do { + if(dict_attack_ctx->current_block >= instance->sectors_total * 4) { + // We've read all blocks, reset current_block and move to next state + dict_attack_ctx->current_block = 0; + instance->state = MfClassicPollerStateNestedController; + break; + } + + // Authenticate with the backdoor key + error = mf_classic_poller_auth( + instance, + first_block_in_sector, // Authenticate to the first block of the sector + &(dict_attack_ctx->current_key), + MfClassicKeyTypeA, + NULL, + true); + + if(error != MfClassicErrorNone) { + FURI_LOG_E( + TAG, "Failed to authenticate with backdoor key for sector %d", current_sector); + break; + } + + // Read all blocks in the sector + for(uint8_t block_in_sector = 0; block_in_sector < blocks_in_sector; block_in_sector++) { + uint8_t block_to_read = first_block_in_sector + block_in_sector; + + error = mf_classic_poller_read_block(instance, block_to_read, &block); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to read block %d", block_to_read); + break; + } + + // Set the block as read in the data structure + mf_classic_set_block_read(instance->data, block_to_read, &block); + } + + if(error != MfClassicErrorNone) { + break; + } + + // Move to the next sector + current_sector++; + dict_attack_ctx->current_block = mf_classic_get_first_block_num_of_sector(current_sector); + + // Update blocks_in_sector and first_block_in_sector for the next sector + if(current_sector < instance->sectors_total) { + blocks_in_sector = mf_classic_get_blocks_num_in_sector(current_sector); + first_block_in_sector = mf_classic_get_first_block_num_of_sector(current_sector); + } + + // Halt the card after each sector to reset the authentication state + mf_classic_poller_halt(instance); + + // Send an event to the app that a sector has been read + command = mf_classic_poller_handle_data_update(instance); + + } while(false); + + return command; +} + NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -535,7 +709,7 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); mf_classic_set_key_found( @@ -574,7 +748,7 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); mf_classic_set_key_found( @@ -629,7 +803,8 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { block_num, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) { instance->state = MfClassicPollerStateNextSector; FURI_LOG_W(TAG, "Failed to re-auth. Go to next sector"); @@ -680,24 +855,51 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { - dict_attack_ctx->current_key_type = MfClassicKeyTypeB; - instance->state = MfClassicPollerStateKeyReuseAuthKeyB; - } else { - dict_attack_ctx->reuse_key_sector++; - if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { - instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; - command = instance->callback(instance->general_event, instance->context); - instance->state = MfClassicPollerStateRequestKey; + do { + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + instance->state = MfClassicPollerStateKeyReuseAuthKeyB; } else { - instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; - instance->mfc_event_data.key_attack_data.current_sector = - dict_attack_ctx->reuse_key_sector; - command = instance->callback(instance->general_event, instance->context); + dict_attack_ctx->reuse_key_sector++; + if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; + command = instance->callback(instance->general_event, instance->context); + // Nested entrypoint + bool nested_active = dict_attack_ctx->nested_phase != MfClassicNestedPhaseNone; + if((dict_attack_ctx->enhanced_dict) && + ((nested_active && + (dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) || + (!(nested_active) && !(mf_classic_is_card_read(instance->data))))) { + instance->state = MfClassicPollerStateNestedController; + break; + } + instance->state = MfClassicPollerStateRequestKey; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = + dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); - dict_attack_ctx->current_key_type = MfClassicKeyTypeA; - instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + } } + } while(false); + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_start_no_offset(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + } else { + instance->state = MfClassicPollerStateKeyReuseAuthKeyB; } return command; @@ -718,7 +920,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); mf_classic_set_key_found( @@ -754,7 +956,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); mf_classic_set_key_found( @@ -793,7 +995,8 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst block_num, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) { instance->state = MfClassicPollerStateKeyReuseStart; break; @@ -829,6 +1032,1100 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst return command; } +// Helper function to add a nonce to the array +static bool add_nested_nonce( + MfClassicNestedNonceArray* array, + uint32_t cuid, + uint16_t key_idx, + uint32_t nt, + uint32_t nt_enc, + uint8_t par, + uint16_t dist) { + MfClassicNestedNonce* new_nonces; + if(array->count == 0) { + new_nonces = malloc(sizeof(MfClassicNestedNonce)); + } else { + new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); + } + if(new_nonces == NULL) return false; + + array->nonces = new_nonces; + array->nonces[array->count].cuid = cuid; + array->nonces[array->count].key_idx = key_idx; + array->nonces[array->count].nt = nt; + array->nonces[array->count].nt_enc = nt_enc; + array->nonces[array->count].par = par; + array->nonces[array->count].dist = dist; + array->count++; + return true; +} + +NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + uint8_t hard_nt_count = 0; + + for(uint8_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { + MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; + if(!crypto1_is_weak_prng_nonce(nonce->nt)) hard_nt_count++; + } + + if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) { + dict_attack_ctx->prng_type = MfClassicPrngTypeHard; + FURI_LOG_D(TAG, "Detected Hard PRNG"); + } else { + dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + FURI_LOG_D(TAG, "Detected Weak PRNG"); + } + + instance->state = MfClassicPollerStateNestedController; + return command; +} + +NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance) { + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + MfClassicNt nt = {}; + MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt, false); + + if(error != MfClassicErrorNone) { + dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; + FURI_LOG_E(TAG, "Failed to collect nt"); + } else { + FURI_LOG_T(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]); + uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); + if(!add_nested_nonce( + &dict_attack_ctx->nested_nonce, + iso14443_3a_get_cuid(instance->data->iso14443_3a_data), + 0, + nt_data, + 0, + 0, + 0)) { + dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; + } + } + + instance->state = MfClassicPollerStateNestedController; + return command; +} + +NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { + 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; + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error; + + uint32_t nt_prev = 0; + uint32_t nt_enc_prev = 0; + uint32_t same_nt_enc_cnt = 0; + uint8_t nt_enc_collected = 0; + bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); + + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, + &auth_ctx, + use_backdoor); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } + + FURI_LOG_D(TAG, "Full authentication successful"); + + nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + + if((dict_attack_ctx->static_encrypted) && + (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3)) { + 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, + target_key_type, + &auth_ctx, + use_backdoor, + false); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication for static encrypted tag"); + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } + + 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 = + crypto1_decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); + + dict_attack_ctx->calibrated = true; + + FURI_LOG_D(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc); + + instance->state = MfClassicPollerStateNestedController; + return command; + } + + // Original calibration logic for non-static encrypted tags + // Step 2: Perform nested authentication multiple times + for(uint8_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; + collection_cycle++) { + error = mf_classic_poller_auth_nested( + instance, + block, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, + &auth_ctx, + use_backdoor, + false); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle); + continue; + } + + nt_enc_temp_arr[collection_cycle] = + bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + nt_enc_collected++; + } + + for(int i = 0; i < nt_enc_collected; i++) { + if(nt_enc_temp_arr[i] == nt_enc_prev) { + same_nt_enc_cnt++; + if(same_nt_enc_cnt > 3) { + dict_attack_ctx->static_encrypted = true; + break; + } + } else { + same_nt_enc_cnt = 0; + nt_enc_prev = nt_enc_temp_arr[i]; + } + } + + if(dict_attack_ctx->static_encrypted) { + FURI_LOG_D(TAG, "Static encrypted nonce detected"); + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + return command; + } + + // Find the distance between each nonce + FURI_LOG_D(TAG, "Calculating distance between nonces"); + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); + 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 = 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 = crypto1_prng_successor(nt_prev, i); + if(nth_successor == decrypted_nt_enc) { + FURI_LOG_D(TAG, "nt_enc (plain) %08lx", nth_successor); + FURI_LOG_D(TAG, "dist from nt prev: %i", i); + distances[valid_distances++] = i; + nt_prev = nth_successor; + found = true; + break; + } + } + if(!found) { + FURI_LOG_E( + TAG, + "Failed to find distance for nt_enc %08lx", + nt_enc_temp_arr[collection_cycle]); + FURI_LOG_E( + TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev); + } + } + + // 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 + 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_D( + TAG, + "Calibration completed: min=%u max=%u static=%s", + dict_attack_ctx->d_min, + dict_attack_ctx->d_max, + ((d_dist >= 3) && (d_dist <= 6)) ? "true" : "false"); + + return command; +} + +static inline void set_byte_found(uint8_t* found, uint8_t byte) { + SET_PACKED_BIT(found, byte); +} + +static inline bool is_byte_found(uint8_t* found, uint8_t byte) { + return GET_PACKED_BIT(found, byte) != 0; +} + +NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { + // TODO FL-3926: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) + // TODO FL-3926: Look into using MfClassicNt more + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + do { + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error; + + bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0; + uint8_t nt_enc_per_collection = + (is_weak && !(dict_attack_ctx->static_encrypted)) ? + ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : + 1; + 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; + + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, + &auth_ctx, + use_backdoor); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + break; + } + + FURI_LOG_D(TAG, "Full authentication successful"); + + // Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset + // eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the + // most commonly observed nonce from 4 auths to known sector and 5th to target. This gets us a nonce pair, + // at a known distance (confirmed by parity bits) telling us the nt_enc plain. + for(uint8_t collection_cycle = 0; collection_cycle < (nt_enc_per_collection - 1); + collection_cycle++) { + // This loop must match the calibrated loop + error = mf_classic_poller_auth_nested( + instance, + block, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, + &auth_ctx, + use_backdoor, + false); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle); + break; + } + + nt_enc_temp_arr[collection_cycle] = + bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + nt_enc_collected++; + } + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->nested_known_key, + target_key_type, + &auth_ctx, + false, + true); + + if(nt_enc_collected != (nt_enc_per_collection - 1)) { + FURI_LOG_E(TAG, "Failed to collect sufficient nt_enc values"); + break; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); + for(int i = 0; i < 4; i++) { + parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + } + + 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_D(TAG, "Duplicate nonce, dismissing collection attempt"); + break; + } + + // Decrypt the previous nonce + nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; + 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 = 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) { + FURI_LOG_D(TAG, "Ambiguous nonce, dismissing collection attempt"); + break; + } + found_nt = nth_successor; + } + current_dist++; + } + if(found_nt_cnt != 1) { + break; + } + } else if(dict_attack_ctx->static_encrypted) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { + found_nt = dict_attack_ctx->static_encrypted_nonce; + } else { + dist = UINT16_MAX; + } + } else { + // 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); + } + parity ^= 0x0F; + } + + // Add the nonce to the array + if(add_nested_nonce( + &dict_attack_ctx->nested_nonce, + cuid, + dict_attack_ctx->nested_target_key, + found_nt, + nt_enc, + parity, + dist)) { + dict_attack_ctx->auth_passed = true; + } else { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + } + + FURI_LOG_D( + TAG, + "Target: %u (nonce pair %u, key type %s, block %u)", + dict_attack_ctx->nested_target_key, + nonce_pair_index, + (target_key_type == MfClassicKeyTypeA) ? "A" : "B", + target_block); + FURI_LOG_T(TAG, "cuid: %08lx", cuid); + FURI_LOG_T(TAG, "nt_enc: %08lx", nt_enc); + FURI_LOG_T( + TAG, + "parity: %u%u%u%u", + ((parity >> 3) & 1), + ((parity >> 2) & 1), + ((parity >> 1) & 1), + (parity & 1)); + FURI_LOG_T(TAG, "nt_enc prev: %08lx", nt_prev); + FURI_LOG_T(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); + } while(false); + + instance->state = MfClassicPollerStateNestedController; + + mf_classic_poller_halt(instance); + return command; +} + +static MfClassicKey* search_dicts_for_nonce_key( + MfClassicPollerDictAttackContext* dict_attack_ctx, + MfClassicNestedNonceArray* nonce_array, + KeysDict* system_dict, + KeysDict* user_dict, + bool is_weak) { + MfClassicKey stack_key; + KeysDict* dicts[] = {user_dict, system_dict}; + bool is_resumed = dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume; + bool found_resume_point = false; + + for(int i = 0; i < 2; i++) { + if(!dicts[i]) continue; + keys_dict_rewind(dicts[i]); + while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { + if(is_resumed && !found_resume_point) { + found_resume_point = + (memcmp( + dict_attack_ctx->current_key.data, + stack_key.data, + sizeof(MfClassicKey)) == 0); + continue; + } + 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 = crypto1_decrypt_nt_enc( + nonce_array->nonces[j].cuid, nonce_array->nonces[j].nt_enc, stack_key); + if(is_weak) { + full_match &= crypto1_is_weak_prng_nonce(nt_enc_plain); + if(!full_match) break; + } + 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); + if(!full_match) break; + } + if(full_match) { + MfClassicKey* new_candidate = malloc(sizeof(MfClassicKey)); + if(new_candidate == NULL) return NULL; // malloc failed + memcpy(new_candidate, &stack_key, sizeof(MfClassicKey)); + return new_candidate; + } + } + } + + return NULL; +} + +NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { + // TODO FL-3926: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) + // TODO FL-3926: Look into using MfClassicNt more + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + do { + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error; + + bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + bool is_last_iter_for_hard_key = + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + 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)) || + ((!is_weak) && (dict_attack_ctx->nested_nonce.count < 8))) { + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, + &auth_ctx, + use_backdoor_for_initial_auth); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + dict_attack_ctx->auth_passed = false; + break; + } + + FURI_LOG_D(TAG, "Full authentication successful"); + + // Step 2: Collect nested nt and parity + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->nested_known_key, + target_key_type, + &auth_ctx, + false, + true); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication"); + dict_attack_ctx->auth_passed = false; + break; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); + for(int i = 0; i < 4; i++) { + parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + } + + bool success = add_nested_nonce( + &dict_attack_ctx->nested_nonce, + cuid, + dict_attack_ctx->nested_target_key, + 0, + nt_enc, + parity, + 0); + if(!success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + dict_attack_ctx->auth_passed = false; + break; + } + + dict_attack_ctx->auth_passed = true; + } + // If we have sufficient nonces, search the dictionaries for the key + if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || + (is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) { + // Identify key candidates + MfClassicKey* key_candidate = search_dicts_for_nonce_key( + dict_attack_ctx, + &dict_attack_ctx->nested_nonce, + dict_attack_ctx->mf_classic_system_dict, + dict_attack_ctx->mf_classic_user_dict, + is_weak); + if(key_candidate != NULL) { + FURI_LOG_I( + TAG, + "Found key candidate %06llx", + bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); + dict_attack_ctx->current_key = *key_candidate; + dict_attack_ctx->reuse_key_sector = target_sector; + dict_attack_ctx->current_key_type = target_key_type; + free(key_candidate); + break; + } else { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + } + + FURI_LOG_D( + TAG, + "Target: %u (key type %s, block %u) cuid: %08lx", + dict_attack_ctx->nested_target_key, + (target_key_type == MfClassicKeyTypeA) ? "A" : "B", + target_block, + cuid); + } while(false); + + instance->state = MfClassicPollerStateNestedController; + + mf_classic_poller_halt(instance); + return command; +} + +NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { + furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); + furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); + + NfcCommand command = NfcCommandContinue; + bool params_saved = false; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + bool weak_prng = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + bool static_encrypted = dict_attack_ctx->static_encrypted; + + do { + if(weak_prng && (!(static_encrypted)) && (dict_attack_ctx->nested_nonce.count != 2)) { + FURI_LOG_E( + TAG, + "MfClassicPollerStateNestedLog expected 2 nonces, received %zu", + dict_attack_ctx->nested_nonce.count); + break; + } + + uint32_t nonce_pair_count = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak ? + 1 : + dict_attack_ctx->nested_nonce.count; + + if(!buffered_file_stream_open( + stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) + break; + + 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 FL-3926: 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_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++) { + if(nt_idx == 1) { + nonce = &dict_attack_ctx->nested_nonce.nonces[i + 1]; + } + furi_string_cat_printf( + temp_str, + " nt%u %08lx ks%u %08lx par%u ", + nt_idx, + nonce->nt, + nt_idx, + nonce->nt_enc ^ nonce->nt, + nt_idx); + for(uint8_t pb = 0; pb < 4; pb++) { + furi_string_cat_printf(temp_str, "%u", (nonce->par >> (3 - pb)) & 1); + } + } + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + furi_string_cat_printf(temp_str, " dist %u\n", nonce->dist); + } else { + furi_string_cat_printf(temp_str, "\n"); + } + if(!stream_write_string(stream, temp_str)) { + params_write_success = false; + break; + } + } + if(!params_write_success) break; + + params_saved = true; + } while(false); + + furi_assert(params_saved); + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + furi_string_free(temp_str); + buffered_file_stream_close(stream); + stream_free(stream); + furi_record_close(RECORD_STORAGE); + instance->state = MfClassicPollerStateNestedController; + return command; +} + +bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) { + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + uint16_t nested_target_key = dict_attack_ctx->nested_target_key; + + MfClassicKeyType target_key_type; + uint8_t target_sector; + + if(is_dict_attack) { + target_key_type = (((is_weak) && ((nested_target_key % 2) == 0)) || + ((!is_weak) && ((nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + target_sector = is_weak ? (nested_target_key / 2) : (nested_target_key / 16); + } else { + target_key_type = (((is_weak) && ((nested_target_key % 4) < 2)) || + ((!is_weak) && ((nested_target_key % 2) == 0))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + target_sector = is_weak ? (nested_target_key / 4) : (nested_target_key / 2); + } + + return mf_classic_is_key_found(instance->data, target_sector, target_key_type); +} + +bool is_valid_sum(uint16_t sum) { + for(size_t i = 0; i < 19; i++) { + if(sum == valid_sums[i]) { + return true; + } + } + return false; +} + +NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { + // 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; + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { + dict_attack_ctx->auth_passed = true; + bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); + if(!(backdoor_present)) { + for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { + for(uint8_t key_type = 0; key_type < 2; key_type++) { + if(mf_classic_is_key_found(instance->data, sector, key_type)) { + dict_attack_ctx->nested_known_key = + mf_classic_get_key(instance->data, sector, key_type); + dict_attack_ctx->nested_known_key_sector = sector; + dict_attack_ctx->nested_known_key_type = key_type; + break; + } + } + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; + } else { + dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; + dict_attack_ctx->nested_known_key_sector = 0; + dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; + dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { + dict_attack_ctx->static_encrypted = true; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + 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; + return command; + } else if( + (dict_attack_ctx->nested_nonce.count == MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) && + (dict_attack_ctx->prng_type == MfClassicPrngTypeUnknown)) { + instance->state = MfClassicPollerStateNestedAnalyzePRNG; + return command; + } else if(dict_attack_ctx->prng_type == MfClassicPrngTypeNoTag) { + FURI_LOG_E(TAG, "No tag detected"); + // Free nonce array + // TODO FL-3926: Consider using .count here + 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; + } + instance->state = MfClassicPollerStateFail; + return command; + } + if(dict_attack_ctx->nested_nonce.nonces) { + // Free nonce array + // TODO FL-3926: Consider using .count here + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + initial_dict_attack_iter = true; + } + // Accelerated nested dictionary attack + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? + (instance->sectors_total * 2) : + (instance->sectors_total * 16); + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackVerify) { + if(!(mf_classic_nested_is_target_key_found(instance, true)) && + (dict_attack_ctx->nested_nonce.count > 0)) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } else { + dict_attack_ctx->auth_passed = true; + if(dict_attack_ctx->nested_nonce.count > 0) { + // Free nonce array + furi_assert(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; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + } + } + if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack || + dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) && + (dict_attack_ctx->nested_target_key < dict_target_key_max)) { + bool is_last_iter_for_hard_key = + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); + if(initial_dict_attack_iter) { + // Initialize dictionaries + // Note: System dict should always exist + dict_attack_ctx->mf_classic_system_dict = + keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH) ? + keys_dict_alloc( + MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)) : + NULL; + + dict_attack_ctx->mf_classic_user_dict = + keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH) ? + keys_dict_alloc( + MF_CLASSIC_NESTED_USER_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)) : + NULL; + } + if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { + // Key verify and reuse + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify; + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateKeyReuseStartNoOffset; + return command; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + dict_attack_ctx->auth_passed = true; + } + if(!(dict_attack_ctx->auth_passed)) { + dict_attack_ctx->attempt_count++; + } else if(!(initial_dict_attack_iter)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->auth_passed = true; + 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)) { + // All keys have been collected + FURI_LOG_D(TAG, "All keys collected and sectors read"); + dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; + 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; + } + if(dict_attack_ctx->attempt_count == 0) { + // Check if the nested target key is a known key + if(mf_classic_nested_is_target_key_found(instance, true)) { + // Continue to next key + instance->state = MfClassicPollerStateNestedController; + return command; + } + } + if(dict_attack_ctx->attempt_count >= 3) { + // Unpredictable, skip + FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + } + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } + // 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->calibrated = true; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { + initial_collect_nt_enc_iter = true; + 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 + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || + ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && + (dict_attack_ctx->nested_nonce.count == 1)) || + ((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) { + instance->state = MfClassicPollerStateNestedLog; + return command; + } + uint16_t nonce_collect_key_max; + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + nonce_collect_key_max = instance->sectors_total * 4; + } else { + nonce_collect_key_max = instance->sectors_total * 2; + } + // Target all remaining sectors, key A and B + if(dict_attack_ctx->nested_target_key < nonce_collect_key_max) { + 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++; + dict_attack_ctx->current_key_checked = false; + instance->state = MfClassicPollerStateNestedController; + } else { + // Nonces do not match an expected sum + 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; + } + 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) && !(recalibrated)) { + if(!(dict_attack_ctx->static_encrypted)) { + dict_attack_ctx->nested_target_key++; + } else { + dict_attack_ctx->nested_target_key += 2; + } + if(dict_attack_ctx->nested_target_key % 2 == 0) { + dict_attack_ctx->current_key_checked = false; + } + } + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->auth_passed = true; + + // 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))) { + // Unpredictable, skip + FURI_LOG_W(TAG, "Failed to collect nonce, skipping key"); + 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; + } + if(is_weak) { + 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++; + dict_attack_ctx->current_key_checked = false; + } + dict_attack_ctx->attempt_count = 0; + } + + FURI_LOG_D( + TAG, + "Nested target key: %u (max: %u)", + dict_attack_ctx->nested_target_key, + nonce_collect_key_max); + + if(!(dict_attack_ctx->current_key_checked)) { + if(dict_attack_ctx->nested_target_key == nonce_collect_key_max) { + // All nonces have been collected + FURI_LOG_D(TAG, "All nonces collected"); + instance->state = MfClassicPollerStateNestedController; + return command; + } + + 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(!(dict_attack_ctx->static_encrypted)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + } + instance->state = MfClassicPollerStateNestedController; + return command; + } + + // 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; + } + } + FURI_LOG_T(TAG, "Collecting a nonce"); + + // Collect a nonce + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateNestedCollectNtEnc; + return command; + } + } + dict_attack_ctx->nested_target_key = 0; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; + instance->state = MfClassicPollerStateSuccess; + return command; +} + NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; instance->mfc_event.type = MfClassicPollerEventTypeSuccess; @@ -857,6 +2154,8 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateWriteBlock] = mf_classic_poller_handler_write_block, [MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block, [MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector, + [MfClassicPollerStateAnalyzeBackdoor] = mf_classic_poller_handler_analyze_backdoor, + [MfClassicPollerStateBackdoorReadSector] = mf_classic_poller_handler_backdoor_read_sector, [MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key, [MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector, [MfClassicPollerStateReadSectorBlocks] = @@ -865,9 +2164,18 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateAuthKeyB] = mf_classic_poller_handler_auth_b, [MfClassicPollerStateReadSector] = mf_classic_poller_handler_read_sector, [MfClassicPollerStateKeyReuseStart] = mf_classic_poller_handler_key_reuse_start, + [MfClassicPollerStateKeyReuseStartNoOffset] = + mf_classic_poller_handler_key_reuse_start_no_offset, [MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a, [MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b, [MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector, + [MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng, + [MfClassicPollerStateNestedCalibrate] = mf_classic_poller_handler_nested_calibrate, + [MfClassicPollerStateNestedCollectNt] = mf_classic_poller_handler_nested_collect_nt, + [MfClassicPollerStateNestedController] = mf_classic_poller_handler_nested_controller, + [MfClassicPollerStateNestedCollectNtEnc] = mf_classic_poller_handler_nested_collect_nt_enc, + [MfClassicPollerStateNestedDictAttack] = mf_classic_poller_handler_nested_dict_attack, + [MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log, [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, }; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 518d029d0..8efb931aa 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -44,9 +44,46 @@ typedef enum { typedef enum { MfClassicPollerModeRead, /**< Poller reading mode. */ MfClassicPollerModeWrite, /**< Poller writing mode. */ - MfClassicPollerModeDictAttack, /**< Poller dictionary attack mode. */ + MfClassicPollerModeDictAttackStandard, /**< Poller dictionary attack mode. */ + MfClassicPollerModeDictAttackEnhanced, /**< Poller enhanced dictionary attack mode. */ } MfClassicPollerMode; +/** + * @brief MfClassic poller nested attack phase. + */ +typedef enum { + MfClassicNestedPhaseNone, /**< No nested attack has taken place yet. */ + MfClassicNestedPhaseAnalyzePRNG, /**< Analyze nonces produced by the PRNG to determine if they fit a weak PRNG */ + MfClassicNestedPhaseDictAttack, /**< Search keys which match the expected PRNG properties and parity for collected nonces */ + MfClassicNestedPhaseDictAttackVerify, /**< Verify candidate keys by authenticating to the sector with the key */ + MfClassicNestedPhaseDictAttackResume, /**< Resume nested dictionary attack from the last tested (invalid) key */ + MfClassicNestedPhaseCalibrate, /**< Perform necessary calculations to recover the plaintext nonce during later collection phase (weak PRNG tags only) */ + MfClassicNestedPhaseRecalibrate, /**< Collect the next plaintext static encrypted nonce for backdoor static encrypted nonce nested attack */ + MfClassicNestedPhaseCollectNtEnc, /**< Log nonces collected during nested authentication for key recovery */ + MfClassicNestedPhaseFinished, /**< Nested attack has finished */ +} MfClassicNestedPhase; + +/** + * @brief MfClassic pseudorandom number generator (PRNG) type. + */ +typedef enum { + MfClassicPrngTypeUnknown, // Tag not yet tested + MfClassicPrngTypeNoTag, // No tag detected during test + MfClassicPrngTypeWeak, // Weak PRNG, standard Nested + MfClassicPrngTypeHard, // Hard PRNG, Hardnested +} MfClassicPrngType; + +/** + * @brief MfClassic authentication backdoor type. + */ +typedef enum { + MfClassicBackdoorUnknown, // Tag not yet tested + MfClassicBackdoorNone, // No observed backdoor + MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (sometimes static encrypted) + MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce) +} MfClassicBackdoor; + /** * @brief MfClassic poller request mode event data. * @@ -77,6 +114,12 @@ typedef struct { uint8_t sectors_read; /**< Number of sectors read. */ uint8_t keys_found; /**< Number of keys found. */ uint8_t current_sector; /**< Current sector number. */ + MfClassicNestedPhase nested_phase; /**< Nested attack phase. */ + MfClassicPrngType prng_type; /**< PRNG (weak or hard). */ + MfClassicBackdoor 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; /** @@ -170,13 +213,15 @@ typedef struct { * @param[in] block_num block number for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_get_nt( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt); + MfClassicNt* nt, + bool backdoor_auth); /** * @brief Collect tag nonce during nested authentication. @@ -189,13 +234,15 @@ MfClassicError mf_classic_poller_get_nt( * @param[in] block_num block number for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_get_nt_nested( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt); + MfClassicNt* nt, + bool backdoor_auth); /** * @brief Perform authentication. @@ -210,6 +257,7 @@ MfClassicError mf_classic_poller_get_nt_nested( * @param[in] key key to be used for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_auth( @@ -217,20 +265,23 @@ MfClassicError mf_classic_poller_auth( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data); + MfClassicAuthContext* data, + bool backdoor_auth); /** * @brief Perform nested authentication. * * Must ONLY be used inside the callback function. * - * Perform nested authentication as specified in Mf Classic protocol. + * Perform nested authentication as specified in Mf Classic protocol. * * @param[in, out] instance pointer to the instance to be used in the transaction. * @param[in] block_num block number for authentication. * @param[in] key key to be used for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. + * @param[in] early_ret return immediately after receiving encrypted nonce. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_auth_nested( @@ -238,7 +289,9 @@ MfClassicError mf_classic_poller_auth_nested( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data); + MfClassicAuthContext* data, + bool backdoor_auth, + bool early_ret); /** * @brief Halt the tag. diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 949ef8e66..deccdbcda 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -5,7 +5,7 @@ #include -#define TAG "MfCLassicPoller" +#define TAG "MfClassicPoller" MfClassicError mf_classic_process_error(Iso14443_3aError error) { MfClassicError ret = MfClassicErrorNone; @@ -38,13 +38,20 @@ static MfClassicError mf_classic_poller_get_nt_common( uint8_t block_num, MfClassicKeyType key_type, MfClassicNt* nt, - bool is_nested) { + bool is_nested, + bool backdoor_auth) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; do { - uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : - MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_type; + if(!backdoor_auth) { + auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + } else { + auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : + MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; + } uint8_t auth_cmd[2] = {auth_type, block_num}; bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); @@ -89,29 +96,34 @@ MfClassicError mf_classic_poller_get_nt( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt) { + MfClassicNt* nt, + bool backdoor_auth) { furi_check(instance); - return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, false); + return mf_classic_poller_get_nt_common( + instance, block_num, key_type, nt, false, backdoor_auth); } MfClassicError mf_classic_poller_get_nt_nested( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt) { + MfClassicNt* nt, + bool backdoor_auth) { furi_check(instance); - return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, true); + return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, true, backdoor_auth); } -static MfClassicError mf_classic_poller_auth_common( +MfClassicError mf_classic_poller_auth_common( MfClassicPoller* instance, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, - bool is_nested) { + bool is_nested, + bool backdoor_auth, + bool early_ret) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -122,14 +134,16 @@ static MfClassicError mf_classic_poller_auth_common( MfClassicNt nt = {}; if(is_nested) { - ret = mf_classic_poller_get_nt_nested(instance, block_num, key_type, &nt); + ret = + mf_classic_poller_get_nt_nested(instance, block_num, key_type, &nt, backdoor_auth); } else { - ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); + ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt, backdoor_auth); } if(ret != MfClassicErrorNone) break; if(data) { data->nt = nt; } + if(early_ret) break; uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); uint64_t key_num = bit_lib_bytes_to_num_be(key->data, sizeof(MfClassicKey)); @@ -182,10 +196,12 @@ MfClassicError mf_classic_poller_auth( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data) { + MfClassicAuthContext* data, + bool backdoor_auth) { furi_check(instance); furi_check(key); - return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false); + return mf_classic_poller_auth_common( + instance, block_num, key, key_type, data, false, backdoor_auth, false); } MfClassicError mf_classic_poller_auth_nested( @@ -193,10 +209,13 @@ MfClassicError mf_classic_poller_auth_nested( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data) { + MfClassicAuthContext* data, + bool backdoor_auth, + bool early_ret) { furi_check(instance); furi_check(key); - return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, true); + return mf_classic_poller_auth_common( + instance, block_num, key, key_type, data, true, backdoor_auth, early_ret); } MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { 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 14a7c61fd..915c899c3 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,13 +3,40 @@ #include "mf_classic_poller.h" #include #include +#include #include +#include +#include +#include +#include #ifdef __cplusplus extern "C" { #endif -#define MF_CLASSIC_FWT_FC (60000) +#define MF_CLASSIC_FWT_FC (60000) +#define NFC_FOLDER EXT_PATH("nfc") +#define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") +#define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) +#define MF_CLASSIC_NESTED_NT_HARD_MINIMUM (3) +#define MF_CLASSIC_NESTED_RETRY_MAXIMUM (60) +#define MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM (3) +#define MF_CLASSIC_NESTED_CALIBRATION_COUNT (21) +#define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" +#define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" +#define MF_CLASSIC_NESTED_USER_DICT_FILE_NAME "mf_classic_dict_user_nested.nfc" +#define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) +#define MF_CLASSIC_NESTED_SYSTEM_DICT_PATH \ + (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME) +#define MF_CLASSIC_NESTED_USER_DICT_PATH \ + (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_USER_DICT_FILE_NAME) +#define SET_PACKED_BIT(arr, bit) ((arr)[(bit) / 8] |= (1 << ((bit) % 8))) +#define GET_PACKED_BIT(arr, bit) ((arr)[(bit) / 8] & (1 << ((bit) % 8))) + +extern const MfClassicKey auth1_backdoor_key; +extern const MfClassicKey auth2_backdoor_key; +extern const MfClassicKey auth3_backdoor_key; +extern const uint16_t valid_sums[19]; typedef enum { MfClassicAuthStateIdle, @@ -21,6 +48,28 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; +typedef struct { + MfClassicKey key; + MfClassicBackdoor type; +} MfClassicBackdoorKeyPair; + +extern const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[]; +extern const size_t mf_classic_backdoor_keys_count; + +typedef struct { + uint32_t cuid; // Card UID + uint8_t key_idx; // Key index + uint32_t nt; // Nonce + uint32_t nt_enc; // Encrypted nonce + uint8_t par; // Parity + uint16_t dist; // Distance +} MfClassicNestedNonce; + +typedef struct { + MfClassicNestedNonce* nonces; + size_t count; +} MfClassicNestedNonceArray; + typedef enum { MfClassicPollerStateDetectType, MfClassicPollerStateStart, @@ -38,17 +87,29 @@ typedef enum { // Dict attack states MfClassicPollerStateNextSector, + MfClassicPollerStateAnalyzeBackdoor, + MfClassicPollerStateBackdoorReadSector, MfClassicPollerStateRequestKey, MfClassicPollerStateReadSector, MfClassicPollerStateAuthKeyA, MfClassicPollerStateAuthKeyB, MfClassicPollerStateKeyReuseStart, + MfClassicPollerStateKeyReuseStartNoOffset, MfClassicPollerStateKeyReuseAuthKeyA, MfClassicPollerStateKeyReuseAuthKeyB, MfClassicPollerStateKeyReuseReadSector, MfClassicPollerStateSuccess, MfClassicPollerStateFail, + // Enhanced dictionary attack states + MfClassicPollerStateNestedAnalyzePRNG, + MfClassicPollerStateNestedCalibrate, + MfClassicPollerStateNestedCollectNt, + MfClassicPollerStateNestedController, + MfClassicPollerStateNestedCollectNtEnc, + MfClassicPollerStateNestedDictAttack, + MfClassicPollerStateNestedLog, + MfClassicPollerStateNum, } MfClassicPollerState; @@ -70,6 +131,30 @@ typedef struct { bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; + MfClassicBackdoor backdoor; + // Enhanced dictionary attack and nested nonce collection + bool enhanced_dict; + MfClassicNestedPhase nested_phase; + MfClassicKey nested_known_key; + MfClassicKeyType nested_known_key_type; + bool current_key_checked; + uint8_t nested_known_key_sector; + uint16_t nested_target_key; + MfClassicNestedNonceArray nested_nonce; + MfClassicPrngType prng_type; + bool static_encrypted; + uint32_t static_encrypted_nonce; + bool calibrated; + uint16_t d_min; + uint16_t d_max; + uint8_t attempt_count; + KeysDict* mf_classic_system_dict; + KeysDict* mf_classic_user_dict; + // Hardnested + 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/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c index cc6bc0908..13b51786f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c @@ -37,7 +37,8 @@ static MfClassicError mf_classic_poller_collect_nt_handler( poller, data->collect_nt_context.block, data->collect_nt_context.key_type, - &data->collect_nt_context.nt); + &data->collect_nt_context.nt, + false); } static MfClassicError @@ -47,7 +48,8 @@ static MfClassicError data->auth_context.block_num, &data->auth_context.key, data->auth_context.key_type, - &data->auth_context); + &data->auth_context, + false); } static MfClassicError mf_classic_poller_read_block_handler( @@ -61,7 +63,8 @@ static MfClassicError mf_classic_poller_read_block_handler( data->read_block_context.block_num, &data->read_block_context.key, data->read_block_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_read_block( @@ -87,7 +90,8 @@ static MfClassicError mf_classic_poller_write_block_handler( data->read_block_context.block_num, &data->read_block_context.key, data->read_block_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_write_block( @@ -113,7 +117,8 @@ static MfClassicError mf_classic_poller_read_value_handler( data->read_value_context.block_num, &data->read_value_context.key, data->read_value_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; MfClassicBlock block = {}; @@ -144,7 +149,8 @@ static MfClassicError mf_classic_poller_change_value_handler( data->change_value_context.block_num, &data->change_value_context.key, data->change_value_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_value_cmd( diff --git a/lib/toolbox/bit_buffer.c b/lib/toolbox/bit_buffer.c index 85a52e79d..e261e80d4 100644 --- a/lib/toolbox/bit_buffer.c +++ b/lib/toolbox/bit_buffer.c @@ -113,7 +113,7 @@ void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size uint8_t bit = FURI_BIT(data[bits_processed / BITS_IN_BYTE + 1], bits_processed % BITS_IN_BYTE); - if(bits_processed % BITS_IN_BYTE) { + if((bits_processed % BITS_IN_BYTE) == 0) { buf->parity[curr_byte / BITS_IN_BYTE] = bit; } else { buf->parity[curr_byte / BITS_IN_BYTE] |= bit << (bits_processed % BITS_IN_BYTE); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7943c4cfc..be60c6a1c 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.2,, +Version,+,77.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c121fc716..5ad8c3be3 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.2,, +Version,+,77.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -890,10 +890,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* @@ -2507,6 +2512,7 @@ Function,+,mf_classic_get_base_data,Iso14443_3aData*,const MfClassicData* Function,+,mf_classic_get_blocks_num_in_sector,uint8_t,uint8_t Function,+,mf_classic_get_device_name,const char*,"const MfClassicData*, NfcDeviceNameType" Function,+,mf_classic_get_first_block_num_of_sector,uint8_t,uint8_t +Function,+,mf_classic_get_key,MfClassicKey,"const MfClassicData*, uint8_t, MfClassicKeyType" Function,+,mf_classic_get_read_sectors_and_keys,void,"const MfClassicData*, uint8_t*, uint8_t*" Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"const MfClassicData*, uint8_t" @@ -2525,10 +2531,10 @@ Function,+,mf_classic_is_sector_read,_Bool,"const MfClassicData*, uint8_t" Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" -Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" -Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" -Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" -Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool" +Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool, _Bool" +Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*, _Bool" +Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*, _Bool" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" Function,+,mf_classic_poller_send_custom_parity_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" @@ -2824,6 +2830,7 @@ Function,+,nfc_set_mask_receive_time_fc,void,"Nfc*, uint32_t" Function,+,nfc_start,void,"Nfc*, NfcEventCallback, void*" Function,+,nfc_stop,void,Nfc* 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,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" @@ -2938,7 +2945,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,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t"