MX пре 1 година
родитељ
комит
1ea778633f

+ 12 - 0
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,
+)

+ 23 - 0
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);
 }
 

+ 8 - 0
picopass_i.h

@@ -34,6 +34,11 @@
 #include "protocol/picopass_poller.h"
 #include "protocol/picopass_listener.h"
 
+#include "plugin/interface.h"
+#include <flipper_application/flipper_application.h>
+#include <flipper_application/plugins/plugin_manager.h>
+#include <loader/firmware_api/firmware_api.h>
+
 #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;

+ 14 - 0
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"],
+)
+```

+ 20 - 0
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 <stdint.h>
+#include <stddef.h>
+#include <furi.h>
+
+#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;

+ 152 - 0
plugin/wiegand.c

@@ -0,0 +1,152 @@
+
+#include "interface.h"
+
+#include <lib/bit_lib/bit_lib.h>
+#include <flipper_application/flipper_application.h>
+
+/*
+ * 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] <name> 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;
+}

+ 24 - 0
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(

+ 1 - 0
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)

+ 22 - 1
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;

+ 54 - 0
scenes/picopass_scene_formats.c

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