diff --git a/applications/external/picopass/picopass.c b/applications/external/picopass/picopass.c index c1428b2fb..f984eec99 100644 --- a/applications/external/picopass/picopass.c +++ b/applications/external/picopass/picopass.c @@ -73,6 +73,12 @@ Picopass* picopass_alloc() { view_dispatcher_add_view( picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget)); + picopass->dict_attack = dict_attack_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, + PicopassViewDictAttack, + dict_attack_get_view(picopass->dict_attack)); + return picopass; } @@ -103,6 +109,9 @@ void picopass_free(Picopass* picopass) { view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget); widget_free(picopass->widget); + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewDictAttack); + dict_attack_free(picopass->dict_attack); + // Worker picopass_worker_stop(picopass->worker); picopass_worker_free(picopass->worker); diff --git a/applications/external/picopass/picopass_device.h b/applications/external/picopass/picopass_device.h index d7d0977df..7fc35ebda 100644 --- a/applications/external/picopass/picopass_device.h +++ b/applications/external/picopass/picopass_device.h @@ -27,8 +27,16 @@ #define PICOPASS_APP_EXTENSION ".picopass" #define PICOPASS_APP_SHADOW_EXTENSION ".pas" +#define PICOPASS_DICT_KEY_BATCH_SIZE 10 + typedef void (*PicopassLoadingCallback)(void* context, bool state); +typedef struct { + IclassEliteDict* dict; + IclassEliteDictType type; + uint8_t current_sector; +} IclassEliteDictAttackData; + typedef enum { PicopassDeviceEncryptionUnknown = 0, PicopassDeviceEncryptionNone = 0x14, @@ -69,6 +77,7 @@ typedef struct { typedef struct { PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT]; PicopassPacs pacs; + IclassEliteDictAttackData iclass_elite_dict_attack_data; } PicopassDeviceData; typedef struct { diff --git a/applications/external/picopass/picopass_i.h b/applications/external/picopass/picopass_i.h index 79c2a1af8..23da3056e 100644 --- a/applications/external/picopass/picopass_i.h +++ b/applications/external/picopass/picopass_i.h @@ -21,6 +21,7 @@ #include #include "scenes/picopass_scene.h" +#include "views/dict_attack.h" #include #include @@ -36,6 +37,7 @@ enum PicopassCustomEvent { PicopassCustomEventWorkerExit, PicopassCustomEventByteInputDone, PicopassCustomEventTextInputDone, + PicopassCustomEventDictAttackSkip, }; typedef enum { @@ -60,6 +62,7 @@ struct Picopass { Loading* loading; TextInput* text_input; Widget* widget; + DictAttack* dict_attack; }; typedef enum { @@ -68,6 +71,7 @@ typedef enum { PicopassViewLoading, PicopassViewTextInput, PicopassViewWidget, + PicopassViewDictAttack, } PicopassView; Picopass* picopass_alloc(); diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c index 2f7aba35f..06d361fb5 100644 --- a/applications/external/picopass/picopass_worker.c +++ b/applications/external/picopass/picopass_worker.c @@ -23,7 +23,7 @@ PicopassWorker* picopass_worker_alloc() { // Worker thread attributes picopass_worker->thread = - furi_thread_alloc_ex("PicopassWorker", 8192, picopass_worker_task, picopass_worker); + furi_thread_alloc_ex("PicopassWorker", 8 * 1024, picopass_worker_task, picopass_worker); picopass_worker->callback = NULL; picopass_worker->context = NULL; @@ -66,14 +66,12 @@ void picopass_worker_start( void picopass_worker_stop(PicopassWorker* picopass_worker) { furi_assert(picopass_worker); - if(picopass_worker->state == PicopassWorkerStateBroken || - picopass_worker->state == PicopassWorkerStateReady) { - return; - } - picopass_worker_disable_field(ERR_NONE); + furi_assert(picopass_worker->thread); - picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop); - furi_thread_join(picopass_worker->thread); + if(furi_thread_get_state(picopass_worker->thread) != FuriThreadStateStopped) { + picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop); + furi_thread_join(picopass_worker->thread); + } } void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) { @@ -460,6 +458,132 @@ ReturnCode picopass_write_block(PicopassBlock* AA1, uint8_t blockNo, uint8_t* ne return ERR_NONE; } +void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) { + furi_assert(picopass_worker); + furi_assert(picopass_worker->callback); + + picopass_device_data_clear(picopass_worker->dev_data); + PicopassDeviceData* dev_data = picopass_worker->dev_data; + PicopassBlock* AA1 = dev_data->AA1; + PicopassPacs* pacs = &dev_data->pacs; + + for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { + memset(AA1[i].data, 0, sizeof(AA1[i].data)); + } + memset(pacs, 0, sizeof(PicopassPacs)); + + IclassEliteDictAttackData* dict_attack_data = + &picopass_worker->dev_data->iclass_elite_dict_attack_data; + bool elite = (dict_attack_data->type != IclassStandardDictTypeFlipper); + + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + + size_t index = 0; + uint8_t key[PICOPASS_BLOCK_LEN] = {0}; + + // Load dictionary + IclassEliteDict* dict = dict_attack_data->dict; + if(!dict) { + FURI_LOG_E(TAG, "Dictionary not found"); + picopass_worker->callback(PicopassWorkerEventNoDictFound, picopass_worker->context); + return; + } + + do { + if(picopass_detect_card(1000) == ERR_NONE) { + picopass_worker->callback(PicopassWorkerEventCardDetected, picopass_worker->context); + + // Process first found device + err = picopass_read_preauth(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_preauth error %d", err); + picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); + return; + } + + // Thank you proxmark! + pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8); + pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); + if(pacs->se_enabled) { + FURI_LOG_D(TAG, "SE enabled"); + picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); + return; + } + + break; + } else { + picopass_worker->callback(PicopassWorkerEventNoCardDetected, picopass_worker->context); + } + if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break; + + furi_delay_ms(100); + } while(true); + + FURI_LOG_D( + TAG, "Start Dictionary attack, Key Count %lu", iclass_elite_dict_get_total_keys(dict)); + while(iclass_elite_dict_get_next_key(dict, key)) { + FURI_LOG_T(TAG, "Key %zu", index); + if(++index % PICOPASS_DICT_KEY_BATCH_SIZE == 0) { + picopass_worker->callback( + PicopassWorkerEventNewDictKeyBatch, picopass_worker->context); + } + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + break; + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; + uint8_t* div_key = AA1[PICOPASS_KD_BLOCK_INDEX].data; + + loclass_iclass_calc_div_key(csn, key, div_key, elite); + loclass_opt_doReaderMAC(ccnr, div_key, mac); + + err = rfalPicoPassPollerCheck(mac, &chkRes); + if(err == ERR_NONE) { + FURI_LOG_I(TAG, "Found key"); + memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); + err = picopass_read_card(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_card error %d", err); + picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); + break; + } + + err = picopass_device_parse_credential(AA1, pacs); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); + picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); + break; + } + + err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); + picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); + break; + } + picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); + break; + } + + if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break; + } + FURI_LOG_D(TAG, "Dictionary complete"); + if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { + picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); + } else { + picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); + } +} + int32_t picopass_worker_task(void* context) { PicopassWorker* picopass_worker = context; @@ -470,9 +594,12 @@ int32_t picopass_worker_task(void* context) { picopass_worker_write(picopass_worker); } else if(picopass_worker->state == PicopassWorkerStateWriteKey) { picopass_worker_write_key(picopass_worker); + } else if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { + picopass_worker_elite_dict_attack(picopass_worker); + } else { + FURI_LOG_W(TAG, "Unknown state %d", picopass_worker->state); } picopass_worker_disable_field(ERR_NONE); - picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); return 0; diff --git a/applications/external/picopass/picopass_worker.h b/applications/external/picopass/picopass_worker.h index f5e9f3039..e9d37481b 100644 --- a/applications/external/picopass/picopass_worker.h +++ b/applications/external/picopass/picopass_worker.h @@ -14,6 +14,7 @@ typedef enum { PicopassWorkerStateDetect, PicopassWorkerStateWrite, PicopassWorkerStateWriteKey, + PicopassWorkerStateEliteDictAttack, // Transition PicopassWorkerStateStop, } PicopassWorkerState; @@ -27,8 +28,10 @@ typedef enum { PicopassWorkerEventFail, PicopassWorkerEventNoCardDetected, PicopassWorkerEventSeEnabled, - - PicopassWorkerEventStartReading, + PicopassWorkerEventAborted, + PicopassWorkerEventCardDetected, + PicopassWorkerEventNewDictKeyBatch, + PicopassWorkerEventNoDictFound, } PicopassWorkerEvent; typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context); diff --git a/applications/external/picopass/scenes/picopass_scene_config.h b/applications/external/picopass/scenes/picopass_scene_config.h index f5a90d46e..8ea970498 100644 --- a/applications/external/picopass/scenes/picopass_scene_config.h +++ b/applications/external/picopass/scenes/picopass_scene_config.h @@ -14,3 +14,4 @@ ADD_SCENE(picopass, write_card_success, WriteCardSuccess) ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess) ADD_SCENE(picopass, write_key, WriteKey) ADD_SCENE(picopass, key_menu, KeyMenu) +ADD_SCENE(picopass, elite_dict_attack, EliteDictAttack) diff --git a/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c new file mode 100644 index 000000000..c76a8ffae --- /dev/null +++ b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c @@ -0,0 +1,170 @@ +#include "../picopass_i.h" +#include + +#define TAG "IclassEliteDictAttack" + +typedef enum { + DictAttackStateIdle, + DictAttackStateUserDictInProgress, + DictAttackStateFlipperDictInProgress, + DictAttackStateStandardDictInProgress, +} DictAttackState; + +void picopass_dict_attack_worker_callback(PicopassWorkerEvent event, void* context) { + furi_assert(context); + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, event); +} + +void picopass_dict_attack_result_callback(void* context) { + furi_assert(context); + Picopass* picopass = context; + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventDictAttackSkip); +} + +static void + picopass_scene_elite_dict_attack_prepare_view(Picopass* picopass, DictAttackState state) { + IclassEliteDictAttackData* dict_attack_data = + &picopass->dev->dev_data.iclass_elite_dict_attack_data; + PicopassWorkerState worker_state = PicopassWorkerStateReady; + IclassEliteDict* dict = NULL; + + // Identify scene state + if(state == DictAttackStateIdle) { + if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) { + FURI_LOG_D(TAG, "Starting with user dictionary"); + state = DictAttackStateUserDictInProgress; + } else { + FURI_LOG_D(TAG, "Starting with standard dictionary"); + state = DictAttackStateStandardDictInProgress; + } + } else if(state == DictAttackStateUserDictInProgress) { + FURI_LOG_D(TAG, "Moving from user dictionary to standard dictionary"); + state = DictAttackStateStandardDictInProgress; + } else if(state == DictAttackStateStandardDictInProgress) { + FURI_LOG_D(TAG, "Moving from standard dictionary to elite dictionary"); + state = DictAttackStateFlipperDictInProgress; + } + + // Setup view + if(state == DictAttackStateUserDictInProgress) { + worker_state = PicopassWorkerStateEliteDictAttack; + dict_attack_set_header(picopass->dict_attack, "Elite User Dictionary"); + dict_attack_data->type = IclassEliteDictTypeUser; + dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser); + + // If failed to load user dictionary - try the system dictionary + if(!dict) { + FURI_LOG_E(TAG, "User dictionary not found"); + state = DictAttackStateStandardDictInProgress; + } + } + if(state == DictAttackStateStandardDictInProgress) { + worker_state = PicopassWorkerStateEliteDictAttack; + dict_attack_set_header(picopass->dict_attack, "Standard System Dictionary"); + dict_attack_data->type = IclassStandardDictTypeFlipper; + dict = iclass_elite_dict_alloc(IclassStandardDictTypeFlipper); + + if(!dict) { + FURI_LOG_E(TAG, "Flipper standard dictionary not found"); + state = DictAttackStateFlipperDictInProgress; + } + } + if(state == DictAttackStateFlipperDictInProgress) { + worker_state = PicopassWorkerStateEliteDictAttack; + dict_attack_set_header(picopass->dict_attack, "Elite System Dictionary"); + dict_attack_data->type = IclassEliteDictTypeFlipper; + dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper); + if(!dict) { + FURI_LOG_E(TAG, "Flipper Elite dictionary not found"); + // Pass through to let the worker handle the failure + } + } + // Free previous dictionary + if(dict_attack_data->dict) { + iclass_elite_dict_free(dict_attack_data->dict); + } + dict_attack_data->dict = dict; + scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack, state); + dict_attack_set_callback( + picopass->dict_attack, picopass_dict_attack_result_callback, picopass); + dict_attack_set_current_sector(picopass->dict_attack, 0); + dict_attack_set_card_detected(picopass->dict_attack); + dict_attack_set_total_dict_keys( + picopass->dict_attack, dict ? iclass_elite_dict_get_total_keys(dict) : 0); + picopass_worker_start( + picopass->worker, + worker_state, + &picopass->dev->dev_data, + picopass_dict_attack_worker_callback, + picopass); +} + +void picopass_scene_elite_dict_attack_on_enter(void* context) { + Picopass* picopass = context; + picopass_scene_elite_dict_attack_prepare_view(picopass, DictAttackStateIdle); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewDictAttack); + picopass_blink_start(picopass); + notification_message(picopass->notifications, &sequence_display_backlight_enforce_on); +} + +bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassWorkerEventSuccess || + event.event == PicopassWorkerEventAborted) { + if(state == DictAttackStateUserDictInProgress || + state == DictAttackStateStandardDictInProgress) { + picopass_worker_stop(picopass->worker); + picopass_scene_elite_dict_attack_prepare_view(picopass, state); + consumed = true; + } else { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); + consumed = true; + } + } else if(event.event == PicopassWorkerEventCardDetected) { + dict_attack_set_card_detected(picopass->dict_attack); + consumed = true; + } else if(event.event == PicopassWorkerEventNoCardDetected) { + dict_attack_set_card_removed(picopass->dict_attack); + consumed = true; + } else if(event.event == PicopassWorkerEventNewDictKeyBatch) { + dict_attack_inc_current_dict_key(picopass->dict_attack, PICOPASS_DICT_KEY_BATCH_SIZE); + consumed = true; + } else if(event.event == PicopassCustomEventDictAttackSkip) { + if(state == DictAttackStateUserDictInProgress) { + picopass_worker_stop(picopass->worker); + consumed = true; + } else if(state == DictAttackStateFlipperDictInProgress) { + picopass_worker_stop(picopass->worker); + consumed = true; + } else if(state == DictAttackStateStandardDictInProgress) { + picopass_worker_stop(picopass->worker); + consumed = true; + } + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + return consumed; +} + +void picopass_scene_elite_dict_attack_on_exit(void* context) { + Picopass* picopass = context; + IclassEliteDictAttackData* dict_attack_data = + &picopass->dev->dev_data.iclass_elite_dict_attack_data; + // Stop worker + picopass_worker_stop(picopass->worker); + if(dict_attack_data->dict) { + iclass_elite_dict_free(dict_attack_data->dict); + dict_attack_data->dict = NULL; + } + dict_attack_reset(picopass->dict_attack); + picopass_blink_stop(picopass); + notification_message(picopass->notifications, &sequence_display_backlight_enforce_auto); +} diff --git a/applications/external/picopass/scenes/picopass_scene_read_card_success.c b/applications/external/picopass/scenes/picopass_scene_read_card_success.c index f078d460a..198b21d98 100644 --- a/applications/external/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/external/picopass/scenes/picopass_scene_read_card_success.c @@ -47,8 +47,21 @@ void picopass_scene_read_card_success_on_enter(void* context) { if(pacs->se_enabled) { furi_string_cat_printf(credential_str, "SE enabled"); } + + widget_add_button_element( + widget, + GuiButtonTypeCenter, + "Menu", + picopass_scene_read_card_success_widget_callback, + picopass); } else if(empty) { furi_string_cat_printf(wiegand_str, "Empty"); + widget_add_button_element( + widget, + GuiButtonTypeCenter, + "Menu", + picopass_scene_read_card_success_widget_callback, + picopass); } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { // Neither of these are valid. Indicates the block was all 0x00 or all 0xff furi_string_cat_printf(wiegand_str, "Invalid PACS"); @@ -56,6 +69,12 @@ void picopass_scene_read_card_success_on_enter(void* context) { if(pacs->se_enabled) { furi_string_cat_printf(credential_str, "SE enabled"); } + widget_add_button_element( + widget, + GuiButtonTypeCenter, + "Menu", + picopass_scene_read_card_success_widget_callback, + picopass); } else { size_t bytesLength = 1 + pacs->record.bitLength / 8; furi_string_set(credential_str, ""); @@ -137,6 +156,9 @@ bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent picopass_device_set_name(picopass->dev, ""); scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu); consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + consumed = scene_manager_search_and_switch_to_another_scene( + picopass->scene_manager, PicopassSceneStart); } } return consumed; diff --git a/applications/external/picopass/scenes/picopass_scene_start.c b/applications/external/picopass/scenes/picopass_scene_start.c index d33a1d264..8f7b627aa 100644 --- a/applications/external/picopass/scenes/picopass_scene_start.c +++ b/applications/external/picopass/scenes/picopass_scene_start.c @@ -1,10 +1,8 @@ #include "../picopass_i.h" enum SubmenuIndex { SubmenuIndexRead, - SubmenuIndexRunScript, + SubmenuIndexEliteDictAttack, SubmenuIndexSaved, - SubmenuIndexAddManually, - SubmenuIndexDebug, }; void picopass_scene_start_submenu_callback(void* context, uint32_t index) { @@ -17,6 +15,12 @@ void picopass_scene_start_on_enter(void* context) { Submenu* submenu = picopass->submenu; submenu_add_item( submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); + submenu_add_item( + submenu, + "Elite Dict. Attack", + SubmenuIndexEliteDictAttack, + picopass_scene_start_submenu_callback, + picopass); submenu_add_item( submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass); @@ -43,6 +47,11 @@ bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { picopass->scene_manager, PicopassSceneStart, SubmenuIndexSaved); scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect); consumed = true; + } else if(event.event == SubmenuIndexEliteDictAttack) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneStart, SubmenuIndexEliteDictAttack); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneEliteDictAttack); + consumed = true; } } diff --git a/applications/external/picopass/views/dict_attack.c b/applications/external/picopass/views/dict_attack.c new file mode 100644 index 000000000..fb7335f6c --- /dev/null +++ b/applications/external/picopass/views/dict_attack.c @@ -0,0 +1,281 @@ +#include "dict_attack.h" + +#include + +typedef enum { + DictAttackStateRead, + DictAttackStateCardRemoved, +} DictAttackState; + +struct DictAttack { + View* view; + DictAttackCallback callback; + void* context; +}; + +typedef struct { + DictAttackState state; + MfClassicType type; + FuriString* header; + uint8_t sectors_total; + uint8_t sectors_read; + uint8_t sector_current; + uint8_t keys_total; + uint8_t keys_found; + uint16_t dict_keys_total; + uint16_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; +} DictAttackViewModel; + +static void dict_attack_draw_callback(Canvas* canvas, void* model) { + DictAttackViewModel* m = model; + if(m->state == DictAttackStateCardRemoved) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Lost the tag!"); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); + } else if(m->state == DictAttackStateRead) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + 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->sector_current); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + float dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + float progress = m->sectors_total == 0 ? 0 : + ((float)(m->sector_current) + dict_progress) / + (float)(m->sectors_total); + if(progress > 1.0) { + progress = 1.0; + } + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%d", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); + } + 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->keys_total); + 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"); +} + +static bool dict_attack_input_callback(InputEvent* event, void* context) { + DictAttack* dict_attack = context; + bool consumed = false; + if(event->type == InputTypeShort && event->key == InputKeyOk) { + if(dict_attack->callback) { + dict_attack->callback(dict_attack->context); + } + consumed = true; + } + return consumed; +} + +DictAttack* dict_attack_alloc() { + DictAttack* dict_attack = malloc(sizeof(DictAttack)); + dict_attack->view = view_alloc(); + view_allocate_model(dict_attack->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); + view_set_draw_callback(dict_attack->view, dict_attack_draw_callback); + view_set_input_callback(dict_attack->view, dict_attack_input_callback); + view_set_context(dict_attack->view, dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { model->header = furi_string_alloc(); }, + false); + return dict_attack; +} + +void dict_attack_free(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { furi_string_free(model->header); }, + false); + view_free(dict_attack->view); + free(dict_attack); +} + +void dict_attack_reset(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->state = DictAttackStateRead; + model->type = MfClassicType1k; + model->sectors_total = 1; + model->sectors_read = 0; + model->sector_current = 0; + model->keys_total = 0; + model->keys_found = 0; + model->dict_keys_total = 0; + model->dict_keys_current = 0; + model->is_key_attack = false; + furi_string_reset(model->header); + }, + false); +} + +View* dict_attack_get_view(DictAttack* dict_attack) { + furi_assert(dict_attack); + return dict_attack->view; +} + +void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context) { + furi_assert(dict_attack); + furi_assert(callback); + dict_attack->callback = callback; + dict_attack->context = context; +} + +void dict_attack_set_header(DictAttack* dict_attack, const char* header) { + furi_assert(dict_attack); + furi_assert(header); + + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { furi_string_set(model->header, header); }, + true); +} + +void dict_attack_set_card_detected(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->state = DictAttackStateRead; + model->sectors_total = 1; + model->keys_total = model->sectors_total; + }, + true); +} + +void dict_attack_set_card_removed(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { model->state = DictAttackStateCardRemoved; }, + true); +} + +void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, DictAttackViewModel * model, { model->sectors_read = sec_read; }, true); +} + +void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); +} + +void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->sector_current = curr_sec; + model->dict_keys_current = 0; + }, + true); +} + +void dict_attack_inc_current_sector(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->sector_current < model->sectors_total) { + model->sector_current++; + model->dict_keys_current = 0; + } + }, + true); +} + +void dict_attack_inc_keys_found(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->keys_found < model->keys_total) { + model->keys_found++; + } + }, + true); +} + +void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { model->dict_keys_total = dict_keys_total; }, + true); +} + +void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->dict_keys_current + keys_tried < model->dict_keys_total) { + model->dict_keys_current += keys_tried; + } + }, + true); +} + +void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->is_key_attack = is_key_attack; + model->key_attack_current_sector = sector; + }, + true); +} + +void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->key_attack_current_sector < model->sectors_total) { + model->key_attack_current_sector++; + } + }, + true); +} diff --git a/applications/external/picopass/views/dict_attack.h b/applications/external/picopass/views/dict_attack.h new file mode 100644 index 000000000..bdfa3e952 --- /dev/null +++ b/applications/external/picopass/views/dict_attack.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include + +#include + +typedef struct DictAttack DictAttack; + +typedef void (*DictAttackCallback)(void* context); + +DictAttack* dict_attack_alloc(); + +void dict_attack_free(DictAttack* dict_attack); + +void dict_attack_reset(DictAttack* dict_attack); + +View* dict_attack_get_view(DictAttack* dict_attack); + +void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context); + +void dict_attack_set_header(DictAttack* dict_attack, const char* header); + +void dict_attack_set_card_detected(DictAttack* dict_attack); + +void dict_attack_set_card_removed(DictAttack* dict_attack); + +void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read); + +void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found); + +void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec); + +void dict_attack_inc_current_sector(DictAttack* dict_attack); + +void dict_attack_inc_keys_found(DictAttack* dict_attack); + +void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total); + +void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried); + +void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector); + +void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack); diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index b68505c51..ad3bbd665 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -37,9 +37,11 @@ char cli_getc(Cli* cli) { if(cli->session != NULL) { if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) { cli_reset(cli); + furi_delay_tick(10); } } else { cli_reset(cli); + furi_delay_tick(10); } return c; } diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 50c05a406..4d84cac50 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -81,7 +81,7 @@ void view_allocate_model(View* view, ViewModelType type, size_t size) { view->model = malloc(size); } else if(view->model_type == ViewModelTypeLocking) { ViewModelLocking* model = malloc(sizeof(ViewModelLocking)); - model->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + model->mutex = furi_mutex_alloc(FuriMutexTypeRecursive); furi_check(model->mutex); model->data = malloc(size); view->model = model; diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index fc679114f..011add953 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -73,12 +73,11 @@ typedef enum { #define USB_SRV_ALL_EVENTS (UsbEventReset | UsbEventRequest | UsbEventMessage) PLACE_IN_SECTION("MB_MEM2") static UsbSrv usb = {0}; +PLACE_IN_SECTION("MB_MEM2") static uint32_t ubuf[0x20]; +PLACE_IN_SECTION("MB_MEM2") usbd_device udev; static const struct usb_string_descriptor dev_lang_desc = USB_ARRAY_DESC(USB_LANGID_ENG_US); -static uint32_t ubuf[0x20]; -usbd_device udev; - static int32_t furi_hal_usb_thread(void* context); static usbd_respond usb_descriptor_get(usbd_ctlreq* req, void** address, uint16_t* length); static void reset_evt(usbd_device* dev, uint8_t event, uint8_t ep); diff --git a/firmware/targets/f7/inc/FreeRTOSConfig.h b/firmware/targets/f7/inc/FreeRTOSConfig.h index 69ef9406b..9486f501c 100644 --- a/firmware/targets/f7/inc/FreeRTOSConfig.h +++ b/firmware/targets/f7/inc/FreeRTOSConfig.h @@ -58,6 +58,7 @@ extern uint32_t SystemCoreClock; #define configTIMER_SERVICE_TASK_NAME "TimersSrv" #define configIDLE_TASK_NAME "(-_-)" +#define configIDLE_TASK_STACK_DEPTH 128 /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */ @@ -138,3 +139,7 @@ standard names. */ #define traceTASK_SWITCHED_IN() \ extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack) + +#define portCLEAN_UP_TCB(pxTCB) \ + extern void furi_thread_cleanup_tcb_event(TaskHandle_t task); \ + furi_thread_cleanup_tcb_event(pxTCB) diff --git a/furi/core/thread.c b/furi/core/thread.c index b45651c29..d78070d61 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -24,7 +24,6 @@ struct FuriThreadStdout { }; struct FuriThread { - bool is_service; FuriThreadState state; int32_t ret; @@ -37,14 +36,19 @@ struct FuriThread { char* name; char* appid; - configSTACK_DEPTH_TYPE stack_size; FuriThreadPriority priority; TaskHandle_t task_handle; - bool heap_trace_enabled; size_t heap_size; FuriThreadStdout output; + + // Keep all non-alignable byte types in one place, + // this ensures that the size of this structure is minimal + bool is_service; + bool heap_trace_enabled; + + configSTACK_DEPTH_TYPE stack_size; }; static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); @@ -107,14 +111,8 @@ static void furi_thread_body(void* context) { // flush stdout __furi_thread_stdout_flush(thread); - // from here we can't use thread pointer furi_thread_set_state(thread, FuriThreadStateStopped); - // clear thread local storage - furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); - vTaskSetThreadLocalStoragePointer(NULL, 0, NULL); - - thread->task_handle = NULL; vTaskDelete(NULL); furi_thread_catch(); } @@ -249,11 +247,11 @@ void furi_thread_start(FuriThread* thread) { furi_assert(thread); furi_assert(thread->callback); furi_assert(thread->state == FuriThreadStateStopped); - furi_assert(thread->stack_size > 0 && thread->stack_size < 0xFFFF * 4); + furi_assert(thread->stack_size > 0 && thread->stack_size < (UINT16_MAX * sizeof(StackType_t))); furi_thread_set_state(thread, FuriThreadStateStarting); - uint32_t stack = thread->stack_size / 4; + uint32_t stack = thread->stack_size / sizeof(StackType_t); UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal; if(thread->is_service) { thread->task_handle = xTaskCreateStatic( @@ -273,12 +271,25 @@ void furi_thread_start(FuriThread* thread) { furi_check(thread->task_handle); } +void furi_thread_cleanup_tcb_event(TaskHandle_t task) { + FuriThread* thread = pvTaskGetThreadLocalStoragePointer(task, 0); + if(thread) { + // clear thread local storage + vTaskSetThreadLocalStoragePointer(task, 0, NULL); + + thread->task_handle = NULL; + } +} + bool furi_thread_join(FuriThread* thread) { furi_assert(thread); furi_check(furi_thread_get_current() != thread); - // Wait for thread to stop + // !!! IMPORTANT NOTICE !!! + // + // If your thread exited, but your app stuck here: some other thread uses + // all cpu time, which delays kernel from releasing task handle while(thread->task_handle) { furi_delay_ms(10); } diff --git a/furi/core/thread.h b/furi/core/thread.h index 8f4398419..b11a225b5 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -75,6 +75,8 @@ FuriThread* furi_thread_alloc_ex( void* context); /** Release FuriThread + * + * @warning see furi_thread_join * * @param thread FuriThread instance */ @@ -173,6 +175,9 @@ FuriThreadState furi_thread_get_state(FuriThread* thread); void furi_thread_start(FuriThread* thread); /** Join FuriThread + * + * @warning Use this method only when CPU is not busy(Idle task receives + * control), otherwise it will wait forever. * * @param thread FuriThread instance * diff --git a/furi/flipper.c b/furi/flipper.c index 5c2ad8138..8806ce27f 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -54,8 +54,8 @@ void vApplicationGetIdleTaskMemory( StackType_t** stack_ptr, uint32_t* stack_size) { *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configMINIMAL_STACK_SIZE); - *stack_size = configMINIMAL_STACK_SIZE; + *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configIDLE_TASK_STACK_DEPTH); + *stack_size = configIDLE_TASK_STACK_DEPTH; } void vApplicationGetTimerTaskMemory(