From 3f34537b5b244ea7935283e04a8d413f985331b1 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 28 Oct 2024 11:08:10 -0400 Subject: [PATCH 01/21] Add dictionary asset --- .../main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc diff --git a/applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc new file mode 100644 index 000000000..6d537e36e --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc @@ -0,0 +1,10 @@ +# Hexadecimal-Reversed Sample Key +12E4143455F495649454D4B414542524 +# Byte-Reversed Sample Key (!NACUOYFIEMKAERB) +214E4143554F594649454D4B41455242 +# Sample Key (BREAKMEIFYOUCAN!) +425245414B4D454946594F5543414E21 +# Semnox Key (IEMKAERB!NACUOY ) +49454D4B41455242214E4143554F5900 +# Modified Semnox Key (IEMKAERB!NACUOYF) +49454D4B41455242214E4143554F5946 \ No newline at end of file From 60e0a697fa9dadf5f1587bd20cd2252cfe572522 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 28 Oct 2024 19:56:28 -0400 Subject: [PATCH 02/21] Initial structure for UL-C dictionary attacks --- .../mf_ultralight/mf_ultralight.c | 21 +- applications/main/nfc/nfc_app_i.h | 14 ++ ...ul_c_dict.nfc => mf_ultralight_c_dict.nfc} | 0 .../main/nfc/scenes/nfc_scene_config.h | 1 + .../main/nfc/scenes/nfc_scene_extra_actions.c | 10 + .../nfc_scene_mf_ultralight_c_dict_attack.c | 202 ++++++++++++++++++ .../mf_ultralight/mf_ultralight_poller.c | 64 +++++- .../mf_ultralight/mf_ultralight_poller.h | 21 +- .../mf_ultralight/mf_ultralight_poller_i.h | 8 + 9 files changed, 330 insertions(+), 11 deletions(-) rename applications/main/nfc/resources/nfc/assets/{mf_ul_c_dict.nfc => mf_ultralight_c_dict.nfc} (100%) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 3adf2a1f5..6edbc06a2 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -166,15 +166,26 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - nfc_unlock_helper_card_detected_handler(instance); - } else if(event.event == NfcCustomEventPollerIncomplete) { - notification_message(instance->notifications, &sequence_semi_success); + if(event.event == NfcCustomEventPollerSuccess) { + notification_message(instance->notifications, &sequence_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); + return true; + } else if(event.event == NfcCustomEventPollerIncomplete) { + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + if(data->type == MfUltralightTypeMfulC) { + // Start dict attack for MFUL C cards + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } else { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + return true; } } - return true; + return false; } static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 14e484622..e13af79aa 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,10 @@ #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") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict_user.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict.nfc") typedef enum { NfcRpcStateIdle, @@ -105,6 +110,14 @@ typedef struct { bool enhanced_dict; } NfcMfClassicDictAttackContext; +typedef struct { + KeysDict* dict; + bool auth_success; + bool is_card_present; + size_t dict_keys_total; + size_t dict_keys_current; +} NfcMfUltralightCDictContext; + struct NfcApp { DialogsApp* dialogs; Storage* storage; @@ -143,6 +156,7 @@ struct NfcApp { MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; + NfcMfUltralightCDictContext mf_ultralight_c_dict_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; MfClassicKeyCache* mfc_key_cache; diff --git a/applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc similarity index 100% rename from applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc rename to applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 83c8ffeed..1b9d4367d 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -25,6 +25,7 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, save_confirm, SaveConfirm) +ADD_SCENE(nfc, mf_ultralight_c_dict_attack, MfUltralightCDictAttack) ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail) diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 2943c0c55..5753d6449 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -3,6 +3,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, + SubmenuIndexMfUlCKeys, SubmenuIndexMfUltralightUnlock, SubmenuIndexSlixUnlock, }; @@ -29,6 +30,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, instance); + submenu_add_item( + submenu, + "MIFARE Ultralight C Keys", + SubmenuIndexMfUlCKeys, + nfc_scene_extra_actions_submenu_callback, + instance); submenu_add_item( submenu, "Unlock NTAG/Ultralight", @@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexMfClassicKeys) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); consumed = true; + } else if(event.event == SubmenuIndexMfUlCKeys) { + // TODO: Add MFUL C key management scene here + consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { mf_ultralight_auth_reset(instance->mf_ul_auth); scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c new file mode 100644 index 000000000..d1b2c0978 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -0,0 +1,202 @@ +#include "../nfc_app_i.h" +#include + +#define TAG "NfcMfUlCDictAttack" + +enum { + DictAttackStateUserDictInProgress, + DictAttackStateSystemDictInProgress, +}; + +NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfUltralight); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; + MfUltralightPollerEvent* poller_event = event.event_data; + + if(poller_event->type == MfUltralightPollerEventTypeRequestMode) { + poller_event->data->poller_mode = MfUltralightPollerModeDictAttack; + command = NfcCommandContinue; + } else if(poller_event->type == MfUltralightPollerEventTypeRequestKey) { + MfUltralightC3DesAuthKey key = {}; + if(keys_dict_get_next_key( + instance->mf_ultralight_c_dict_context.dict, + key.data, + sizeof(MfUltralightC3DesAuthKey))) { + poller_event->data->key_request_data.key = key; + poller_event->data->key_request_data.key_provided = true; + instance->mf_ultralight_c_dict_context.dict_keys_current++; + + if(instance->mf_ultralight_c_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } + } else { + poller_event->data->key_request_data.key_provided = false; + } + } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackComplete); + command = NfcCommandStop; + } + return command; +} + +void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback( + DictAttackEvent event, + void* context) { + furi_assert(context); + NfcApp* instance = context; + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventDictAttackSkip); + } +} + +void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + if(state == DictAttackStateUserDictInProgress) { + do { + if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict) == 0) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + dict_attack_set_header(instance->dict_attack, "MFUL C User Dictionary"); + } while(false); + } + if(state == DictAttackStateSystemDictInProgress) { + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + dict_attack_set_header(instance->dict_attack, "MFUL C System Dictionary"); + } + + instance->mf_ultralight_c_dict_context.dict_keys_total = + keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict); + dict_attack_set_total_dict_keys( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_total); + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + dict_attack_set_callback( + instance->dict_attack, + nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, + instance); + scene_manager_set_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack, state); +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_enter(void* context) { + NfcApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start(instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + + nfc_blink_read_start(instance); +} + +void nfc_scene_mf_ul_c_dict_attack_update_view(NfcApp* instance) { + dict_attack_set_card_state( + instance->dict_attack, instance->mf_ultralight_c_dict_context.is_card_present); + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); +} + +bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventDictAttackComplete) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + consumed = true; + } else { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } else if(event.event == NfcCustomEventDictAttackDataUpdate) { + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + consumed = true; + } else if(event.event == NfcCustomEventDictAttackSkip) { + if(instance->mf_ultralight_c_dict_context.is_card_present) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, + nfc_mf_ultralight_c_dict_attack_worker_callback, + instance); + } else { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + return consumed; +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) { + NfcApp* instance = context; + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict_keys_total = 0; + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + instance->mf_ultralight_c_dict_context.auth_success = false; + instance->mf_ultralight_c_dict_context.is_card_present = false; + nfc_blink_stop(instance); +} diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 0e5ff0011..08136041c 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -181,6 +181,10 @@ MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_po instance->general_event.protocol = NfcProtocolMfUltralight; instance->general_event.event_data = &instance->mfu_event; instance->general_event.instance = instance; + + instance->dict_attack_ctx.auth_success = false; + instance->dict_attack_ctx.is_card_present = false; + mbedtls_des3_init(&instance->des_context); return instance; } @@ -521,7 +525,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in } else { if(instance->data->type == MfUltralightTypeMfulC && !mf_ultralight_3des_key_valid(instance->data)) { - instance->state = MfUltralightPollerStateCheckMfulCAuthStatus; + instance->state = MfUltralightPollerStateDictAttack; } else { FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read); if(instance->pages_read) { @@ -730,6 +734,63 @@ static NfcCommand mf_ultralight_poller_handler_write_success(MfUltralightPoller* return command; } +static NfcCommand mf_ultralight_poller_handler_dict_attack(MfUltralightPoller* instance) { + NfcCommand command = NfcCommandContinue; + const MfUltralightData* tag_data = instance->data; + uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type); + FURI_LOG_D(TAG, "Dict Attack"); + do { + if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportAuthenticate)) { + instance->error = MfUltralightErrorProtocol; + instance->state = MfUltralightPollerStateReadFailed; + break; + } + + instance->dict_attack_ctx.is_card_present = true; + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadSuccess; + break; + } + + FURI_LOG_D(TAG, "Trying next 3DES key"); + instance->error = MfUltralightErrorNone; + instance->auth_context.auth_success = false; + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + do { + uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; + uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + furi_hal_random_fill_buf(RndA, sizeof(RndA)); + instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output); + if(instance->error != MfUltralightErrorNone) break; + + uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; + instance->error = mf_ultralight_poller_authenticate_end( + instance, RndB, output, decoded_shifted_RndA); + if(instance->error != MfUltralightErrorNone) break; + + mf_ultralight_3des_shift_data(RndA); + instance->auth_context.auth_success = + (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); + if(instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Dict attack success"); + instance->state = MfUltralightPollerStateReadPages; + instance->dict_attack_ctx.auth_success = true; + break; + } + } while(false); + + if(!instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Dict attack auth failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + } while(false); + + return command; +} + static const MfUltralightPollerReadHandler mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = { [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle, @@ -755,6 +816,7 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, + [MfUltralightPollerStateDictAttack] = mf_ultralight_poller_handler_dict_attack, }; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index e50017324..2552abeb5 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -27,6 +27,7 @@ typedef enum { MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */ MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ + MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */ } MfUltralightPollerEventType; /** @@ -35,6 +36,7 @@ typedef enum { typedef enum { MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */ MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */ + MfUltralightPollerModeDictAttack, /**< Poller will perform dictionary attack against card. */ } MfUltralightPollerMode; /** @@ -42,20 +44,29 @@ typedef enum { */ typedef struct { MfUltralightAuthPassword password; /**< Password to be used for authentication. */ - MfUltralightC3DesAuthKey tdes_key; - MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */ + MfUltralightC3DesAuthKey tdes_key; /**< 3DES key to be used for authentication. */ + MfUltralightAuthPack pack; /**< Pack received on successful authentication. */ bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */ bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */ } MfUltralightPollerAuthContext; +/** + * @brief MfUltralight poller key request data. + */ +typedef struct { + MfUltralightC3DesAuthKey key; /**< Key to try. */ + bool key_provided; /**< Set to true if key was provided, false to stop attack. */ +} MfUltralightPollerKeyRequestData; + /** * @brief MfUltralight poller event data. */ typedef union { MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ MfUltralightError error; /**< Error code indicating reading fail reason. */ - const MfUltralightData* write_data; - MfUltralightPollerMode poller_mode; + const MfUltralightData* write_data; /**< Data to be written to card. */ + MfUltralightPollerMode poller_mode; /**< Mode to operate in. */ + MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */ } MfUltralightPollerEventData; /** @@ -64,7 +75,7 @@ typedef union { * Upon emission of an event, an instance of this struct will be passed to the callback. */ typedef struct { - MfUltralightPollerEventType type; /**< Type of emmitted event. */ + MfUltralightPollerEventType type; /**< Type of emitted event. */ MfUltralightPollerEventData* data; /**< Pointer to event specific data. */ } MfUltralightPollerEvent; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index b35c49aea..bbd9c6c3e 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -68,10 +68,17 @@ typedef enum { MfUltralightPollerStateWritePages, MfUltralightPollerStateWriteFail, MfUltralightPollerStateWriteSuccess, + MfUltralightPollerStateDictAttack, MfUltralightPollerStateNum, } MfUltralightPollerState; +typedef struct { + uint8_t sectors_total; + bool auth_success; + bool is_card_present; +} MfUltralightPollerDictAttackContext; + struct MfUltralightPoller { Iso14443_3aPoller* iso14443_3a_poller; MfUltralightPollerState state; @@ -89,6 +96,7 @@ struct MfUltralightPoller { uint8_t tearing_flag_total; uint16_t current_page; MfUltralightError error; + MfUltralightPollerDictAttackContext dict_attack_ctx; mbedtls_des3_context des_context; NfcGenericEvent general_event; From 147a81c38e65faae526f1dc8270ec59fe0facc42 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 28 Oct 2024 22:28:13 -0400 Subject: [PATCH 03/21] Document Ultralight C dictionary file format --- documentation/file_formats/NfcFileFormats.md | 26 +++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index da0b0a19d..d89483390 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -36,7 +36,7 @@ Version differences: ATQA: 00 44 SAK: 00 -### Description +### Description This file format is used to store the UID, SAK and ATQA of an ISO14443-3A device. UID must be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. @@ -56,7 +56,7 @@ None, there are no versions yet. Application data: 00 12 34 FF Protocol info: 11 81 E1 -### Description +### Description This file format is used to store the UID, Application data and Protocol info of a ISO14443-3B device. UID must be 4 bytes long. Application data is 4 bytes long. Protocol info is 3 bytes long. @@ -80,7 +80,7 @@ None, there are no versions yet. # ISO14443-4A specific data ATS: 06 75 77 81 02 80 -### Description +### Description This file format is used to store the UID, SAK and ATQA of a ISO14443-4A device. It also stores the Answer to Select (ATS) data of the card. ATS must be no less than 5 bytes long. @@ -303,6 +303,26 @@ None, there are no versions yet. This file contains a list of Mifare Classic keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well. +## Mifare Ultralight C Dictionary + +### Example + + # Hexadecimal-Reversed Sample Key + 12E4143455F495649454D4B414542524 + # Byte-Reversed Sample Key (!NACUOYFIEMKAERB) + 214E4143554F594649454D4B41455242 + # Sample Key (BREAKMEIFYOUCAN!) + 425245414B4D454946594F5543414E21 + # Semnox Key (IEMKAERB!NACUOY ) + 49454D4B41455242214E4143554F5900 + # Modified Semnox Key (IEMKAERB!NACUOYF) + 49454D4B41455242214E4143554F5946 + ... + +### Description + +This file contains a list of Mifare Ultralight C keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well. + ## EMV resources ### Example From c3b8c54aa0fb852994b2bf05c0107b55abcd07f6 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 10 Nov 2024 16:19:44 -0500 Subject: [PATCH 04/21] Support adding and removing new Ultralight C keys --- .../main/nfc/scenes/nfc_scene_config.h | 6 + .../nfc/scenes/nfc_scene_delete_success.c | 4 + .../main/nfc/scenes/nfc_scene_extra_actions.c | 8 +- .../nfc/scenes/nfc_scene_mf_classic_keys.c | 2 - .../scenes/nfc_scene_mf_classic_keys_add.c | 2 +- .../scenes/nfc_scene_mf_ultralight_c_keys.c | 96 ++++++++++++++++ .../nfc_scene_mf_ultralight_c_keys_add.c | 63 ++++++++++ .../nfc_scene_mf_ultralight_c_keys_delete.c | 108 ++++++++++++++++++ .../nfc_scene_mf_ultralight_c_keys_list.c | 66 +++++++++++ ...cene_mf_ultralight_c_keys_warn_duplicate.c | 49 ++++++++ .../main/nfc/scenes/nfc_scene_save_success.c | 4 + .../services/dolphin/helpers/dolphin_deed.c | 2 +- .../services/dolphin/helpers/dolphin_deed.h | 2 +- applications/system/mfkey/mfkey.c | 2 +- lib/toolbox/keys_dict.c | 19 +-- 15 files changed, 409 insertions(+), 24 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 1b9d4367d..399d59b92 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -58,6 +58,12 @@ ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) +ADD_SCENE(nfc, mf_ultralight_c_keys, MfUltralightCKeys) +ADD_SCENE(nfc, mf_ultralight_c_keys_list, MfUltralightCKeysList) +ADD_SCENE(nfc, mf_ultralight_c_keys_delete, MfUltralightCKeysDelete) +ADD_SCENE(nfc, mf_ultralight_c_keys_add, MfUltralightCKeysAdd) +ADD_SCENE(nfc, mf_ultralight_c_keys_warn_duplicate, MfUltralightCKeysWarnDuplicate) + ADD_SCENE(nfc, set_type, SetType) ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index d41e52549..d8308addd 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -28,6 +28,10 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneFileSelect); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 5753d6449..6720b2d7b 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -3,7 +3,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, - SubmenuIndexMfUlCKeys, + SubmenuIndexMfUltralightCKeys, SubmenuIndexMfUltralightUnlock, SubmenuIndexSlixUnlock, }; @@ -33,7 +33,7 @@ void nfc_scene_extra_actions_on_enter(void* context) { submenu_add_item( submenu, "MIFARE Ultralight C Keys", - SubmenuIndexMfUlCKeys, + SubmenuIndexMfUltralightCKeys, nfc_scene_extra_actions_submenu_callback, instance); submenu_add_item( @@ -61,8 +61,8 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexMfClassicKeys) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); consumed = true; - } else if(event.event == SubmenuIndexMfUlCKeys) { - // TODO: Add MFUL C key management scene here + } else if(event.event == SubmenuIndexMfUltralightCKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeys); consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { mf_ultralight_auth_reset(instance->mf_ul_auth); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index eaa054149..7ee203285 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -1,7 +1,5 @@ #include "../nfc_app_i.h" -#define NFC_SCENE_MF_CLASSIC_KEYS_MAX (100) - void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) { NfcApp* instance = context; if(type == InputTypeShort) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c index a963f44ac..131b5e230 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c @@ -39,7 +39,7 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve instance->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); } else if(keys_dict_add_key(dict, key.data, sizeof(MfClassicKey))) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } else { scene_manager_previous_scene(instance->scene_manager); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c new file mode 100644 index 000000000..9bf96f0b4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c @@ -0,0 +1,96 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_on_enter(void* context) { + NfcApp* instance = context; + + // Load flipper dict keys total + uint32_t flipper_dict_keys_total = 0; + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + flipper_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + // Load user dict keys total + uint32_t user_dict_keys_total = 0; + dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + user_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + FuriString* temp_str = furi_string_alloc(); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Ultralight C Keys"); + furi_string_printf(temp_str, "System dict: %lu", flipper_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 20, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_printf(temp_str, "User dict: %lu", user_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 32, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(instance->widget, 87, 13, &I_Keychain_39x36); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "Add", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + if(user_dict_keys_total > 0) { + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "List", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + } + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysList); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c new file mode 100644 index 000000000..63fdaed49 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c @@ -0,0 +1,63 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_add_byte_input_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_mf_ultralight_c_keys_add_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + ByteInput* byte_input = instance->byte_input; + byte_input_set_header_text(byte_input, "Enter the key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_mf_ultralight_c_keys_add_byte_input_callback, + NULL, + instance, + instance->byte_input_store, + sizeof(MfUltralightC3DesAuthKey)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_mf_ultralight_c_keys_add_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + // Add key to dict + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + MfUltralightC3DesAuthKey key = {}; + memcpy(key.data, instance->byte_input_store, sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_is_key_present(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysWarnDuplicate); + } else if(keys_dict_add_key(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed(DolphinDeedNfcKeyAdd); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + + keys_dict_free(dict); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_add_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c new file mode 100644 index 000000000..db2903939 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c @@ -0,0 +1,108 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_delete_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_enter(void* context) { + NfcApp* instance = context; + + uint32_t key_index = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + FuriString* key_str = furi_string_alloc(); + + widget_add_string_element( + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Cancel", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Delete", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + furi_string_reset(key_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(key_str, "%02X", stack_key.data[i]); + } + + widget_add_string_element( + instance->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(key_str)); + + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(key_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_delete_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + uint32_t key_index = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + bool key_delete_success = keys_dict_delete_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + keys_dict_free(mf_ultralight_c_user_dict); + if(key_delete_success) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDeleteSuccess); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(instance->scene_manager); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c new file mode 100644 index 000000000..e2fda3aea --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c @@ -0,0 +1,66 @@ +#include "../nfc_app_i.h" + +#define NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX (100) + +void nfc_scene_mf_ultralight_c_keys_list_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_mf_ultralight_c_keys_list_on_enter(void* context) { + NfcApp* instance = context; + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + submenu_set_header(instance->submenu, "Select key to delete:"); + FuriString* temp_str = furi_string_alloc(); + + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + size_t keys_num = MIN((size_t)NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX, dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + + if(keys_num > 0) { + for(size_t i = 0; i < keys_num; i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + furi_string_reset(temp_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(temp_str, "%02X", stack_key.data[i]); + } + submenu_add_item( + instance->submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_mf_ultralight_c_keys_list_submenu_callback, + instance); + } + } + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mf_ultralight_c_keys_list_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_list_on_exit(void* context) { + NfcApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c new file mode 100644 index 000000000..c8881e5d4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c @@ -0,0 +1,49 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + Popup* popup = instance->popup; + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); + popup_set_header(popup, "Key Already Exists!", 64, 3, AlignCenter, AlignTop); + popup_set_text( + popup, + "Please enter a\n" + "different key.", + 4, + 24, + AlignLeft, + AlignTop); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_exit(void* context) { + NfcApp* instance = context; + + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 1c76b3f87..9dbfd1ae7 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -28,6 +28,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); consumed = true; diff --git a/applications/services/dolphin/helpers/dolphin_deed.c b/applications/services/dolphin/helpers/dolphin_deed.c index f1f42b770..43f30dced 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.c +++ b/applications/services/dolphin/helpers/dolphin_deed.c @@ -20,7 +20,7 @@ static const DolphinDeedWeight dolphin_deed_weights[] = { {3, DolphinAppNfc}, // DolphinDeedNfcSave {1, DolphinAppNfc}, // DolphinDeedNfcDetectReader {2, DolphinAppNfc}, // DolphinDeedNfcEmulate - {2, DolphinAppNfc}, // DolphinDeedNfcMfcAdd + {2, DolphinAppNfc}, // DolphinDeedNfcKeyAdd {1, DolphinAppNfc}, // DolphinDeedNfcAddSave {1, DolphinAppNfc}, // DolphinDeedNfcAddEmulate diff --git a/applications/services/dolphin/helpers/dolphin_deed.h b/applications/services/dolphin/helpers/dolphin_deed.h index c9cd18f31..7202dcf07 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.h +++ b/applications/services/dolphin/helpers/dolphin_deed.h @@ -36,7 +36,7 @@ typedef enum { DolphinDeedNfcSave, DolphinDeedNfcDetectReader, DolphinDeedNfcEmulate, - DolphinDeedNfcMfcAdd, + DolphinDeedNfcKeyAdd, DolphinDeedNfcAddSave, DolphinDeedNfcAddEmulate, diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index ae5cc90f2..ad39d186b 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -702,7 +702,7 @@ void mfkey(ProgramState* program_state) { keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); } if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } free(nonce_arr); keys_dict_free(user_dict); diff --git a/lib/toolbox/keys_dict.c b/lib/toolbox/keys_dict.c index 602653e8f..c26e9c1e7 100644 --- a/lib/toolbox/keys_dict.c +++ b/lib/toolbox/keys_dict.c @@ -134,22 +134,21 @@ static void keys_dict_int_to_str(KeysDict* instance, const uint8_t* key_int, Fur furi_string_cat_printf(key_str, "%02X", key_int[i]); } -static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint64_t* key_int) { +static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint8_t* key_out) { furi_assert(instance); furi_assert(key_str); - furi_assert(key_int); + furi_assert(key_out); uint8_t key_byte_tmp; char h, l; - *key_int = 0ULL; - + // Process two hex characters at a time to create each byte for(size_t i = 0; i < instance->key_size_symbols - 1; i += 2) { h = furi_string_get_char(key_str, i); l = furi_string_get_char(key_str, i + 1); args_char_to_hex(h, l, &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2)); + key_out[i / 2] = key_byte_tmp; } } @@ -193,15 +192,7 @@ bool keys_dict_get_next_key(KeysDict* instance, uint8_t* key, size_t key_size) { bool key_read = keys_dict_get_next_key_str(instance, temp_key); if(key_read) { - size_t tmp_len = key_size; - uint64_t key_int = 0; - - keys_dict_str_to_int(instance, temp_key, &key_int); - - while(tmp_len--) { - key[tmp_len] = (uint8_t)key_int; - key_int >>= 8; - } + keys_dict_str_to_int(instance, temp_key, key); } furi_string_free(temp_key); From e583c105b98215e8192dce3a73e8723d20679294 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 10 Nov 2024 17:08:55 -0500 Subject: [PATCH 05/21] Fix ByteInput to properly zero bytes --- applications/services/gui/modules/byte_input.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index be94ed9ab..71a13413f 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -861,6 +861,10 @@ void byte_input_set_result_callback( model->callback_context = callback_context; model->bytes = bytes; model->bytes_count = bytes_count; + // Zero out the bytes + for(uint8_t i = 0; i < bytes_count; i++) { + model->bytes[i] = 0; + } }, true); } From 92b9bd039ee0231f52777d37614e65b9c76ec5d0 Mon Sep 17 00:00:00 2001 From: Nathan N Date: Sun, 10 Nov 2024 22:40:14 -0500 Subject: [PATCH 06/21] Add more misc keys to mf_ultralight_c_dict.nfc --- .../nfc/assets/mf_ultralight_c_dict.nfc | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc index 6d537e36e..fa5dbb1fb 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc @@ -1,10 +1,55 @@ +# Sample Key (BREAKMEIFYOUCAN!) +425245414B4D454946594F5543414E21 # Hexadecimal-Reversed Sample Key 12E4143455F495649454D4B414542524 # Byte-Reversed Sample Key (!NACUOYFIEMKAERB) 214E4143554F594649454D4B41455242 -# Sample Key (BREAKMEIFYOUCAN!) -425245414B4D454946594F5543414E21 # Semnox Key (IEMKAERB!NACUOY ) 49454D4B41455242214E4143554F5900 # Modified Semnox Key (IEMKAERB!NACUOYF) -49454D4B41455242214E4143554F5946 \ No newline at end of file +49454D4B41455242214E4143554F5946 + +# Mix of Proxmark and ChameleonMiniLiveDebugger +00000000000000000000000000000000 +000102030405060708090A0B0C0D0E0F +01010101010101010101010101010101 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +00112233445566778899AABBCCDDEEFF +47454D5850524553534F53414D504C45 +79702553797025537970255379702553 +4E617468616E2E4C6920546564647920 +43464F494D48504E4C4359454E528841 +6AC292FAA1315B4D858AB3A3D7D5933A +404142434445464748494A4B4C4D4E4F +2B7E151628AED2A6ABF7158809CF4F3C +FBEED618357133667C85E08F7236A8DE +F7DDAC306AE266CCF90BC11EE46D513B +54686973206973206D79206B65792020 +A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 +B0B1B2B3B4B5B6B7B0B1B2B3B4B5B6B7 +B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF +D3F7D3F7D3F7D3F7D3F7D3F7D3F7D3F7 +11111111111111111111111111111111 +22222222222222222222222222222222 +33333333333333333333333333333333 +44444444444444444444444444444444 +55555555555555555555555555555555 +66666666666666666666666666666666 +77777777777777777777777777777777 +88888888888888888888888888888888 +99999999999999999999999999999999 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +0102030405060708090A0B0C0D0E0F10 +00010203040506070809101112131415 +01020304050607080910111213141516 +16151413121110090807060504030201 +15141312111009080706050403020100 +0F0E0D0C0B0A09080706050403020100 +100F0E0D0C0B0A090807060504030201 +303132333435363738393A3B3C3D3E3F +9CABF398358405AE2F0E2B3D31C99A8A +605F5E5D5C5B5A59605F5E5D5C5B5A59 From 948dffadcc1f8748d32583455209ee77035a21e8 Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 20 Dec 2024 18:07:18 -0500 Subject: [PATCH 07/21] Dictionary attack functional --- .../mf_ultralight/mf_ultralight.c | 18 ++- .../mf_ultralight/mf_ultralight_poller.c | 139 ++++++++---------- .../mf_ultralight/mf_ultralight_poller_i.c | 6 +- .../mf_ultralight/mf_ultralight_poller_i.h | 8 - 4 files changed, 81 insertions(+), 90 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 6edbc06a2..daf9bedea 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -15,6 +15,7 @@ enum { SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, SubmenuIndexWrite, + SubmenuIndexDictAttack }; enum { @@ -150,7 +151,8 @@ static NfcCommand } if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - mf_ultralight_event->data->auth_context.tdes_key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + // TODO: Key provided attribute is not set } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; @@ -201,6 +203,14 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc SubmenuIndexUnlock, nfc_protocol_support_common_submenu_callback, instance); + if(data->type == MfUltralightTypeMfulC) { + submenu_add_item( + submenu, + "Unlock with Dictionary", + SubmenuIndexDictAttack, + nfc_protocol_support_common_submenu_callback, + instance); + } } else if( data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 || data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 || @@ -269,6 +279,12 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( } else if(event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); consumed = true; + } else if(event.event == SubmenuIndexDictAttack) { + if(!scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCDictAttack)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } + consumed = true; } } return consumed; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index f48348c17..c697544c7 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -181,10 +181,6 @@ MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_po instance->general_event.protocol = NfcProtocolMfUltralight; instance->general_event.event_data = &instance->mfu_event; instance->general_event.instance = instance; - - instance->dict_attack_ctx.auth_success = false; - instance->dict_attack_ctx.is_card_present = false; - mbedtls_des3_init(&instance->des_context); return instance; } @@ -255,7 +251,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check Ultralight C"); + FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->state = MfUltralightPollerStateDetectMfulC; } @@ -270,7 +266,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo instance->data->type = MfUltralightTypeMfulC; instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check NTAG 203"); + FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203"); instance->state = MfUltralightPollerStateDetectNtag203; } iso14443_3a_poller_halt(instance->iso14443_3a_poller); @@ -456,7 +452,28 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol command = instance->callback(instance->general_event, instance->context); if(!instance->mfu_event.data->auth_context.skip_auth) { FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); - instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + instance->auth_context.auth_success = false; + // For debugging + FURI_LOG_D( + "TAG", + "Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + instance->auth_context.tdes_key.data[0], + instance->auth_context.tdes_key.data[1], + instance->auth_context.tdes_key.data[2], + instance->auth_context.tdes_key.data[3], + instance->auth_context.tdes_key.data[4], + instance->auth_context.tdes_key.data[5], + instance->auth_context.tdes_key.data[6], + instance->auth_context.tdes_key.data[7], + instance->auth_context.tdes_key.data[8], + instance->auth_context.tdes_key.data[9], + instance->auth_context.tdes_key.data[10], + instance->auth_context.tdes_key.data[11], + instance->auth_context.tdes_key.data[12], + instance->auth_context.tdes_key.data[13], + instance->auth_context.tdes_key.data[14], + instance->auth_context.tdes_key.data[15]); do { uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; @@ -473,20 +490,41 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol mf_ultralight_3des_shift_data(RndA); instance->auth_context.auth_success = (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); - if(instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth success"); + FURI_LOG_E(TAG, "Auth success"); + if(instance->mode == MfUltralightPollerModeDictAttack) { + memcpy( + &instance->data->page[44], + instance->auth_context.tdes_key.data, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + instance->data->pages_read = instance->pages_total; + instance->pages_read = instance->pages_total; + instance->state = MfUltralightPollerStateReadSuccess; + } } } while(false); - if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth failed"); + FURI_LOG_E(TAG, "Auth failed"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); + if(instance->mode == MfUltralightPollerModeDictAttack) { + // Not needed? We already do a callback earlier? + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + } else { + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; + instance->state = MfUltralightPollerStateAuthMfulC; + } + } } } } - instance->state = MfUltralightPollerStateReadPages; - + // Regression review + if(instance->mode != MfUltralightPollerModeDictAttack) { + instance->state = MfUltralightPollerStateReadPages; + } return command; } @@ -509,12 +547,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } + // Regression review + const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4; if(instance->error == MfUltralightErrorNone) { - if(start_page < instance->pages_total) { - FURI_LOG_D(TAG, "Read page %d success", start_page); - instance->data->page[start_page] = data.page[0]; - instance->pages_read++; - instance->data->pages_read = instance->pages_read; + for(size_t i = 0; i < read_cnt; i++) { + if(start_page + i < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page + i); + instance->data->page[start_page + i] = data.page[i]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; + } } if(instance->pages_read == instance->pages_total) { @@ -523,7 +565,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in } else { if(instance->data->type == MfUltralightTypeMfulC && !mf_ultralight_3des_key_valid(instance->data)) { - instance->state = MfUltralightPollerStateDictAttack; + instance->state = MfUltralightPollerStateCheckMfulCAuthStatus; } else { FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read); if(instance->pages_read) { @@ -732,63 +774,6 @@ static NfcCommand mf_ultralight_poller_handler_write_success(MfUltralightPoller* return command; } -static NfcCommand mf_ultralight_poller_handler_dict_attack(MfUltralightPoller* instance) { - NfcCommand command = NfcCommandContinue; - const MfUltralightData* tag_data = instance->data; - uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type); - FURI_LOG_D(TAG, "Dict Attack"); - do { - if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportAuthenticate)) { - instance->error = MfUltralightErrorProtocol; - instance->state = MfUltralightPollerStateReadFailed; - break; - } - - instance->dict_attack_ctx.is_card_present = true; - instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; - command = instance->callback(instance->general_event, instance->context); - if(!instance->mfu_event.data->key_request_data.key_provided) { - instance->state = MfUltralightPollerStateReadSuccess; - break; - } - - FURI_LOG_D(TAG, "Trying next 3DES key"); - instance->error = MfUltralightErrorNone; - instance->auth_context.auth_success = false; - instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; - do { - uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; - uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - furi_hal_random_fill_buf(RndA, sizeof(RndA)); - instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output); - if(instance->error != MfUltralightErrorNone) break; - - uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; - instance->error = mf_ultralight_poller_authenticate_end( - instance, RndB, output, decoded_shifted_RndA); - if(instance->error != MfUltralightErrorNone) break; - - mf_ultralight_3des_shift_data(RndA); - instance->auth_context.auth_success = - (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); - if(instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Dict attack success"); - instance->state = MfUltralightPollerStateReadPages; - instance->dict_attack_ctx.auth_success = true; - break; - } - } while(false); - - if(!instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Dict attack auth failed"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); - } - } while(false); - - return command; -} - static const MfUltralightPollerReadHandler mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = { [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle, @@ -814,8 +799,6 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, - [MfUltralightPollerStateDictAttack] = mf_ultralight_poller_handler_dict_attack, - }; static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index 141ab6c46..c769273ec 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -134,7 +134,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start( uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; mf_ultralight_3des_decrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, iv, encRndB, sizeof(encRndB), @@ -145,7 +145,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start( mf_ultralight_3des_encrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, encRndB, output, MF_ULTRALIGHT_C_AUTH_DATA_SIZE, @@ -179,7 +179,7 @@ MfUltralightError mf_ultralight_poller_authenticate_end( mf_ultralight_3des_decrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, RndB, bit_buffer_get_data(instance->rx_buffer) + 1, MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index bbd9c6c3e..b35c49aea 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -68,17 +68,10 @@ typedef enum { MfUltralightPollerStateWritePages, MfUltralightPollerStateWriteFail, MfUltralightPollerStateWriteSuccess, - MfUltralightPollerStateDictAttack, MfUltralightPollerStateNum, } MfUltralightPollerState; -typedef struct { - uint8_t sectors_total; - bool auth_success; - bool is_card_present; -} MfUltralightPollerDictAttackContext; - struct MfUltralightPoller { Iso14443_3aPoller* iso14443_3a_poller; MfUltralightPollerState state; @@ -96,7 +89,6 @@ struct MfUltralightPoller { uint8_t tearing_flag_total; uint16_t current_page; MfUltralightError error; - MfUltralightPollerDictAttackContext dict_attack_ctx; mbedtls_des3_context des_context; NfcGenericEvent general_event; From 374f49319b3b960bad7fc3ca086dc83dfc1e7f57 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 00:21:36 -0400 Subject: [PATCH 08/21] Fix uninitialized key attempt --- .../mf_ultralight/mf_ultralight.c | 11 ++++++++-- .../mf_ultralight/mf_ultralight_poller.c | 21 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index daf9bedea..3c041c077 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -151,8 +151,15 @@ static NfcCommand } if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; - // TODO: Key provided attribute is not set + + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index c697544c7..0c24dadd6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -452,7 +452,26 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol command = instance->callback(instance->general_event, instance->context); if(!instance->mfu_event.data->auth_context.skip_auth) { FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); - instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + // Only use the key if it was actually provided + if(instance->mfu_event.data->key_request_data.key_provided) { + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + } else if(instance->mode == MfUltralightPollerModeDictAttack) { + // TODO: Can logic be rearranged to request this key before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller? + FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary"); + // Trigger dictionary key request + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + return command; + } else { + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + } + } else { + FURI_LOG_D(TAG, "No key provided, skipping auth"); + instance->state = MfUltralightPollerStateReadPages; + return command; + } instance->auth_context.auth_success = false; // For debugging FURI_LOG_D( From 38b5d9fb16f8b3dc579bb02f88f01c411374405b Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 01:10:23 -0400 Subject: [PATCH 09/21] Properly return when a key is found in user dict --- .../nfc_scene_mf_ultralight_c_dict_attack.c | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index d1b2c0978..7e850af98 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -39,6 +39,7 @@ NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { nfc_device_set_data( instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + instance->mf_ultralight_c_dict_context.auth_success = true; view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackComplete); command = NfcCommandStop; @@ -130,18 +131,25 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventDictAttackComplete) { if(state == DictAttackStateUserDictInProgress) { - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - keys_dict_free(instance->mf_ultralight_c_dict_context.dict); - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightCDictAttack, - DictAttackStateSystemDictInProgress); - nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); - instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); - nfc_poller_start( - instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); - consumed = true; + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + consumed = true; + } } else { notification_message(instance->notifications, &sequence_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); From 1c37a0e01cedb99fa107d6600f6ed538a913f7fe Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 01:42:20 -0400 Subject: [PATCH 10/21] Don't skip the system dictionary --- .../nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 7e850af98..f0b2e5de6 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -39,7 +39,12 @@ NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { nfc_device_set_data( instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); - instance->mf_ultralight_c_dict_context.auth_success = true; + // Check if this is a successful authentication by looking at the poller's auth context + const MfUltralightData* data = nfc_poller_get_data(instance->poller); + if(data->pages_read == data->pages_total) { + // Full read indicates successful authentication in dict attack mode + instance->mf_ultralight_c_dict_context.auth_success = true; + } view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackComplete); command = NfcCommandStop; From 9882c586aeff1b727acfdc299c032c9538f8ae6d Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 02:13:51 -0400 Subject: [PATCH 11/21] Fix regression, read card. Don't try dict if user chose "Unlock with key". --- .../helpers/protocol_support/mf_ultralight/mf_ultralight.c | 5 +++-- lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 3c041c077..656034f7a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -183,8 +183,9 @@ bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent e } else if(event.event == NfcCustomEventPollerIncomplete) { const MfUltralightData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); - if(data->type == MfUltralightTypeMfulC) { - // Start dict attack for MFUL C cards + if(data->type == MfUltralightTypeMfulC && + instance->mf_ul_auth->type == MfUltralightAuthTypeNone) { + // Start dict attack for MFUL C cards only if no specific auth was attempted scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); } else { notification_message(instance->notifications, &sequence_success); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 0c24dadd6..7639abaa9 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -516,9 +516,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol &instance->data->page[44], instance->auth_context.tdes_key.data, MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); - instance->data->pages_read = instance->pages_total; - instance->pages_read = instance->pages_total; - instance->state = MfUltralightPollerStateReadSuccess; + // Continue to read pages after successful authentication + instance->state = MfUltralightPollerStateReadPages; } } } while(false); From 603e1bd978f0f7f2c620296bc89681ce8104a387 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 03:16:36 -0400 Subject: [PATCH 12/21] Now reading an UL-C card gives the success tone only if the key is found --- .../protocol_support/mf_ultralight/mf_ultralight.c | 6 +++++- .../nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 656034f7a..915c8799b 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -188,7 +188,11 @@ bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent e // Start dict attack for MFUL C cards only if no specific auth was attempted scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); } else { - notification_message(instance->notifications, &sequence_success); + if(data->pages_read == data->pages_total) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index f0b2e5de6..cf31e0f2d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -156,7 +156,12 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE consumed = true; } } else { - notification_message(instance->notifications, &sequence_success); + // TODO: Could check if card is fully read here like MFC dict attack, but found key means fully read + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; @@ -184,7 +189,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE } else { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); - notification_message(instance->notifications, &sequence_success); + notification_message(instance->notifications, &sequence_semi_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); } From 0ec7a10ba908c634a43ec2ec4a89c587892416c9 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 04:00:20 -0400 Subject: [PATCH 13/21] UL-C dict attack scene is now visible --- .../main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index cf31e0f2d..94e1e9d80 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -116,6 +116,8 @@ void nfc_scene_mf_ultralight_c_dict_attack_on_enter(void* context) { instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); nfc_poller_start(instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); nfc_blink_read_start(instance); } From 9499c489c2ec8d4a6b0ac4bbaf2a89f8d474e555 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 04:13:46 -0400 Subject: [PATCH 14/21] Fix display of key index during dict attack scene --- .../main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 94e1e9d80..946fccf60 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -96,6 +96,8 @@ void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { dict_attack_set_total_dict_keys( instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_total); instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); dict_attack_set_callback( instance->dict_attack, nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, From afe4559dd39f9136565bfb7513eb04b107e7cb3b Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 22:00:45 -0400 Subject: [PATCH 15/21] Fix buffer overflow --- applications/main/nfc/nfc_app_i.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 9eb26a847..f7af85ea8 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -65,7 +65,7 @@ #define NFC_NAME_SIZE 22 #define NFC_TEXT_STORE_SIZE 128 -#define NFC_BYTE_INPUT_STORE_SIZE 10 +#define NFC_BYTE_INPUT_STORE_SIZE 16 #define NFC_LOG_SIZE_MAX (1024) #define NFC_APP_FOLDER EXT_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" From 6e05de476fb6169ec5a2c10331afc66fb33f81ba Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 23:05:23 -0400 Subject: [PATCH 16/21] Skip working --- .../nfc_scene_mf_ultralight_c_dict_attack.c | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 946fccf60..562e6c1d8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -175,30 +175,26 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); consumed = true; } else if(event.event == NfcCustomEventDictAttackSkip) { - if(instance->mf_ultralight_c_dict_context.is_card_present) { - if(state == DictAttackStateUserDictInProgress) { - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - keys_dict_free(instance->mf_ultralight_c_dict_context.dict); - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightCDictAttack, - DictAttackStateSystemDictInProgress); - nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); - instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); - nfc_poller_start( - instance->poller, - nfc_mf_ultralight_c_dict_attack_worker_callback, - instance); - } else { - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - notification_message(instance->notifications, &sequence_semi_success); - scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - } - consumed = true; + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, + nfc_mf_ultralight_c_dict_attack_worker_callback, + instance); + } else { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); } + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); From 8f279a682bd57acd45fecb49113bef2b9126b5f5 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 23:54:00 -0400 Subject: [PATCH 17/21] Pretty UI --- .../nfc_scene_mf_ultralight_c_dict_attack.c | 20 +- applications/main/nfc/views/dict_attack.c | 324 ++++++++++++------ applications/main/nfc/views/dict_attack.h | 13 + 3 files changed, 246 insertions(+), 111 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 562e6c1d8..99066bef0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -3,6 +3,8 @@ #define TAG "NfcMfUlCDictAttack" +// TODO: Support card_detected properly + enum { DictAttackStateUserDictInProgress, DictAttackStateSystemDictInProgress, @@ -41,9 +43,15 @@ NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); // Check if this is a successful authentication by looking at the poller's auth context const MfUltralightData* data = nfc_poller_get_data(instance->poller); + + // Update page information + dict_attack_set_pages_read(instance->dict_attack, data->pages_read); + dict_attack_set_pages_total(instance->dict_attack, data->pages_total); + if(data->pages_read == data->pages_total) { // Full read indicates successful authentication in dict attack mode instance->mf_ultralight_c_dict_context.auth_success = true; + dict_attack_set_key_found(instance->dict_attack, true); } view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackComplete); @@ -65,6 +73,10 @@ void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback( void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + // Set attack type to Ultralight C + dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC); + if(state == DictAttackStateUserDictInProgress) { do { if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { @@ -98,6 +110,12 @@ void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { instance->mf_ultralight_c_dict_context.dict_keys_current = 0; dict_attack_set_current_dict_key( instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + + // Set initial Ultralight C specific values + dict_attack_set_key_found(instance->dict_attack, false); + dict_attack_set_pages_total(instance->dict_attack, 48); // Ultralight C page count + dict_attack_set_pages_read(instance->dict_attack, 0); + dict_attack_set_callback( instance->dict_attack, nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, @@ -160,7 +178,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE consumed = true; } } else { - // TODO: Could check if card is fully read here like MFC dict attack, but found key means fully read + // Could check if card is fully read here like MFC dict attack, but found key means fully read if(instance->mf_ultralight_c_dict_context.auth_success) { notification_message(instance->notifications, &sequence_success); } else { diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 726076972..5a873fcfb 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -13,12 +13,13 @@ struct DictAttack { typedef struct { FuriString* header; bool card_detected; + DictAttackType attack_type; + + // MIFARE Classic specific uint8_t sectors_total; uint8_t sectors_read; uint8_t current_sector; uint8_t keys_found; - size_t dict_keys_total; - size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; MfClassicNestedPhase nested_phase; @@ -26,8 +27,161 @@ typedef struct { MfClassicBackdoor backdoor; uint16_t nested_target_key; uint16_t msb_count; + + // Ultralight C specific + uint8_t pages_total; + uint8_t pages_read; + bool key_found; + + // Common + size_t dict_keys_total; + size_t dict_keys_current; } DictAttackViewModel; +static void dict_attack_draw_mf_classic(Canvas* canvas, DictAttackViewModel* m) { + 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, 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), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + 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 = 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 { + 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); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + snprintf( + draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + +static void dict_attack_draw_mf_ultralight_c(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + + snprintf(draw_str, sizeof(draw_str), "Trying keys"); + 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); + if(m->dict_keys_current == 0) { + 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); + snprintf(draw_str, sizeof(draw_str), "Key found: %s", m->key_found ? "Yes" : "No"); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + + snprintf(draw_str, sizeof(draw_str), "Pages read: %d/%d", m->pages_read, m->pages_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + static void dict_attack_draw_callback(Canvas* canvas, void* model) { DictAttackViewModel* m = model; if(!m->card_detected) { @@ -37,113 +191,11 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { elements_multiline_text_aligned( canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); } 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->attack_type == DictAttackTypeMfClassic) { + dict_attack_draw_mf_classic(canvas, m); + } else if(m->attack_type == DictAttackTypeMfUltralightC) { + dict_attack_draw_mf_ultralight_c(canvas, m); } - - 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, 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), - "Reuse key check for sector: %d", - m->key_attack_current_sector); - } else { - 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 = 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 { - 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); - snprintf( - draw_str, - sizeof(draw_str), - "Keys found: %d/%d", - m->keys_found, - m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); - canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); - canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } elements_button_center(canvas, "Skip"); } @@ -195,18 +247,28 @@ void dict_attack_reset(DictAttack* instance) { instance->view, DictAttackViewModel * model, { + model->attack_type = DictAttackTypeMfClassic; + + // MIFARE Classic fields model->sectors_total = 0; model->sectors_read = 0; model->current_sector = 0; model->keys_found = 0; - 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; + + // Ultralight C fields + model->pages_total = 0; + model->pages_read = 0; + model->key_found = false; + + // Common fields + model->dict_keys_total = 0; + model->dict_keys_current = 0; furi_string_reset(model->header); }, false); @@ -355,3 +417,45 @@ void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) { with_view_model( instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true); } + +void dict_attack_set_type(DictAttack* instance, DictAttackType type) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { + model->attack_type = type; + }, + true); +} + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->pages_total = pages_total; }, + true); +} + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->pages_read = pages_read; }, + true); +} + +void dict_attack_set_key_found(DictAttack* instance, bool key_found) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->key_found = key_found; }, + true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index b6c6fdbdc..70709f86e 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -8,6 +8,11 @@ extern "C" { #endif +typedef enum { + DictAttackTypeMfClassic, + DictAttackTypeMfUltralightC, +} DictAttackType; + typedef struct DictAttack DictAttack; typedef enum { @@ -56,6 +61,14 @@ 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); +void dict_attack_set_type(DictAttack* instance, DictAttackType type); + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total); + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read); + +void dict_attack_set_key_found(DictAttack* instance, bool key_found); + #ifdef __cplusplus } #endif From f9507132984487f587eba935d3f60252832bc2da Mon Sep 17 00:00:00 2001 From: noproto Date: Sat, 23 Aug 2025 11:11:36 -0400 Subject: [PATCH 18/21] Bump Picopass to latest --- applications/system/picopass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/picopass b/applications/system/picopass index 00f8a221f..9889c7119 160000 --- a/applications/system/picopass +++ b/applications/system/picopass @@ -1 +1 @@ -Subproject commit 00f8a221fa020ab0fcc22d77e43ae1ab6ac51c6b +Subproject commit 9889c71198c73f61400f718ed50cb033c0facc8b From 2cfe6f61301f994cb887ad5c6485fdefcb402b25 Mon Sep 17 00:00:00 2001 From: noproto Date: Sat, 23 Aug 2025 15:36:34 -0400 Subject: [PATCH 19/21] Initial commit: MFKey 3.1 --- applications/system/mfkey/.catalog/README.md | 2 +- .../system/mfkey/.catalog/changelog.md | 2 + applications/system/mfkey/application.fam | 2 +- applications/system/mfkey/crypto1.h | 405 ++-- applications/system/mfkey/mfkey.c | 1923 ++++++++++------- applications/system/mfkey/mfkey.h | 170 +- 6 files changed, 1431 insertions(+), 1073 deletions(-) diff --git a/applications/system/mfkey/.catalog/README.md b/applications/system/mfkey/.catalog/README.md index 7f130b41f..22ff43aa6 100644 --- a/applications/system/mfkey/.catalog/README.md +++ b/applications/system/mfkey/.catalog/README.md @@ -8,5 +8,5 @@ After collecting nonces using the Extract MF Keys option, press the Start button ## Credits -Developers: noproto, AG, Flipper Devices, WillyJL +Developers: noproto, AG, Flipper Devices, WillyJL, CavallUwU Thanks: AloneLiberty, Foxushka, bettse, Equip diff --git a/applications/system/mfkey/.catalog/changelog.md b/applications/system/mfkey/.catalog/changelog.md index 2b05351e0..c93c6e8f8 100644 --- a/applications/system/mfkey/.catalog/changelog.md +++ b/applications/system/mfkey/.catalog/changelog.md @@ -1,3 +1,5 @@ +## 3.1 + - Key recovery is 20% faster, added write buffering of Static Encrypted Nested key candidates ## 3.0 - Added Static Encrypted Nested key recovery, added NFC app support, dropped FlipperNested support ## 2.7 diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam index dfd513847..0d81c1050 100644 --- a/applications/system/mfkey/application.fam +++ b/applications/system/mfkey/application.fam @@ -15,7 +15,7 @@ App( fap_icon_assets="images", fap_weburl="https://github.com/noproto/FlipperMfkey", fap_description="MIFARE Classic key recovery tool", - fap_version="3.0", + fap_version="3.1", ) App( diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h index 9caf5b35e..b9f7c7725 100644 --- a/applications/system/mfkey/crypto1.h +++ b/applications/system/mfkey/crypto1.h @@ -6,251 +6,260 @@ #include #include -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) static inline uint32_t prng_successor(uint32_t x, uint32_t n); static inline int filter(uint32_t const x); static inline uint8_t evenparity32(uint32_t x); static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); -void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); -static inline uint32_t crypt_word(struct Crypto1State* s); -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); -static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); +void crypto1_get_lfsr(struct Crypto1State *state, MfClassicKey *lfsr); +static inline uint32_t crypt_word(struct Crypto1State *s); +static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x); static uint32_t crypt_word_par( - struct Crypto1State* s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t* parity_keystream_bits); -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); -static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); -static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); + struct Crypto1State *s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t *parity_keystream_bits); +static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x); +static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb); +static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb); static const uint8_t lookup1[256] = { - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, - 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; static const uint8_t lookup2[256] = { - 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, - 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, - 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, - 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, - 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, - 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, - 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, - 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, - 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; -static inline int filter(uint32_t const x) { - uint32_t f; - f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; - f |= 0x0d938 >> (x >> 16 & 0xf) & 1; - return BIT(0xEC57E80A, f); +static inline int filter(uint32_t const x) +{ + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); } -#ifndef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - return __builtin_parity(x); -} -#endif - #ifdef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - uint32_t result; - __asm__ volatile("eor r1, %[x], %[x], lsr #16 \n\t" // r1 = x ^ (x >> 16) - "eor r1, r1, r1, lsr #8 \n\t" // r1 = r1 ^ (r1 >> 8) - "eor r1, r1, r1, lsr #4 \n\t" // r1 = r1 ^ (r1 >> 4) - "eor r1, r1, r1, lsr #2 \n\t" // r1 = r1 ^ (r1 >> 2) - "eor r1, r1, r1, lsr #1 \n\t" // r1 = r1 ^ (r1 >> 1) - "and %[result], r1, #1 \n\t" // result = r1 & 1 - : [result] "=r"(result) - : [x] "r"(x) - : "r1"); - return result; +static inline uint8_t evenparity32(uint32_t x) +{ + // fold 32 bits -> 16 -> 8 -> 4 + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) + return (uint8_t)((0x6996u >> (x & 0xF)) & 1); } #endif -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { - int p = data[item] >> 25; - p = p << 1 | evenparity32(data[item] & mask1); - p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) +{ + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); } -static inline uint32_t crypt_word(struct Crypto1State* s) { - // "in" and "x" are always 0 (last iteration) - uint32_t res_ret = 0; - uint32_t feedin, t; - for(int i = 0; i <= 31; i++) { - res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 - feedin = LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return res_ret; +static inline uint32_t crypt_word(struct Crypto1State *s) +{ + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for (int i = 0; i <= 31; i++) + { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; } -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return; +static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x) +{ + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 0; i <= 31; i++) + { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; } -static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { - uint32_t ret = 0; - uint32_t feedin, t, next_in; - uint8_t next_ret; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - next_ret = filter(s->odd); - feedin = next_ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - ret |= next_ret << (24 ^ i); - } - return ret; +static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x) +{ + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for (int i = 0; i <= 31; i++) + { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; } -static uint8_t get_nth_byte(uint32_t value, int n) { - if(n < 0 || n > 3) { - // Handle invalid input - return 0; - } - return (value >> (8 * (3 - n))) & 0xFF; +static uint8_t get_nth_byte(uint32_t value, int n) +{ + if (n < 0 || n > 3) + { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; } -static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { - uint32_t feedin, t; - uint8_t ret = filter(s->odd); - feedin = ret & !!is_encrypted; - feedin ^= !!in; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= LF_POLY_EVEN & s->even; - s->even = s->even << 1 | evenparity32(feedin); - t = s->odd, s->odd = s->even, s->even = t; - return ret; +static uint8_t crypt_bit(struct Crypto1State *s, uint8_t in, int is_encrypted) +{ + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; } static inline uint32_t crypt_word_par( - struct Crypto1State* s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t* parity_keystream_bits) { - uint32_t ret = 0; - *parity_keystream_bits = 0; // Reset parity keystream bits + struct Crypto1State *s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t *parity_keystream_bits) +{ + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits - for(int i = 0; i < 32; i++) { - uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); - ret |= bit << (24 ^ i); - // Save keystream parity bit - if((i + 1) % 8 == 0) { - *parity_keystream_bits |= - (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) - << (3 - (i / 8)); - } - } - return ret; + for (int i = 0; i < 32; i++) + { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if ((i + 1) % 8 == 0) + { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; } -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - } - return; +static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x) +{ + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) + { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; } // TODO: /* uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { - uint32_t res_ret = 0; - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - res_ret |= (ret << (24 ^ i)); - } - return res_ret; + uint32_t res_ret = 0; + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + res_ret |= (ret << (24 ^ i)); + } + return res_ret; } */ -uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { - int out; - uint8_t ret; - uint32_t t; - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; +uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb) +{ + int out; + uint8_t ret; + uint32_t t; + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; - out = s->even & 1; - out ^= LF_POLY_EVEN & (s->even >>= 1); - out ^= LF_POLY_ODD & s->odd; - out ^= !!in; - out ^= (ret = filter(s->odd)) & !!fb; + out = s->even & 1; + out ^= LF_POLY_EVEN & (s->even >>= 1); + out ^= LF_POLY_ODD & s->odd; + out ^= !!in; + out ^= (ret = filter(s->odd)) & !!fb; - s->even |= evenparity32(out) << 23; - return ret; + s->even |= evenparity32(out) << 23; + return ret; } -uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { - int i; - uint32_t ret = 0; - for(i = 31; i >= 0; --i) - ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); - return ret; +uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb) +{ + int i; + uint32_t ret = 0; + for (i = 31; i >= 0; --i) + ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + return ret; } -static inline uint32_t prng_successor(uint32_t x, uint32_t n) { - SWAPENDIAN(x); - while(n--) - x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - return SWAPENDIAN(x); +static inline uint32_t prng_successor(uint32_t x, uint32_t n) +{ + SWAPENDIAN(x); + while (n--) + x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); } -#endif // CRYPTO1_H +#endif // CRYPTO1_H \ No newline at end of file diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index ad39d186b..0b68e31e0 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -37,879 +37,1214 @@ // TODO: Remove defines that are not needed #define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MAX_NAME_LEN 32 -#define MAX_PATH_LEN 64 +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAP(a, b) \ + do \ + { \ + unsigned int t = a; \ + a = b; \ + b = t; \ + } while (0) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) -//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +// #define SIZEOF(arr) sizeof(arr) / sizeof(*arr) -static int eta_round_time = 44; -static int eta_total_time = 705; -// MSB_LIMIT: Chunk size (out of 256) -static int MSB_LIMIT = 16; +// Reduced to 16-bit as these values are small and don't need 32-bit +static int16_t eta_round_time = 44; +static int16_t eta_total_time = 705; +// MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value +static uint8_t MSB_LIMIT = 16; static inline int - check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { - if(!(t->odd | t->even)) return 0; - if(n->attack == mfkey32) { - uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); - if(rb != n->ar0_enc) { - return 0; - } - rollback_word_noret(t, n->nr0_enc, 1); - rollback_word_noret(t, n->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, n->uid_xor_nt1, 0); - crypt_word_noret(t, n->nr1_enc, 1); - if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_nested) { - struct Crypto1State temp = {t->odd, t->even}; - rollback_word_noret(t, n->uid_xor_nt1, 0); - if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { - rollback_word_noret(&temp, n->uid_xor_nt1, 0); - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_encrypted) { - // TODO: Parity bits from rollback_word? - if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { - // Reduce with parity - uint8_t local_parity_keystream_bits; - struct Crypto1State temp = {t->odd, t->even}; - if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == - n->ks1_1_enc) && - (local_parity_keystream_bits == n->par_1)) { - // Found key candidate - crypto1_get_lfsr(t, &(n->key)); - program_state->num_candidates++; - keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); - } - } - } - return 0; +check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) +{ + if (!(t->odd | t->even)) + return 0; + if (n->attack == mfkey32) + { + uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); + if (rb != n->ar0_enc) + { + return 0; + } + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if (n->ar1_enc == (crypt_word(t) ^ n->p64b)) + { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } + else if (n->attack == static_nested) + { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if (n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) + { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } + else if (n->attack == static_encrypted) + { + // TODO: Parity bits from rollback_word? + if (n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) + { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if ((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) + { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); + } + } + } + return 0; } -static inline int state_loop( - unsigned int* states_buffer, - int xks, - int m1, - int m2, - unsigned int in, - uint8_t and_val) { - int states_tail = 0; - int round = 0, s = 0, xks_bit = 0, round_in = 0; +static inline __attribute__((hot)) int state_loop( + unsigned int *states_buffer, + int xks, + int m1, + int m2, + unsigned int in, + int and_val) +{ + int states_tail = 0; + int xks_bit = 0, round_in = 0; - for(round = 1; round <= 12; round++) { - xks_bit = BIT(xks, round); - if(round > 4) { - round_in = ((in >> (2 * (round - 4))) & and_val) << 24; - } + // Unroll first 4 rounds (no round_in calculations needed) + // Hoist the filter() calls to just one iteration and reuse the results + // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) + // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! + for (int round = 1; round <= 4; ++round) + { + xks_bit = BIT(xks, round); + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v); + int f1 = filter(v | 1); - for(s = 0; s <= states_tail; s++) { - states_buffer[s] <<= 1; + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { - states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; - if(round > 4) { - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } - } else if(filter(states_buffer[s]) == xks_bit) { - // TODO: Refactor - if(round > 4) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = states_buffer[s] | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= round_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } else { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Round 5 (unrolled) + { + xks_bit = BIT(xks, 5); + int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r5_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - return states_tail; + // Round 6 (unrolled) + { + xks_bit = BIT(xks, 6); + int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r6_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + // Loop rounds 7–12 + for (int round = 7; round <= 12; ++round) + { + xks_bit = BIT(xks, round); + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + return states_tail; } -int binsearch(unsigned int data[], int start, int stop) { - int mid, val = data[stop] & 0xff000000; - while(start != stop) { - mid = (stop - start) >> 1; - if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; +int binsearch(unsigned int data[], int start, int stop) +{ + int mid, val = data[stop] & 0xff000000; + while (start != stop) + { + mid = (stop - start) >> 1; + if ((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; } -void quicksort(unsigned int array[], int low, int high) { - //if (SIZEOF(array) == 0) - // return; - if(low >= high) return; - int middle = low + (high - low) / 2; - unsigned int pivot = array[middle]; - int i = low, j = high; - while(i <= j) { - while(array[i] < pivot) { - i++; - } - while(array[j] > pivot) { - j--; - } - if(i <= j) { // swap - int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } - if(low < j) { - quicksort(array, low, j); - } - if(high > i) { - quicksort(array, i, high); - } + +void quicksort(unsigned int array[], int low, int high) +{ + // Use insertion sort for small arrays (threshold determined by testing) + if (high - low < 16) + { + // Insertion sort + for (int i = low + 1; i <= high; i++) + { + unsigned int key = array[i]; + int j = i - 1; + while (j >= low && array[j] > key) + { + array[j + 1] = array[j]; + j--; + } + array[j + 1] = key; + } + return; + } + + if (low >= high) + return; + + // Median-of-three pivot selection + int middle = low + (high - low) / 2; + if (array[middle] < array[low]) + SWAP(array[middle], array[low]); + if (array[high] < array[low]) + SWAP(array[high], array[low]); + if (array[high] < array[middle]) + SWAP(array[high], array[middle]); + + unsigned int pivot = array[middle]; + + // Rest of quicksort with improved partitioning + int i = low, j = high; + while (i <= j) + { + while (array[i] < pivot) + i++; + while (array[j] > pivot) + j--; + if (i <= j) + { + // swap + unsigned int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } + + if (low < j) + quicksort(array, low, j); + if (high > i) + quicksort(array, i, high); } -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { - in <<= 24; - for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { - if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else if(filter(data[tbl]) == bit) { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - data[tbl++] ^= in; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else { - data[tbl--] = data[end--]; - } - } - return end; + +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) +{ + in <<= 24; + for (data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) + { + if ((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) + { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } + else if (filter(data[tbl]) == bit) + { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } + else + { + data[tbl--] = data[end--]; + } + } + return end; } int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - MfClassicNonce* n, - unsigned int in, - int first_run, - ProgramState* program_state) { - int o, e, i; - if(rem == -1) { - for(e = e_head; e <= e_tail; ++e) { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); - for(o = o_head; o <= o_tail; ++o, ++s) { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, n, program_state)) { - return -1; - } - } - } - return s; - } - if(first_run == 0) { - for(i = 0; (i < 4) && (rem-- != 0); i++) { - oks >>= 1; - eks >>= 1; - in >>= 2; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); - if(o_head > o_tail) return s; - e_tail = extend_table( - even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); - if(e_head > e_tail) return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while(o_tail >= o_head && e_tail >= e_head) { - if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover( - odd, - o_tail--, - o, - oks, - even, - e_tail--, - e, - eks, - rem, - s, - n, - in, - first_run, - program_state); - if(s == -1) { - break; - } - } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } else { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce *n, + unsigned int in, + int first_run, + ProgramState *program_state) +{ + int o, e, i; + if (rem == -1) + { + for (e = e_head; e <= e_tail; ++e) + { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for (o = o_head; o <= o_tail; ++o, ++s) + { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if (check_state(&temp, n, program_state)) + { + return -1; + } + } + } + return s; + } + if (first_run == 0) + { + for (i = 0; (i < 4) && (rem-- != 0); i++) + { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if (o_head > o_tail) + return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if (e_head > e_tail) + return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while (o_tail >= o_head && e_tail >= e_head) + { + if (((odd[o_tail] ^ even[e_tail]) >> 24) == 0) + { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); + if (s == -1) + { + break; + } + } + else if ((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) + { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } + else + { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; } -static inline int sync_state(ProgramState* program_state) { - int ts = furi_hal_rtc_get_timestamp(); - int elapsed_time = ts - program_state->eta_timestamp; - if(elapsed_time < program_state->eta_round) { - program_state->eta_round -= elapsed_time; - } else { - program_state->eta_round = 0; - } - if(elapsed_time < program_state->eta_total) { - program_state->eta_total -= elapsed_time; - } else { - program_state->eta_total = 0; - } - program_state->eta_timestamp = ts; - if(program_state->close_thread_please) { - return 1; - } - return 0; +static inline int sync_state(ProgramState *program_state) +{ + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if (elapsed_time < program_state->eta_round) + { + program_state->eta_round -= elapsed_time; + } + else + { + program_state->eta_round = 0; + } + if (elapsed_time < program_state->eta_total) + { + program_state->eta_total -= elapsed_time; + } + else + { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if (program_state->close_thread_please) + { + return 1; + } + return 0; } int calculate_msb_tables( - int oks, - int eks, - int msb_round, - MfClassicNonce* n, - unsigned int* states_buffer, - struct Msb* odd_msbs, - struct Msb* even_msbs, - unsigned int* temp_states_odd, - unsigned int* temp_states_even, - unsigned int in, - ProgramState* program_state) { - //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG - unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0, tail = 0; - int i = 0, j = 0, semi_state = 0, found = 0; - unsigned int msb = 0; - in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - // TODO: Why is this necessary? - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + int oks, + int eks, + int msb_round, + MfClassicNonce *n, + unsigned int *states_buffer, + struct Msb *odd_msbs, + struct Msb *even_msbs, + unsigned int *temp_states_odd, + unsigned int *temp_states_even, + unsigned int in, + ProgramState *program_state) +{ + unsigned int msb_head = (MSB_LIMIT * msb_round); + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0; + int semi_state = 0; + unsigned int msb = 0; - for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { - if(semi_state % 32768 == 0) { - if(sync_state(program_state) == 1) { - return 0; - } - } + // Preprocessed in value + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - if(filter(semi_state) == (oks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + // Clear MSB arrays once before loop instead of inside loop + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - for(i = states_tail; i >= 0; i--) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; - for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { - if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } + // Bit values to check - calculate once outside the loop + int oks_bit = oks & 1; + int eks_bit = eks & 1; - if(!found) { - tail = odd_msbs[msb - msb_head].tail++; - odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } + // Check for stop request less frequently + int sync_check_interval = 32768 * 2; // Doubled the interval - if(filter(semi_state) == (eks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + for (semi_state = 1 << 20; semi_state >= 0; semi_state--) + { + if (semi_state % sync_check_interval == 0) + { + if (sync_state(program_state) == 1) + { + return 0; + } + } - for(i = 0; i <= states_tail; i++) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; + // Process both filter conditions in one pass when possible + int filter_semi_state = filter(semi_state); - for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { - if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } + // Check oks condition + if (filter_semi_state == oks_bit) + { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); - if(!found) { - tail = even_msbs[msb - msb_head].tail++; - even_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } - } + for (int i = states_tail; i >= 0; i--) + { + msb = states_buffer[i] >> 24; + if ((msb >= msb_head) && (msb < msb_tail)) + { + // Calculate index once + int msb_idx = msb - msb_head; - oks >>= 12; - eks >>= 12; + // Avoid sequential scan by using a direct flag + int found = 0; + for (int j = 0; j < odd_msbs[msb_idx].tail; j++) + { + if (odd_msbs[msb_idx].states[j] == states_buffer[i]) + { + found = 1; + break; + } + } - for(i = 0; i < MSB_LIMIT; i++) { - if(sync_state(program_state) == 1) { - return 0; - } - // TODO: Why is this necessary? - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - n, - in >> 16, - 1, - program_state); - if(res == -1) { - return 1; - } - //odd_msbs[i].tail = 0; - //even_msbs[i].tail = 0; - } + if (!found) + { + int tail = odd_msbs[msb_idx].tail++; + odd_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } - return 0; + // Check eks condition + if (filter_semi_state == eks_bit) + { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + + for (int i = 0; i <= states_tail; i++) + { + msb = states_buffer[i] >> 24; + if ((msb >= msb_head) && (msb < msb_tail)) + { + // Calculate index once + int msb_idx = msb - msb_head; + + // Avoid sequential scan + int found = 0; + for (int j = 0; j < even_msbs[msb_idx].tail; j++) + { + if (even_msbs[msb_idx].states[j] == states_buffer[i]) + { + found = 1; + break; + } + } + + if (!found) + { + int tail = even_msbs[msb_idx].tail++; + even_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } + } + + // Shift once outside the loop + oks >>= 12; + eks >>= 12; + + // Process results + for (int i = 0; i < MSB_LIMIT; i++) + { + if ((i % 4) == 0 && sync_state(program_state) == 1) + { + return 0; + } + + // Only clear buffers if they're going to be used + if (odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) + { + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1, + program_state); + + if (res == -1) + { + return 1; + } + } + } + + return 0; } -void** allocate_blocks(const size_t* block_sizes, int num_blocks) { - void** block_pointers = malloc(num_blocks * sizeof(void*)); +void **allocate_blocks(const size_t *block_sizes, int num_blocks) +{ + void **block_pointers = malloc(num_blocks * sizeof(void *)); + if (!block_pointers) + { + return NULL; + } - for(int i = 0; i < num_blocks; i++) { - if(memmgr_heap_get_max_free_block() < block_sizes[i]) { - // Not enough memory, free previously allocated blocks - for(int j = 0; j < i; j++) { - free(block_pointers[j]); - } - free(block_pointers); - return NULL; - } + for (int i = 0; i < num_blocks; i++) + { + if (memmgr_heap_get_max_free_block() < block_sizes[i]) + { + // Not enough memory, free previously allocated blocks + for (int j = 0; j < i; j++) + { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } - block_pointers[i] = malloc(block_sizes[i]); - } + block_pointers[i] = malloc(block_sizes[i]); + if (!block_pointers[i]) + { + // Allocation failed + for (int j = 0; j < i; j++) + { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + } - return block_pointers; + return block_pointers; } -bool is_full_speed() { - return MSB_LIMIT == 16; +// Inline function for checking if we're at full speed +static inline bool is_full_speed() +{ + return MSB_LIMIT == 16; } -bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { - bool found = false; - const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; - const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; - const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - void** block_pointers = allocate_blocks(block_sizes, num_blocks); - if(block_pointers == NULL) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - if(is_full_speed()) { - //eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - } - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if(block_pointers == NULL) { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - // Adjust estimates for static encrypted attacks - if(n->attack == static_encrypted) { - eta_round_time *= 4; - eta_total_time *= 4; - if(is_full_speed()) { - eta_round_time *= 4; - eta_total_time *= 4; - } - } - struct Msb* odd_msbs = block_pointers[0]; - struct Msb* even_msbs = block_pointers[1]; - unsigned int* temp_states_odd = block_pointers[2]; - unsigned int* temp_states_even = block_pointers[3]; - unsigned int* states_buffer = block_pointers[4]; - int oks = 0, eks = 0; - int i = 0, msb = 0; - for(i = 31; i >= 0; i -= 2) { - oks = oks << 1 | BEBIT(ks2, i); - } - for(i = 30; i >= 0; i -= 2) { - eks = eks << 1 | BEBIT(ks2, i); - } - int bench_start = furi_hal_rtc_get_timestamp(); - program_state->eta_total = eta_total_time; - program_state->eta_timestamp = bench_start; - for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { - program_state->search = msb; - program_state->eta_round = eta_round_time; - program_state->eta_total = eta_total_time - (eta_round_time * msb); - if(calculate_msb_tables( - oks, - eks, - msb, - n, - states_buffer, - odd_msbs, - even_msbs, - temp_states_odd, - temp_states_even, - in, - program_state)) { - //int bench_stop = furi_hal_rtc_get_timestamp(); - //FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); - found = true; - break; - } - if(program_state->close_thread_please) { - break; - } - } - // Free the allocated blocks - for(int i = 0; i < num_blocks; i++) { - free(block_pointers[i]); - } - free(block_pointers); - return found; +bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) +{ + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + void **block_pointers = allocate_blocks(block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + if (is_full_speed()) + { + // eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + // Adjust estimates for static encrypted attacks + if (n->attack == static_encrypted) + { + eta_round_time *= 4; + eta_total_time *= 4; + if (is_full_speed()) + { + eta_round_time *= 4; + eta_total_time *= 4; + } + } + struct Msb *odd_msbs = block_pointers[0]; + struct Msb *even_msbs = block_pointers[1]; + unsigned int *temp_states_odd = block_pointers[2]; + unsigned int *temp_states_even = block_pointers[3]; + unsigned int *states_buffer = block_pointers[4]; + int oks = 0, eks = 0; + int i = 0, msb = 0; + for (i = 31; i >= 0; i -= 2) + { + oks = oks << 1 | BEBIT(ks2, i); + } + for (i = 30; i >= 0; i -= 2) + { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for (msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) + { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if (calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) + { + // int bench_stop = furi_hal_rtc_get_timestamp(); + // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if (program_state->close_thread_please) + { + break; + } + } + // Free the allocated blocks + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + return found; } bool key_already_found_for_nonce_in_solved( - MfClassicKey* keyarray, - int keyarray_size, - MfClassicNonce* nonce) { - for(int k = 0; k < keyarray_size; k++) { - uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); - struct Crypto1State temp = {0, 0}; - for(int i = 0; i < 24; i++) { - (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); - } - if(nonce->attack == mfkey32) { - crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); - crypt_word_noret(&temp, nonce->nr1_enc, 1); - if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { - return true; - } - } else if(nonce->attack == static_nested) { - uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); - if(nonce->ks1_1_enc == expected_ks1) { - return true; - } - } - } - return false; + MfClassicKey *keyarray, + int keyarray_size, + MfClassicNonce *nonce) +{ + for (int k = 0; k < keyarray_size; k++) + { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for (int i = 0; i < 24; i++) + { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if (nonce->attack == mfkey32) + { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if (nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) + { + return true; + } + } + else if (nonce->attack == static_nested) + { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if (nonce->ks1_1_enc == expected_ks1) + { + return true; + } + } + } + return false; } #pragma GCC push_options #pragma GCC optimize("Os") -static void finished_beep() { - // Beep to indicate completion - NotificationApp* notification = furi_record_open("notification"); - notification_message(notification, &sequence_audiovisual_alert); - notification_message(notification, &sequence_display_backlight_on); - furi_record_close("notification"); +static void finished_beep() +{ + // Beep to indicate completion + NotificationApp *notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); } -void mfkey(ProgramState* program_state) { - uint32_t ks_enc = 0, nt_xor_uid = 0; - MfClassicKey found_key; // Recovered key - size_t keyarray_size = 0; - MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); - uint32_t i = 0, j = 0; - //FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); - flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); - flipper_application_map_to_memory(app); - const FlipperAppPluginDescriptor* app_descriptor = - flipper_application_plugin_get_descriptor(app); - const MfkeyPlugin* init_plugin = app_descriptor->entry_point; - // Check for nonces - program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); - program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); - if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { - program_state->err = MissingNonces; - program_state->mfkey_state = Error; - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - free(keyarray); - return; - } - // Read dictionaries (optional) - KeysDict* system_dict = {0}; - bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); - KeysDict* user_dict = {0}; - bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); - uint32_t total_dict_keys = 0; - if(system_dict_exists) { - system_dict = - keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); - total_dict_keys += keys_dict_get_total_keys(system_dict); - } - user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); - if(user_dict_exists) { - total_dict_keys += keys_dict_get_total_keys(user_dict); - } - user_dict_exists = true; - program_state->dict_count = total_dict_keys; - program_state->mfkey_state = DictionaryAttack; - // Read nonces - MfClassicNonceArray* nonce_arr; - nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( - system_dict, system_dict_exists, user_dict, program_state); - if(system_dict_exists) { - keys_dict_free(system_dict); - } - if(nonce_arr->total_nonces == 0) { - // Nothing to crack - program_state->err = ZeroNonces; - program_state->mfkey_state = Error; - init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - keys_dict_free(user_dict); - free(keyarray); - return; - } - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - // TODO: Track free state at the time this is called to ensure double free does not happen - furi_assert(nonce_arr); - furi_assert(nonce_arr->stream); - // TODO: Already closed? - buffered_file_stream_close(nonce_arr->stream); - stream_free(nonce_arr->stream); - //FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); - program_state->mfkey_state = MFKeyAttack; - // TODO: Work backwards on this array and free memory - for(i = 0; i < nonce_arr->total_nonces; i++) { - MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; - if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { - nonce_arr->remaining_nonces--; - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - //FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); - FuriString* cuid_dict_path; - switch(next_nonce.attack) { - case mfkey32: - ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; - nt_xor_uid = 0; - break; - case static_nested: - ks_enc = next_nonce.ks1_2_enc; - nt_xor_uid = next_nonce.uid_xor_nt1; - break; - case static_encrypted: - ks_enc = next_nonce.ks1_1_enc; - nt_xor_uid = next_nonce.uid_xor_nt0; - cuid_dict_path = furi_string_alloc_printf( - "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); - // May need RECORD_STORAGE? - program_state->cuid_dict = keys_dict_alloc( - furi_string_get_cstr(cuid_dict_path), - KeysDictModeOpenAlways, - sizeof(MfClassicKey)); - break; - } +void mfkey(ProgramState *program_state) +{ + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key + size_t keyarray_size = 0; + MfClassicKey *keyarray = malloc(sizeof(MfClassicKey) * 1); + if (!keyarray) + { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return; + } - if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { - if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { - keys_dict_free(program_state->cuid_dict); - } - if(program_state->close_thread_please) { - break; - } - // No key found in recover() or static encrypted - (program_state->num_completed)++; - continue; - } - (program_state->cracked)++; - (program_state->num_completed)++; - found_key = next_nonce.key; - bool already_found = false; - for(j = 0; j < keyarray_size; j++) { - if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { - already_found = true; - break; - } - } - if(already_found == false) { - // New key - keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); //-V701 - keyarray_size += 1; - keyarray[keyarray_size - 1] = found_key; - (program_state->unique_cracked)++; - } - } - // TODO: Update display to show all keys were found - // TODO: Prepend found key(s) to user dictionary file - //FURI_LOG_I(TAG, "Unique keys found:"); - for(i = 0; i < keyarray_size; i++) { - //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); - keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); - } - if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcKeyAdd); - } - free(nonce_arr); - keys_dict_free(user_dict); - free(keyarray); - if(program_state->mfkey_state == Error) { - return; - } - //FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG - program_state->mfkey_state = Complete; - // No need to alert the user if they asked it to stop - if(!(program_state->close_thread_please)) { - finished_beep(); - } - return; + uint32_t i = 0, j = 0; + // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage *storage = furi_record_open(RECORD_STORAGE); + FlipperApplication *app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor *app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin *init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if (!(program_state->mfkey32_present) && !(program_state->nested_present)) + { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict *system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict *user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if (system_dict_exists) + { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if (user_dict_exists) + { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray *nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if (system_dict_exists) + { + keys_dict_free(system_dict); + } + if (nonce_arr->total_nonces == 0) + { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + // TODO: Already closed? + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for (i = 0; i < nonce_arr->total_nonces; i++) + { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if (key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) + { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + FuriString *cuid_dict_path; + switch (next_nonce.attack) + { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + furi_string_free(cuid_dict_path); + break; + } + + if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) + { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + } + if (program_state->close_thread_please) + { + break; + } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for (j = 0; j < keyarray_size; j++) + { + if (memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) + { + already_found = true; + break; + } + } + if (already_found == false) + { + // New key + MfClassicKey *new_keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); + if (!new_keyarray) + { + // Realloc failed - continue with existing keyarray + FURI_LOG_E(TAG, "Failed to realloc keyarray"); + } + else + { + keyarray = new_keyarray; + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + // FURI_LOG_I(TAG, "Unique keys found:"); + for (i = 0; i < keyarray_size; i++) + { + // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if (keyarray_size > 0) + { + dolphin_deed(DolphinDeedNfcKeyAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if (program_state->mfkey_state == Error) + { + return; + } + // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if (!(program_state->close_thread_please)) + { + finished_beep(); + } + return; } // Screen is 128x64 px -static void render_callback(Canvas* const canvas, void* ctx) { - furi_assert(ctx); - ProgramState* program_state = ctx; - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - char draw_str[44] = {}; +static void render_callback(Canvas *const canvas, void *ctx) +{ + furi_assert(ctx); + ProgramState *program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; - canvas_draw_frame(canvas, 0, 0, 128, 64); - canvas_draw_frame(canvas, 0, 15, 128, 64); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); - // FontSecondary by default, title is drawn at the end - snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); - canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); - canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if(program_state->mfkey_state == MFKeyAttack) { - float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); - float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); - float progress = (float)program_state->num_completed / (float)program_state->total; - if(eta_round < 0 || eta_round > 1) { - // Round ETA miscalculated - eta_round = 1; - program_state->eta_round = 0; - } - if(eta_total < 0 || eta_round > 1) { - // Total ETA miscalculated - eta_total = 1; - program_state->eta_total = 0; - } - snprintf( - draw_str, - sizeof(draw_str), - "Cracking: %d/%d - in prog.", - program_state->num_completed, - program_state->total); - elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Round: %d/%d - ETA %02d Sec", - (program_state->search) + 1, // Zero indexed - 256 / MSB_LIMIT, - program_state->eta_round); - elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); - snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); - elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } else if(program_state->mfkey_state == DictionaryAttack) { - snprintf( - draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); - canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); - snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); - canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); - } else if(program_state->mfkey_state == Complete) { - // TODO: Scrollable list view to see cracked keys if user presses down - elements_progress_bar(canvas, 5, 18, 118, 1); - canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); - snprintf( - draw_str, - sizeof(draw_str), - "Keys added to user dict: %d", - program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); - if(program_state->num_candidates > 0) { - snprintf( - draw_str, - sizeof(draw_str), - "SEN key candidates: %d", - program_state->num_candidates); - canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); - } - } else if(program_state->mfkey_state == Ready) { - canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); - elements_button_center(canvas, "Start"); - elements_button_right(canvas, "Help"); - } else if(program_state->mfkey_state == Help) { - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); - } else if(program_state->mfkey_state == Error) { - canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - if(program_state->err == MissingNonces) { - canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); - } else if(program_state->err == ZeroNonces) { - canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); - } else if(program_state->err == InsufficientRAM) { - canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); - } else { - // Unhandled error - } - } else { - // Unhandled program state - } - // Title - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); - furi_mutex_release(program_state->mutex); + // FontSecondary by default, title is drawn at the end + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if (program_state->mfkey_state == MFKeyAttack) + { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if (eta_round < 0 || eta_round > 1) + { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if (eta_total < 0 || eta_round > 1) + { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } + else if (program_state->mfkey_state == DictionaryAttack) + { + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } + else if (program_state->mfkey_state == Complete) + { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar(canvas, 5, 18, 118, 1); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if (program_state->num_candidates > 0) + { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } + } + else if (program_state->mfkey_state == Ready) + { + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } + else if (program_state->mfkey_state == Help) + { + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); + } + else if (program_state->mfkey_state == Error) + { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + if (program_state->err == MissingNonces) + { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } + else if (program_state->err == ZeroNonces) + { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } + else if (program_state->err == InsufficientRAM) + { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } + else + { + // Unhandled error + } + } + else + { + // Unhandled program state + } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + furi_mutex_release(program_state->mutex); } -static void input_callback(InputEvent* input_event, void* event_queue) { - furi_assert(event_queue); - furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever); +static void input_callback(InputEvent *input_event, void *event_queue) +{ + furi_assert(event_queue); + furi_message_queue_put((FuriMessageQueue *)event_queue, input_event, FuriWaitForever); } -static void mfkey_state_init(ProgramState* program_state) { - program_state->mfkey_state = Ready; - program_state->cracked = 0; - program_state->unique_cracked = 0; - program_state->num_completed = 0; - program_state->num_candidates = 0; - program_state->total = 0; - program_state->dict_count = 0; +static void mfkey_state_init(ProgramState *program_state) +{ + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->num_candidates = 0; + program_state->total = 0; + program_state->dict_count = 0; } // Entrypoint for worker thread -static int32_t mfkey_worker_thread(void* ctx) { - ProgramState* program_state = ctx; - program_state->mfkey_state = Initializing; - mfkey(program_state); - return 0; +static int32_t mfkey_worker_thread(void *ctx) +{ + ProgramState *program_state = ctx; + program_state->mfkey_state = Initializing; + mfkey(program_state); + return 0; } -int32_t mfkey_main() { - FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); +int32_t mfkey_main() +{ + FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - ProgramState* program_state = malloc(sizeof(ProgramState)); + ProgramState *program_state = malloc(sizeof(ProgramState)); - mfkey_state_init(program_state); + mfkey_state_init(program_state); - program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, program_state); - view_port_input_callback_set(view_port, input_callback, event_queue); + // Set system callbacks + ViewPort *view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); + // Open GUI and register view_port + Gui *gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); - program_state->mfkeythread = - furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); - InputEvent input_event; - for(bool main_loop = true; main_loop;) { - FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); + InputEvent input_event; + for (bool main_loop = true; main_loop;) + { + FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); - furi_mutex_acquire(program_state->mutex, FuriWaitForever); + furi_mutex_acquire(program_state->mutex, FuriWaitForever); - if(event_status == FuriStatusOk) { - if(input_event.type == InputTypePress) { - switch(input_event.key) { - case InputKeyRight: - if(program_state->mfkey_state == Ready) { - program_state->mfkey_state = Help; - } - break; - case InputKeyOk: - if(program_state->mfkey_state == Ready) { - furi_thread_start(program_state->mfkeythread); - } - break; - case InputKeyBack: - if(program_state->mfkey_state == Help) { - program_state->mfkey_state = Ready; - } else { - program_state->close_thread_please = true; - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - main_loop = false; - } - break; - default: - break; - } - } - } + if (event_status == FuriStatusOk) + { + if (input_event.type == InputTypePress) + { + switch (input_event.key) + { + case InputKeyRight: + if (program_state->mfkey_state == Ready) + { + program_state->mfkey_state = Help; + } + break; + case InputKeyOk: + if (program_state->mfkey_state == Ready) + { + furi_thread_start(program_state->mfkeythread); + } + break; + case InputKeyBack: + if (program_state->mfkey_state == Help) + { + program_state->mfkey_state = Ready; + } + else + { + program_state->close_thread_please = true; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + main_loop = false; + } + break; + default: + break; + } + } + } - furi_mutex_release(program_state->mutex); - view_port_update(view_port); - } + furi_mutex_release(program_state->mutex); + view_port_update(view_port); + } - // Thread joined in back event handler - furi_thread_free(program_state->mfkeythread); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close(RECORD_GUI); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(program_state->mutex); - free(program_state); + // Thread joined in back event handler + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); - return 0; + return 0; } #pragma GCC pop_options diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 4a7ab3423..74274edc8 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -9,100 +9,112 @@ #include #include -struct Crypto1State { - uint32_t odd, even; +struct Crypto1State +{ + uint32_t odd, even; }; -struct Msb { - int tail; - uint32_t states[768]; +struct Msb +{ + int tail; + uint32_t states[768]; }; -typedef enum { - MissingNonces, - ZeroNonces, - InsufficientRAM, +typedef enum +{ + MissingNonces, + ZeroNonces, + InsufficientRAM, } MFKeyError; -typedef enum { - Ready, - Initializing, - DictionaryAttack, - MFKeyAttack, - Complete, - Error, - Help, +typedef enum +{ + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, } MFKeyState; // TODO: Can we eliminate any of the members of this struct? -typedef struct { - FuriMutex* mutex; - MFKeyError err; - MFKeyState mfkey_state; - int cracked; - int unique_cracked; - int num_completed; - int num_candidates; - int total; - int dict_count; - int search; - int eta_timestamp; - int eta_total; - int eta_round; - bool mfkey32_present; - bool nested_present; - bool close_thread_please; - FuriThread* mfkeythread; - KeysDict* cuid_dict; +typedef struct +{ + FuriMutex *mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int num_candidates; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool close_thread_please; + FuriThread *mfkeythread; + KeysDict *cuid_dict; } ProgramState; -typedef enum { - mfkey32, - static_nested, - static_encrypted +typedef enum +{ + mfkey32, + static_nested, + static_encrypted } AttackType; -typedef struct { - AttackType attack; - MfClassicKey key; // key - uint32_t uid; // serial number - uint32_t nt0; // tag challenge first - uint32_t nt1; // tag challenge second - uint32_t uid_xor_nt0; // uid ^ nt0 - uint32_t uid_xor_nt1; // uid ^ nt1 - union { - // Mfkey32 - struct { - uint32_t p64; // 64th successor of nt0 - uint32_t p64b; // 64th successor of nt1 - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response - }; - // Nested - struct { - uint32_t ks1_1_enc; // first encrypted keystream - uint32_t ks1_2_enc; // second encrypted keystream - char par_1_str[5]; // first parity bits (string representation) - char par_2_str[5]; // second parity bits (string representation) - uint8_t par_1; // first parity bits - uint8_t par_2; // second parity bits - }; - }; +typedef struct +{ + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + union + { + // Mfkey32 + struct + { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct + { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; } MfClassicNonce; -typedef struct { - Stream* stream; - uint32_t total_nonces; - MfClassicNonce* remaining_nonce_array; - size_t remaining_nonces; +typedef struct +{ + Stream *stream; + uint32_t total_nonces; + MfClassicNonce *remaining_nonce_array; + size_t remaining_nonces; } MfClassicNonceArray; -struct KeysDict { - Stream* stream; - size_t key_size; - size_t key_size_symbols; - size_t total_keys; +struct KeysDict +{ + Stream *stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; }; -#endif // MFKEY_H +#endif // MFKEY_H \ No newline at end of file From 2125e210c89120220d9cfebe261b8f1f21cde04a Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 24 Aug 2025 19:00:10 -0400 Subject: [PATCH 20/21] Static encrypted performance fixed --- applications/system/mfkey/.catalog/README.md | 2 +- .../system/mfkey/.catalog/changelog.md | 2 +- applications/system/mfkey/mfkey.c | 189 +++++++++++++++--- applications/system/mfkey/mfkey.h | 3 + 4 files changed, 164 insertions(+), 32 deletions(-) diff --git a/applications/system/mfkey/.catalog/README.md b/applications/system/mfkey/.catalog/README.md index 22ff43aa6..770e46880 100644 --- a/applications/system/mfkey/.catalog/README.md +++ b/applications/system/mfkey/.catalog/README.md @@ -8,5 +8,5 @@ After collecting nonces using the Extract MF Keys option, press the Start button ## Credits -Developers: noproto, AG, Flipper Devices, WillyJL, CavallUwU +Developers: noproto, AG, Flipper Devices, WillyJL, CavallUwU, Ivisayan Thanks: AloneLiberty, Foxushka, bettse, Equip diff --git a/applications/system/mfkey/.catalog/changelog.md b/applications/system/mfkey/.catalog/changelog.md index c93c6e8f8..7b5be62b2 100644 --- a/applications/system/mfkey/.catalog/changelog.md +++ b/applications/system/mfkey/.catalog/changelog.md @@ -1,5 +1,5 @@ ## 3.1 - - Key recovery is 20% faster, added write buffering of Static Encrypted Nested key candidates + - Key recovery is 20% faster, new write buffering of Static Encrypted Nested key candidates performs recovery 70x faster ## 3.0 - Added Static Encrypted Nested key recovery, added NFC app support, dropped FlipperNested support ## 2.7 diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index 0b68e31e0..e49b96263 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -11,8 +11,6 @@ // TODO: Find ~1 KB memory leak // TODO: Use seednt16 to reduce static encrypted key candidates: https://gist.github.com/noproto/8102f8f32546564cd674256e62ff76ea // https://eprint.iacr.org/2024/1275.pdf section X -// TODO: Static Encrypted: Minimum RAM for adding to keys dict (avoid crashes) -// TODO: Static Encrypted: Optimize KeysDict or buffer keys to write in chunks #include #include @@ -40,6 +38,7 @@ #define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") #define MAX_NAME_LEN 32 #define MAX_PATH_LEN 64 +#define STATIC_ENCRYPTED_RAM_THRESHOLD 4096 #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) @@ -66,6 +65,50 @@ static int16_t eta_total_time = 705; // MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value static uint8_t MSB_LIMIT = 16; +static inline void flush_key_buffer(ProgramState *program_state) +{ + if (program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) + { + // Pre-allocate exact size needed: 12 hex chars + 1 newline per key + size_t total_size = program_state->key_buffer_count * 13; + //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); + //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); + char* batch_buffer = malloc(total_size + 1); // +1 for null terminator + + char* ptr = batch_buffer; + const char hex_chars[] = "0123456789ABCDEF"; + + for (size_t i = 0; i < program_state->key_buffer_count; i++) + { + // Convert key to hex string directly into buffer + for (size_t j = 0; j < sizeof(MfClassicKey); j++) + { + uint8_t byte = program_state->key_buffer[i].data[j]; + *ptr++ = hex_chars[byte >> 4]; + *ptr++ = hex_chars[byte & 0x0F]; + } + *ptr++ = '\n'; + } + *ptr = '\0'; + + // Write all keys at once by directly accessing the stream + Stream* stream = program_state->cuid_dict->stream; + uint32_t actual_pos = stream_tell(stream); + + if (stream_seek(stream, 0, StreamOffsetFromEnd) && + stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) + { + // Update total key count + program_state->cuid_dict->total_keys += program_state->key_buffer_count; + } + + // May not be needed + stream_seek(stream, actual_pos, StreamOffsetFromStart); + free(batch_buffer); + program_state->key_buffer_count = 0; + } +} + static inline int check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) { @@ -115,7 +158,16 @@ check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_sta // Found key candidate crypto1_get_lfsr(t, &(n->key)); program_state->num_candidates++; - keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); + + // Use key buffer - buffer is guaranteed to be available for static_encrypted + program_state->key_buffer[program_state->key_buffer_count] = n->key; + program_state->key_buffer_count++; + + // Flush buffer when full + if (program_state->key_buffer_count >= program_state->key_buffer_size) + { + flush_key_buffer(program_state); + } } } } @@ -680,53 +732,102 @@ void **allocate_blocks(const size_t *block_sizes, int num_blocks) return block_pointers; } -// Inline function for checking if we're at full speed -static inline bool is_full_speed() -{ - return MSB_LIMIT == 16; -} - bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) { bool found = false; const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - void **block_pointers = allocate_blocks(block_sizes, num_blocks); - if (block_pointers == NULL) + // Reset globals each nonce + eta_round_time = 44; + eta_total_time = 705; + MSB_LIMIT = 16; + + // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys + bool use_half_speed = (n->attack == static_encrypted); + if (use_half_speed) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - if (is_full_speed()) + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } + + void **block_pointers = allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); + if (block_pointers == NULL) { + if (n->attack != static_encrypted) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed // eta_round_time *= 2; eta_total_time *= 2; MSB_LIMIT /= 2; - } - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if (block_pointers == NULL) + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further program_state->err = InsufficientRAM; program_state->mfkey_state = Error; return false; } } - // Adjust estimates for static encrypted attacks - if (n->attack == static_encrypted) - { - eta_round_time *= 4; - eta_total_time *= 4; - if (is_full_speed()) - { - eta_round_time *= 4; - eta_total_time *= 4; - } - } struct Msb *odd_msbs = block_pointers[0]; struct Msb *even_msbs = block_pointers[1]; unsigned int *temp_states_odd = block_pointers[2]; unsigned int *temp_states_even = block_pointers[3]; unsigned int *states_buffer = block_pointers[4]; + + // Allocate key buffer for static encrypted nonces + if (n->attack == static_encrypted) + { + size_t available_ram = memmgr_heap_get_max_free_block(); + // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string + // Plus original 6 bytes in buffer = 19 bytes total per key + // Add extra safety margin for string overhead and other allocations + const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; + const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation + if (available_ram > safety_threshold) + { + program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; + program_state->key_buffer = malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); + program_state->key_buffer_count = 0; + if (!program_state->key_buffer) + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + int oks = 0, eks = 0; int i = 0, msb = 0; for (i = 31; i >= 0; i -= 2) @@ -768,6 +869,17 @@ bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_ break; } } + + // Final flush and cleanup for key buffer + if (n->attack == static_encrypted && program_state->key_buffer) + { + flush_key_buffer(program_state); + free(program_state->key_buffer); + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + // Free the allocated blocks for (int i = 0; i < num_blocks; i++) { @@ -946,16 +1058,33 @@ void mfkey(ProgramState *program_state) if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + // Check for non-recoverable errors and break the loop + if (program_state->mfkey_state == Error) { - keys_dict_free(program_state->cuid_dict); + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; } if (program_state->close_thread_please) { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } break; } // No key found in recover() or static encrypted (program_state->num_completed)++; + // Free CUID dictionary after each static_encrypted nonce processing + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } continue; } (program_state->cracked)++; diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 74274edc8..7b5be5c5a 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -58,6 +58,9 @@ typedef struct bool close_thread_please; FuriThread *mfkeythread; KeysDict *cuid_dict; + MfClassicKey *key_buffer; + size_t key_buffer_size; + size_t key_buffer_count; } ProgramState; typedef enum From 37d8ed1d6fe1c29b797f5061f564e12e4588e2af Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 24 Aug 2025 22:50:35 -0400 Subject: [PATCH 21/21] Update README.md --- ReadMe.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 074c73150..c30c3bb22 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -32,12 +32,12 @@ Coming from other custom firmware, you'll get: - *MIFARE Classic Accelerated dictionary attack*: dictionary attacks reduced to several seconds - checks ~3500 keys per second - *MIFARE Classic Nested attack support*: collects nested nonces to be cracked by MFKey - *MIFARE Classic Static encrypted backdoor support*: collects static encrypted nonces to be cracked by MFKey using NXP/Fudan backdoor -- **MFKey 3.0**: Mfkey32, Static Nested, and Static Encrypted attacks all on your Flipper Zero -- **MIFARE Ultralight C Dictionary attack** (coming soon!) -- **MIFARE Ultralight C Emulation** (coming soon!) +- **MFKey 3.1**: Mfkey32, Static Nested, and Static Encrypted attacks all on your Flipper Zero +- **MIFARE Ultralight C Dictionary attack**: Ultralight C key management and bruteforce attacks, feature parity with MIFARE Classic +- **MIFARE Ultralight C Key recovery** (NXP disclosure in progress!) - **NFC app memory improvements** (coming soon!) - **Minimal theme** (coming soon!) -- *Future*: MIFARE DESFire Dictionary attack, Tesla credential enrollment +- *Future*: Tesla credential enrollment, MIFARE Classic EV1 support (signature sector Nested attacks), fully locked Ultralight C support, faster static encrypted attacks # Contributing