Skip to content

Commit

Permalink
Improve loclass logic for readers doing keyrolling. (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
nvx authored Sep 25, 2023
1 parent 639ec9b commit 25a57e2
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 40 deletions.
6 changes: 5 additions & 1 deletion picopass/lib/loclass/optimized_cipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,11 @@ void loclass_opt_doBothMAC_2(
loclass_opt_output(div_key_p, s, tmac);
}

void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite) {
void loclass_iclass_calc_div_key(
const uint8_t* csn,
const uint8_t* key,
uint8_t* div_key,
bool elite) {
if(elite) {
uint8_t keytable[128] = {0};
uint8_t key_index[8] = {0};
Expand Down
6 changes: 5 additions & 1 deletion picopass/lib/loclass/optimized_cipher.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,9 @@ void loclass_opt_doBothMAC_2(
const uint8_t* div_key_p);

void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]);
void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite);
void loclass_iclass_calc_div_key(
const uint8_t* csn,
const uint8_t* key,
uint8_t* div_key,
bool elite);
#endif // OPTIMIZED_CIPHER_H
2 changes: 1 addition & 1 deletion picopass/lib/loclass/optimized_ikeys.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) {
* @param key
* @param div_key
*/
void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
mbedtls_des_context loclass_ctx_enc;

// Prepare the DES key
Expand Down
2 changes: 1 addition & 1 deletion picopass/lib/loclass/optimized_ikeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]);
* @param div_key
*/

void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key);
void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key);
/**
* @brief Permutes a key from standard NIST format to Iclass specific format
* @param key
Expand Down
2 changes: 1 addition & 1 deletion picopass/loclass_writer.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@ bool loclass_writer_write_params(
bool write_success = stream_write_string(instance->file_stream, str);
furi_string_free(str);
return write_success;
}
}
9 changes: 9 additions & 0 deletions picopass/picopass_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
#include <optimized_cipher.h>
#include "helpers/iclass_elite_dict.h"

#define LOCLASS_NUM_CSNS 9
#ifndef LOCLASS_NUM_PER_CSN
// Collect 2 MACs per CSN to account for keyroll modes by default
#define LOCLASS_NUM_PER_CSN 2
#endif
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN)

#define PICOPASS_DEV_NAME_MAX_LEN 22
#define PICOPASS_READER_DATA_MAX_SIZE 64
#define PICOPASS_MAX_APP_LIMIT 32
Expand Down Expand Up @@ -70,6 +77,7 @@ typedef enum {
PicopassEmulatorStateIdle,
PicopassEmulatorStateActive,
PicopassEmulatorStateSelected,
PicopassEmulatorStateStopEmulation,
} PicopassEmulatorState;

typedef struct {
Expand Down Expand Up @@ -110,6 +118,7 @@ typedef struct {
uint8_t key_block_num; // in loclass mode used to store csn#
bool loclass_mode;
bool loclass_got_std_key;
uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
LoclassWriter* loclass_writer;
} PicopassEmulatorCtx;

Expand Down
4 changes: 0 additions & 4 deletions picopass/picopass_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@

#define PICOPASS_TEXT_STORE_SIZE 128

#define LOCLASS_NUM_CSNS 9
// Collect 2 MACs per CSN to account for keyroll modes
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * 2)

enum PicopassCustomEvent {
// Reserve first 100 events for button types and indexes, starting from 0
PicopassCustomEventReserved = 100,
Expand Down
100 changes: 69 additions & 31 deletions picopass/picopass_worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -835,22 +835,37 @@ static inline void picopass_emu_write_blocks(
block_count * RFAL_PICOPASS_BLOCK_LEN);
}

static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
static void picopass_init_cipher_state_key(
NfcVData* nfcv_data,
PicopassEmulatorCtx* ctx,
const uint8_t key[RFAL_PICOPASS_BLOCK_LEN]) {
uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);

ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
}

static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
uint8_t key[RFAL_PICOPASS_BLOCK_LEN];

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

ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
picopass_init_cipher_state_key(nfcv_data, ctx, key);
}

static void
loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
// collect two nonces in a row for each CSN
uint8_t csn_num = (ctx->key_block_num / 2) % LOCLASS_NUM_CSNS;
memcpy(nfc_data->uid, loclass_csns[csn_num], RFAL_PICOPASS_BLOCK_LEN);
picopass_emu_write_blocks(nfcv_data, loclass_csns[csn_num], PICOPASS_CSN_BLOCK_INDEX, 1);
// collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
const uint8_t* csn =
loclass_csns[(ctx->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
memcpy(nfc_data->uid, csn, RFAL_PICOPASS_BLOCK_LEN);
picopass_emu_write_blocks(nfcv_data, csn, PICOPASS_CSN_BLOCK_INDEX, 1);

uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
picopass_emu_write_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);

picopass_init_cipher_state_key(nfcv_data, ctx, key);
}

static void picopass_emu_handle_packet(
Expand All @@ -865,7 +880,7 @@ static void picopass_emu_handle_packet(

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

if(nfcv_data->frame_length < 1) {
if(nfcv_data->frame_length < 1 || ctx->state == PicopassEmulatorStateStopEmulation) {
return;
}

Expand Down Expand Up @@ -999,6 +1014,8 @@ static void picopass_emu_handle_packet(
return;
}

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

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

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

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

ctx->state = PicopassEmulatorStateIdle;
// Don't reset the state as the reader may try a different key next without going through anticoll
// The reader is always free to redo the anticoll if it wants to anyway

return;
}
#endif

// Copy CHALLENGE (nr) and READERSIGNATURE (mac) from frame
uint8_t nr[4];
memcpy(nr, nfcv_data->frame + 1, 4);
uint8_t mac[4];
memcpy(mac, nfcv_data->frame + 5, 4);

FURI_LOG_I(TAG, "loclass: got nr/mac pair");
loclass_writer_write_params(
ctx->loclass_writer, ctx->key_block_num, nfc_data->uid, cc, nr, mac);

// Rotate to the next CSN
ctx->key_block_num = (ctx->key_block_num + 1) % (LOCLASS_NUM_CSNS * 2);
loclass_update_csn(nfc_data, nfcv_data, ctx);
// Save to buffer to defer flushing when we rotate CSN
memcpy(
ctx->loclass_mac_buffer + ((ctx->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
nfcv_data->frame + 1,
8);

// Rotate to the next CSN/attempt
ctx->key_block_num++;

// CSN changed
if(ctx->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
// Flush NR-MACs for this CSN to SD card
uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);

for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
loclass_writer_write_params(
ctx->loclass_writer,
ctx->key_block_num + i - LOCLASS_NUM_PER_CSN,
nfc_data->uid,
cc,
ctx->loclass_mac_buffer + (i * 8),
ctx->loclass_mac_buffer + (i * 8) + 4);
}

ctx->state = PicopassEmulatorStateIdle;
if(ctx->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
loclass_update_csn(nfc_data, nfcv_data, ctx);
// Only reset the state when we change to a new CSN for the same reason as when we get a standard key
ctx->state = PicopassEmulatorStateIdle;
} else {
ctx->state = PicopassEmulatorStateStopEmulation;
}
}

return;
}
Expand All @@ -1079,7 +1117,7 @@ static void picopass_emu_handle_packet(
break;
case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2)
if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) ||
ctx->state != PicopassEmulatorStateSelected) {
ctx->state != PicopassEmulatorStateSelected || ctx->loclass_mode) {
return;
}

Expand Down Expand Up @@ -1248,9 +1286,6 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
}

// Setup blocks for loclass attack
emu_ctx.key_block_num = 0;
loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);

uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1);

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

emu_ctx.key_block_num = 0;
loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);

loclass_writer_write_start_stop(emu_ctx.loclass_writer, true);
} else {
memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);
Expand Down

0 comments on commit 25a57e2

Please sign in to comment.