Skip to content

Commit 25a57e2

Browse files
authored
Improve loclass logic for readers doing keyrolling. (#50)
1 parent 639ec9b commit 25a57e2

File tree

8 files changed

+91
-40
lines changed

8 files changed

+91
-40
lines changed

picopass/lib/loclass/optimized_cipher.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,11 @@ void loclass_opt_doBothMAC_2(
296296
loclass_opt_output(div_key_p, s, tmac);
297297
}
298298

299-
void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite) {
299+
void loclass_iclass_calc_div_key(
300+
const uint8_t* csn,
301+
const uint8_t* key,
302+
uint8_t* div_key,
303+
bool elite) {
300304
if(elite) {
301305
uint8_t keytable[128] = {0};
302306
uint8_t key_index[8] = {0};

picopass/lib/loclass/optimized_cipher.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,9 @@ void loclass_opt_doBothMAC_2(
109109
const uint8_t* div_key_p);
110110

111111
void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]);
112-
void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite);
112+
void loclass_iclass_calc_div_key(
113+
const uint8_t* csn,
114+
const uint8_t* key,
115+
uint8_t* div_key,
116+
bool elite);
113117
#endif // OPTIMIZED_CIPHER_H

picopass/lib/loclass/optimized_ikeys.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) {
302302
* @param key
303303
* @param div_key
304304
*/
305-
void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
305+
void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
306306
mbedtls_des_context loclass_ctx_enc;
307307

308308
// Prepare the DES key

picopass/lib/loclass/optimized_ikeys.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]);
5656
* @param div_key
5757
*/
5858

59-
void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key);
59+
void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key);
6060
/**
6161
* @brief Permutes a key from standard NIST format to Iclass specific format
6262
* @param key

picopass/loclass_writer.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,4 @@ bool loclass_writer_write_params(
9999
bool write_success = stream_write_string(instance->file_stream, str);
100100
furi_string_free(str);
101101
return write_success;
102-
}
102+
}

picopass/picopass_device.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
#include <optimized_cipher.h>
1313
#include "helpers/iclass_elite_dict.h"
1414

15+
#define LOCLASS_NUM_CSNS 9
16+
#ifndef LOCLASS_NUM_PER_CSN
17+
// Collect 2 MACs per CSN to account for keyroll modes by default
18+
#define LOCLASS_NUM_PER_CSN 2
19+
#endif
20+
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN)
21+
1522
#define PICOPASS_DEV_NAME_MAX_LEN 22
1623
#define PICOPASS_READER_DATA_MAX_SIZE 64
1724
#define PICOPASS_MAX_APP_LIMIT 32
@@ -70,6 +77,7 @@ typedef enum {
7077
PicopassEmulatorStateIdle,
7178
PicopassEmulatorStateActive,
7279
PicopassEmulatorStateSelected,
80+
PicopassEmulatorStateStopEmulation,
7381
} PicopassEmulatorState;
7482

7583
typedef struct {
@@ -110,6 +118,7 @@ typedef struct {
110118
uint8_t key_block_num; // in loclass mode used to store csn#
111119
bool loclass_mode;
112120
bool loclass_got_std_key;
121+
uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
113122
LoclassWriter* loclass_writer;
114123
} PicopassEmulatorCtx;
115124

picopass/picopass_i.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@
3131

3232
#define PICOPASS_TEXT_STORE_SIZE 128
3333

34-
#define LOCLASS_NUM_CSNS 9
35-
// Collect 2 MACs per CSN to account for keyroll modes
36-
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * 2)
37-
3834
enum PicopassCustomEvent {
3935
// Reserve first 100 events for button types and indexes, starting from 0
4036
PicopassCustomEventReserved = 100,

picopass/picopass_worker.c

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -835,22 +835,37 @@ static inline void picopass_emu_write_blocks(
835835
block_count * RFAL_PICOPASS_BLOCK_LEN);
836836
}
837837

838-
static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
838+
static void picopass_init_cipher_state_key(
839+
NfcVData* nfcv_data,
840+
PicopassEmulatorCtx* ctx,
841+
const uint8_t key[RFAL_PICOPASS_BLOCK_LEN]) {
839842
uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
843+
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
844+
845+
ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
846+
}
847+
848+
static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
840849
uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
841850

842-
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
843851
picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);
844852

845-
ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
853+
picopass_init_cipher_state_key(nfcv_data, ctx, key);
846854
}
847855

848856
static void
849857
loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
850-
// collect two nonces in a row for each CSN
851-
uint8_t csn_num = (ctx->key_block_num / 2) % LOCLASS_NUM_CSNS;
852-
memcpy(nfc_data->uid, loclass_csns[csn_num], RFAL_PICOPASS_BLOCK_LEN);
853-
picopass_emu_write_blocks(nfcv_data, loclass_csns[csn_num], PICOPASS_CSN_BLOCK_INDEX, 1);
858+
// collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
859+
const uint8_t* csn =
860+
loclass_csns[(ctx->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
861+
memcpy(nfc_data->uid, csn, RFAL_PICOPASS_BLOCK_LEN);
862+
picopass_emu_write_blocks(nfcv_data, csn, PICOPASS_CSN_BLOCK_INDEX, 1);
863+
864+
uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
865+
loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
866+
picopass_emu_write_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
867+
868+
picopass_init_cipher_state_key(nfcv_data, ctx, key);
854869
}
855870

856871
static void picopass_emu_handle_packet(
@@ -865,7 +880,7 @@ static void picopass_emu_handle_packet(
865880

866881
const uint8_t block_ff[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
867882

868-
if(nfcv_data->frame_length < 1) {
883+
if(nfcv_data->frame_length < 1 || ctx->state == PicopassEmulatorStateStopEmulation) {
869884
return;
870885
}
871886

@@ -999,6 +1014,8 @@ static void picopass_emu_handle_packet(
9991014
return;
10001015
}
10011016

1017+
// loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode
1018+
// we can also no-op if the key block is the same, CHECK re-inits if it failed already
10021019
if(ctx->key_block_num != key_block_num && !ctx->loclass_mode) {
10031020
ctx->key_block_num = key_block_num;
10041021
picopass_init_cipher_state(nfcv_data, ctx);
@@ -1020,13 +1037,13 @@ static void picopass_emu_handle_packet(
10201037
uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
10211038
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
10221039

1023-
// Check if the nonce is from a standard key
1040+
#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
10241041
uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
1025-
loclass_iclass_calc_div_key(nfc_data->uid, picopass_iclass_key, key, false);
1026-
ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
1042+
// loclass mode stores the derived standard debit key in Kd to check
1043+
picopass_emu_read_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
10271044

10281045
uint8_t rmac[4];
1029-
loclass_opt_doBothMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, response, key);
1046+
loclass_opt_doReaderMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, key);
10301047

10311048
if(!memcmp(nfcv_data->frame + 5, rmac, 4)) {
10321049
// MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
@@ -1035,25 +1052,46 @@ static void picopass_emu_handle_packet(
10351052
FURI_LOG_W(TAG, "loclass: standard key detected during collection");
10361053
ctx->loclass_got_std_key = true;
10371054

1038-
ctx->state = PicopassEmulatorStateIdle;
1055+
// Don't reset the state as the reader may try a different key next without going through anticoll
1056+
// The reader is always free to redo the anticoll if it wants to anyway
1057+
10391058
return;
10401059
}
1060+
#endif
10411061

1042-
// Copy CHALLENGE (nr) and READERSIGNATURE (mac) from frame
1043-
uint8_t nr[4];
1044-
memcpy(nr, nfcv_data->frame + 1, 4);
1045-
uint8_t mac[4];
1046-
memcpy(mac, nfcv_data->frame + 5, 4);
1047-
1048-
FURI_LOG_I(TAG, "loclass: got nr/mac pair");
1049-
loclass_writer_write_params(
1050-
ctx->loclass_writer, ctx->key_block_num, nfc_data->uid, cc, nr, mac);
1051-
1052-
// Rotate to the next CSN
1053-
ctx->key_block_num = (ctx->key_block_num + 1) % (LOCLASS_NUM_CSNS * 2);
1054-
loclass_update_csn(nfc_data, nfcv_data, ctx);
1062+
// Save to buffer to defer flushing when we rotate CSN
1063+
memcpy(
1064+
ctx->loclass_mac_buffer + ((ctx->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
1065+
nfcv_data->frame + 1,
1066+
8);
1067+
1068+
// Rotate to the next CSN/attempt
1069+
ctx->key_block_num++;
1070+
1071+
// CSN changed
1072+
if(ctx->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
1073+
// Flush NR-MACs for this CSN to SD card
1074+
uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
1075+
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
1076+
1077+
for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
1078+
loclass_writer_write_params(
1079+
ctx->loclass_writer,
1080+
ctx->key_block_num + i - LOCLASS_NUM_PER_CSN,
1081+
nfc_data->uid,
1082+
cc,
1083+
ctx->loclass_mac_buffer + (i * 8),
1084+
ctx->loclass_mac_buffer + (i * 8) + 4);
1085+
}
10551086

1056-
ctx->state = PicopassEmulatorStateIdle;
1087+
if(ctx->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
1088+
loclass_update_csn(nfc_data, nfcv_data, ctx);
1089+
// Only reset the state when we change to a new CSN for the same reason as when we get a standard key
1090+
ctx->state = PicopassEmulatorStateIdle;
1091+
} else {
1092+
ctx->state = PicopassEmulatorStateStopEmulation;
1093+
}
1094+
}
10571095

10581096
return;
10591097
}
@@ -1079,7 +1117,7 @@ static void picopass_emu_handle_packet(
10791117
break;
10801118
case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2)
10811119
if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) ||
1082-
ctx->state != PicopassEmulatorStateSelected) {
1120+
ctx->state != PicopassEmulatorStateSelected || ctx->loclass_mode) {
10831121
return;
10841122
}
10851123

@@ -1248,9 +1286,6 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
12481286
}
12491287

12501288
// Setup blocks for loclass attack
1251-
emu_ctx.key_block_num = 0;
1252-
loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
1253-
12541289
uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
12551290
picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1);
12561291

@@ -1260,6 +1295,9 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
12601295
uint8_t aia[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
12611296
picopass_emu_write_blocks(nfcv_data, aia, PICOPASS_SECURE_AIA_BLOCK_INDEX, 1);
12621297

1298+
emu_ctx.key_block_num = 0;
1299+
loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
1300+
12631301
loclass_writer_write_start_stop(emu_ctx.loclass_writer, true);
12641302
} else {
12651303
memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);

0 commit comments

Comments
 (0)