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

Merge picopass from https://github.com/flipperdevices/flipperzero-good-faps

Willy-JL 2 лет назад
Родитель
Сommit
6e4c0f3b84

+ 11 - 5
picopass/.catalog/README.md

@@ -3,19 +3,17 @@
 
 
 This application allows you to read, write, save, and emulate legacy HID iClass cards and fobs (based on the picopass chipset).  Also supports saving the credential to the Flipper Zero LFRFID data format, changing the keys on the card, performing dictionary attack, and performing the 'online' part of the loclass attack.
 This application allows you to read, write, save, and emulate legacy HID iClass cards and fobs (based on the picopass chipset).  Also supports saving the credential to the Flipper Zero LFRFID data format, changing the keys on the card, performing dictionary attack, and performing the 'online' part of the loclass attack.
 
 
-NOTE: Does not support iClass SE
-
 # Loclass
 # Loclass
 
 
-The loclass attack emulates specific CSN and collects responses from the reader which can be used to calculate the elite or custom key configured for that reader.  This key is then used to read data on the cards used with that reader.
+The loclass attack emulates specific CSN and collects responses from the reader which can be used to calculate the elite or (some) custom key configured for that reader.  This key is then used to read data on the cards used with that reader.
 
 
 ## Online part
 ## Online part
 
 
 1. Run _loclass_ from the picopass main menu
 1. Run _loclass_ from the picopass main menu
-2. Present the flipper to the reader.
+2. Present the flipper to the reader.  Holding flipper directly to reader may not work, vary distance by a few inches.
 3. Collect responses until the progress bar is full.
 3. Collect responses until the progress bar is full.
 
 
-NOTE: If the screen says “Got std key” AND stays on 0/18, the reader is not elite or custom keyed.
+NOTE: If the screen says “Got std key” AND stays on 0/18, then loclass isn't needed.
 
 
 ## Offline part
 ## Offline part
 
 
@@ -24,3 +22,11 @@ NOTE: If the screen says “Got std key” AND stays on 0/18, the reader is not
 3. Copy the key to _iclass_elite_dict_user.txt_ and place in _sdcard/apps_data/picopass/_
 3. Copy the key to _iclass_elite_dict_user.txt_ and place in _sdcard/apps_data/picopass/_
 4. Run _Elite Dict. Attack_ from the picopass main menu
 4. Run _Elite Dict. Attack_ from the picopass main menu
 5. Present card to the back of the Flipper Zero.
 5. Present card to the back of the Flipper Zero.
+
+## Failure
+
+There are some situations when the offline loclass may not find a key, such as:
+ * iClass SE
+ * Readers configured with Standard-2 keyset
+ * Custom keyed readers using Standard KDF
+ * Custom keyed readers using SE KDF

+ 5 - 0
picopass/.catalog/changelog.md

@@ -1,3 +1,8 @@
+## 1.9
+ - Fix bug (#77) with loclass
+ - Better loclass notes
+ - Read card using nr-mac
+ - Save as Seader format
 ## 1.8
 ## 1.8
  - Minimal changes for recent API updates
  - Minimal changes for recent API updates
 ## 1.7
 ## 1.7

+ 1 - 1
picopass/application.fam

@@ -10,7 +10,7 @@ App(
     ],
     ],
     stack_size=4 * 1024,
     stack_size=4 * 1024,
     fap_description="App to communicate with NFC tags using the PicoPass(iClass) format",
     fap_description="App to communicate with NFC tags using the PicoPass(iClass) format",
-    fap_version="1.8",
+    fap_version="1.9",
     fap_icon="125_10px.png",
     fap_icon="125_10px.png",
     fap_category="NFC",
     fap_category="NFC",
     fap_libs=["mbedtls"],
     fap_libs=["mbedtls"],

+ 60 - 2
picopass/picopass_device.c

@@ -36,6 +36,59 @@ void picopass_device_set_name(PicopassDevice* dev, const char* name) {
     strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN);
     strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN);
 }
 }
 
 
+// For use with Seader's virtual card processing.
+static bool picopass_device_save_file_seader(
+    PicopassDevice* dev,
+    FlipperFormat* file,
+    FuriString* file_path) {
+    furi_assert(dev);
+    PicopassPacs* pacs = &dev->dev_data.pacs;
+    PicopassBlock* AA1 = dev->dev_data.AA1;
+    bool result = false;
+
+    const char* seader_file_header = "Flipper Seader Credential";
+    const uint32_t seader_file_version = 1;
+
+    do {
+        FURI_LOG_D(
+            TAG,
+            "Save %s %ld to %s",
+            seader_file_header,
+            seader_file_version,
+            furi_string_get_cstr(file_path));
+        if(!flipper_format_file_open_always(file, furi_string_get_cstr(file_path))) break;
+        if(!flipper_format_write_header_cstr(file, seader_file_header, seader_file_version)) break;
+        if(!flipper_format_write_uint32(file, "Bits", (uint32_t*)&pacs->bitLength, 1)) break;
+        if(!flipper_format_write_hex(file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN))
+            break;
+
+        FURI_LOG_D(TAG, "Pre-sio");
+        // Seader only captures 64 byte SIO so I'm going to leave it at that
+        uint8_t sio[64];
+
+        // TODO: save SR vs SE more properly
+        if(pacs->sio) { // SR
+            for(uint8_t i = 0; i < 8; i++) {
+                memcpy(sio + (i * 8), AA1[10 + i].data, PICOPASS_BLOCK_LEN);
+            }
+            if(!flipper_format_write_hex(file, "SIO", sio, sizeof(sio))) break;
+        } else if(pacs->se_enabled) { //SE
+            for(uint8_t i = 0; i < 8; i++) {
+                memcpy(sio + (i * 8), AA1[6 + i].data, PICOPASS_BLOCK_LEN);
+            }
+            if(!flipper_format_write_hex(file, "SIO", sio, sizeof(sio))) break;
+        }
+        FURI_LOG_D(TAG, "post sio");
+        if(!flipper_format_write_hex(
+               file, "Diversifier", AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN))
+            break;
+
+        result = true;
+    } while(false);
+
+    return result;
+}
+
 static bool picopass_device_save_file_lfrfid(PicopassDevice* dev, FuriString* file_path) {
 static bool picopass_device_save_file_lfrfid(PicopassDevice* dev, FuriString* file_path) {
     furi_assert(dev);
     furi_assert(dev);
     PicopassPacs* pacs = &dev->dev_data.pacs;
     PicopassPacs* pacs = &dev->dev_data.pacs;
@@ -153,11 +206,13 @@ static bool picopass_device_save_file(
                 }
                 }
             }
             }
             if(!block_saved) break;
             if(!block_saved) break;
+            saved = true;
         } else if(dev->format == PicopassDeviceSaveFormatLF) {
         } else if(dev->format == PicopassDeviceSaveFormatLF) {
             saved = picopass_device_save_file_lfrfid(dev, temp_str);
             saved = picopass_device_save_file_lfrfid(dev, temp_str);
+        } else if(dev->format == PicopassDeviceSaveFormatSeader) {
+            saved = picopass_device_save_file_seader(dev, file, temp_str);
         }
         }
-        saved = true;
-    } while(0);
+    } while(false);
 
 
     if(!saved) {
     if(!saved) {
         dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile");
         dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile");
@@ -173,6 +228,9 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
             dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true);
             dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true);
     } else if(dev->format == PicopassDeviceSaveFormatLF) {
     } else if(dev->format == PicopassDeviceSaveFormatLF) {
         return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true);
         return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true);
+    } else if(dev->format == PicopassDeviceSaveFormatSeader) {
+        return picopass_device_save_file(
+            dev, dev_name, EXT_PATH("apps_data/seader"), ".credential", true);
     }
     }
 
 
     return false;
     return false;

+ 1 - 0
picopass/picopass_device.h

@@ -70,6 +70,7 @@ typedef enum {
 typedef enum {
 typedef enum {
     PicopassDeviceSaveFormatHF,
     PicopassDeviceSaveFormatHF,
     PicopassDeviceSaveFormatLF,
     PicopassDeviceSaveFormatLF,
+    PicopassDeviceSaveFormatSeader,
 } PicopassDeviceSaveFormat;
 } PicopassDeviceSaveFormat;
 
 
 typedef enum {
 typedef enum {

+ 1 - 0
picopass/picopass_i.h

@@ -53,6 +53,7 @@ enum PicopassCustomEvent {
     PicopassCustomEventDictAttackUpdateView,
     PicopassCustomEventDictAttackUpdateView,
     PicopassCustomEventLoclassGotMac,
     PicopassCustomEventLoclassGotMac,
     PicopassCustomEventLoclassGotStandardKey,
     PicopassCustomEventLoclassGotStandardKey,
+    PicopassCustomEventNrMacSaved,
 
 
     PicopassCustomEventPollerSuccess,
     PicopassCustomEventPollerSuccess,
     PicopassCustomEventPollerFail,
     PicopassCustomEventPollerFail,

+ 77 - 13
picopass/protocol/picopass_listener.c

@@ -1,3 +1,4 @@
+#include "picopass_i.h"
 #include "picopass_listener_i.h"
 #include "picopass_listener_i.h"
 #include "picopass_keys.h"
 #include "picopass_keys.h"
 
 
@@ -299,6 +300,61 @@ PicopassListenerCommand
     return command;
     return command;
 }
 }
 
 
+PicopassListenerCommand picopass_listener_save_mac(PicopassListener* instance, uint8_t* rx_data) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+    Picopass* picopass = instance->context;
+
+    PicopassDevice* dev = picopass->dev;
+
+    const uint8_t* csn = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
+    const uint8_t* epurse = instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data;
+
+    FuriString* temp_str = furi_string_alloc();
+    FuriString* filename = furi_string_alloc();
+    FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+
+    for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+        furi_string_cat_printf(filename, "%02x", csn[i]);
+    }
+    furi_string_cat_printf(filename, "_");
+    for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+        furi_string_cat_printf(filename, "%02x", epurse[i]);
+    }
+
+    furi_string_printf(
+        temp_str, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, furi_string_get_cstr(filename), ".mac");
+    do {
+        // Open file
+        if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
+
+        if(!flipper_format_write_hex(file, "NR-MAC", rx_data + 1, PICOPASS_BLOCK_LEN)) break;
+
+        FURI_LOG_D(
+            TAG,
+            "Saved nr-mac: %02x %02x %02x %02x %02x %02x %02x %02x",
+            // Skip command byte [0]
+            rx_data[1],
+            rx_data[2],
+            rx_data[3],
+            rx_data[4],
+            rx_data[5],
+            rx_data[6],
+            rx_data[7],
+            rx_data[8]);
+
+        notification_message(picopass->notifications, &sequence_double_vibro);
+        command = PicopassListenerCommandStop;
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventNrMacSaved);
+    } while(0);
+
+    furi_string_free(temp_str);
+    furi_string_free(filename);
+    flipper_format_free(file);
+
+    return command;
+}
+
 PicopassListenerCommand
 PicopassListenerCommand
     picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) {
     picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) {
     PicopassListenerCommand command = PicopassListenerCommandSilent;
     PicopassListenerCommand command = PicopassListenerCommandSilent;
@@ -310,23 +366,31 @@ PicopassListenerCommand
         // Since nr isn't const in loclass_opt_doBothMAC_2() copy buffer
         // Since nr isn't const in loclass_opt_doBothMAC_2() copy buffer
         uint8_t rx_data[9] = {};
         uint8_t rx_data[9] = {};
         bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
         bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
-        loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
+        bool no_key = picopass_is_memset(key, 0x00, PICOPASS_BLOCK_LEN);
 
 
-#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
-        if(memcmp(&rx_data[5], rmac, 4)) {
-            // Bad MAC from reader, do not send a response.
-            FURI_LOG_I(TAG, "Got bad MAC from reader");
-            // Reset the cipher state since we don't do it in READCHECK
-            picopass_listener_init_cipher_state(instance);
+        if(no_key) {
+            // We're emulating a partial dump of an iClass SE card and should capture the NR and MAC
+            command = picopass_listener_save_mac(instance, rx_data);
             break;
             break;
-        }
+        } else {
+            loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
+
+#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
+            if(memcmp(&rx_data[5], rmac, PICOPASS_MAC_LEN)) {
+                // Bad MAC from reader, do not send a response.
+                FURI_LOG_I(TAG, "Got bad MAC from reader");
+                // Reset the cipher state since we don't do it in READCHECK
+                picopass_listener_init_cipher_state(instance);
+                break;
+            }
 #endif
 #endif
 
 
-        bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
-        NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
-        if(error != NfcErrorNone) {
-            FURI_LOG_D(TAG, "Failed tx update response: %d", error);
-            break;
+            bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
+            NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
+            if(error != NfcErrorNone) {
+                FURI_LOG_D(TAG, "Failed tx update response: %d", error);
+                break;
+            }
         }
         }
 
 
         command = PicopassListenerCommandProcessed;
         command = PicopassListenerCommandProcessed;

+ 136 - 3
picopass/protocol/picopass_poller.c

@@ -1,3 +1,4 @@
+#include "picopass_i.h"
 #include "picopass_poller_i.h"
 #include "picopass_poller_i.h"
 
 
 #include "../loclass/optimized_cipher.h"
 #include "../loclass/optimized_cipher.h"
@@ -95,7 +96,7 @@ NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
             instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
             instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
 
 
         PicopassBlock block = {};
         PicopassBlock block = {};
-        error = picopass_poller_read_block(instance, 1, &block);
+        error = picopass_poller_read_block(instance, PICOPASS_CONFIG_BLOCK_INDEX, &block);
         if(error != PicopassErrorNone) {
         if(error != PicopassErrorNone) {
             instance->state = PicopassPollerStateFail;
             instance->state = PicopassPollerStateFail;
             break;
             break;
@@ -116,6 +117,27 @@ NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
             instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
             instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
             instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
             instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
 
 
+        error = picopass_poller_read_block(instance, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, &block);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        memcpy(
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data,
+            block.data,
+            sizeof(PicopassBlock));
+        FURI_LOG_D(
+            TAG,
+            "epurse %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[0],
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[1],
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[2],
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[3],
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[4],
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[5],
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[6],
+            instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[7]);
+
         error = picopass_poller_read_block(instance, 5, &block);
         error = picopass_poller_read_block(instance, 5, &block);
         if(error != PicopassErrorNone) {
         if(error != PicopassErrorNone) {
             instance->state = PicopassPollerStateFail;
             instance->state = PicopassPollerStateFail;
@@ -165,10 +187,120 @@ NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
 
 
     if(instance->data->pacs.se_enabled) {
     if(instance->data->pacs.se_enabled) {
         FURI_LOG_D(TAG, "SE enabled");
         FURI_LOG_D(TAG, "SE enabled");
-        instance->state = PicopassPollerStateFail;
+        instance->state = PicopassPollerStateNrMacAuth;
     } else {
     } else {
         instance->state = PicopassPollerStateAuth;
         instance->state = PicopassPollerStateAuth;
     }
     }
+    return command;
+}
+
+NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    Picopass* picopass = instance->context;
+    PicopassDevice* dev = picopass->dev;
+
+    uint8_t* csn = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
+    uint8_t* epurse = instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data;
+
+    FuriString* temp_str = furi_string_alloc();
+    FuriString* filename = furi_string_alloc();
+    FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+    PicopassMac mac = {};
+
+    for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+        furi_string_cat_printf(filename, "%02x", csn[i]);
+    }
+    furi_string_cat_printf(filename, "_");
+    for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+        furi_string_cat_printf(filename, "%02x", epurse[i]);
+    }
+
+    furi_string_printf(
+        temp_str, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, furi_string_get_cstr(filename), ".mac");
+
+    FURI_LOG_D(TAG, "Looking for %s", furi_string_get_cstr(temp_str));
+    uint8_t nr_mac[PICOPASS_BLOCK_LEN];
+
+    // Presume failure unless all steps are successful and the state is made "read block"
+    instance->state = PicopassPollerStateFail;
+    do {
+        //check for file
+        if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break;
+        // FURI_LOG_D(TAG, "Found %s", furi_string_get_cstr(temp_str));
+
+        furi_string_printf(temp_str, "NR-MAC");
+        if(!flipper_format_read_hex(
+               file, furi_string_get_cstr(temp_str), nr_mac, PICOPASS_BLOCK_LEN))
+            break;
+        memcpy(mac.data, nr_mac + 4, PICOPASS_MAC_LEN);
+        /*
+        FURI_LOG_D(
+            TAG,
+            "Read nr-mac: %02x %02x %02x %02x %02x %02x %02x %02x",
+            nr_mac[0],
+            nr_mac[1],
+            nr_mac[2],
+            nr_mac[3],
+            nr_mac[4],
+            nr_mac[5],
+            nr_mac[6],
+            nr_mac[7]);
+        FURI_LOG_D(
+            TAG, "MAC: %02x %02x %02x %02x", mac.data[0], mac.data[1], mac.data[2], mac.data[3]);
+        */
+
+        uint8_t ccnr[12] = {};
+        PicopassReadCheckResp read_check_resp = {};
+        PicopassError error = picopass_poller_read_check(instance, &read_check_resp);
+        if(error == PicopassErrorTimeout) {
+            instance->event.type = PicopassPollerEventTypeCardLost;
+            instance->callback(instance->event, instance->context);
+            instance->state = PicopassPollerStateDetect;
+            break;
+        } else if(error != PicopassErrorNone) {
+            FURI_LOG_E(TAG, "Read check failed: %d", error);
+            break;
+        }
+        memcpy(ccnr, read_check_resp.data, sizeof(PicopassReadCheckResp)); // last 4 bytes left 0
+
+        /*
+        FURI_LOG_D(
+            TAG,
+            "CCNR: %02x %02x %02x %02x %02x %02x %02x %02x",
+            ccnr[0],
+            ccnr[1],
+            ccnr[2],
+            ccnr[3],
+            ccnr[4],
+            ccnr[5],
+            ccnr[6],
+            ccnr[7]);
+            */
+
+        //use mac
+        PicopassCheckResp check_resp = {};
+        error = picopass_poller_check(instance, nr_mac, &mac, &check_resp);
+        if(error == PicopassErrorNone) {
+            memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
+            if(instance->mode == PicopassPollerModeRead) {
+                picopass_poller_prepare_read(instance);
+                instance->state = PicopassPollerStateReadBlock;
+                // Set to non-zero keys to allow emulation
+                memset(
+                    instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data,
+                    0xff,
+                    PICOPASS_BLOCK_LEN);
+                memset(
+                    instance->data->AA1[PICOPASS_SECURE_KC_BLOCK_INDEX].data,
+                    0xff,
+                    PICOPASS_BLOCK_LEN);
+            }
+        }
+
+    } while(false);
+    furi_string_free(temp_str);
+    furi_string_free(filename);
+    flipper_format_free(file);
 
 
     return command;
     return command;
 }
 }
@@ -234,7 +366,7 @@ NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
         loclass_opt_doReaderMAC(ccnr, div_key, mac.data);
         loclass_opt_doReaderMAC(ccnr, div_key, mac.data);
 
 
         PicopassCheckResp check_resp = {};
         PicopassCheckResp check_resp = {};
-        error = picopass_poller_check(instance, &mac, &check_resp);
+        error = picopass_poller_check(instance, NULL, &mac, &check_resp);
         if(error == PicopassErrorNone) {
         if(error == PicopassErrorNone) {
             FURI_LOG_I(TAG, "Found key");
             FURI_LOG_I(TAG, "Found key");
             memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
             memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
@@ -457,6 +589,7 @@ static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPo
     [PicopassPollerStateSelect] = picopass_poller_select_handler,
     [PicopassPollerStateSelect] = picopass_poller_select_handler,
     [PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler,
     [PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler,
     [PicopassPollerStateCheckSecurity] = picopass_poller_check_security,
     [PicopassPollerStateCheckSecurity] = picopass_poller_check_security,
+    [PicopassPollerStateNrMacAuth] = picopass_poller_nr_mac_auth,
     [PicopassPollerStateAuth] = picopass_poller_auth_handler,
     [PicopassPollerStateAuth] = picopass_poller_auth_handler,
     [PicopassPollerStateReadBlock] = picopass_poller_read_block_handler,
     [PicopassPollerStateReadBlock] = picopass_poller_read_block_handler,
     [PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler,
     [PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler,

+ 7 - 2
picopass/protocol/picopass_poller_i.c

@@ -161,15 +161,20 @@ PicopassError
 
 
 PicopassError picopass_poller_check(
 PicopassError picopass_poller_check(
     PicopassPoller* instance,
     PicopassPoller* instance,
+    uint8_t* nr,
     PicopassMac* mac,
     PicopassMac* mac,
     PicopassCheckResp* check_resp) {
     PicopassCheckResp* check_resp) {
     PicopassError ret = PicopassErrorNone;
     PicopassError ret = PicopassErrorNone;
+    uint8_t null_arr[4] = {};
 
 
     do {
     do {
         bit_buffer_reset(instance->tx_buffer);
         bit_buffer_reset(instance->tx_buffer);
         bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_CHECK);
         bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_CHECK);
-        uint8_t null_arr[4] = {};
-        bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
+        if(nr) {
+            bit_buffer_append_bytes(instance->tx_buffer, nr, 4);
+        } else {
+            bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
+        }
         bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
         bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
 
 
         NfcError error = nfc_poller_trx(
         NfcError error = nfc_poller_trx(

+ 2 - 0
picopass/protocol/picopass_poller_i.h

@@ -20,6 +20,7 @@ typedef enum {
     PicopassPollerStateSelect,
     PicopassPollerStateSelect,
     PicopassPollerStatePreAuth,
     PicopassPollerStatePreAuth,
     PicopassPollerStateCheckSecurity,
     PicopassPollerStateCheckSecurity,
+    PicopassPollerStateNrMacAuth,
     PicopassPollerStateAuth,
     PicopassPollerStateAuth,
     PicopassPollerStateReadBlock,
     PicopassPollerStateReadBlock,
     PicopassPollerStateWriteBlock,
     PicopassPollerStateWriteBlock,
@@ -75,6 +76,7 @@ PicopassError
 
 
 PicopassError picopass_poller_check(
 PicopassError picopass_poller_check(
     PicopassPoller* instance,
     PicopassPoller* instance,
+    uint8_t* nr,
     PicopassMac* mac,
     PicopassMac* mac,
     PicopassCheckResp* check_resp);
     PicopassCheckResp* check_resp);
 
 

+ 76 - 22
picopass/scenes/picopass_scene_card_menu.c

@@ -3,9 +3,11 @@
 enum SubmenuIndex {
 enum SubmenuIndex {
     SubmenuIndexSave,
     SubmenuIndexSave,
     SubmenuIndexSaveAsLF,
     SubmenuIndexSaveAsLF,
+    SubmenuIndexSaveAsSeader,
     SubmenuIndexChangeKey,
     SubmenuIndexChangeKey,
     SubmenuIndexWrite,
     SubmenuIndexWrite,
     SubmenuIndexEmulate,
     SubmenuIndexEmulate,
+    SubmenuIndexSavePartial,
 };
 };
 
 
 void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) {
 void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) {
@@ -17,29 +19,69 @@ void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) {
 void picopass_scene_card_menu_on_enter(void* context) {
 void picopass_scene_card_menu_on_enter(void* context) {
     Picopass* picopass = context;
     Picopass* picopass = context;
     Submenu* submenu = picopass->submenu;
     Submenu* submenu = picopass->submenu;
+    PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
+    PicopassBlock* AA1 = picopass->dev->dev_data.AA1;
 
 
-    submenu_add_item(
-        submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass);
-    submenu_add_item(
-        submenu,
-        "Save as LFRFID",
-        SubmenuIndexSaveAsLF,
-        picopass_scene_card_menu_submenu_callback,
-        picopass);
-    submenu_add_item(
-        submenu, "Write", SubmenuIndexWrite, picopass_scene_card_menu_submenu_callback, picopass);
-    submenu_add_item(
-        submenu,
-        "Emulate",
-        SubmenuIndexEmulate,
-        picopass_scene_card_menu_submenu_callback,
-        picopass);
-    submenu_add_item(
-        submenu,
-        "Change Key",
-        SubmenuIndexChangeKey,
-        picopass_scene_card_menu_submenu_callback,
-        picopass);
+    bool sio = 0x30 == AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data[0];
+
+    if(pacs->se_enabled) {
+        if(sio) {
+            submenu_add_item(
+                submenu,
+                "Save",
+                SubmenuIndexSave,
+                picopass_scene_card_menu_submenu_callback,
+                picopass);
+            submenu_add_item(
+                submenu,
+                "Save in Seader fmt",
+                SubmenuIndexSaveAsSeader,
+                picopass_scene_card_menu_submenu_callback,
+                picopass);
+        } else {
+            submenu_add_item(
+                submenu,
+                "Save Partial",
+                SubmenuIndexSavePartial,
+                picopass_scene_card_menu_submenu_callback,
+                picopass);
+        }
+    } else {
+        submenu_add_item(
+            submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass);
+        submenu_add_item(
+            submenu,
+            "Save as LFRFID",
+            SubmenuIndexSaveAsLF,
+            picopass_scene_card_menu_submenu_callback,
+            picopass);
+        if(pacs->sio) { // SR
+            submenu_add_item(
+                submenu,
+                "Save in Seader fmt",
+                SubmenuIndexSaveAsSeader,
+                picopass_scene_card_menu_submenu_callback,
+                picopass);
+        }
+        submenu_add_item(
+            submenu,
+            "Write",
+            SubmenuIndexWrite,
+            picopass_scene_card_menu_submenu_callback,
+            picopass);
+        submenu_add_item(
+            submenu,
+            "Emulate",
+            SubmenuIndexEmulate,
+            picopass_scene_card_menu_submenu_callback,
+            picopass);
+        submenu_add_item(
+            submenu,
+            "Change Key",
+            SubmenuIndexChangeKey,
+            picopass_scene_card_menu_submenu_callback,
+            picopass);
+    }
 
 
     submenu_set_selected_item(
     submenu_set_selected_item(
         picopass->submenu,
         picopass->submenu,
@@ -59,6 +101,18 @@ bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
             picopass->dev->format = PicopassDeviceSaveFormatHF;
             picopass->dev->format = PicopassDeviceSaveFormatHF;
             consumed = true;
             consumed = true;
+        } else if(event.event == SubmenuIndexSavePartial) {
+            scene_manager_set_scene_state(
+                picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSave);
+            scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
+            picopass->dev->format = PicopassDeviceSaveFormatHF;
+            consumed = true;
+        } else if(event.event == SubmenuIndexSaveAsSeader) {
+            scene_manager_set_scene_state(
+                picopass->scene_manager, PicopassSceneCardMenu, event.event);
+            scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
+            picopass->dev->format = PicopassDeviceSaveFormatSeader;
+            consumed = true;
         } else if(event.event == SubmenuIndexSaveAsLF) {
         } else if(event.event == SubmenuIndexSaveAsLF) {
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF);
                 picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF);

+ 1 - 0
picopass/scenes/picopass_scene_config.h

@@ -19,3 +19,4 @@ ADD_SCENE(picopass, elite_dict_attack, EliteDictAttack)
 ADD_SCENE(picopass, emulate, Emulate)
 ADD_SCENE(picopass, emulate, Emulate)
 ADD_SCENE(picopass, loclass, Loclass)
 ADD_SCENE(picopass, loclass, Loclass)
 ADD_SCENE(picopass, key_input, KeyInput)
 ADD_SCENE(picopass, key_input, KeyInput)
+ADD_SCENE(picopass, nr_mac_saved, NrMacSaved)

+ 5 - 2
picopass/scenes/picopass_scene_device_info.c

@@ -30,8 +30,11 @@ void picopass_scene_device_info_on_enter(void* context) {
     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
         furi_string_cat_printf(csn_str, "%02X ", csn[i]);
         furi_string_cat_printf(csn_str, "%02X ", csn[i]);
     }
     }
+    bool sio = 0x30 == AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data[0];
 
 
-    if(pacs->bitLength == 0 || pacs->bitLength == 255) {
+    if(sio) {
+        furi_string_cat_printf(wiegand_str, "SIO");
+    } else if(pacs->bitLength == 0 || pacs->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");
     } else {
     } else {
@@ -46,7 +49,7 @@ void picopass_scene_device_info_on_enter(void* context) {
         }
         }
         furi_string_cat_printf(wiegand_str, "%d bits", pacs->bitLength);
         furi_string_cat_printf(wiegand_str, "%d bits", pacs->bitLength);
 
 
-        if(pacs->sio) {
+        if(pacs->sio) { // SR
             furi_string_cat_printf(credential_str, " +SIO");
             furi_string_cat_printf(credential_str, " +SIO");
         }
         }
     }
     }

+ 3 - 0
picopass/scenes/picopass_scene_emulate.c

@@ -32,6 +32,9 @@ bool picopass_scene_emulate_on_event(void* context, SceneManagerEvent event) {
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == PicopassCustomEventWorkerExit) {
         if(event.event == PicopassCustomEventWorkerExit) {
             consumed = true;
             consumed = true;
+        } else if(event.event == PicopassCustomEventNrMacSaved) {
+            scene_manager_next_scene(picopass->scene_manager, PicopassSceneNrMacSaved);
+            consumed = true;
         }
         }
     } else if(event.type == SceneManagerEventTypeBack) {
     } else if(event.type == SceneManagerEventTypeBack) {
         consumed = scene_manager_previous_scene(picopass->scene_manager);
         consumed = scene_manager_previous_scene(picopass->scene_manager);

+ 44 - 0
picopass/scenes/picopass_scene_nr_mac_saved.c

@@ -0,0 +1,44 @@
+#include "../picopass_i.h"
+#include <dolphin/dolphin.h>
+
+void picopass_scene_nr_mac_saved_popup_callback(void* context) {
+    Picopass* picopass = context;
+    view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit);
+}
+
+void picopass_scene_nr_mac_saved_on_enter(void* context) {
+    Picopass* picopass = context;
+    dolphin_deed(DolphinDeedNfcSave);
+
+    // Setup view
+    Popup* popup = picopass->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "NR-MAC\nSaved!", 13, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, picopass);
+    popup_set_callback(popup, picopass_scene_nr_mac_saved_popup_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
+}
+
+bool picopass_scene_nr_mac_saved_on_event(void* context, SceneManagerEvent event) {
+    Picopass* picopass = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == PicopassCustomEventViewExit) {
+            scene_manager_set_scene_state(
+                picopass->scene_manager, PicopassSceneStart, 0); // Set back to "read card"
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                picopass->scene_manager, PicopassSceneStart);
+        }
+    }
+    return consumed;
+}
+
+void picopass_scene_nr_mac_saved_on_exit(void* context) {
+    Picopass* picopass = context;
+
+    // Clear view
+    popup_reset(picopass->popup);
+}

+ 19 - 2
picopass/scenes/picopass_scene_read_card_success.c

@@ -43,6 +43,7 @@ void picopass_scene_read_card_success_on_enter(void* context) {
         AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0x00, PICOPASS_BLOCK_LEN);
         AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0x00, PICOPASS_BLOCK_LEN);
     bool empty = picopass_is_memset(
     bool empty = picopass_is_memset(
         AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
         AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN);
+    bool sio = 0x30 == AA1[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data[0];
 
 
     if(no_key) {
     if(no_key) {
         furi_string_cat_printf(wiegand_str, "Read Failed");
         furi_string_cat_printf(wiegand_str, "Read Failed");
@@ -50,6 +51,13 @@ 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,
+                GuiButtonTypeRight,
+                "More",
+                picopass_scene_read_card_success_widget_callback,
+                picopass);
         } else if(!hid_csn) {
         } else if(!hid_csn) {
             furi_string_cat_printf(credential_str, "Non-HID CSN");
             furi_string_cat_printf(credential_str, "Non-HID CSN");
         }
         }
@@ -70,8 +78,11 @@ void picopass_scene_read_card_success_on_enter(void* context) {
             picopass);
             picopass);
     } else if(pacs->bitLength == 0 || pacs->bitLength == 255) {
     } else if(pacs->bitLength == 0 || pacs->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");
-
+        if(sio) {
+            furi_string_cat_printf(wiegand_str, "SIO");
+        } else {
+            furi_string_cat_printf(wiegand_str, "Invalid PACS");
+        }
         if(pacs->se_enabled) {
         if(pacs->se_enabled) {
             furi_string_cat_printf(credential_str, "SE enabled");
             furi_string_cat_printf(credential_str, "SE enabled");
         }
         }
@@ -81,6 +92,12 @@ void picopass_scene_read_card_success_on_enter(void* context) {
             "Menu",
             "Menu",
             picopass_scene_read_card_success_widget_callback,
             picopass_scene_read_card_success_widget_callback,
             picopass);
             picopass);
+        widget_add_button_element(
+            widget,
+            GuiButtonTypeRight,
+            "More",
+            picopass_scene_read_card_success_widget_callback,
+            picopass);
     } else {
     } else {
         size_t bytesLength = 1 + pacs->bitLength / 8;
         size_t bytesLength = 1 + pacs->bitLength / 8;
         furi_string_set(credential_str, "");
         furi_string_set(credential_str, "");