From cf6d6bb9afbf4b714b9e59ac6eaf101505c3c33f Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 1 Aug 2024 09:10:02 -0400 Subject: [PATCH] Initial structure for nonce collection --- .../protocols/mf_classic/mf_classic_poller.c | 342 +++++++++++++++++- .../mf_classic/mf_classic_poller_i.h | 45 +++ 2 files changed, 368 insertions(+), 19 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 8c50230ca..a865de814 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -680,25 +680,32 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { - dict_attack_ctx->current_key_type = MfClassicKeyTypeB; - instance->state = MfClassicPollerStateKeyReuseAuthKeyB; - } else { - dict_attack_ctx->reuse_key_sector++; - if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { - instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; - command = instance->callback(instance->general_event, instance->context); - instance->state = MfClassicPollerStateRequestKey; - } else { - instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; - instance->mfc_event_data.key_attack_data.current_sector = - dict_attack_ctx->reuse_key_sector; - command = instance->callback(instance->general_event, instance->context); - - dict_attack_ctx->current_key_type = MfClassicKeyTypeA; - instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + do { + if(!dict_attack_ctx->prng_type) { + instance->state = MfClassicPollerStateNestedAnalyzePRNG; + break; } - } + + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + instance->state = MfClassicPollerStateKeyReuseAuthKeyB; + } else { + dict_attack_ctx->reuse_key_sector++; + if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateRequestKey; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = + dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); + + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + } + } + } while(false); return command; } @@ -829,6 +836,294 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst return command; } +void nonce_distance(uint32_t* msb, uint32_t* lsb) { + uint16_t x = 1, pos; + uint8_t calc_ok = 0; + + for(uint16_t i = 1; i; ++i) { + pos = (x & 0xff) << 8 | x >> 8; + + if((pos == *msb) & !(calc_ok >> 0 & 0x01)) { + *msb = i; + calc_ok |= 0x01; + } + + if((pos == *lsb) & !(calc_ok >> 1 & 0x01)) { + *lsb = i; + calc_ok |= 0x02; + } + + if(calc_ok == 0x03) { + return; + } + + x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; + } +} + +// TODO: Faster? https://github.com/RfidResearchGroup/proxmark3/commit/bd3e8db852186d4ab9d5dda890d1cd52389b1254 +bool validate_prng_nonce(uint32_t nonce) { + uint32_t msb = nonce >> 16; + uint32_t lsb = nonce & 0xffff; + nonce_distance(&msb, &lsb); + return ((65535 - msb + lsb) % 65535) == 16; +} + +// Helper function to add a nonce to the array +static bool add_nested_nonce(MfClassicNestedNonceArray* array, uint32_t cuid, uint8_t key_idx, uint32_t nt, uint32_t nt_enc, uint8_t par, uint16_t dist) { + MfClassicNestedNonce* new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); + if (new_nonces == NULL) return false; + + array->nonces = new_nonces; + array->nonces[array->count].cuid = cuid; + array->nonces[array->count].key_idx = key_idx; + array->nonces[array->count].nt = nt; + array->nonces[array->count].nt_enc = nt_enc; + array->nonces[array->count].par = par; + array->nonces[array->count].dist = dist; + array->count++; + return true; +} + +NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + // Analyze PRNG by collecting nt + uint8_t nonce_limit = 10; + + if (dict_attack_ctx->nt_count > 0) { + if(!validate_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; + } + + if(dict_attack_ctx->nt_count <= nonce_limit) { + instance->state = MfClassicPollerStateNestedCollectNt; + return command; + } + + if(dict_attack_ctx->hard_nt_count > 3) { + dict_attack_ctx->prng_type = MfClassicPrngTypeHard; + // FIXME: E -> D + FURI_LOG_E(TAG, "Detected Hard PRNG"); + } else { + dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + // FIXME: E -> D + FURI_LOG_E(TAG, "Detected Weak PRNG"); + } + + instance->state = MfClassicPollerStateNestedController; + return command; +} + +NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) { + // Stub + NfcCommand command = NfcCommandContinue; + return command; +} + +NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance) { + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + MfClassicNt nt = {}; + MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt); + + if(error != MfClassicErrorNone) { + instance->state = MfClassicPollerStateKeyReuseStart; + dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; + // FIXME: E -> D + FURI_LOG_E(TAG, "Failed to collect nt"); + } else { + // FIXME: E -> D + FURI_LOG_E(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]); + uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); + dict_attack_ctx->nt_prev = nt_data; + dict_attack_ctx->nt_count++; + instance->state = MfClassicPollerStateNestedAnalyzePRNG; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { + // TODO: Collect parity + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + MfClassicNestedNonceArray result = {NULL, 0}; + + do { + if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + FURI_LOG_E(TAG, "Hard PRNG, skipping calibration"); + // TODO: Collect hardnested nonces (cuid, sec, par, nt_enc, A/B) + // https://github.com/AloneLiberty/FlipperNested/blob/eba6163d7ef22adef5f9fe4d77a78ccfcc27a952/lib/nested/nested.c#L564-L645 + break; + } + + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error; + + uint32_t davg = 0, dmin = 0, dmax = 0; + uint32_t collected = 0; + const uint32_t rounds = 10; + + uint32_t nt_enc_arr[rounds]; + uint32_t nt_prev = 0; + uint32_t nt_enc_prev = 0; + uint32_t same_nt_enc_cnt = 0; + uint32_t dist = 0; + bool static_encrypted = false; + + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); + + if (error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + break; + } + + FURI_LOG_E(TAG, "Full authentication successful"); + + nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + + // Step 2: Perform nested authentication multiple times + for (uint32_t round_no = 0; round_no < rounds; round_no++) { + error = mf_classic_poller_auth_nested( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); + + if (error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication %lu", round_no); + continue; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + if (nt_enc == nt_enc_prev) { + same_nt_enc_cnt++; + if (same_nt_enc_cnt > 3) { + static_encrypted = true; + break; + } + } else { + same_nt_enc_cnt = 0; + nt_enc_prev = nt_enc; + } + nt_enc_arr[round_no] = nt_enc; + } + + if (static_encrypted) { + FURI_LOG_E(TAG, "Static encrypted nonce detected"); + if (dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + // TODO: Backdoor static nested attack + break; + } else { + // TODO: If not present, just log nonces with parity bits + bool success = add_nested_nonce(&result, cuid, dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, 0, 0); + if (!success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + } + break; + } + } + + // Find the distance between each nonce + // TODO: Distance calculation + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); + Crypto1 crypto; + crypto1_init(&crypto, known_key); + for (uint32_t rtr = 0; rtr < rounds; rtr++) { + bool found = false; + for (int i = 0; i < 65535; i++) { + Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even}; + uint32_t nth_successor = prng_successor(nt_prev, i); + if ((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) == nt_enc_arr[rtr]) { + FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); + FURI_LOG_E(TAG, "dist from nt prev: %i", i); + nt_prev = nth_successor; + found = true; + /* + dist_a = i; + found_dist++; + printf("ks: %08x\n", nth_successor ^ nt_enc); + */ + break; + } + } + if (!found) { + FURI_LOG_E(TAG, "Failed to find distance for nt_enc %08lx", nt_enc_arr[rtr]); + FURI_LOG_E(TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev); + } + } + + if (collected > 0) { + davg /= collected; + FURI_LOG_E( + TAG, + "Calibration completed: min=%lu max=%lu avg=%lu collected=%lu", + dmin, + dmax, + davg, + collected); + } else { + FURI_LOG_E(TAG, "Failed to collect any valid nonce distances"); + } + } while(false); + + instance->state = MfClassicPollerStateNestedController; + + mf_classic_poller_halt(instance); + return command; +} + +NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { + // Log collected nonces to /ext/nfc/.nested.log + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + size_t nonce_idx = dict_attack_ctx->nested_nonce.count; + while (nonce_idx > 0) { + // TODO: Make sure we're not off by one here + FURI_LOG_E(TAG, "nt: %08lx", dict_attack_ctx->nested_nonce.nonces[nonce_idx].nt); + nonce_idx--; + } + free(dict_attack_ctx->nested_nonce.nonces); + return command; +} + +NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { + // Iterate through keys + // To run an accelerated dictionary attack, first we need static nested nonces. + // If the PRNG is hard, we can't run an accelerated dictionary attack. + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + if (dict_attack_ctx->nested_nonce.count > 0) { + instance->state = MfClassicPollerStateNestedLog; + return command; + } + if (dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { + instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; + return command; + } + // Target all sectors, key A and B + for (uint8_t key_idx = 0; key_idx <= (instance->sectors_total * 2); key_idx++) { + dict_attack_ctx->nested_target_key = key_idx; + instance->state = MfClassicPollerStateNestedCollectNtEnc; + return command; + } + // TODO: If we've recovered all keys, read blocks and go to complete + instance->state = MfClassicPollerStateKeyReuseStart; + return command; +} + NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; instance->mfc_event.type = MfClassicPollerEventTypeSuccess; @@ -868,6 +1163,15 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a, [MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b, [MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector, + [MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng, + [MfClassicPollerStateNestedAnalyzeBackdoor] = mf_classic_poller_handler_nested_analyze_backdoor, + [MfClassicPollerStateNestedCollectNt] = + mf_classic_poller_handler_nested_collect_nt, + [MfClassicPollerStateNestedController] = + mf_classic_poller_handler_nested_controller, + [MfClassicPollerStateNestedCollectNtEnc] = + mf_classic_poller_handler_nested_collect_nt_enc, + [MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log, [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, }; @@ -953,4 +1257,4 @@ const NfcPollerBase mf_classic_poller = { .run = (NfcPollerRun)mf_classic_poller_run, .detect = (NfcPollerDetect)mf_classic_poller_detect, .get_data = (NfcPollerGetData)mf_classic_poller_get_data, -}; +}; \ No newline at end of file diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 14a7c61fd..f72145e8f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -21,6 +21,33 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; +typedef enum { + MfClassicPrngTypeUnknown, // Tag not yet tested + MfClassicPrngTypeNoTag, // No tag detected during test + MfClassicPrngTypeWeak, // Weak PRNG, standard Nested + MfClassicPrngTypeHard, // Hard PRNG, Hardnested +} MfClassicPrngType; + +typedef enum { + MfClassicBackdoorUnknown, // Tag not yet tested + MfClassicBackdoorNone, // No observed backdoor + MfClassicBackdoorFM11RF08S, // Tag responds to Fudan FM11RF08S backdoor (static encrypted nonce tags) +} MfClassicBackdoor; + +typedef struct { + uint32_t cuid; // Card UID + uint8_t key_idx; // Key index + uint32_t nt; // Nonce + uint32_t nt_enc; // Encrypted nonce + uint8_t par; // Parity + uint16_t dist; // Distance +} MfClassicNestedNonce; + +typedef struct { + MfClassicNestedNonce* nonces; + size_t count; +} MfClassicNestedNonceArray; + typedef enum { MfClassicPollerStateDetectType, MfClassicPollerStateStart, @@ -49,6 +76,14 @@ typedef enum { MfClassicPollerStateSuccess, MfClassicPollerStateFail, + // Enhanced dictionary attack states + MfClassicPollerStateNestedAnalyzePRNG, + MfClassicPollerStateNestedAnalyzeBackdoor, + MfClassicPollerStateNestedCollectNt, + MfClassicPollerStateNestedController, + MfClassicPollerStateNestedCollectNtEnc, + MfClassicPollerStateNestedLog, + MfClassicPollerStateNum, } MfClassicPollerState; @@ -63,6 +98,7 @@ typedef struct { MfClassicBlock tag_block; } MfClassicPollerWriteContext; +// TODO: Investigate reducing the number of members of this struct typedef struct { uint8_t current_sector; MfClassicKey current_key; @@ -70,6 +106,15 @@ typedef struct { bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; + // Enhanced dictionary attack + MfClassicPrngType prng_type; + MfClassicBackdoor backdoor; + uint32_t nt_prev; + uint32_t nt_next; + uint8_t nt_count; + uint8_t hard_nt_count; + uint8_t nested_target_key; + MfClassicNestedNonceArray nested_nonce; } MfClassicPollerDictAttackContext; typedef struct {