Просмотр исходного кода

Elite progress (#2481)

* WIP: builds
* can read standard
* Test standard picopass dictiony during attack
* correctly save diversified key
* read card on success
* more logs
* update file location
* Call setup methods
* backbutton and attempt at skip
* fixed skip
* remove found key state
* rename dictionary attack
* move notification
* center button back to start menu
* wait for card
* Picopass: proper integer formatting
* Picopass: even more proper integer formatting
* remove nextState

Co-authored-by: あく <alleteam@gmail.com>
Eric Betts 2 лет назад
Родитель
Сommit
0161d49d80

+ 9 - 0
applications/external/picopass/picopass.c

@@ -73,6 +73,12 @@ Picopass* picopass_alloc() {
     view_dispatcher_add_view(
     view_dispatcher_add_view(
         picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget));
         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;
     return picopass;
 }
 }
 
 
@@ -103,6 +109,9 @@ void picopass_free(Picopass* picopass) {
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget);
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget);
     widget_free(picopass->widget);
     widget_free(picopass->widget);
 
 
+    view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewDictAttack);
+    dict_attack_free(picopass->dict_attack);
+
     // Worker
     // Worker
     picopass_worker_stop(picopass->worker);
     picopass_worker_stop(picopass->worker);
     picopass_worker_free(picopass->worker);
     picopass_worker_free(picopass->worker);

+ 9 - 0
applications/external/picopass/picopass_device.h

@@ -27,8 +27,16 @@
 #define PICOPASS_APP_EXTENSION ".picopass"
 #define PICOPASS_APP_EXTENSION ".picopass"
 #define PICOPASS_APP_SHADOW_EXTENSION ".pas"
 #define PICOPASS_APP_SHADOW_EXTENSION ".pas"
 
 
+#define PICOPASS_DICT_KEY_BATCH_SIZE 10
+
 typedef void (*PicopassLoadingCallback)(void* context, bool state);
 typedef void (*PicopassLoadingCallback)(void* context, bool state);
 
 
+typedef struct {
+    IclassEliteDict* dict;
+    IclassEliteDictType type;
+    uint8_t current_sector;
+} IclassEliteDictAttackData;
+
 typedef enum {
 typedef enum {
     PicopassDeviceEncryptionUnknown = 0,
     PicopassDeviceEncryptionUnknown = 0,
     PicopassDeviceEncryptionNone = 0x14,
     PicopassDeviceEncryptionNone = 0x14,
@@ -69,6 +77,7 @@ typedef struct {
 typedef struct {
 typedef struct {
     PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT];
     PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT];
     PicopassPacs pacs;
     PicopassPacs pacs;
+    IclassEliteDictAttackData iclass_elite_dict_attack_data;
 } PicopassDeviceData;
 } PicopassDeviceData;
 
 
 typedef struct {
 typedef struct {

+ 4 - 0
applications/external/picopass/picopass_i.h

@@ -21,6 +21,7 @@
 #include <input/input.h>
 #include <input/input.h>
 
 
 #include "scenes/picopass_scene.h"
 #include "scenes/picopass_scene.h"
+#include "views/dict_attack.h"
 
 
 #include <storage/storage.h>
 #include <storage/storage.h>
 #include <lib/toolbox/path.h>
 #include <lib/toolbox/path.h>
@@ -36,6 +37,7 @@ enum PicopassCustomEvent {
     PicopassCustomEventWorkerExit,
     PicopassCustomEventWorkerExit,
     PicopassCustomEventByteInputDone,
     PicopassCustomEventByteInputDone,
     PicopassCustomEventTextInputDone,
     PicopassCustomEventTextInputDone,
+    PicopassCustomEventDictAttackSkip,
 };
 };
 
 
 typedef enum {
 typedef enum {
@@ -60,6 +62,7 @@ struct Picopass {
     Loading* loading;
     Loading* loading;
     TextInput* text_input;
     TextInput* text_input;
     Widget* widget;
     Widget* widget;
+    DictAttack* dict_attack;
 };
 };
 
 
 typedef enum {
 typedef enum {
@@ -68,6 +71,7 @@ typedef enum {
     PicopassViewLoading,
     PicopassViewLoading,
     PicopassViewTextInput,
     PicopassViewTextInput,
     PicopassViewWidget,
     PicopassViewWidget,
+    PicopassViewDictAttack,
 } PicopassView;
 } PicopassView;
 
 
 Picopass* picopass_alloc();
 Picopass* picopass_alloc();

+ 136 - 9
applications/external/picopass/picopass_worker.c

@@ -23,7 +23,7 @@ PicopassWorker* picopass_worker_alloc() {
 
 
     // Worker thread attributes
     // Worker thread attributes
     picopass_worker->thread =
     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->callback = NULL;
     picopass_worker->context = NULL;
     picopass_worker->context = NULL;
@@ -66,14 +66,12 @@ void picopass_worker_start(
 
 
 void picopass_worker_stop(PicopassWorker* picopass_worker) {
 void picopass_worker_stop(PicopassWorker* picopass_worker) {
     furi_assert(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) {
 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;
     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) {
 int32_t picopass_worker_task(void* context) {
     PicopassWorker* picopass_worker = context;
     PicopassWorker* picopass_worker = context;
 
 
@@ -470,9 +594,12 @@ int32_t picopass_worker_task(void* context) {
         picopass_worker_write(picopass_worker);
         picopass_worker_write(picopass_worker);
     } else if(picopass_worker->state == PicopassWorkerStateWriteKey) {
     } else if(picopass_worker->state == PicopassWorkerStateWriteKey) {
         picopass_worker_write_key(picopass_worker);
         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_disable_field(ERR_NONE);
-
     picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
     picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
 
 
     return 0;
     return 0;

+ 5 - 2
applications/external/picopass/picopass_worker.h

@@ -14,6 +14,7 @@ typedef enum {
     PicopassWorkerStateDetect,
     PicopassWorkerStateDetect,
     PicopassWorkerStateWrite,
     PicopassWorkerStateWrite,
     PicopassWorkerStateWriteKey,
     PicopassWorkerStateWriteKey,
+    PicopassWorkerStateEliteDictAttack,
     // Transition
     // Transition
     PicopassWorkerStateStop,
     PicopassWorkerStateStop,
 } PicopassWorkerState;
 } PicopassWorkerState;
@@ -27,8 +28,10 @@ typedef enum {
     PicopassWorkerEventFail,
     PicopassWorkerEventFail,
     PicopassWorkerEventNoCardDetected,
     PicopassWorkerEventNoCardDetected,
     PicopassWorkerEventSeEnabled,
     PicopassWorkerEventSeEnabled,
-
-    PicopassWorkerEventStartReading,
+    PicopassWorkerEventAborted,
+    PicopassWorkerEventCardDetected,
+    PicopassWorkerEventNewDictKeyBatch,
+    PicopassWorkerEventNoDictFound,
 } PicopassWorkerEvent;
 } PicopassWorkerEvent;
 
 
 typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);
 typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);

+ 1 - 0
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, read_factory_success, ReadFactorySuccess)
 ADD_SCENE(picopass, write_key, WriteKey)
 ADD_SCENE(picopass, write_key, WriteKey)
 ADD_SCENE(picopass, key_menu, KeyMenu)
 ADD_SCENE(picopass, key_menu, KeyMenu)
+ADD_SCENE(picopass, elite_dict_attack, EliteDictAttack)

+ 170 - 0
applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c

@@ -0,0 +1,170 @@
+#include "../picopass_i.h"
+#include <dolphin/dolphin.h>
+
+#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);
+}

+ 22 - 0
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) {
         if(pacs->se_enabled) {
             furi_string_cat_printf(credential_str, "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) {
     } else if(empty) {
         furi_string_cat_printf(wiegand_str, "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) {
     } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
         // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
         // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
         furi_string_cat_printf(wiegand_str, "Invalid PACS");
         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) {
         if(pacs->se_enabled) {
             furi_string_cat_printf(credential_str, "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 {
     } else {
         size_t bytesLength = 1 + pacs->record.bitLength / 8;
         size_t bytesLength = 1 + pacs->record.bitLength / 8;
         furi_string_set(credential_str, "");
         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, "");
             picopass_device_set_name(picopass->dev, "");
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu);
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu);
             consumed = true;
             consumed = true;
+        } else if(event.event == GuiButtonTypeCenter) {
+            consumed = scene_manager_search_and_switch_to_another_scene(
+                picopass->scene_manager, PicopassSceneStart);
         }
         }
     }
     }
     return consumed;
     return consumed;

+ 12 - 3
applications/external/picopass/scenes/picopass_scene_start.c

@@ -1,10 +1,8 @@
 #include "../picopass_i.h"
 #include "../picopass_i.h"
 enum SubmenuIndex {
 enum SubmenuIndex {
     SubmenuIndexRead,
     SubmenuIndexRead,
-    SubmenuIndexRunScript,
+    SubmenuIndexEliteDictAttack,
     SubmenuIndexSaved,
     SubmenuIndexSaved,
-    SubmenuIndexAddManually,
-    SubmenuIndexDebug,
 };
 };
 
 
 void picopass_scene_start_submenu_callback(void* context, uint32_t index) {
 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* submenu = picopass->submenu;
     submenu_add_item(
     submenu_add_item(
         submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass);
         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_add_item(
         submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass);
         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);
                 picopass->scene_manager, PicopassSceneStart, SubmenuIndexSaved);
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect);
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect);
             consumed = true;
             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;
         }
         }
     }
     }
 
 

+ 281 - 0
applications/external/picopass/views/dict_attack.c

@@ -0,0 +1,281 @@
+#include "dict_attack.h"
+
+#include <gui/elements.h>
+
+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);
+}

+ 44 - 0
applications/external/picopass/views/dict_attack.h

@@ -0,0 +1,44 @@
+#pragma once
+#include <stdint.h>
+#include <gui/view.h>
+#include <gui/modules/widget.h>
+
+#include <lib/nfc/protocols/mifare_classic.h>
+
+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);