From ad8745dedccfb878e00a06604eca2a8c67144b9c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 May 2024 17:05:56 +0300 Subject: [PATCH] upd picopass --- base_pack/picopass/application.fam | 12 ++ base_pack/picopass/picopass.c | 23 +++ base_pack/picopass/picopass_i.h | 8 + base_pack/picopass/plugin/README.md | 14 ++ base_pack/picopass/plugin/interface.h | 20 +++ base_pack/picopass/plugin/wiegand.c | 152 ++++++++++++++++++ .../scenes/picopass_scene_card_menu.c | 24 +++ .../picopass/scenes/picopass_scene_config.h | 1 + .../scenes/picopass_scene_device_info.c | 23 ++- .../picopass/scenes/picopass_scene_formats.c | 54 +++++++ 10 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 base_pack/picopass/plugin/README.md create mode 100644 base_pack/picopass/plugin/interface.h create mode 100644 base_pack/picopass/plugin/wiegand.c create mode 100644 base_pack/picopass/scenes/picopass_scene_formats.c diff --git a/base_pack/picopass/application.fam b/base_pack/picopass/application.fam index 7831b59744f..bfcb54b73d8 100644 --- a/base_pack/picopass/application.fam +++ b/base_pack/picopass/application.fam @@ -4,6 +4,9 @@ App( apptype=FlipperAppType.EXTERNAL, targets=["f7"], entry_point="picopass_app", + sources=[ + "*.c", "!plugin/*.c", + ], requires=[ "storage", "gui", @@ -23,3 +26,12 @@ App( fap_icon_assets="icons", fap_file_assets="files", ) + +App( + appid="plugin_wiegand", + apptype=FlipperAppType.PLUGIN, + entry_point="plugin_wiegand_ep", + requires=["picopass"], + sources=["plugin/wiegand.c"], + fal_embedded=True, +) diff --git a/base_pack/picopass/picopass.c b/base_pack/picopass/picopass.c index 9b5532e534d..d9ca9d8fa60 100644 --- a/base_pack/picopass/picopass.c +++ b/base_pack/picopass/picopass.c @@ -97,6 +97,27 @@ Picopass* picopass_alloc() { view_dispatcher_add_view( picopass->view_dispatcher, PicopassViewLoclass, loclass_get_view(picopass->loclass)); + picopass->plugin_manager = + plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface); + + picopass->plugin_wiegand = NULL; + if(plugin_manager_load_all(picopass->plugin_manager, APP_DATA_PATH("plugins")) != + PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + } else { + uint32_t plugin_count = plugin_manager_get_count(picopass->plugin_manager); + FURI_LOG_I(TAG, "Loaded %lu plugin(s)", plugin_count); + + for(uint32_t i = 0; i < plugin_count; i++) { + const PluginWiegand* plugin = plugin_manager_get_ep(picopass->plugin_manager, i); + FURI_LOG_I(TAG, "plugin name: %s", plugin->name); + if(strcmp(plugin->name, "Plugin Wiegand") == 0) { + // Have to cast to drop "const" qualifier + picopass->plugin_wiegand = (PluginWiegand*)plugin; + } + } + } + return picopass; } @@ -158,6 +179,8 @@ void picopass_free(Picopass* picopass) { furi_record_close(RECORD_NOTIFICATION); picopass->notifications = NULL; + plugin_manager_free(picopass->plugin_manager); + free(picopass); } diff --git a/base_pack/picopass/picopass_i.h b/base_pack/picopass/picopass_i.h index 5ec15c4bdd2..0dd687dc170 100644 --- a/base_pack/picopass/picopass_i.h +++ b/base_pack/picopass/picopass_i.h @@ -34,6 +34,11 @@ #include "protocol/picopass_poller.h" #include "protocol/picopass_listener.h" +#include "plugin/interface.h" +#include +#include +#include + #define PICOPASS_TEXT_STORE_SIZE 129 #define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt") @@ -107,6 +112,9 @@ struct Picopass { DictAttack* dict_attack; Loclass* loclass; + PluginManager* plugin_manager; + PluginWiegand* plugin_wiegand; + PicopassDictAttackContext dict_attack_ctx; PicopassWriteKeyContext write_key_context; PicopassLoclassContext loclass_context; diff --git a/base_pack/picopass/plugin/README.md b/base_pack/picopass/plugin/README.md new file mode 100644 index 00000000000..2fe51c876c2 --- /dev/null +++ b/base_pack/picopass/plugin/README.md @@ -0,0 +1,14 @@ +# Flipper zero wiegand plugin + +Add as git submodule: `git submodule add https://gitlab.com/bettse/flipper-wiegand-plugin.git plugin` + +Add to your `application.fam` +``` +App( + appid="plugin_wiegand", + apptype=FlipperAppType.PLUGIN, + entry_point="plugin_wiegand_ep", + requires=["seader"], + sources=["plugin/wiegand.c"], +) +``` diff --git a/base_pack/picopass/plugin/interface.h b/base_pack/picopass/plugin/interface.h new file mode 100644 index 00000000000..714b24598b7 --- /dev/null +++ b/base_pack/picopass/plugin/interface.h @@ -0,0 +1,20 @@ +/** + * @file plugin_interface.h + * @brief Example plugin interface. + * + * Common interface between a plugin and host application + */ +#pragma once + +#include +#include +#include + +#define PLUGIN_APP_ID "plugin_wiegand" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + int (*count)(uint8_t, uint64_t); + void (*description)(uint8_t, uint64_t, size_t, FuriString*); +} PluginWiegand; diff --git a/base_pack/picopass/plugin/wiegand.c b/base_pack/picopass/plugin/wiegand.c new file mode 100644 index 00000000000..1a87112d06d --- /dev/null +++ b/base_pack/picopass/plugin/wiegand.c @@ -0,0 +1,152 @@ + +#include "interface.h" + +#include +#include + +/* + * Huge thanks to the proxmark codebase: + * https://github.com/RfidResearchGroup/proxmark3/blob/master/client/src/wiegand_formats.c + */ + +// Structure for packed wiegand messages +// Always align lowest value (last transmitted) bit to ordinal position 0 (lowest valued bit bottom) +typedef struct { + uint8_t Length; // Number of encoded bits in wiegand message (excluding headers and preamble) + uint32_t Top; // Bits in x<<64 positions + uint32_t Mid; // Bits in x<<32 positions + uint32_t Bot; // Lowest ordinal positions +} wiegand_message_t; + +static inline uint8_t oddparity32(uint32_t x) { + return bit_lib_test_parity_32(x, BitLibParityOdd); +} + +static inline uint8_t evenparity32(uint32_t x) { + return bit_lib_test_parity_32(x, BitLibParityEven); +} + +static int wiegand_C1k35s_parse(uint8_t bit_length, uint64_t bits, FuriString* description) { + if(bit_length != 35) { + return 0; + } + + wiegand_message_t value; + value.Mid = bits >> 32; + value.Bot = bits; + wiegand_message_t* packed = &value; + + uint32_t cn = (packed->Bot >> 1) & 0x000FFFFF; + uint32_t fc = ((packed->Mid & 1) << 11) | ((packed->Bot >> 21)); + bool valid = (evenparity32((packed->Mid & 0x1) ^ (packed->Bot & 0xB6DB6DB6)) == + ((packed->Mid >> 1) & 1)) && + (oddparity32((packed->Mid & 0x3) ^ (packed->Bot & 0x6DB6DB6C)) == + ((packed->Bot >> 0) & 1)) && + (oddparity32((packed->Mid & 0x3) ^ (packed->Bot & 0xFFFFFFFF)) == + ((packed->Mid >> 2) & 1)); + + if(valid) { + furi_string_cat_printf(description, "C1k35s\nFC: %ld CN: %ld\n", fc, cn); + return 1; + } + + return 0; +} + +static int wiegand_h10301_parse(uint8_t bit_length, uint64_t bits, FuriString* description) { + if(bit_length != 26) { + return 0; + } + + //E XXXX XXXX XXXX + //XXXX XXXX XXXX O + uint32_t eBitMask = 0x02000000; + uint32_t oBitMask = 0x00000001; + uint32_t eParityMask = 0x01FFE000; + uint32_t oParityMask = 0x00001FFE; + uint8_t eBit = (eBitMask & bits) >> 25; + uint8_t oBit = (oBitMask & bits) >> 0; + + bool eParity = bit_lib_test_parity_32((bits & eParityMask) >> 13, BitLibParityEven) == + (eBit == 1); + bool oParity = bit_lib_test_parity_32((bits & oParityMask) >> 1, BitLibParityOdd) == + (oBit == 1); + + FURI_LOG_D( + PLUGIN_APP_ID, + "eBit: %d, oBit: %d, eParity: %d, oParity: %d", + eBit, + oBit, + eParity, + oParity); + + if(eParity && oParity) { + uint32_t cnMask = 0x1FFFE; + uint16_t cn = ((bits & cnMask) >> 1); + + uint32_t fcMask = 0x1FE0000; + uint16_t fc = ((bits & fcMask) >> 17); + + furi_string_cat_printf(description, "H10301\nFC: %d CN: %d\n", fc, cn); + return 1; + } + + return 0; +} + +static int wiegand_format_count(uint8_t bit_length, uint64_t bits) { + UNUSED(bit_length); + UNUSED(bits); + int count = 0; + FuriString* ignore = furi_string_alloc(); + + count += wiegand_h10301_parse(bit_length, bits, ignore); + count += wiegand_C1k35s_parse(bit_length, bits, ignore); + + furi_string_free(ignore); + + FURI_LOG_I(PLUGIN_APP_ID, "count: %i", count); + return count; +} + +static void wiegand_format_description( + uint8_t bit_length, + uint64_t bits, + size_t index, + FuriString* description) { + FURI_LOG_I(PLUGIN_APP_ID, "description %d", index); + UNUSED(bit_length); + UNUSED(bits); + + size_t i = 0; + + i += wiegand_h10301_parse(bit_length, bits, description); + if(i - 1 == index) { + return; + } + i += wiegand_C1k35s_parse(bit_length, bits, description); + if(i - 1 == index) { + return; + } + + furi_string_cat_printf(description, "[%i] FC: CN:", index); +} + +/* Actual implementation of app<>plugin interface */ +static const PluginWiegand plugin_wiegand = { + .name = "Plugin Wiegand", + .count = &wiegand_format_count, + .description = &wiegand_format_description, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor plugin_wiegand_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &plugin_wiegand, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* plugin_wiegand_ep(void) { + return &plugin_wiegand_descriptor; +} diff --git a/base_pack/picopass/scenes/picopass_scene_card_menu.c b/base_pack/picopass/scenes/picopass_scene_card_menu.c index d9e27ea5c4b..9d44f5642d9 100644 --- a/base_pack/picopass/scenes/picopass_scene_card_menu.c +++ b/base_pack/picopass/scenes/picopass_scene_card_menu.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexSave, SubmenuIndexSaveAsLF, SubmenuIndexSaveAsSeader, + SubmenuIndexParse, SubmenuIndexChangeKey, SubmenuIndexWrite, SubmenuIndexEmulate, @@ -22,6 +23,7 @@ void picopass_scene_card_menu_on_enter(void* context) { PicopassPacs* pacs = &picopass->dev->dev_data.pacs; PicopassBlock* card_data = picopass->dev->dev_data.card_data; PicopassDeviceAuthMethod auth = picopass->dev->dev_data.auth; + PluginWiegand* plugin = picopass->plugin_wiegand; bool SE = card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid && 0x30 == card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data[0]; @@ -59,6 +61,23 @@ void picopass_scene_card_menu_on_enter(void* context) { SubmenuIndexSaveAsLF, picopass_scene_card_menu_submenu_callback, picopass); + + if(plugin) { + // Convert from byte array to uint64_t + uint64_t credential = 0; + memcpy(&credential, pacs->credential, sizeof(uint64_t)); + credential = __builtin_bswap64(credential); + + size_t format_count = plugin->count(pacs->bitLength, credential); + if(format_count > 0) { + submenu_add_item( + submenu, + "Parse", + SubmenuIndexParse, + picopass_scene_card_menu_submenu_callback, + picopass); + } + } } if(auth == PicopassDeviceAuthMethodNone || auth == PicopassDeviceAuthMethodKey) { @@ -131,6 +150,11 @@ bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) { picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexChangeKey); scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu); consumed = true; + } else if(event.event == SubmenuIndexParse) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexParse); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneFormats); + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { consumed = scene_manager_search_and_switch_to_previous_scene( diff --git a/base_pack/picopass/scenes/picopass_scene_config.h b/base_pack/picopass/scenes/picopass_scene_config.h index 0830834f811..9eb5f7f5ff6 100644 --- a/base_pack/picopass/scenes/picopass_scene_config.h +++ b/base_pack/picopass/scenes/picopass_scene_config.h @@ -21,3 +21,4 @@ ADD_SCENE(picopass, loclass, Loclass) ADD_SCENE(picopass, key_input, KeyInput) ADD_SCENE(picopass, nr_mac_saved, NrMacSaved) ADD_SCENE(picopass, more_info, MoreInfo) +ADD_SCENE(picopass, formats, Formats) diff --git a/base_pack/picopass/scenes/picopass_scene_device_info.c b/base_pack/picopass/scenes/picopass_scene_device_info.c index c08adfda236..4d4aba5ae9b 100644 --- a/base_pack/picopass/scenes/picopass_scene_device_info.c +++ b/base_pack/picopass/scenes/picopass_scene_device_info.c @@ -23,6 +23,7 @@ void picopass_scene_device_info_on_enter(void* context) { // Setup view PicopassBlock* card_data = picopass->dev->dev_data.card_data; PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + PluginWiegand* plugin = picopass->plugin_wiegand; Widget* widget = picopass->widget; uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; @@ -77,10 +78,27 @@ void picopass_scene_device_info_on_enter(void* context) { "Back", picopass_scene_device_info_widget_callback, picopass); + + if(plugin) { + // Convert from byte array to uint64_t + uint64_t credential = 0; + memcpy(&credential, pacs->credential, sizeof(uint64_t)); + credential = __builtin_bswap64(credential); + + size_t format_count = plugin->count(pacs->bitLength, credential); + if(format_count > 0) { + widget_add_button_element( + picopass->widget, + GuiButtonTypeCenter, + "Parse", + picopass_scene_device_info_widget_callback, + picopass); + } + } widget_add_button_element( picopass->widget, GuiButtonTypeRight, - "More", + "Raw", picopass_scene_device_info_widget_callback, picopass); @@ -97,6 +115,9 @@ bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event) } else if(event.event == GuiButtonTypeRight) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneMoreInfo); consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneFormats); + consumed = true; } else if(event.event == PicopassCustomEventViewExit) { view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); consumed = true; diff --git a/base_pack/picopass/scenes/picopass_scene_formats.c b/base_pack/picopass/scenes/picopass_scene_formats.c new file mode 100644 index 00000000000..5cf97b16392 --- /dev/null +++ b/base_pack/picopass/scenes/picopass_scene_formats.c @@ -0,0 +1,54 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_formats_on_enter(void* context) { + Picopass* picopass = context; + PluginWiegand* plugin = picopass->plugin_wiegand; + PicopassDevice* dev = picopass->dev; + PicopassDeviceData dev_data = dev->dev_data; + PicopassPacs pacs = dev_data.pacs; + + FuriString* str = picopass->text_box_store; + furi_string_reset(str); + + // Convert from byte array to uint64_t + uint64_t credential = 0; + memcpy(&credential, pacs.credential, sizeof(pacs.credential)); + credential = __builtin_bswap64(credential); + + if(plugin) { + FuriString* description = furi_string_alloc(); + size_t format_count = plugin->count(pacs.bitLength, credential); + for(size_t i = 0; i < format_count; i++) { + plugin->description(pacs.bitLength, credential, i, description); + + furi_string_cat_printf(str, "%s\n", furi_string_get_cstr(description)); + } + furi_string_free(description); + } + + text_box_set_font(picopass->text_box, TextBoxFontHex); + text_box_set_text(picopass->text_box, furi_string_get_cstr(picopass->text_box_store)); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextBox); +} + +bool picopass_scene_formats_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + return consumed; +} + +void picopass_scene_formats_on_exit(void* context) { + Picopass* picopass = context; + + // Clear views + text_box_reset(picopass->text_box); +}