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(