From d9dec5b20e4cdb36cb7600d765af97b819acacba Mon Sep 17 00:00:00 2001 From: Tiernan Date: Mon, 25 Sep 2023 11:45:43 +1000 Subject: [PATCH] Improve loclass logic for readers doing keyrolling. (#50) --- lib/loclass/optimized_cipher.c | 6 +- lib/loclass/optimized_cipher.h | 6 +- lib/loclass/optimized_ikeys.c | 2 +- lib/loclass/optimized_ikeys.h | 2 +- loclass_writer.c | 2 +- picopass_device.h | 9 +++ picopass_i.h | 4 -- picopass_worker.c | 100 +++++++++++++++++++++++---------- 8 files changed, 91 insertions(+), 40 deletions(-) diff --git a/lib/loclass/optimized_cipher.c b/lib/loclass/optimized_cipher.c index b2ea4538b8d..e42dc08139b 100644 --- a/lib/loclass/optimized_cipher.c +++ b/lib/loclass/optimized_cipher.c @@ -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}; diff --git a/lib/loclass/optimized_cipher.h b/lib/loclass/optimized_cipher.h index c96c97d8ae9..5830bec97aa 100644 --- a/lib/loclass/optimized_cipher.h +++ b/lib/loclass/optimized_cipher.h @@ -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 diff --git a/lib/loclass/optimized_ikeys.c b/lib/loclass/optimized_ikeys.c index 1e6f12c5678..d19f5607d5d 100644 --- a/lib/loclass/optimized_ikeys.c +++ b/lib/loclass/optimized_ikeys.c @@ -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 diff --git a/lib/loclass/optimized_ikeys.h b/lib/loclass/optimized_ikeys.h index f2711d31ef5..f40e87b035c 100644 --- a/lib/loclass/optimized_ikeys.h +++ b/lib/loclass/optimized_ikeys.h @@ -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 diff --git a/loclass_writer.c b/loclass_writer.c index 3539a9e3d2f..be59e59ba59 100644 --- a/loclass_writer.c +++ b/loclass_writer.c @@ -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; -} \ No newline at end of file +} diff --git a/picopass_device.h b/picopass_device.h index 4cd4ba83c43..8f942abc63b 100644 --- a/picopass_device.h +++ b/picopass_device.h @@ -12,6 +12,13 @@ #include #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 @@ -70,6 +77,7 @@ typedef enum { PicopassEmulatorStateIdle, PicopassEmulatorStateActive, PicopassEmulatorStateSelected, + PicopassEmulatorStateStopEmulation, } PicopassEmulatorState; typedef struct { @@ -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; diff --git a/picopass_i.h b/picopass_i.h index 77952cdb004..9ae3300a2c7 100644 --- a/picopass_i.h +++ b/picopass_i.h @@ -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, diff --git a/picopass_worker.c b/picopass_worker.c index 494362cfcdc..d1ec189cf5d 100644 --- a/picopass_worker.c +++ b/picopass_worker.c @@ -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( @@ -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; } @@ -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); @@ -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. @@ -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; } @@ -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; } @@ -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); @@ -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);