From cf6d6bb9afbf4b714b9e59ac6eaf101505c3c33f Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 1 Aug 2024 09:10:02 -0400 Subject: [PATCH 01/63] 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 { From b15460315ea5664f839dac59ed2eba106ab6ceea Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 1 Aug 2024 22:16:10 -0400 Subject: [PATCH 02/63] Nonce logging --- .../protocols/mf_classic/mf_classic_poller.c | 116 +++++++++++++++--- .../mf_classic/mf_classic_poller_i.h | 3 + 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index a865de814..cb1d645e7 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -3,6 +3,8 @@ #include #include +#include +#include #define TAG "MfClassicPoller" @@ -869,6 +871,15 @@ bool validate_prng_nonce(uint32_t nonce) { return ((65535 - msb + lsb) % 65535) == 16; } +// Return 1 if the nonce is invalid else return 0 +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { + return ( + (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ + (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ + (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) + ) ? 1 : 0; +} + // 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)); @@ -916,8 +927,10 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan } NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) { - // Stub NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + // TODO: Check for Fudan backdoor + dict_attack_ctx->backdoor = MfClassicBackdoorNone; return command; } @@ -947,6 +960,24 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Collect parity + // TODO: FURI_CRITICAL_ENTER + // xTickCount + // Modify targets/f7/furi_hal/furi_hal_rtc.c to expose sub-second time (https://stackoverflow.com/questions/55534873/how-to-setup-the-stm32f4-real-time-clockrtc-to-get-valid-values-in-the-sub-sec) + // furi_hal_rtc_get_timestamp isn't precise enough + // TODO: Measure ticks with https://github.com/flipperdevices/flipperzero-firmware/blob/bec6bd381f222cf14658fd862a2c7f6e620bbf00/targets/f7/furi_hal/furi_hal_idle_timer.h#L51 + /* + uint32_t ncount = 0; + uint32_t nttest = prng_successor(nt1, dmin - 1); + + for(j = dmin; j < dmax + 1; j++) { + nttest = prng_successor(nttest, 1); + ks1 = nt2 ^ nttest; + + if(valid_nonce(nttest, nt2, ks1, par_array)) { + if(ncount > 0) { // we are only interested in disambiguous nonces, try again + FURI_LOG_D(TAG, "Nonce#%lu: dismissed (ambiguous), ntdist=%lu", i + 1, j); + (..) + */ NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicNestedNonceArray result = {NULL, 0}; @@ -975,6 +1006,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst uint32_t same_nt_enc_cnt = 0; uint32_t dist = 0; bool static_encrypted = false; + // Store last timestamp + // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -995,14 +1028,14 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // 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); + error = mf_classic_poller_auth_nested( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); - if (error != MfClassicErrorNone) { + if (error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform nested authentication %lu", round_no); continue; } @@ -1012,8 +1045,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst same_nt_enc_cnt++; if (same_nt_enc_cnt > 3) { static_encrypted = true; - break; - } + break; + } } else { same_nt_enc_cnt = 0; nt_enc_prev = nt_enc; @@ -1086,16 +1119,64 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { - // Log collected nonces to /ext/nfc/.nested.log + furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); + furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); + NfcCommand command = NfcCommandContinue; + bool params_saved = false; 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--; - } + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + do { + if ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && (dict_attack_ctx->nested_nonce.count != 2)) { + FURI_LOG_E(TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %lu", dict_attack_ctx->nested_nonce.count); + break; + } + + if(!buffered_file_stream_open(stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) break; + + bool params_write_success = true; + for(size_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { + MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; + furi_string_printf(temp_str, "Sec %d key %c cuid %08lx", (nonce->key_idx / 2), (nonce->key_idx % 2 == 0) ? 'A' : 'B', nonce->cuid); + for(uint8_t nt_idx = 0; nt_idx < ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? 2 : 1); nt_idx++) { + furi_string_cat_printf( + temp_str, + " nt%u %08lx ks%u %08lx par%u ", + nt_idx, + nonce->nt, + nt_idx, + nonce->nt_enc ^ nonce->nt, + nt_idx); + for(uint8_t pb = 0; pb < 4; pb++) { + furi_string_cat_printf(temp_str, "%u", (nonce->par >> (3 - pb)) & 1); + } + } + if (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + furi_string_cat_printf(temp_str, " dist %u\n", nonce->dist); + } else { + furi_string_cat_printf(temp_str, "\n"); + } + if(!stream_write_string(stream, temp_str)) { + params_write_success = false; + break; + } + } + if(!params_write_success) break; + + params_saved = true; + } while(false); + + furi_assert(params_saved); free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.count = 0; + furi_string_free(temp_str); + buffered_file_stream_close(stream); + stream_free(stream); + furi_record_close(RECORD_STORAGE); + instance->state = MfClassicPollerStateNestedController; return command; } @@ -1103,6 +1184,7 @@ 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. + // TODO: Look ahead dictionary attack NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; if (dict_attack_ctx->nested_nonce.count > 0) { 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 f72145e8f..ede052e20 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -10,6 +10,9 @@ extern "C" { #endif #define MF_CLASSIC_FWT_FC (60000) +#define NFC_FOLDER ANY_PATH("nfc") +#define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" +#define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) typedef enum { MfClassicAuthStateIdle, From ef16770f5e0a3e757587e5248fef58b2cea8ca6b Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 2 Aug 2024 07:04:43 -0400 Subject: [PATCH 03/63] Dictionary attack structure --- .../protocols/mf_classic/mf_classic_poller.c | 25 ++++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index cb1d645e7..3a2562893 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -981,6 +981,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicNestedNonceArray result = {NULL, 0}; + // TODO: Target sector, key A/B do { if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { @@ -1061,7 +1062,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst 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); + bool success = add_nested_nonce(&result, cuid, dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, 0, 65535); if (!success) { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } @@ -1118,6 +1119,14 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst return command; } +NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + // TODO: Nested dictionary attack with ks1 + instance->state = MfClassicPollerStateNestedLog; + return command; +} + NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); @@ -1182,14 +1191,16 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { 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. - // TODO: Look ahead 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->prng_type == MfClassicPrngTypeWeak) { + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } else if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + instance->state = MfClassicPollerStateNestedLog; + return command; + } } if (dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; @@ -1253,6 +1264,8 @@ static const MfClassicPollerReadHandler mf_classic_poller_handler_nested_controller, [MfClassicPollerStateNestedCollectNtEnc] = mf_classic_poller_handler_nested_collect_nt_enc, + [MfClassicPollerStateNestedDictAttack] = + mf_classic_poller_handler_nested_dict_attack, [MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log, [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, 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 ede052e20..1565c80ad 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -85,6 +85,7 @@ typedef enum { MfClassicPollerStateNestedCollectNt, MfClassicPollerStateNestedController, MfClassicPollerStateNestedCollectNtEnc, + MfClassicPollerStateNestedDictAttack, MfClassicPollerStateNestedLog, MfClassicPollerStateNum, From 213ec1dfb0564897e248d00763205f86b8d8ae2f Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 2 Aug 2024 08:15:53 -0400 Subject: [PATCH 04/63] Fix compilation --- .../protocols/mf_classic/mf_classic_poller.c | 44 +++++++++---------- .../mf_classic/mf_classic_poller_i.h | 2 + 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3a2562893..cb55dfcbc 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -872,6 +872,7 @@ bool validate_prng_nonce(uint32_t nonce) { } // Return 1 if the nonce is invalid else return 0 +/* uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { return ( (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ @@ -879,6 +880,7 @@ uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) ) ? 1 : 0; } +*/ // 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) { @@ -997,19 +999,14 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - uint32_t davg = 0, dmin = 0, dmax = 0; - uint32_t collected = 0; - const uint32_t rounds = 10; - + const uint32_t rounds = 11; 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; // Store last timestamp - // Step 1: Perform full authentication once error = mf_classic_poller_auth( instance, @@ -1075,7 +1072,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst 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++) { + // Skip the first round (typically an outlier) + for (uint32_t rtr = 1; rtr < rounds; rtr++) { bool found = false; for (int i = 0; i < 65535; i++) { Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even}; @@ -1083,13 +1081,14 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst 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); + if (i > dict_attack_ctx->d_max) { + dict_attack_ctx->d_max = i; + } + if (i < dict_attack_ctx->d_min) { + dict_attack_ctx->d_min = i; + } nt_prev = nth_successor; found = true; - /* - dist_a = i; - found_dist++; - printf("ks: %08x\n", nth_successor ^ nt_enc); - */ break; } } @@ -1098,19 +1097,16 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst FURI_LOG_E(TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev); } } + bool is_static = (dict_attack_ctx->d_min == dict_attack_ctx->d_max); - 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"); - } + "Calibration completed: min=%u max=%u static: %s", + dict_attack_ctx->d_min, + dict_attack_ctx->d_max, + is_static ? "true" : "false"); + + //printf("ks: %08x\n", nth_successor ^ nt_enc); } while(false); instance->state = MfClassicPollerStateNestedController; @@ -1121,7 +1117,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; - MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + //MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; // TODO: Nested dictionary attack with ks1 instance->state = MfClassicPollerStateNestedLog; return command; @@ -1140,7 +1136,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { do { if ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && (dict_attack_ctx->nested_nonce.count != 2)) { - FURI_LOG_E(TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %lu", dict_attack_ctx->nested_nonce.count); + FURI_LOG_E(TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %u", dict_attack_ctx->nested_nonce.count); break; } 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 1565c80ad..2a5341197 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -119,6 +119,8 @@ typedef struct { uint8_t hard_nt_count; uint8_t nested_target_key; MfClassicNestedNonceArray nested_nonce; + uint16_t d_min; + uint16_t d_max; } MfClassicPollerDictAttackContext; typedef struct { From 9ba78bf085b5e0ad525467b642d44cfff8a06f20 Mon Sep 17 00:00:00 2001 From: noproto Date: Sat, 3 Aug 2024 21:35:52 -0400 Subject: [PATCH 05/63] Identified method to reduce candidate states --- .../protocols/mf_classic/mf_classic_poller.c | 189 ++++++++++-------- 1 file changed, 101 insertions(+), 88 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index cb55dfcbc..c0daffe75 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -838,37 +838,15 @@ 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; - } - +bool validate_prng_nonce(uint32_t nonce) { + if(nonce == 0) return false; + uint16_t x = nonce >> 16; + x = (x & 0xff) << 8 | x >> 8; + for(uint8_t i = 0; i < 16; i++) { 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; + x = (x & 0xff) << 8 | x >> 8; + return x == (nonce & 0xFFFF); } // Return 1 if the nonce is invalid else return 0 @@ -883,9 +861,17 @@ uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) */ // 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; +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; @@ -905,11 +891,11 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan // Analyze PRNG by collecting nt uint8_t nonce_limit = 10; - if (dict_attack_ctx->nt_count > 0) { + 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) { + if(dict_attack_ctx->nt_count < nonce_limit) { instance->state = MfClassicPollerStateNestedCollectNt; return command; } @@ -933,6 +919,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; // TODO: Check for Fudan backdoor dict_attack_ctx->backdoor = MfClassicBackdoorNone; + instance->state = MfClassicPollerStateNestedController; return command; } @@ -962,11 +949,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Collect parity - // TODO: FURI_CRITICAL_ENTER - // xTickCount - // Modify targets/f7/furi_hal/furi_hal_rtc.c to expose sub-second time (https://stackoverflow.com/questions/55534873/how-to-setup-the-stm32f4-real-time-clockrtc-to-get-valid-values-in-the-sub-sec) - // furi_hal_rtc_get_timestamp isn't precise enough - // TODO: Measure ticks with https://github.com/flipperdevices/flipperzero-firmware/blob/bec6bd381f222cf14658fd862a2c7f6e620bbf00/targets/f7/furi_hal/furi_hal_idle_timer.h#L51 + // TODO: Use the mode (most frequently observed) value and parity to reliably get a single candidate state /* uint32_t ncount = 0; uint32_t nttest = prng_successor(nt1, dmin - 1); @@ -986,16 +969,17 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // TODO: Target sector, key A/B do { - if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + 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); + 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; @@ -1005,7 +989,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; bool static_encrypted = false; - // Store last timestamp // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -1015,7 +998,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst dict_attack_ctx->current_key_type, &auth_ctx); - if (error != MfClassicErrorNone) { + if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); break; } @@ -1025,7 +1008,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst 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++) { + for(uint32_t round_no = 0; round_no < rounds; round_no++) { error = mf_classic_poller_auth_nested( instance, block, @@ -1033,15 +1016,15 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst dict_attack_ctx->current_key_type, &auth_ctx); - if (error != MfClassicErrorNone) { + 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) { + if(nt_enc == nt_enc_prev) { same_nt_enc_cnt++; - if (same_nt_enc_cnt > 3) { + if(same_nt_enc_cnt > 3) { static_encrypted = true; break; } @@ -1052,15 +1035,22 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst nt_enc_arr[round_no] = nt_enc; } - if (static_encrypted) { + if(static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if (dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + 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, 65535); - if (!success) { + bool success = add_nested_nonce( + &result, + cuid, + dict_attack_ctx->reuse_key_sector, + nt_prev, + nt_enc_prev, + 0, + UINT16_MAX); + if(!success) { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } break; @@ -1069,44 +1059,57 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Find the distance between each nonce // TODO: Distance calculation + // TODO: Avoid repeated calibration + FURI_LOG_E(TAG, "Calibrating distance between nonces"); + // Checking for d_min == 0 is more of a workaround for not having a proper calibration process + if(dict_attack_ctx->d_min == 0) dict_attack_ctx->d_min = UINT16_MAX; uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); Crypto1 crypto; crypto1_init(&crypto, known_key); - // Skip the first round (typically an outlier) - for (uint32_t rtr = 1; rtr < rounds; rtr++) { + for(uint32_t rtr = 0; rtr < rounds; rtr++) { bool found = false; - for (int i = 0; i < 65535; i++) { + 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]) { + if((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) != + nt_enc_arr[rtr]) { + continue; + } + if(rtr > 0) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); - if (i > dict_attack_ctx->d_max) { + if(i > dict_attack_ctx->d_max) { dict_attack_ctx->d_max = i; } - if (i < dict_attack_ctx->d_min) { + if(i < dict_attack_ctx->d_min) { dict_attack_ctx->d_min = i; } - nt_prev = nth_successor; - found = true; - break; } + nt_prev = nth_successor; + found = true; + break; } - if (!found) { + 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); + FURI_LOG_E( + TAG, + "using key %06llx and uid %08lx, nt_prev is %08lx", + known_key, + cuid, + nt_prev); } } bool is_static = (dict_attack_ctx->d_min == dict_attack_ctx->d_max); FURI_LOG_E( TAG, - "Calibration completed: min=%u max=%u static: %s", - dict_attack_ctx->d_min, - dict_attack_ctx->d_max, - is_static ? "true" : "false"); + "Calibration completed: min=%u max=%u static: %s", + dict_attack_ctx->d_min, + dict_attack_ctx->d_max, + is_static ? "true" : "false"); //printf("ks: %08x\n", nth_successor ^ nt_enc); + //dict_attack_ctx->nested_target_key } while(false); instance->state = MfClassicPollerStateNestedController; @@ -1135,18 +1138,31 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { FuriString* temp_str = furi_string_alloc(); do { - if ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && (dict_attack_ctx->nested_nonce.count != 2)) { - FURI_LOG_E(TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %u", dict_attack_ctx->nested_nonce.count); + if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (dict_attack_ctx->nested_nonce.count != 2)) { + FURI_LOG_E( + TAG, + "MfClassicPollerStateNestedLog expected 2 nonces, received %u", + dict_attack_ctx->nested_nonce.count); break; } - if(!buffered_file_stream_open(stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) break; + if(!buffered_file_stream_open( + stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) + break; bool params_write_success = true; for(size_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; - furi_string_printf(temp_str, "Sec %d key %c cuid %08lx", (nonce->key_idx / 2), (nonce->key_idx % 2 == 0) ? 'A' : 'B', nonce->cuid); - for(uint8_t nt_idx = 0; nt_idx < ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? 2 : 1); nt_idx++) { + furi_string_printf( + temp_str, + "Sec %d key %c cuid %08lx", + (nonce->key_idx / 2), + (nonce->key_idx % 2 == 0) ? 'A' : 'B', + nonce->cuid); + for(uint8_t nt_idx = 0; + nt_idx < ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? 2 : 1); + nt_idx++) { furi_string_cat_printf( temp_str, " nt%u %08lx ks%u %08lx par%u ", @@ -1159,7 +1175,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { furi_string_cat_printf(temp_str, "%u", (nonce->par >> (3 - pb)) & 1); } } - if (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { furi_string_cat_printf(temp_str, " dist %u\n", nonce->dist); } else { furi_string_cat_printf(temp_str, "\n"); @@ -1189,21 +1205,21 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if (dict_attack_ctx->nested_nonce.count > 0) { - if (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + if(dict_attack_ctx->nested_nonce.count > 0) { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedDictAttack; return command; - } else if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + } else if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { instance->state = MfClassicPollerStateNestedLog; return command; } } - if (dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { + 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++) { + 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; @@ -1253,15 +1269,12 @@ static const MfClassicPollerReadHandler [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, - [MfClassicPollerStateNestedDictAttack] = - mf_classic_poller_handler_nested_dict_attack, + [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, + [MfClassicPollerStateNestedDictAttack] = mf_classic_poller_handler_nested_dict_attack, [MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log, [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, @@ -1348,4 +1361,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 +}; From 6d666389fb7e09b332e53339729c27724251b7e4 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 4 Aug 2024 08:30:02 -0400 Subject: [PATCH 06/63] Use EXT_PATH instead of ANY_PATH --- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2a5341197..d29b92921 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -9,8 +9,8 @@ extern "C" { #endif -#define MF_CLASSIC_FWT_FC (60000) -#define NFC_FOLDER ANY_PATH("nfc") +#define MF_CLASSIC_FWT_FC (60000) +#define NFC_FOLDER EXT_PATH("nfc") #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" #define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) From 09f8a7375105e748a09b11421e95a6b33b03466c Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 4 Aug 2024 23:53:21 -0400 Subject: [PATCH 07/63] Use median calibrated distance, collect parity bits --- .../protocols/mf_classic/mf_classic_poller.c | 249 +++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 5 +- 2 files changed, 191 insertions(+), 63 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index c0daffe75..3f7f083a7 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -889,7 +889,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; // Analyze PRNG by collecting nt - uint8_t nonce_limit = 10; + uint8_t nonce_limit = 5; if(dict_attack_ctx->nt_count > 0) { if(!validate_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; @@ -900,7 +900,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan return command; } - if(dict_attack_ctx->hard_nt_count > 3) { + if(dict_attack_ctx->hard_nt_count >= 3) { dict_attack_ctx->prng_type = MfClassicPrngTypeHard; // FIXME: E -> D FURI_LOG_E(TAG, "Detected Hard PRNG"); @@ -947,9 +947,162 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance return command; } +void bubble_sort(uint16_t arr[], int n) { + for(int i = 0; i < n - 1; i++) { + for(int j = 0; j < n - i - 1; j++) { + if(arr[j] > arr[j + 1]) { + uint16_t temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } +} + +uint16_t get_median(uint16_t arr[], int n) { + bubble_sort(arr, n); + return arr[n / 2]; +} + +NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { + // TODO: Calibrate backdoored tags too + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + uint8_t nt_enc_calibration_cnt = 11; + uint32_t nt_enc_temp_arr[nt_enc_calibration_cnt]; + + uint16_t d_min = UINT16_MAX; + uint16_t d_max = 0; + uint16_t d_all[nt_enc_calibration_cnt - 1]; + uint8_t d_all_cnt = 0; + 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 nt_prev = 0; + uint32_t nt_enc_prev = 0; + uint32_t same_nt_enc_cnt = 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"); + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } + + 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(uint8_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; + collection_cycle++) { + 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 %u", collection_cycle); + 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_temp_arr[collection_cycle] = nt_enc; + } + + if(static_encrypted) { + FURI_LOG_E(TAG, "Static encrypted nonce detected"); + if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + // TODO: Backdoor static nested attack calibration + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + return command; + } else { + // TODO: Log these + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + return command; + } + } + + // Find the distance between each nonce + FURI_LOG_E(TAG, "Calculating distance between nonces"); + 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 collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; + collection_cycle++) { + 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_temp_arr[collection_cycle]) { + continue; + } + if(collection_cycle > 0) { + FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); + FURI_LOG_E(TAG, "dist from nt prev: %i", i); + d_all[d_all_cnt++] = i; + if(i < d_min) d_min = i; + if(i > d_max) d_max = i; + } + nt_prev = nth_successor; + found = true; + break; + } + if(!found) { + FURI_LOG_E( + TAG, + "Failed to find distance for nt_enc %08lx", + nt_enc_temp_arr[collection_cycle]); + FURI_LOG_E( + TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev); + } + } + + dict_attack_ctx->d_median = get_median(d_all, d_all_cnt); + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + + mf_classic_poller_halt(instance); + FURI_LOG_E( + TAG, + "Calibration completed: med=%u min=%u max=%u static=%s", + dict_attack_ctx->d_median, + d_min, + d_max, + (d_min == d_max) ? "true" : "false"); + + return command; +} + NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Collect parity - // TODO: Use the mode (most frequently observed) value and parity to reliably get a single candidate state + // TODO: Use d_median value and parity to get candidate states. d_median is the center, keep adding +/- 1 until a parity match is found. /* uint32_t ncount = 0; uint32_t nttest = prng_successor(nt1, dmin - 1); @@ -966,11 +1119,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicNestedNonceArray result = {NULL, 0}; - // TODO: Target sector, key A/B do { if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { - FURI_LOG_E(TAG, "Hard PRNG, skipping calibration"); + FURI_LOG_E(TAG, "Hard PRNG, skipping"); // 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; @@ -983,8 +1135,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - const uint32_t rounds = 11; - uint32_t nt_enc_arr[rounds]; + uint8_t nt_enc_per_collection = 2; + uint32_t nt_enc_arr[nt_enc_per_collection]; + uint8_t par_arr[nt_enc_per_collection]; + uint8_t parity = 0; uint32_t nt_prev = 0; uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; @@ -1007,8 +1161,16 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst 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++) { + // Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset + // eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the + // most commonly observed nonce from 4 auths to known sector and 5th to target. This gets us a nonce pair, + // at a known distance (confirmed by parity bits) telling us the nt_enc plain. + // We also have backup values for the distance in case of rare issues. + //printf("ks: %08x\n", nth_successor ^ nt_enc); + //MfClassicKeyType target_key_type = + // (dict_attack_ctx->nested_target_key % 2 == 0) ? MfClassicKeyTypeA : MfClassicKeyTypeB; + //uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 2)) + 3; + for(uint32_t round_no = 0; round_no < nt_enc_per_collection; round_no++) { error = mf_classic_poller_auth_nested( instance, block, @@ -1032,6 +1194,12 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst same_nt_enc_cnt = 0; nt_enc_prev = nt_enc; } + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_encrypted_buffer); + for(int i = 0; i < 4; i++) { + parity |= ((parity_data[i / 8] >> (i % 8)) & 0x01) << i; + } + par_arr[round_no] = parity; nt_enc_arr[round_no] = nt_enc; } @@ -1057,59 +1225,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } } - // Find the distance between each nonce - // TODO: Distance calculation - // TODO: Avoid repeated calibration - FURI_LOG_E(TAG, "Calibrating distance between nonces"); - // Checking for d_min == 0 is more of a workaround for not having a proper calibration process - if(dict_attack_ctx->d_min == 0) dict_attack_ctx->d_min = UINT16_MAX; - 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]) { - continue; - } - if(rtr > 0) { - FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); - FURI_LOG_E(TAG, "dist from nt prev: %i", i); - if(i > dict_attack_ctx->d_max) { - dict_attack_ctx->d_max = i; - } - if(i < dict_attack_ctx->d_min) { - dict_attack_ctx->d_min = i; - } - } - nt_prev = nth_successor; - found = true; - 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); - } + for(uint8_t i = 0; i < nt_enc_per_collection; i++) { + FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc_arr[i]); + FURI_LOG_E(TAG, "parity: %02x", par_arr[i]); } - bool is_static = (dict_attack_ctx->d_min == dict_attack_ctx->d_max); - - FURI_LOG_E( - TAG, - "Calibration completed: min=%u max=%u static: %s", - dict_attack_ctx->d_min, - dict_attack_ctx->d_max, - is_static ? "true" : "false"); - - //printf("ks: %08x\n", nth_successor ^ nt_enc); - //dict_attack_ctx->nested_target_key } while(false); instance->state = MfClassicPollerStateNestedController; @@ -1203,6 +1322,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys + // TODO: Fix infinite loop somewhere NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; if(dict_attack_ctx->nested_nonce.count > 0) { @@ -1218,6 +1338,12 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } + // TODO: Need to think about how this works for Fudan backdoored tags. + // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak && !dict_attack_ctx->calibrated) { + instance->state = MfClassicPollerStateNestedCalibrate; + 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; @@ -1271,6 +1397,7 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng, [MfClassicPollerStateNestedAnalyzeBackdoor] = mf_classic_poller_handler_nested_analyze_backdoor, + [MfClassicPollerStateNestedCalibrate] = mf_classic_poller_handler_nested_calibrate, [MfClassicPollerStateNestedCollectNt] = mf_classic_poller_handler_nested_collect_nt, [MfClassicPollerStateNestedController] = mf_classic_poller_handler_nested_controller, [MfClassicPollerStateNestedCollectNtEnc] = mf_classic_poller_handler_nested_collect_nt_enc, 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 d29b92921..f24f3496e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -82,6 +82,7 @@ typedef enum { // Enhanced dictionary attack states MfClassicPollerStateNestedAnalyzePRNG, MfClassicPollerStateNestedAnalyzeBackdoor, + MfClassicPollerStateNestedCalibrate, MfClassicPollerStateNestedCollectNt, MfClassicPollerStateNestedController, MfClassicPollerStateNestedCollectNtEnc, @@ -119,8 +120,8 @@ typedef struct { uint8_t hard_nt_count; uint8_t nested_target_key; MfClassicNestedNonceArray nested_nonce; - uint16_t d_min; - uint16_t d_max; + bool calibrated; + uint16_t d_median; } MfClassicPollerDictAttackContext; typedef struct { From 7d2cab5d77c7b07225e0b026768fe5c812b9c94d Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 5 Aug 2024 10:29:54 -0400 Subject: [PATCH 08/63] Modify parity collection --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3f7f083a7..87986b5fd 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1101,7 +1101,6 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { - // TODO: Collect parity // TODO: Use d_median value and parity to get candidate states. d_median is the center, keep adding +/- 1 until a parity match is found. /* uint32_t ncount = 0; @@ -1197,7 +1196,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Collect parity bits const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_encrypted_buffer); for(int i = 0; i < 4; i++) { - parity |= ((parity_data[i / 8] >> (i % 8)) & 0x01) << i; + parity |= ((parity_data[i] & 0x01) << (3 - i)); } par_arr[round_no] = parity; nt_enc_arr[round_no] = nt_enc; @@ -1216,7 +1215,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, - 0, + parity, UINT16_MAX); if(!success) { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); @@ -1227,7 +1226,13 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst for(uint8_t i = 0; i < nt_enc_per_collection; i++) { FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc_arr[i]); - FURI_LOG_E(TAG, "parity: %02x", par_arr[i]); + FURI_LOG_E( + TAG, + "parity: %u%u%u%u", + ((par_arr[i] >> 3) & 1), + ((par_arr[i] >> 2) & 1), + ((par_arr[i] >> 1) & 1), + (par_arr[i] & 1)); } } while(false); From 8dd3daf6257a1047a31d5ef30b3efb1d24620524 Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 9 Aug 2024 20:22:19 -0400 Subject: [PATCH 09/63] Fixed parity bit collection --- lib/nfc/helpers/crypto1.c | 43 +++ lib/nfc/helpers/crypto1.h | 4 + .../protocols/mf_classic/mf_classic_poller.c | 289 +++++++++++------- .../protocols/mf_classic/mf_classic_poller.h | 4 +- .../mf_classic/mf_classic_poller_i.c | 12 +- .../mf_classic/mf_classic_poller_i.h | 2 + lib/toolbox/bit_buffer.c | 2 +- targets/f7/api_symbols.csv | 6 +- 8 files changed, 252 insertions(+), 110 deletions(-) diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index bd4fc8d61..97b92edfd 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -177,3 +177,46 @@ void crypto1_encrypt_reader_nonce( bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); } } + +static uint8_t lfsr_rollback_bit(Crypto1* crypto1, uint32_t in, int fb) { + int out; + uint8_t ret; + uint32_t t; + + crypto1->odd &= 0xffffff; + t = crypto1->odd; + crypto1->odd = crypto1->even; + crypto1->even = t; + + out = crypto1->even & 1; + out ^= LF_POLY_EVEN & (crypto1->even >>= 1); + out ^= LF_POLY_ODD & crypto1->odd; + out ^= !!in; + out ^= (ret = crypto1_filter(crypto1->odd)) & (!!fb); + + crypto1->even |= (nfc_util_even_parity32(out)) << 23; + return ret; +} + +uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { + uint32_t ret = 0; + for(int i = 31; i >= 0; i--) { + ret |= lfsr_rollback_bit(crypto1, BEBIT(in, i), fb) << (24 ^ i); + } + return ret; +} + +// Return true if the nonce is invalid else return false +bool valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t parity) { + return ((nfc_util_odd_parity8((Nt >> 24) & 0xFF) == + (((parity >> 3) & 1) ^ nfc_util_odd_parity8((NtEnc >> 24) & 0xFF) ^ + FURI_BIT(Ks1, 16))) && + (nfc_util_odd_parity8((Nt >> 16) & 0xFF) == + (((parity >> 2) & 1) ^ nfc_util_odd_parity8((NtEnc >> 16) & 0xFF) ^ + FURI_BIT(Ks1, 8))) && + (nfc_util_odd_parity8((Nt >> 8) & 0xFF) == + (((parity >> 1) & 1) ^ nfc_util_odd_parity8((NtEnc >> 8) & 0xFF) ^ + FURI_BIT(Ks1, 0)))) ? + true : + false; +} diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index e71ab9a40..ed71bc3e1 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -38,6 +38,10 @@ void crypto1_encrypt_reader_nonce( BitBuffer* out, bool is_nested); +uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); + +bool valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t parity); + uint32_t prng_successor(uint32_t x, uint32_t n); #ifdef __cplusplus diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 87986b5fd..3b7c499a5 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -849,17 +849,6 @@ bool validate_prng_nonce(uint32_t nonce) { return x == (nonce & 0xFFFF); } -// Return 1 if the nonce is invalid else return 0 -/* -uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { - return ( - (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ - (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ - (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) - ) ? 1 : 0; -} -*/ - // Helper function to add a nonce to the array static bool add_nested_nonce( MfClassicNestedNonceArray* array, @@ -918,6 +907,28 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; // TODO: Check for Fudan backdoor + // Can use on more than S variant as a free key for Nested + /* + do { + uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_cmd[2] = {auth_type, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + + if(is_nested) { + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + */ dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateNestedController; return command; @@ -966,6 +977,7 @@ uint16_t get_median(uint16_t arr[], int n) { NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { // TODO: Calibrate backdoored tags too + // TODO: Check if we have already identified the tag as static encrypted NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint8_t nt_enc_calibration_cnt = 11; @@ -984,7 +996,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) uint32_t nt_prev = 0; uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; - bool static_encrypted = false; + uint8_t nt_enc_collected = 0; // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -1012,28 +1024,33 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) block, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle); continue; } - uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); - if(nt_enc == nt_enc_prev) { + nt_enc_temp_arr[collection_cycle] = + bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + nt_enc_collected++; + } + + for(int i = 0; i < nt_enc_collected; i++) { + if(nt_enc_temp_arr[i] == nt_enc_prev) { same_nt_enc_cnt++; if(same_nt_enc_cnt > 3) { - static_encrypted = true; + dict_attack_ctx->static_encrypted = true; break; } } else { same_nt_enc_cnt = 0; - nt_enc_prev = nt_enc; + nt_enc_prev = nt_enc_temp_arr[i]; } - nt_enc_temp_arr[collection_cycle] = nt_enc; } - if(static_encrypted) { + if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { // TODO: Backdoor static nested attack calibration @@ -1101,23 +1118,10 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { - // TODO: Use d_median value and parity to get candidate states. d_median is the center, keep adding +/- 1 until a parity match is found. - /* - uint32_t ncount = 0; - uint32_t nttest = prng_successor(nt1, dmin - 1); - - for(j = dmin; j < dmax + 1; j++) { - nttest = prng_successor(nttest, 1); - ks1 = nt2 ^ nttest; - - if(valid_nonce(nttest, nt2, ks1, par_array)) { - if(ncount > 0) { // we are only interested in disambiguous nonces, try again - FURI_LOG_D(TAG, "Nonce#%lu: dismissed (ambiguous), ntdist=%lu", i + 1, j); - (..) - */ - NfcCommand command = NfcCommandContinue; + // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) + NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - MfClassicNestedNonceArray result = {NULL, 0}; + bool collection_success = false; do { if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { @@ -1127,6 +1131,30 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst break; } + if(dict_attack_ctx->static_encrypted) { + FURI_LOG_E(TAG, "Static encrypted nonce detected"); + if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + // TODO: Backdoor static nested attack with calibrated distance + break; + } else { + // TODO: If not present, just log nonces with parity bits, e.g. + /* + bool success = add_nested_nonce( + &result, + cuid, + dict_attack_ctx->reuse_key_sector, + nt_prev, + nt_enc_prev, + parity, + UINT16_MAX); + if(!success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + } + */ + 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); @@ -1134,14 +1162,15 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - uint8_t nt_enc_per_collection = 2; - uint32_t nt_enc_arr[nt_enc_per_collection]; - uint8_t par_arr[nt_enc_per_collection]; + uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; + uint8_t nt_enc_per_collection = 2 + nonce_pair_index; + MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; + uint32_t nt_enc_temp_arr[nt_enc_per_collection]; + uint8_t nt_enc_collected = 0; uint8_t parity = 0; - uint32_t nt_prev = 0; - uint32_t nt_enc_prev = 0; - uint32_t same_nt_enc_cnt = 0; - bool static_encrypted = false; // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -1158,84 +1187,132 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst 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 a variable number of times to get nt_enc at a different PRNG offset // eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the // most commonly observed nonce from 4 auths to known sector and 5th to target. This gets us a nonce pair, // at a known distance (confirmed by parity bits) telling us the nt_enc plain. - // We also have backup values for the distance in case of rare issues. - //printf("ks: %08x\n", nth_successor ^ nt_enc); - //MfClassicKeyType target_key_type = - // (dict_attack_ctx->nested_target_key % 2 == 0) ? MfClassicKeyTypeA : MfClassicKeyTypeB; - //uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 2)) + 3; - for(uint32_t round_no = 0; round_no < nt_enc_per_collection; round_no++) { + for(uint8_t collection_cycle = 0; collection_cycle < (nt_enc_per_collection - 1); + collection_cycle++) { + // This loop must match the calibrated loop error = mf_classic_poller_auth_nested( instance, block, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { - FURI_LOG_E(TAG, "Failed to perform nested authentication %lu", round_no); - continue; + FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle); + break; } - 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; + nt_enc_temp_arr[collection_cycle] = + bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + nt_enc_collected++; + } + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->current_key, + target_key_type, + &auth_ctx, + true); + + if(nt_enc_collected != (nt_enc_per_collection - 1)) { + FURI_LOG_E(TAG, "Failed to collect sufficient nt_enc values"); + break; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); + for(int i = 0; i < 4; i++) { + parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + } + // Decrypt the previous nonce + uint32_t nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); + Crypto1 crypto_temp; + crypto1_init(&crypto_temp, known_key); + crypto1_word(&crypto_temp, nt_prev ^ cuid, 1); + uint32_t decrypted_nt_prev = + (nt_prev ^ lfsr_rollback_word(&crypto_temp, nt_prev ^ cuid, 1)); + + // Find matching nt_enc plain at expected distance + bool found_matching_nt = false; + uint32_t found_nt = 0; + uint16_t current_dist = 0; + const uint16_t max_dist = 16; // 32 would work too + // TODO: Better handling of overflows (allow wrap instead of stopping?) + while(!found_matching_nt && current_dist < max_dist && + ((dict_attack_ctx->d_median - current_dist) != 0) && + ((dict_attack_ctx->d_median + current_dist) != UINT16_MAX)) { + uint32_t nth_successor_positive = + prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median + current_dist); + if(valid_nonce( + nth_successor_positive, nt_enc, nth_successor_positive ^ nt_enc, parity)) { + found_matching_nt = true; + found_nt = nth_successor_positive; + break; + } + if(current_dist > 0) { + uint32_t nth_successor_negative = + prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median - current_dist); + if(valid_nonce( + nth_successor_negative, nt_enc, nth_successor_negative ^ nt_enc, parity)) { + found_matching_nt = true; + found_nt = nth_successor_negative; break; } - } else { - same_nt_enc_cnt = 0; - nt_enc_prev = nt_enc; } - // Collect parity bits - const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_encrypted_buffer); - for(int i = 0; i < 4; i++) { - parity |= ((parity_data[i] & 0x01) << (3 - i)); - } - par_arr[round_no] = parity; - nt_enc_arr[round_no] = nt_enc; + current_dist++; } - 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, - parity, - UINT16_MAX); - if(!success) { - FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); - } - break; + // Add the nonce to the array + if(found_matching_nt) { + collection_success = add_nested_nonce( + &dict_attack_ctx->nested_nonce, + cuid, + dict_attack_ctx->nested_target_key, + found_nt, + nt_enc, + parity, + 0); + if(!collection_success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } + } else { + FURI_LOG_E(TAG, "Failed to find matching nonce"); } - for(uint8_t i = 0; i < nt_enc_per_collection; i++) { - FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc_arr[i]); - FURI_LOG_E( - TAG, - "parity: %u%u%u%u", - ((par_arr[i] >> 3) & 1), - ((par_arr[i] >> 2) & 1), - ((par_arr[i] >> 1) & 1), - (par_arr[i] & 1)); - } + FURI_LOG_E( + TAG, + "Target: %u (nonce pair %u, key type %s, block %u)", + dict_attack_ctx->nested_target_key, + nonce_pair_index, + (target_key_type == MfClassicKeyTypeA) ? "A" : "B", + target_block); + FURI_LOG_E(TAG, "cuid: %08lx", cuid); + FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); + FURI_LOG_E( + TAG, + "parity: %u%u%u%u", + ((parity >> 3) & 1), + ((parity >> 2) & 1), + ((parity >> 1) & 1), + (parity & 1)); + FURI_LOG_E(TAG, "nt_enc prev: %08lx", nt_prev); + FURI_LOG_E(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); } while(false); + // Probably belongs in the controller + if(collection_success) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->retry_counter = 0; + } else { + dict_attack_ctx->retry_counter++; + } instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); @@ -1327,10 +1404,10 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys - // TODO: Fix infinite loop somewhere NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if(dict_attack_ctx->nested_nonce.count > 0) { + if((dict_attack_ctx->nested_nonce.count > 0) && + (dict_attack_ctx->nested_target_key % 2 == 0)) { if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedDictAttack; return command; @@ -1349,9 +1426,17 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedCalibrate; 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; + // Target all sectors, key A and B, first and second nonce + if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { + if(dict_attack_ctx->retry_counter > 3) { + // Bad sector, skip + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.count = 0; + } + dict_attack_ctx->nested_target_key += 4; + dict_attack_ctx->retry_counter = 0; + } instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 518d029d0..043424989 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -231,6 +231,7 @@ MfClassicError mf_classic_poller_auth( * @param[in] key key to be used for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @param[in] early_ret return immediately after receiving encrypted nonce. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_auth_nested( @@ -238,7 +239,8 @@ MfClassicError mf_classic_poller_auth_nested( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data); + MfClassicAuthContext* data, + bool early_ret); /** * @brief Halt the tag. diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 949ef8e66..d3a3882c1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -111,7 +111,8 @@ static MfClassicError mf_classic_poller_auth_common( MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, - bool is_nested) { + bool is_nested, + bool early_ret) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -130,6 +131,7 @@ static MfClassicError mf_classic_poller_auth_common( if(data) { data->nt = nt; } + if(early_ret) break; uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); uint64_t key_num = bit_lib_bytes_to_num_be(key->data, sizeof(MfClassicKey)); @@ -185,7 +187,7 @@ MfClassicError mf_classic_poller_auth( MfClassicAuthContext* data) { furi_check(instance); furi_check(key); - return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false); + return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false, false); } MfClassicError mf_classic_poller_auth_nested( @@ -193,10 +195,12 @@ MfClassicError mf_classic_poller_auth_nested( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data) { + MfClassicAuthContext* data, + bool early_ret) { furi_check(instance); furi_check(key); - return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, true); + return mf_classic_poller_auth_common( + instance, block_num, key, key_type, data, true, early_ret); } MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { 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 f24f3496e..0614f99c1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -120,8 +120,10 @@ typedef struct { uint8_t hard_nt_count; uint8_t nested_target_key; MfClassicNestedNonceArray nested_nonce; + bool static_encrypted; bool calibrated; uint16_t d_median; + uint8_t retry_counter; } MfClassicPollerDictAttackContext; typedef struct { diff --git a/lib/toolbox/bit_buffer.c b/lib/toolbox/bit_buffer.c index 85a52e79d..e261e80d4 100644 --- a/lib/toolbox/bit_buffer.c +++ b/lib/toolbox/bit_buffer.c @@ -113,7 +113,7 @@ void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size uint8_t bit = FURI_BIT(data[bits_processed / BITS_IN_BYTE + 1], bits_processed % BITS_IN_BYTE); - if(bits_processed % BITS_IN_BYTE) { + if((bits_processed % BITS_IN_BYTE) == 0) { buf->parity[curr_byte / BITS_IN_BYTE] = bit; } else { buf->parity[curr_byte / BITS_IN_BYTE] |= bit << (bits_processed % BITS_IN_BYTE); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 4bfe37abb..b2e818fb8 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,71.0,, +Version,+,72.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -2211,6 +2211,7 @@ Function,+,lfrfid_worker_start_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_stop,void,LFRFIDWorker* Function,+,lfrfid_worker_stop_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_write_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" +Function,+,lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,lgamma,double,double Function,-,lgamma_r,double,"double, int*" Function,-,lgammaf,float,float @@ -2506,7 +2507,7 @@ Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" -Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool" Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* @@ -3525,6 +3526,7 @@ Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" +Function,+,valid_nonce,_Bool,"uint32_t, uint32_t, uint32_t, uint8_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* From 4b44288c960576ec0671d1d450630b9700eb9a0e Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 9 Aug 2024 20:38:45 -0400 Subject: [PATCH 10/63] Add note to fix nonce logging --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3b7c499a5..80cf8aed5 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1328,6 +1328,15 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc } NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { + // TODO: Fix this logging the same nonce twice, and there should only be 16 sectors (1K) + /* + uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; + uint8_t nt_enc_per_collection = 2 + nonce_pair_index; + MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; + */ furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); From 5feeae8972295aec3923c16e4a01d794bf0dd208 Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 9 Aug 2024 22:01:43 -0400 Subject: [PATCH 11/63] Fix nonce logging --- .../protocols/mf_classic/mf_classic_poller.c | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 80cf8aed5..35909a632 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1328,15 +1328,6 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc } NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { - // TODO: Fix this logging the same nonce twice, and there should only be 16 sectors (1K) - /* - uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; - uint8_t nt_enc_per_collection = 2 + nonce_pair_index; - MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; - */ furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); @@ -1346,10 +1337,10 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { Storage* storage = furi_record_open(RECORD_STORAGE); Stream* stream = buffered_file_stream_alloc(storage); FuriString* temp_str = furi_string_alloc(); + bool weak_prng = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; do { - if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (dict_attack_ctx->nested_nonce.count != 2)) { + if(weak_prng && (dict_attack_ctx->nested_nonce.count != 2)) { FURI_LOG_E( TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %u", @@ -1357,22 +1348,27 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { break; } + uint32_t nonce_pair_count = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak ? + 1 : + dict_attack_ctx->nested_nonce.count; + if(!buffered_file_stream_open( stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) break; bool params_write_success = true; - for(size_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { + for(size_t i = 0; i < nonce_pair_count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; furi_string_printf( temp_str, "Sec %d key %c cuid %08lx", - (nonce->key_idx / 2), - (nonce->key_idx % 2 == 0) ? 'A' : 'B', + (nonce->key_idx / 4), + ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', nonce->cuid); - for(uint8_t nt_idx = 0; - nt_idx < ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? 2 : 1); - nt_idx++) { + for(uint8_t nt_idx = 0; nt_idx < (weak_prng ? 2 : 1); nt_idx++) { + if(weak_prng && nt_idx == 1) { + nonce = &dict_attack_ctx->nested_nonce.nonces[i + 1]; + } furi_string_cat_printf( temp_str, " nt%u %08lx ks%u %08lx par%u ", From 3acba77070a3a114b1bb49471fd5a1f14b0b6028 Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 9 Aug 2024 22:08:35 -0400 Subject: [PATCH 12/63] Clean redundant code --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 35909a632..d6d142875 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1366,7 +1366,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', nonce->cuid); for(uint8_t nt_idx = 0; nt_idx < (weak_prng ? 2 : 1); nt_idx++) { - if(weak_prng && nt_idx == 1) { + if(nt_idx == 1) { nonce = &dict_attack_ctx->nested_nonce.nonces[i + 1]; } furi_string_cat_printf( From 6332ec7478ce377c073da63c30d50c950e6b2ccd Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 11 Aug 2024 14:30:26 -0400 Subject: [PATCH 13/63] Fix valid_nonce --- lib/nfc/helpers/crypto1.c | 19 +++++++------------ lib/nfc/helpers/crypto1.h | 2 +- lib/nfc/helpers/nfc_util.c | 4 ++++ lib/nfc/helpers/nfc_util.h | 2 ++ .../protocols/mf_classic/mf_classic_poller.c | 6 ++---- targets/f7/api_symbols.csv | 3 ++- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index 97b92edfd..a228e3f09 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -207,16 +207,11 @@ uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { } // Return true if the nonce is invalid else return false -bool valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t parity) { - return ((nfc_util_odd_parity8((Nt >> 24) & 0xFF) == - (((parity >> 3) & 1) ^ nfc_util_odd_parity8((NtEnc >> 24) & 0xFF) ^ - FURI_BIT(Ks1, 16))) && - (nfc_util_odd_parity8((Nt >> 16) & 0xFF) == - (((parity >> 2) & 1) ^ nfc_util_odd_parity8((NtEnc >> 16) & 0xFF) ^ - FURI_BIT(Ks1, 8))) && - (nfc_util_odd_parity8((Nt >> 8) & 0xFF) == - (((parity >> 1) & 1) ^ nfc_util_odd_parity8((NtEnc >> 8) & 0xFF) ^ - FURI_BIT(Ks1, 0)))) ? - true : - false; +bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { + return (nfc_util_even_parity8((nt >> 24) & 0xFF) == + (((nt_par_enc >> 3) & 1) ^ FURI_BIT(ks, 16))) && + (nfc_util_even_parity8((nt >> 16) & 0xFF) == + (((nt_par_enc >> 2) & 1) ^ FURI_BIT(ks, 8))) && + (nfc_util_even_parity8((nt >> 8) & 0xFF) == + (((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0))); } diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index ed71bc3e1..ef9bd69b5 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -40,7 +40,7 @@ void crypto1_encrypt_reader_nonce( uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); -bool valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t parity); +bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); uint32_t prng_successor(uint32_t x, uint32_t n); diff --git a/lib/nfc/helpers/nfc_util.c b/lib/nfc/helpers/nfc_util.c index f502b4bfb..80af5cf11 100644 --- a/lib/nfc/helpers/nfc_util.c +++ b/lib/nfc/helpers/nfc_util.c @@ -13,6 +13,10 @@ static const uint8_t nfc_util_odd_byte_parity[256] = { 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1}; +uint8_t nfc_util_even_parity8(uint8_t data) { + return !nfc_util_odd_byte_parity[data]; +} + uint8_t nfc_util_even_parity32(uint32_t data) { // data ^= data >> 16; // data ^= data >> 8; diff --git a/lib/nfc/helpers/nfc_util.h b/lib/nfc/helpers/nfc_util.h index f8e86d865..4abde4521 100644 --- a/lib/nfc/helpers/nfc_util.h +++ b/lib/nfc/helpers/nfc_util.h @@ -6,6 +6,8 @@ extern "C" { #endif +uint8_t nfc_util_even_parity8(uint8_t data); + uint8_t nfc_util_even_parity32(uint32_t data); uint8_t nfc_util_odd_parity8(uint8_t data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index d6d142875..b35f6d2df 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1250,8 +1250,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst ((dict_attack_ctx->d_median + current_dist) != UINT16_MAX)) { uint32_t nth_successor_positive = prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median + current_dist); - if(valid_nonce( - nth_successor_positive, nt_enc, nth_successor_positive ^ nt_enc, parity)) { + if(valid_nonce(nth_successor_positive, nth_successor_positive ^ nt_enc, parity)) { found_matching_nt = true; found_nt = nth_successor_positive; break; @@ -1259,8 +1258,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst if(current_dist > 0) { uint32_t nth_successor_negative = prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median - current_dist); - if(valid_nonce( - nth_successor_negative, nt_enc, nth_successor_negative ^ nt_enc, parity)) { + if(valid_nonce(nth_successor_negative, nth_successor_negative ^ nt_enc, parity)) { found_matching_nt = true; found_nt = nth_successor_negative; break; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index b2e818fb8..7dae5837a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2805,6 +2805,7 @@ Function,+,nfc_set_mask_receive_time_fc,void,"Nfc*, uint32_t" Function,+,nfc_start,void,"Nfc*, NfcEventCallback, void*" Function,+,nfc_stop,void,Nfc* Function,+,nfc_util_even_parity32,uint8_t,uint32_t +Function,+,nfc_util_even_parity8,uint8_t,uint8_t Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" @@ -3526,7 +3527,7 @@ Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,+,valid_nonce,_Bool,"uint32_t, uint32_t, uint32_t, uint8_t" +Function,+,valid_nonce,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* From 01b19483c59af70819d994bf2ce18eefa13ae244 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 14 Aug 2024 02:25:57 -0400 Subject: [PATCH 14/63] First attempt disambiguous nonce implementation --- lib/nfc/helpers/crypto1.c | 23 ++- lib/nfc/helpers/crypto1.h | 7 +- .../protocols/mf_classic/mf_classic_poller.c | 177 +++++++----------- .../mf_classic/mf_classic_poller_i.h | 14 +- targets/f7/api_symbols.csv | 4 +- 5 files changed, 108 insertions(+), 117 deletions(-) diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index a228e3f09..e59657a40 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -206,8 +206,7 @@ uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { return ret; } -// Return true if the nonce is invalid else return false -bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { +bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { return (nfc_util_even_parity8((nt >> 24) & 0xFF) == (((nt_par_enc >> 3) & 1) ^ FURI_BIT(ks, 16))) && (nfc_util_even_parity8((nt >> 16) & 0xFF) == @@ -215,3 +214,23 @@ bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { (nfc_util_even_parity8((nt >> 8) & 0xFF) == (((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0))); } + +bool is_weak_prng_nonce(uint32_t nonce) { + if(nonce == 0) return false; + uint16_t x = nonce >> 16; + x = (x & 0xff) << 8 | x >> 8; + for(uint8_t i = 0; i < 16; i++) { + x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; + } + x = (x & 0xff) << 8 | x >> 8; + return x == (nonce & 0xFFFF); +} + +uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { + uint64_t known_key_int = bit_lib_bytes_to_num_be(known_key.data, 6); + Crypto1 crypto_temp; + crypto1_init(&crypto_temp, known_key_int); + crypto1_word(&crypto_temp, nt_enc ^ cuid, 1); + uint32_t decrypted_nt_enc = (nt_enc ^ lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); + return decrypted_nt_enc; +} diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index ef9bd69b5..26862ab49 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -1,5 +1,6 @@ #pragma once +#include "protocols/mf_classic/mf_classic.h" #include #ifdef __cplusplus @@ -40,7 +41,11 @@ void crypto1_encrypt_reader_nonce( uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); -bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); +bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); + +bool is_weak_prng_nonce(uint32_t nonce); + +uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); uint32_t prng_successor(uint32_t x, uint32_t n); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index b35f6d2df..f96590344 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -3,8 +3,6 @@ #include #include -#include -#include #define TAG "MfClassicPoller" @@ -838,17 +836,6 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst return command; } -bool validate_prng_nonce(uint32_t nonce) { - if(nonce == 0) return false; - uint16_t x = nonce >> 16; - x = (x & 0xff) << 8 | x >> 8; - for(uint8_t i = 0; i < 16; i++) { - x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; - } - x = (x & 0xff) << 8 | x >> 8; - return x == (nonce & 0xFFFF); -} - // Helper function to add a nonce to the array static bool add_nested_nonce( MfClassicNestedNonceArray* array, @@ -881,7 +868,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan uint8_t nonce_limit = 5; if(dict_attack_ctx->nt_count > 0) { - if(!validate_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; + if(!is_weak_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; } if(dict_attack_ctx->nt_count < nonce_limit) { @@ -958,35 +945,16 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance return command; } -void bubble_sort(uint16_t arr[], int n) { - for(int i = 0; i < n - 1; i++) { - for(int j = 0; j < n - i - 1; j++) { - if(arr[j] > arr[j + 1]) { - uint16_t temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - } - } - } -} - -uint16_t get_median(uint16_t arr[], int n) { - bubble_sort(arr, n); - return arr[n / 2]; -} - NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { // TODO: Calibrate backdoored tags too // TODO: Check if we have already identified the tag as static encrypted NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - uint8_t nt_enc_calibration_cnt = 11; + uint8_t nt_enc_calibration_cnt = 21; uint32_t nt_enc_temp_arr[nt_enc_calibration_cnt]; - uint16_t d_min = UINT16_MAX; - uint16_t d_max = 0; - uint16_t d_all[nt_enc_calibration_cnt - 1]; - uint8_t d_all_cnt = 0; + dict_attack_ctx->d_min = UINT16_MAX; + dict_attack_ctx->d_max = 0; 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); @@ -1068,24 +1036,21 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); 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 collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; collection_cycle++) { bool found = false; + uint32_t decrypted_nt_enc = + decrypt_nt_enc(cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->current_key); 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_temp_arr[collection_cycle]) { + if(nth_successor != decrypted_nt_enc) { continue; } if(collection_cycle > 0) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); - d_all[d_all_cnt++] = i; - if(i < d_min) d_min = i; - if(i > d_max) d_max = i; + if(i < dict_attack_ctx->d_min) dict_attack_ctx->d_min = i; + if(i > dict_attack_ctx->d_max) dict_attack_ctx->d_max = i; } nt_prev = nth_successor; found = true; @@ -1101,27 +1066,30 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } } - dict_attack_ctx->d_median = get_median(d_all, d_all_cnt); + // Some breathing room, doesn't account for overflows or static nested (FIXME) + dict_attack_ctx->d_min -= 3; + dict_attack_ctx->d_max += 3; + + furi_assert(dict_attack_ctx->d_min <= dict_attack_ctx->d_max); dict_attack_ctx->calibrated = true; instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); FURI_LOG_E( TAG, - "Calibration completed: med=%u min=%u max=%u static=%s", - dict_attack_ctx->d_median, - d_min, - d_max, - (d_min == d_max) ? "true" : "false"); + "Calibration completed: min=%u max=%u static=%s", + dict_attack_ctx->d_min, + dict_attack_ctx->d_max, + (dict_attack_ctx->d_min == dict_attack_ctx->d_max) ? "true" : "false"); return command; } NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) + // TODO: Look into using MfClassicNt more NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - bool collection_success = false; do { if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { @@ -1163,7 +1131,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicError error; uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; - uint8_t nt_enc_per_collection = 2 + nonce_pair_index; + uint8_t nt_enc_per_collection = (dict_attack_ctx->attempt_count + 2) + nonce_pair_index; MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; @@ -1232,56 +1200,40 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } // Decrypt the previous nonce uint32_t nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); - Crypto1 crypto_temp; - crypto1_init(&crypto_temp, known_key); - crypto1_word(&crypto_temp, nt_prev ^ cuid, 1); - uint32_t decrypted_nt_prev = - (nt_prev ^ lfsr_rollback_word(&crypto_temp, nt_prev ^ cuid, 1)); + uint32_t decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->current_key); // Find matching nt_enc plain at expected distance - bool found_matching_nt = false; uint32_t found_nt = 0; - uint16_t current_dist = 0; - const uint16_t max_dist = 16; // 32 would work too - // TODO: Better handling of overflows (allow wrap instead of stopping?) - while(!found_matching_nt && current_dist < max_dist && - ((dict_attack_ctx->d_median - current_dist) != 0) && - ((dict_attack_ctx->d_median + current_dist) != UINT16_MAX)) { - uint32_t nth_successor_positive = - prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median + current_dist); - if(valid_nonce(nth_successor_positive, nth_successor_positive ^ nt_enc, parity)) { - found_matching_nt = true; - found_nt = nth_successor_positive; - break; - } - if(current_dist > 0) { - uint32_t nth_successor_negative = - prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median - current_dist); - if(valid_nonce(nth_successor_negative, nth_successor_negative ^ nt_enc, parity)) { - found_matching_nt = true; - found_nt = nth_successor_negative; + uint8_t found_nt_cnt = 0; + uint16_t current_dist = dict_attack_ctx->d_min; + while(current_dist <= dict_attack_ctx->d_max) { + uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); + if(nonce_matches_encrypted_parity_bits(nth_successor, nth_successor ^ nt_enc, parity)) { + found_nt_cnt++; + if(found_nt_cnt > 1) { + FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt"); break; } + found_nt = nth_successor; } current_dist++; } + if(found_nt_cnt != 1) { + break; + } // Add the nonce to the array - if(found_matching_nt) { - collection_success = add_nested_nonce( - &dict_attack_ctx->nested_nonce, - cuid, - dict_attack_ctx->nested_target_key, - found_nt, - nt_enc, - parity, - 0); - if(!collection_success) { - FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); - } + if(add_nested_nonce( + &dict_attack_ctx->nested_nonce, + cuid, + dict_attack_ctx->nested_target_key, + found_nt, + nt_enc, + parity, + 0)) { + dict_attack_ctx->nested_state = MfClassicNestedStatePassed; } else { - FURI_LOG_E(TAG, "Failed to find matching nonce"); + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } FURI_LOG_E( @@ -1304,13 +1256,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst FURI_LOG_E(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); } while(false); - // Probably belongs in the controller - if(collection_success) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->retry_counter = 0; - } else { - dict_attack_ctx->retry_counter++; - } instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); @@ -1409,15 +1354,16 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if((dict_attack_ctx->nested_nonce.count > 0) && - (dict_attack_ctx->nested_target_key % 2 == 0)) { - if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { - instance->state = MfClassicPollerStateNestedDictAttack; - return command; - } else if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { - instance->state = MfClassicPollerStateNestedLog; - return command; - } + if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (dict_attack_ctx->nested_nonce.count == 2)) { + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } else if( + (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) && + (dict_attack_ctx->nested_nonce.count > 0)) { + // TODO: Need to think about the meaning of nested_target_key for hard PRNG + instance->state = MfClassicPollerStateNestedLog; + return command; } if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; @@ -1430,15 +1376,24 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance return command; } // Target all sectors, key A and B, first and second nonce + // TODO: Missing weak condition, target_key logic doesn't apply the same to hard if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { - if(dict_attack_ctx->retry_counter > 3) { - // Bad sector, skip + if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + dict_attack_ctx->attempt_count++; + } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->nested_state = MfClassicNestedStateNone; + if(dict_attack_ctx->attempt_count >= 20) { + // Unpredictable, skip + FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); if(dict_attack_ctx->nested_nonce.nonces) { free(dict_attack_ctx->nested_nonce.nonces); dict_attack_ctx->nested_nonce.count = 0; } - dict_attack_ctx->nested_target_key += 4; - dict_attack_ctx->retry_counter = 0; + dict_attack_ctx->nested_target_key += 2; + dict_attack_ctx->attempt_count = 0; } instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; 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 0614f99c1..d10401e76 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -24,6 +26,12 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; +typedef enum { + MfClassicNestedStateNone, + MfClassicNestedStateFailed, + MfClassicNestedStatePassed, +} MfClassicNestedState; + typedef enum { MfClassicPrngTypeUnknown, // Tag not yet tested MfClassicPrngTypeNoTag, // No tag detected during test @@ -122,8 +130,10 @@ typedef struct { MfClassicNestedNonceArray nested_nonce; bool static_encrypted; bool calibrated; - uint16_t d_median; - uint8_t retry_counter; + uint16_t d_min; + uint16_t d_max; + uint8_t attempt_count; + MfClassicNestedState nested_state; } MfClassicPollerDictAttackContext; typedef struct { diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7dae5837a..cc3c45b95 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -901,6 +901,7 @@ Function,+,datetime_get_days_per_year,uint16_t,uint16_t Function,+,datetime_is_leap_year,_Bool,uint16_t Function,+,datetime_timestamp_to_datetime,void,"uint32_t, DateTime*" Function,+,datetime_validate_datetime,_Bool,DateTime* +Function,+,decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -2028,6 +2029,7 @@ Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType Function,-,iprintf,int,"const char*, ..." +Function,+,is_weak_prng_nonce,_Bool,uint32_t Function,-,isalnum,int,int Function,-,isalnum_l,int,"int, locale_t" Function,-,isalpha,int,int @@ -2808,6 +2810,7 @@ Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_even_parity8,uint8_t,uint8_t Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t +Function,+,nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -3527,7 +3530,6 @@ Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,+,valid_nonce,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* From cc8cae770f0e9d1c4eec420a37c764ed8c3eaa15 Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 15 Aug 2024 17:58:37 -0400 Subject: [PATCH 15/63] FM11RF08S backdoor detection --- lib/nfc/protocols/mf_classic/mf_classic.h | 18 ++-- .../protocols/mf_classic/mf_classic_poller.c | 86 +++++++++++++------ .../mf_classic/mf_classic_poller_i.h | 1 + 3 files changed, 73 insertions(+), 32 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 801ec1764..53dcf1a54 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -6,14 +6,16 @@ extern "C" { #endif -#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) -#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) -#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) -#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) -#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) -#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) -#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) -#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) +#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) +#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) +#define MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_A (0x64U) +#define MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_B (0x65U) +#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) +#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) +#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) +#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) +#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) +#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) #define MF_CLASSIC_CMD_HALT_MSB (0x50) #define MF_CLASSIC_CMD_HALT_LSB (0x00) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index f96590344..519958325 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -891,32 +891,69 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan } NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) { - NfcCommand command = NfcCommandContinue; - MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - // TODO: Check for Fudan backdoor // Can use on more than S variant as a free key for Nested - /* - do { - uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : - MF_CLASSIC_CMD_AUTH_KEY_A; - uint8_t auth_cmd[2] = {auth_type, block_num}; - bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if(is_nested) { - iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); - crypto1_encrypt( - instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); - error = iso14443_3a_poller_txrx_custom_parity( - instance->iso14443_3a_poller, - instance->tx_encrypted_buffer, - instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth - MF_CLASSIC_FWT_FC); - if(error != Iso14443_3aErrorNone) { - ret = mf_classic_process_error(error); - break; - } - */ - dict_attack_ctx->backdoor = MfClassicBackdoorNone; + 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 = {}; + MfClassicNt nt = {}; + MfClassicKey fm11rf08s_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; + MfClassicError error; + Iso14443_3aError iso_error; + bool backdoor_found = false; + + do { + // 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"); + + // Step 2: Attempt backdoor authentication + uint8_t auth_type = (dict_attack_ctx->current_key_type == MfClassicKeyTypeB) ? + MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_B : + MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_A; + uint8_t auth_cmd[2] = {auth_type, block}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + iso_error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_plain_buffer, + MF_CLASSIC_FWT_FC); + if(iso_error != Iso14443_3aErrorNone) { + FURI_LOG_E(TAG, "Error during nested authentication"); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { + break; + } + bit_buffer_write_bytes(instance->rx_plain_buffer, nt.data, sizeof(MfClassicNt)); + uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); + // Ensure the encrypted nt can be generated by the backdoor + uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, fm11rf08s_backdoor_key); + backdoor_found = is_weak_prng_nonce(decrypted_nt_enc); + } while(false); + if(backdoor_found) { + FURI_LOG_E(TAG, "Backdoor identified"); + dict_attack_ctx->backdoor = MfClassicBackdoorFM11RF08S; + } else { + dict_attack_ctx->backdoor = MfClassicBackdoorNone; + } instance->state = MfClassicPollerStateNestedController; return command; } @@ -1041,6 +1078,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->current_key); + // TODO: Make sure we're not off-by-one here for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); if(nth_successor != decrypted_nt_enc) { 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 d10401e76..0d797b1dc 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,6 +3,7 @@ #include "mf_classic_poller.h" #include #include +#include "nfc/helpers/iso14443_crc.h" #include #include #include From 79bc887f951ad190499ff5b2bce5f1caeec40c6e Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 18 Aug 2024 20:38:24 -0400 Subject: [PATCH 16/63] Initial accelerated dictionary attack for weak PRNGs --- applications/main/nfc/nfc_app_i.h | 6 +- .../scenes/nfc_scene_mf_classic_dict_attack.c | 14 ++ .../protocols/mf_classic/mf_classic_poller.c | 170 +++++++++++++++++- .../mf_classic/mf_classic_poller_i.h | 28 ++- 4 files changed, 207 insertions(+), 11 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 295a75a4e..798c3a5a6 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -74,8 +74,12 @@ #define NFC_APP_MFKEY32_LOGS_FILE_NAME ".mfkey32.log" #define NFC_APP_MFKEY32_LOGS_FILE_PATH (NFC_APP_FOLDER "/" NFC_APP_MFKEY32_LOGS_FILE_NAME) -#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH \ + (NFC_APP_FOLDER "/assets/mf_classic_dict_user_nested.nfc") #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") +#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \ + (NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc") typedef enum { NfcRpcStateIdle, diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 328e39132..5aee8ddd8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -130,6 +130,13 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { break; } + // TODO: Check for errors + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_USER_PATH, + NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + instance->nfc_dict_context.dict = keys_dict_alloc( NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { @@ -142,6 +149,13 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { } while(false); } if(state == DictAttackStateSystemDictInProgress) { + // TODO: Check for errors + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + instance->nfc_dict_context.dict = keys_dict_alloc( NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary"); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 519958325..23ca4a326 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1300,11 +1300,125 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst return command; } +MfClassicKey* search_dicts_for_weak_nonce_key( + KeysDict* system_dict, + KeysDict* user_dict, + uint32_t cuid, + uint32_t nt_enc) { + MfClassicKey stack_key; + KeysDict* dicts[] = {system_dict, user_dict}; + + for(int i = 0; i < 2; i++) { + keys_dict_rewind(dicts[i]); + while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { + if(is_weak_prng_nonce(decrypt_nt_enc(cuid, nt_enc, stack_key))) { + MfClassicKey* heap_key = malloc(sizeof(MfClassicKey)); + if(heap_key) { + memcpy(heap_key, &stack_key, sizeof(MfClassicKey)); + return heap_key; + } + return NULL; // malloc failed + } + } + } + + return NULL; // No matching key found +} + NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { - NfcCommand command = NfcCommandContinue; - //MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - // TODO: Nested dictionary attack with ks1 - instance->state = MfClassicPollerStateNestedLog; + // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) + // TODO: Look into using MfClassicNt more + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + do { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + // TODO: We can do this by collecting enough nonces (e.g. 10 per key) with the parity bits, decrypt and ensure they + // all match against a known key before trying it. + // Not a failed situation + FURI_LOG_E(TAG, "Hard PRNG, skipping"); + 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; + + MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_dict_target_key % 2) == 0) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = (4 * (dict_attack_ctx->nested_dict_target_key / 2)) + 3; + uint8_t parity = 0; + + // 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"); + dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + break; + } + + FURI_LOG_E(TAG, "Full authentication successful"); + + // Step 2: Collect nested nt and parity + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->current_key, + target_key_type, + &auth_ctx, + true); + + // TODO: Check error? If there is one, return MfClassicNestedStateFailed + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); + for(int i = 0; i < 4; i++) { + parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + } + MfClassicKey* found_key = search_dicts_for_weak_nonce_key( + dict_attack_ctx->mf_classic_system_dict, + dict_attack_ctx->mf_classic_user_dict, + cuid, + nt_enc); + if(found_key) { + uint64_t k = bit_lib_bytes_to_num_be(found_key->data, sizeof(MfClassicKey)); + FURI_LOG_E(TAG, "Found key %06llx for nt_enc %08lx", k, nt_enc); + // TODO: Add to found keys in dictionary attack struct + free(found_key); + } + + FURI_LOG_E( + TAG, + "Target: %u (key type %s, block %u)", + dict_attack_ctx->nested_target_key, + (target_key_type == MfClassicKeyTypeA) ? "A" : "B", + target_block); + FURI_LOG_E(TAG, "cuid: %08lx", cuid); + FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); + FURI_LOG_E( + TAG, + "parity: %u%u%u%u", + ((parity >> 3) & 1), + ((parity >> 2) & 1), + ((parity >> 1) & 1), + (parity & 1)); + } while(false); + + dict_attack_ctx->nested_state = MfClassicNestedStatePassed; + instance->state = MfClassicPollerStateNestedController; + + mf_classic_poller_halt(instance); return command; } @@ -1407,6 +1521,54 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } + // Accelerated Nested dictionary attack + if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (dict_attack_ctx->nested_dict_target_key <= (instance->sectors_total * 2))) { + if(dict_attack_ctx->nested_dict_target_key == (instance->sectors_total * 2)) { + if(dict_attack_ctx->mf_classic_system_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + } + if(dict_attack_ctx->mf_classic_user_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + } + dict_attack_ctx->nested_dict_target_key++; + instance->state = MfClassicPollerStateNestedController; + return command; + } + if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + dict_attack_ctx->attempt_count++; + } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->nested_state = MfClassicNestedStateNone; + if(dict_attack_ctx->attempt_count >= 3) { + // Unpredictable, skip + FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); + dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->attempt_count = 0; + } + if(dict_attack_ctx->nested_dict_target_key == 0) { + // Note: System dict should always exist + bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); + bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); + if(system_dict_exists) { + dict_attack_ctx->mf_classic_system_dict = keys_dict_alloc( + MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + } + if(user_dict_exists) { + dict_attack_ctx->mf_classic_user_dict = keys_dict_alloc( + MF_CLASSIC_NESTED_USER_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + } + } + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } + // TODO: Skip all remaining phases if we have collected all keys // TODO: Need to think about how this works for Fudan backdoored tags. // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak && !dict_attack_ctx->calibrated) { 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 0d797b1dc..f86b843cd 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -7,15 +7,23 @@ #include #include #include +#include "keys_dict.h" #ifdef __cplusplus extern "C" { #endif -#define MF_CLASSIC_FWT_FC (60000) -#define NFC_FOLDER EXT_PATH("nfc") -#define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" -#define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) +#define MF_CLASSIC_FWT_FC (60000) +#define NFC_FOLDER EXT_PATH("nfc") +#define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") +#define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" +#define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" +#define MF_CLASSIC_NESTED_USER_DICT_FILE_NAME "mf_classic_dict_user_nested.nfc" +#define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) +#define MF_CLASSIC_NESTED_SYSTEM_DICT_PATH \ + (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME) +#define MF_CLASSIC_NESTED_USER_DICT_PATH \ + (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_USER_DICT_FILE_NAME) typedef enum { MfClassicAuthStateIdle, @@ -55,6 +63,11 @@ typedef struct { uint16_t dist; // Distance } MfClassicNestedNonce; +typedef struct { + MfClassicKey* key_candidates; + size_t count; +} MfClassicNestedKeyCandidateArray; + typedef struct { MfClassicNestedNonce* nonces; size_t count; @@ -112,7 +125,7 @@ typedef struct { MfClassicBlock tag_block; } MfClassicPollerWriteContext; -// TODO: Investigate reducing the number of members of this struct +// TODO: Investigate reducing the number of members of this struct by moving into a separate struct dedicated to nested dict attack typedef struct { uint8_t current_sector; MfClassicKey current_key; @@ -120,13 +133,14 @@ typedef struct { bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; - // Enhanced dictionary attack + // Enhanced dictionary attack and nested nonce collection 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_dict_target_key; uint8_t nested_target_key; MfClassicNestedNonceArray nested_nonce; bool static_encrypted; @@ -135,6 +149,8 @@ typedef struct { uint16_t d_max; uint8_t attempt_count; MfClassicNestedState nested_state; + KeysDict* mf_classic_system_dict; + KeysDict* mf_classic_user_dict; } MfClassicPollerDictAttackContext; typedef struct { From 0af28fb221ee43f9371c264ec2487c27e3465a8e Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 19 Aug 2024 07:33:17 -0400 Subject: [PATCH 17/63] Refactor to nested dictionary attack --- .../protocols/mf_classic/mf_classic_poller.c | 388 ++++++++++++------ .../mf_classic/mf_classic_poller_i.h | 26 +- 2 files changed, 279 insertions(+), 135 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 23ca4a326..f4c5a6f60 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -681,8 +681,9 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - if(!dict_attack_ctx->prng_type) { - instance->state = MfClassicPollerStateNestedAnalyzePRNG; + // Nested entrypoint + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { + instance->state = MfClassicPollerStateNestedController; break; } @@ -840,13 +841,17 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst static bool add_nested_nonce( MfClassicNestedNonceArray* array, uint32_t cuid, - uint8_t key_idx, + uint16_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)); + MfClassicNestedNonce* new_nonces; + if(array->count == 0) { + new_nonces = malloc(sizeof(MfClassicNestedNonce)); + } else { + new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); + } if(new_nonces == NULL) return false; array->nonces = new_nonces; @@ -860,23 +865,34 @@ static bool add_nested_nonce( return true; } +// Helper function to add key candidate to the array +static bool + add_nested_key_candidate(MfClassicNestedKeyCandidateArray* array, MfClassicKey key_candidate) { + MfClassicKey* new_candidates; + if(array->count == 0) { + new_candidates = malloc(sizeof(MfClassicKey)); + } else { + new_candidates = realloc(array->key_candidates, (array->count + 1) * sizeof(MfClassicKey)); + } + if(new_candidates == NULL) return false; + + array->key_candidates = new_candidates; + array->key_candidates[array->count] = key_candidate; + 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; + uint8_t hard_nt_count = 0; - // Analyze PRNG by collecting nt - uint8_t nonce_limit = 5; - - if(dict_attack_ctx->nt_count > 0) { - if(!is_weak_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; + for(uint8_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { + MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; + if(!is_weak_prng_nonce(nonce->nt)) hard_nt_count++; } - if(dict_attack_ctx->nt_count < nonce_limit) { - instance->state = MfClassicPollerStateNestedCollectNt; - return command; - } - - if(dict_attack_ctx->hard_nt_count >= 3) { + if(hard_nt_count >= MF_CLASSIC_NESTED_HARD_MINIMUM) { dict_attack_ctx->prng_type = MfClassicPrngTypeHard; // FIXME: E -> D FURI_LOG_E(TAG, "Detected Hard PRNG"); @@ -900,7 +916,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in MfClassicAuthContext auth_ctx = {}; MfClassicNt nt = {}; - MfClassicKey fm11rf08s_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; + MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; MfClassicError error; Iso14443_3aError iso_error; bool backdoor_found = false; @@ -945,12 +961,12 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in bit_buffer_write_bytes(instance->rx_plain_buffer, nt.data, sizeof(MfClassicNt)); uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); // Ensure the encrypted nt can be generated by the backdoor - uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, fm11rf08s_backdoor_key); + uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, auth2_backdoor_key); backdoor_found = is_weak_prng_nonce(decrypted_nt_enc); } while(false); if(backdoor_found) { FURI_LOG_E(TAG, "Backdoor identified"); - dict_attack_ctx->backdoor = MfClassicBackdoorFM11RF08S; + dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; } else { dict_attack_ctx->backdoor = MfClassicBackdoorNone; } @@ -966,7 +982,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance 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"); @@ -974,11 +989,19 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance // 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; + if(!add_nested_nonce( + &dict_attack_ctx->nested_nonce, + iso14443_3a_get_cuid(instance->data->iso14443_3a_data), + 0, + nt_data, + 0, + 0, + 0)) { + dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; + } } + instance->state = MfClassicPollerStateNestedController; return command; } @@ -1057,7 +1080,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { // TODO: Backdoor static nested attack calibration dict_attack_ctx->calibrated = true; instance->state = MfClassicPollerStateNestedController; @@ -1139,7 +1162,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { // TODO: Backdoor static nested attack with calibrated distance break; } else { @@ -1300,46 +1323,50 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst return command; } -MfClassicKey* search_dicts_for_weak_nonce_key( +static void search_dicts_for_nonce_key( + MfClassicNestedKeyCandidateArray* key_candidates, + MfClassicNestedNonceArray* nonce_array, KeysDict* system_dict, KeysDict* user_dict, - uint32_t cuid, - uint32_t nt_enc) { + bool is_weak) { MfClassicKey stack_key; - KeysDict* dicts[] = {system_dict, user_dict}; + KeysDict* dicts[] = {user_dict, system_dict}; for(int i = 0; i < 2; i++) { keys_dict_rewind(dicts[i]); while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { - if(is_weak_prng_nonce(decrypt_nt_enc(cuid, nt_enc, stack_key))) { - MfClassicKey* heap_key = malloc(sizeof(MfClassicKey)); - if(heap_key) { - memcpy(heap_key, &stack_key, sizeof(MfClassicKey)); - return heap_key; + bool full_match = true; + for(uint8_t j = 0; j < nonce_array->count; j++) { + // Verify nonce matches encrypted parity bits for all nonces + uint32_t nt_enc_plain = decrypt_nt_enc( + nonce_array->nonces[j].cuid, nonce_array->nonces[j].nt_enc, stack_key); + if(is_weak) { + full_match &= is_weak_prng_nonce(nt_enc_plain); + if(!full_match) break; } - return NULL; // malloc failed + full_match &= nonce_matches_encrypted_parity_bits( + nt_enc_plain, + nt_enc_plain ^ nonce_array->nonces[j].nt_enc, + nonce_array->nonces[j].par); + if(!full_match) break; + } + if(full_match && !add_nested_key_candidate(key_candidates, stack_key)) { + return; // malloc failed } } } - return NULL; // No matching key found + return; } NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) // TODO: Look into using MfClassicNt more + // TODO: A method to try the key candidates when we've collected sufficient nonces NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { - // TODO: We can do this by collecting enough nonces (e.g. 10 per key) with the parity bits, decrypt and ensure they - // all match against a known key before trying it. - // Not a failed situation - FURI_LOG_E(TAG, "Hard PRNG, skipping"); - 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); @@ -1347,72 +1374,114 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc MfClassicAuthContext auth_ctx = {}; MfClassicError error; - MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_dict_target_key % 2) == 0) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_block = (4 * (dict_attack_ctx->nested_dict_target_key / 2)) + 3; + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + bool is_last_iter_for_hard_key = + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = (is_weak) ? (4 * (dict_attack_ctx->nested_target_key / 2)) + 3 : + (4 * (dict_attack_ctx->nested_target_key / 16)) + 3; uint8_t parity = 0; - // 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(((is_weak) && (dict_attack_ctx->nested_key_candidates.count == 0)) || + ((!is_weak) && (!is_last_iter_for_hard_key))) { + // 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"); - dict_attack_ctx->nested_state = MfClassicNestedStateFailed; - break; + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + break; + } + + FURI_LOG_E(TAG, "Full authentication successful"); + + // Step 2: Collect nested nt and parity + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->current_key, + target_key_type, + &auth_ctx, + true); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication"); + dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + break; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); + for(int i = 0; i < 4; i++) { + parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + } + + bool success = add_nested_nonce( + &dict_attack_ctx->nested_nonce, + cuid, + dict_attack_ctx->nested_target_key, + 0, + nt_enc, + parity, + 0); + if(!success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + break; + } + + if(!is_weak) { + dict_attack_ctx->nested_state = MfClassicNestedStatePassed; + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } } - - FURI_LOG_E(TAG, "Full authentication successful"); - - // Step 2: Collect nested nt and parity - error = mf_classic_poller_auth_nested( - instance, - target_block, - &dict_attack_ctx->current_key, - target_key_type, - &auth_ctx, - true); - - // TODO: Check error? If there is one, return MfClassicNestedStateFailed - - uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); - // Collect parity bits - const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); - for(int i = 0; i < 4; i++) { - parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); - } - MfClassicKey* found_key = search_dicts_for_weak_nonce_key( - dict_attack_ctx->mf_classic_system_dict, - dict_attack_ctx->mf_classic_user_dict, - cuid, - nt_enc); - if(found_key) { - uint64_t k = bit_lib_bytes_to_num_be(found_key->data, sizeof(MfClassicKey)); - FURI_LOG_E(TAG, "Found key %06llx for nt_enc %08lx", k, nt_enc); - // TODO: Add to found keys in dictionary attack struct - free(found_key); + // If we have sufficient nonces, search the dictionaries for the key + if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || + (is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) { + // Identify candidate keys (there may be multiple) and validate them to the currently tested sector + // stopping on the first valid key + search_dicts_for_nonce_key( + &dict_attack_ctx->nested_key_candidates, + &dict_attack_ctx->nested_nonce, + dict_attack_ctx->mf_classic_system_dict, + dict_attack_ctx->mf_classic_user_dict, + is_weak); + for(uint8_t i = 0; i < dict_attack_ctx->nested_key_candidates.count; i++) { + FURI_LOG_E( + TAG, + "Found key candidate %06llx", + bit_lib_bytes_to_num_be( + dict_attack_ctx->nested_key_candidates.key_candidates[i].data, + sizeof(MfClassicKey))); + // TODO: Add to found keys in dictionary attack struct ONCE AUTH IS VALIDATED + } + // FIXME + free(dict_attack_ctx->nested_key_candidates.key_candidates); + dict_attack_ctx->nested_key_candidates.key_candidates = NULL; + dict_attack_ctx->nested_key_candidates.count = 0; + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; } FURI_LOG_E( TAG, - "Target: %u (key type %s, block %u)", + "Target: %u (key type %s, block %u) cuid: %08lx", dict_attack_ctx->nested_target_key, (target_key_type == MfClassicKeyTypeA) ? "A" : "B", - target_block); - FURI_LOG_E(TAG, "cuid: %08lx", cuid); - FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); - FURI_LOG_E( - TAG, - "parity: %u%u%u%u", - ((parity >> 3) & 1), - ((parity >> 2) & 1), - ((parity >> 1) & 1), - (parity & 1)); + target_block, + cuid); } while(false); dict_attack_ctx->nested_state = MfClassicNestedStatePassed; @@ -1493,6 +1562,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { furi_assert(params_saved); free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; furi_string_free(temp_str); buffered_file_stream_close(stream); @@ -1502,53 +1572,95 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { return command; } +static bool mf_classic_all_keys_collected(const MfClassicData* data) { + uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); + + for(uint8_t sector = 0; sector < total_sectors; sector++) { + if(!mf_classic_is_key_found(data, sector, MfClassicKeyTypeA) || + !mf_classic_is_key_found(data, sector, MfClassicKeyTypeB)) { + return false; + } + } + + return true; +} + NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (dict_attack_ctx->nested_nonce.count == 2)) { - instance->state = MfClassicPollerStateNestedDictAttack; - return command; - } else if( - (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) && - (dict_attack_ctx->nested_nonce.count > 0)) { - // TODO: Need to think about the meaning of nested_target_key for hard PRNG - instance->state = MfClassicPollerStateNestedLog; - return command; + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } - if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { - instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; - return command; + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { + if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { + instance->state = MfClassicPollerStateNestedCollectNt; + return command; + } else if( + (dict_attack_ctx->nested_nonce.count == MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) && + (dict_attack_ctx->prng_type == MfClassicPrngTypeUnknown)) { + instance->state = MfClassicPollerStateNestedAnalyzePRNG; + return command; + } else if(dict_attack_ctx->prng_type == MfClassicPrngTypeNoTag) { + FURI_LOG_E(TAG, "No tag detected"); + // Free nonce array + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + instance->state = MfClassicPollerStateKeyReuseStart; + return command; + } + if(dict_attack_ctx->nested_nonce.nonces) { + // Free nonce array + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } // Accelerated Nested dictionary attack - if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (dict_attack_ctx->nested_dict_target_key <= (instance->sectors_total * 2))) { - if(dict_attack_ctx->nested_dict_target_key == (instance->sectors_total * 2)) { + uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? + (instance->sectors_total * 2) : + (instance->sectors_total * 16); + if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && + (dict_attack_ctx->nested_target_key <= dict_target_key_max)) { + FURI_LOG_E(TAG, "Targeting key %u", dict_attack_ctx->nested_target_key); // DEBUG + if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); } if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); } - dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->nested_target_key = 0; + if(mf_classic_all_keys_collected(instance->data)) { + // TODO: Ensure this works + // All keys have been collected, skip to reading blocks + FURI_LOG_E(TAG, "All keys collected and sectors read"); + dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; + instance->state = MfClassicPollerStateSuccess; + return command; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; instance->state = MfClassicPollerStateNestedController; return command; } if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { dict_attack_ctx->attempt_count++; } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { - dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->nested_state = MfClassicNestedStateNone; if(dict_attack_ctx->attempt_count >= 3) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); - dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } - if(dict_attack_ctx->nested_dict_target_key == 0) { + if(dict_attack_ctx->nested_target_key == 0) { // Note: System dict should always exist bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); @@ -1568,15 +1680,36 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedDictAttack; return command; } - // TODO: Skip all remaining phases if we have collected all keys - // TODO: Need to think about how this works for Fudan backdoored tags. - // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. - if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak && !dict_attack_ctx->calibrated) { - instance->state = MfClassicPollerStateNestedCalibrate; + // Analyze tag for NXP/Fudan backdoor + if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; + instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } + // TODO: Need to think about how this works for NXP/Fudan backdoored tags. + // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. + // Calibration + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { + if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (!dict_attack_ctx->calibrated)) { + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } else { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } + } + // Log collected nonces + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + if(((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (dict_attack_ctx->nested_nonce.count == 2)) || + ((dict_attack_ctx->prng_type == MfClassicPrngTypeHard) && + (dict_attack_ctx->nested_nonce.count > 0))) { + instance->state = MfClassicPollerStateNestedLog; + return command; + } + } // Target all sectors, key A and B, first and second nonce - // TODO: Missing weak condition, target_key logic doesn't apply the same to hard + // TODO: Hardnested nonces logic if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { dict_attack_ctx->attempt_count++; @@ -1585,11 +1718,12 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->nested_state = MfClassicNestedStateNone; - if(dict_attack_ctx->attempt_count >= 20) { + if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); if(dict_attack_ctx->nested_nonce.nonces) { free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; } dict_attack_ctx->nested_target_key += 2; 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 f86b843cd..8ce3e6bfb 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -16,6 +16,9 @@ extern "C" { #define MF_CLASSIC_FWT_FC (60000) #define NFC_FOLDER EXT_PATH("nfc") #define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") +#define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) +#define MF_CLASSIC_NESTED_HARD_MINIMUM (3) +#define MF_CLASSIC_NESTED_RETRY_MAXIMUM (20) #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" #define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" #define MF_CLASSIC_NESTED_USER_DICT_FILE_NAME "mf_classic_dict_user_nested.nfc" @@ -41,6 +44,16 @@ typedef enum { MfClassicNestedStatePassed, } MfClassicNestedState; +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseAnalyzeBackdoor, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + typedef enum { MfClassicPrngTypeUnknown, // Tag not yet tested MfClassicPrngTypeNoTag, // No tag detected during test @@ -51,7 +64,8 @@ typedef enum { typedef enum { MfClassicBackdoorUnknown, // Tag not yet tested MfClassicBackdoorNone, // No observed backdoor - MfClassicBackdoorFM11RF08S, // Tag responds to Fudan FM11RF08S backdoor (static encrypted nonce tags) + MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) } MfClassicBackdoor; typedef struct { @@ -125,7 +139,6 @@ typedef struct { MfClassicBlock tag_block; } MfClassicPollerWriteContext; -// TODO: Investigate reducing the number of members of this struct by moving into a separate struct dedicated to nested dict attack typedef struct { uint8_t current_sector; MfClassicKey current_key; @@ -134,14 +147,11 @@ typedef struct { uint16_t current_block; uint8_t reuse_key_sector; // Enhanced dictionary attack and nested nonce collection + MfClassicNestedPhase nested_phase; 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_dict_target_key; - uint8_t nested_target_key; + uint16_t nested_target_key; + MfClassicNestedKeyCandidateArray nested_key_candidates; MfClassicNestedNonceArray nested_nonce; bool static_encrypted; bool calibrated; From 08ca794b7de61e263d5f7a55d00746af3259081d Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 19 Aug 2024 07:37:21 -0400 Subject: [PATCH 18/63] Renaming some variables --- lib/nfc/protocols/mf_classic/mf_classic.h | 20 +++++++++---------- .../protocols/mf_classic/mf_classic_poller.c | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 53dcf1a54..38c3e9ba7 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -6,16 +6,16 @@ extern "C" { #endif -#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) -#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) -#define MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_A (0x64U) -#define MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_B (0x65U) -#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) -#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) -#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) -#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) -#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) -#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) +#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) +#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) +#define MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A (0x64U) +#define MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B (0x65U) +#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) +#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) +#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) +#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) +#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) +#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) #define MF_CLASSIC_CMD_HALT_MSB (0x50) #define MF_CLASSIC_CMD_HALT_LSB (0x00) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index f4c5a6f60..6997a3e8f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -939,8 +939,8 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in // Step 2: Attempt backdoor authentication uint8_t auth_type = (dict_attack_ctx->current_key_type == MfClassicKeyTypeB) ? - MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_B : - MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_A; + MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : + MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; uint8_t auth_cmd[2] = {auth_type, block}; bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); From b7e63bf499ca76a33fd5b3879d81897acccdcdff Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 19 Aug 2024 23:00:36 -0400 Subject: [PATCH 19/63] Hard PRNG support for accelerated dictionary attack --- .../protocols/mf_classic/mf_classic_poller.c | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 6997a3e8f..bd8f7cf2c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1387,7 +1387,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc uint8_t parity = 0; if(((is_weak) && (dict_attack_ctx->nested_key_candidates.count == 0)) || - ((!is_weak) && (!is_last_iter_for_hard_key))) { + ((!is_weak) && (dict_attack_ctx->nested_key_candidates.count < 8))) { // Step 1: Perform full authentication once error = mf_classic_poller_auth( instance, @@ -1440,11 +1440,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc break; } - if(!is_weak) { - dict_attack_ctx->nested_state = MfClassicNestedStatePassed; - instance->state = MfClassicPollerStateNestedDictAttack; - return command; - } + dict_attack_ctx->nested_state = MfClassicNestedStatePassed; } // If we have sufficient nonces, search the dictionaries for the key if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || @@ -1484,7 +1480,6 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc cuid); } while(false); - dict_attack_ctx->nested_state = MfClassicNestedStatePassed; instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); @@ -1625,8 +1620,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && - (dict_attack_ctx->nested_target_key <= dict_target_key_max)) { - FURI_LOG_E(TAG, "Targeting key %u", dict_attack_ctx->nested_target_key); // DEBUG + (dict_attack_ctx->nested_target_key < dict_target_key_max)) { + if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + dict_attack_ctx->attempt_count++; + } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->nested_state = MfClassicNestedStateNone; if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); @@ -1647,13 +1648,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedController; return command; } - if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { - dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->attempt_count = 0; - } - dict_attack_ctx->nested_state = MfClassicNestedStateNone; if(dict_attack_ctx->attempt_count >= 3) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); From bbc10cdfafc40747aef5bc218f358ae43bab890d Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 20 Aug 2024 16:31:39 -0400 Subject: [PATCH 20/63] Update found keys, initial attempt --- .../protocols/mf_classic/mf_classic_poller.c | 225 ++++++++++-------- .../mf_classic/mf_classic_poller_i.h | 22 +- 2 files changed, 135 insertions(+), 112 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index bd8f7cf2c..ef1af4c56 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -681,12 +681,6 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - // Nested entrypoint - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { - instance->state = MfClassicPollerStateNestedController; - break; - } - if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { dict_attack_ctx->current_key_type = MfClassicKeyTypeB; instance->state = MfClassicPollerStateKeyReuseAuthKeyB; @@ -695,6 +689,12 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; command = instance->callback(instance->general_event, instance->context); + // Nested entrypoint + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || + dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished) { + instance->state = MfClassicPollerStateNestedController; + break; + } instance->state = MfClassicPollerStateRequestKey; } else { instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; @@ -729,6 +729,9 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + dict_attack_ctx->reuse_success = true; + } mf_classic_set_key_found( instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key); @@ -765,6 +768,9 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + dict_attack_ctx->reuse_success = true; + } mf_classic_set_key_found( instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key); @@ -865,23 +871,6 @@ static bool add_nested_nonce( return true; } -// Helper function to add key candidate to the array -static bool - add_nested_key_candidate(MfClassicNestedKeyCandidateArray* array, MfClassicKey key_candidate) { - MfClassicKey* new_candidates; - if(array->count == 0) { - new_candidates = malloc(sizeof(MfClassicKey)); - } else { - new_candidates = realloc(array->key_candidates, (array->count + 1) * sizeof(MfClassicKey)); - } - if(new_candidates == NULL) return false; - - array->key_candidates = new_candidates; - array->key_candidates[array->count] = key_candidate; - 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; @@ -911,7 +900,8 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); MfClassicAuthContext auth_ctx = {}; @@ -926,8 +916,8 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in error = mf_classic_poller_auth( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx); if(error != MfClassicErrorNone) { @@ -938,7 +928,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in FURI_LOG_E(TAG, "Full authentication successful"); // Step 2: Attempt backdoor authentication - uint8_t auth_type = (dict_attack_ctx->current_key_type == MfClassicKeyTypeB) ? + uint8_t auth_type = (dict_attack_ctx->nested_known_key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; uint8_t auth_cmd[2] = {auth_type, block}; @@ -1015,7 +1005,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) dict_attack_ctx->d_min = UINT16_MAX; dict_attack_ctx->d_max = 0; - uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); MfClassicAuthContext auth_ctx = {}; @@ -1030,8 +1021,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) error = mf_classic_poller_auth( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx); if(error != MfClassicErrorNone) { @@ -1050,8 +1041,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) error = mf_classic_poller_auth_nested( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx, false); @@ -1095,12 +1086,12 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); - uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); for(uint32_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; collection_cycle++) { bool found = false; - uint32_t decrypted_nt_enc = - decrypt_nt_enc(cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->current_key); + uint32_t decrypted_nt_enc = decrypt_nt_enc( + cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); // TODO: Make sure we're not off-by-one here for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); @@ -1171,7 +1162,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst bool success = add_nested_nonce( &result, cuid, - dict_attack_ctx->reuse_key_sector, + dict_attack_ctx->nested_known_key_sector, nt_prev, nt_enc_prev, parity, @@ -1185,7 +1176,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } uint8_t block = - mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); MfClassicAuthContext auth_ctx = {}; @@ -1196,6 +1187,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; + // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; uint32_t nt_enc_temp_arr[nt_enc_per_collection]; uint8_t nt_enc_collected = 0; @@ -1205,8 +1197,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst error = mf_classic_poller_auth( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx); if(error != MfClassicErrorNone) { @@ -1226,8 +1218,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst error = mf_classic_poller_auth_nested( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx, false); @@ -1243,7 +1235,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst error = mf_classic_poller_auth_nested( instance, target_block, - &dict_attack_ctx->current_key, + &dict_attack_ctx->nested_known_key, target_key_type, &auth_ctx, true); @@ -1261,7 +1253,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } // Decrypt the previous nonce uint32_t nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - uint32_t decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->current_key); + uint32_t decrypted_nt_prev = + decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); // Find matching nt_enc plain at expected distance uint32_t found_nt = 0; @@ -1292,7 +1285,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst nt_enc, parity, 0)) { - dict_attack_ctx->nested_state = MfClassicNestedStatePassed; + dict_attack_ctx->auth_passed = true; } else { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } @@ -1323,18 +1316,27 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst return command; } -static void search_dicts_for_nonce_key( - MfClassicNestedKeyCandidateArray* key_candidates, +static MfClassicKey* search_dicts_for_nonce_key( + MfClassicPollerDictAttackContext* dict_attack_ctx, MfClassicNestedNonceArray* nonce_array, KeysDict* system_dict, KeysDict* user_dict, bool is_weak) { MfClassicKey stack_key; KeysDict* dicts[] = {user_dict, system_dict}; + bool is_resumed = dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume; + bool found_resume_point = false; for(int i = 0; i < 2; i++) { keys_dict_rewind(dicts[i]); while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { + if(is_resumed && !(found_resume_point) && + memcmp(dict_attack_ctx->current_key.data, stack_key.data, sizeof(MfClassicKey)) == + 0) { + found_resume_point = true; + } else { + continue; + } bool full_match = true; for(uint8_t j = 0; j < nonce_array->count; j++) { // Verify nonce matches encrypted parity bits for all nonces @@ -1350,25 +1352,27 @@ static void search_dicts_for_nonce_key( nonce_array->nonces[j].par); if(!full_match) break; } - if(full_match && !add_nested_key_candidate(key_candidates, stack_key)) { - return; // malloc failed + if(full_match) { + MfClassicKey* new_candidate = malloc(sizeof(MfClassicKey)); + if(new_candidate == NULL) return NULL; // malloc failed + memcpy(new_candidate, &stack_key, sizeof(MfClassicKey)); + return new_candidate; } } } - return; + return NULL; } NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) // TODO: Look into using MfClassicNt more - // TODO: A method to try the key candidates when we've collected sufficient nonces NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { uint8_t block = - mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); MfClassicAuthContext auth_ctx = {}; @@ -1382,23 +1386,24 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? MfClassicKeyTypeA : MfClassicKeyTypeB; + // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? uint8_t target_block = (is_weak) ? (4 * (dict_attack_ctx->nested_target_key / 2)) + 3 : (4 * (dict_attack_ctx->nested_target_key / 16)) + 3; uint8_t parity = 0; - if(((is_weak) && (dict_attack_ctx->nested_key_candidates.count == 0)) || - ((!is_weak) && (dict_attack_ctx->nested_key_candidates.count < 8))) { + if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 0)) || + ((!is_weak) && (dict_attack_ctx->nested_nonce.count < 8))) { // Step 1: Perform full authentication once error = mf_classic_poller_auth( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); - dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + dict_attack_ctx->auth_passed = false; break; } @@ -1408,14 +1413,14 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc error = mf_classic_poller_auth_nested( instance, target_block, - &dict_attack_ctx->current_key, + &dict_attack_ctx->nested_known_key, target_key_type, &auth_ctx, true); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform nested authentication"); - dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + dict_attack_ctx->auth_passed = false; break; } @@ -1436,39 +1441,36 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc 0); if(!success) { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); - dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + dict_attack_ctx->auth_passed = false; break; } - dict_attack_ctx->nested_state = MfClassicNestedStatePassed; + dict_attack_ctx->auth_passed = true; } // If we have sufficient nonces, search the dictionaries for the key if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || (is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) { - // Identify candidate keys (there may be multiple) and validate them to the currently tested sector - // stopping on the first valid key - search_dicts_for_nonce_key( - &dict_attack_ctx->nested_key_candidates, + // Identify key candidates + MfClassicKey* key_candidate = search_dicts_for_nonce_key( + dict_attack_ctx, &dict_attack_ctx->nested_nonce, dict_attack_ctx->mf_classic_system_dict, dict_attack_ctx->mf_classic_user_dict, is_weak); - for(uint8_t i = 0; i < dict_attack_ctx->nested_key_candidates.count; i++) { + if(key_candidate != NULL) { FURI_LOG_E( TAG, "Found key candidate %06llx", - bit_lib_bytes_to_num_be( - dict_attack_ctx->nested_key_candidates.key_candidates[i].data, - sizeof(MfClassicKey))); - // TODO: Add to found keys in dictionary attack struct ONCE AUTH IS VALIDATED + bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); + dict_attack_ctx->current_key = *key_candidate; + dict_attack_ctx->reuse_key_sector = (target_block / 4); + free(key_candidate); + break; + } else { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; } - // FIXME - free(dict_attack_ctx->nested_key_candidates.key_candidates); - dict_attack_ctx->nested_key_candidates.key_candidates = NULL; - dict_attack_ctx->nested_key_candidates.count = 0; - free(dict_attack_ctx->nested_nonce.nonces); - dict_attack_ctx->nested_nonce.nonces = NULL; - dict_attack_ctx->nested_nonce.count = 0; } FURI_LOG_E( @@ -1567,24 +1569,15 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { return command; } -static bool mf_classic_all_keys_collected(const MfClassicData* data) { - uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); - - for(uint8_t sector = 0; sector < total_sectors; sector++) { - if(!mf_classic_is_key_found(data, sector, MfClassicKeyTypeA) || - !mf_classic_is_key_found(data, sector, MfClassicKeyTypeB)) { - return false; - } - } - - return true; -} - NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { + dict_attack_ctx->auth_passed = true; + dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; + dict_attack_ctx->nested_known_key_type = dict_attack_ctx->current_key_type; + dict_attack_ctx->nested_known_key_sector = dict_attack_ctx->reuse_key_sector; dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { @@ -1604,7 +1597,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; } - instance->state = MfClassicPollerStateKeyReuseStart; + instance->state = MfClassicPollerStateFail; return command; } if(dict_attack_ctx->nested_nonce.nonces) { @@ -1619,15 +1612,39 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? (instance->sectors_total * 2) : (instance->sectors_total * 16); + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + if(!(dict_attack_ctx->reuse_success)) { + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + } if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && (dict_attack_ctx->nested_target_key < dict_target_key_max)) { - if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + bool is_last_iter_for_hard_key = + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); + if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { + // Key reuse + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateKeyReuseStart; + return command; + } + if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + } else if(dict_attack_ctx->auth_passed || dict_attack_ctx->reuse_success) { + if(dict_attack_ctx->reuse_success) { + furi_assert(dict_attack_ctx->nested_nonce.nonces); + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + dict_attack_ctx->reuse_success = false; + } dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } - dict_attack_ctx->nested_state = MfClassicNestedStateNone; + dict_attack_ctx->auth_passed = true; if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); @@ -1636,7 +1653,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance keys_dict_free(dict_attack_ctx->mf_classic_user_dict); } dict_attack_ctx->nested_target_key = 0; - if(mf_classic_all_keys_collected(instance->data)) { + if(mf_classic_is_card_read(instance->data)) { // TODO: Ensure this works // All keys have been collected, skip to reading blocks FURI_LOG_E(TAG, "All keys collected and sectors read"); @@ -1648,6 +1665,20 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedController; return command; } + // Check if the nested target key is a known key + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : + (dict_attack_ctx->nested_target_key / 16); + if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + instance->state = MfClassicPollerStateNestedController; + return command; + } if(dict_attack_ctx->attempt_count >= 3) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); @@ -1705,13 +1736,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Target all sectors, key A and B, first and second nonce // TODO: Hardnested nonces logic if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { - if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + } else { dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } - dict_attack_ctx->nested_state = MfClassicNestedStateNone; + dict_attack_ctx->auth_passed = true; if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); 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 8ce3e6bfb..233101904 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -38,16 +38,11 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; -typedef enum { - MfClassicNestedStateNone, - MfClassicNestedStateFailed, - MfClassicNestedStatePassed, -} MfClassicNestedState; - typedef enum { MfClassicNestedPhaseNone, MfClassicNestedPhaseAnalyzePRNG, MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseAnalyzeBackdoor, MfClassicNestedPhaseCalibrate, MfClassicNestedPhaseCollectNtEnc, @@ -77,11 +72,6 @@ typedef struct { uint16_t dist; // Distance } MfClassicNestedNonce; -typedef struct { - MfClassicKey* key_candidates; - size_t count; -} MfClassicNestedKeyCandidateArray; - typedef struct { MfClassicNestedNonce* nonces; size_t count; @@ -144,21 +134,23 @@ typedef struct { MfClassicKey current_key; MfClassicKeyType current_key_type; bool auth_passed; + bool reuse_success; uint16_t current_block; uint8_t reuse_key_sector; // Enhanced dictionary attack and nested nonce collection MfClassicNestedPhase nested_phase; + MfClassicKey nested_known_key; + MfClassicKeyType nested_known_key_type; + uint8_t nested_known_key_sector; + uint16_t nested_target_key; + MfClassicNestedNonceArray nested_nonce; MfClassicPrngType prng_type; MfClassicBackdoor backdoor; - uint16_t nested_target_key; - MfClassicNestedKeyCandidateArray nested_key_candidates; - MfClassicNestedNonceArray nested_nonce; bool static_encrypted; bool calibrated; uint16_t d_min; uint16_t d_max; uint8_t attempt_count; - MfClassicNestedState nested_state; KeysDict* mf_classic_system_dict; KeysDict* mf_classic_user_dict; } MfClassicPollerDictAttackContext; From 75a0e4bc9dbfa902b0b867078782f858f89172df Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 20 Aug 2024 16:33:13 -0400 Subject: [PATCH 21/63] Update found keys, second attempt --- .../protocols/mf_classic/mf_classic_poller.c | 68 ++++++++++--------- .../mf_classic/mf_classic_poller_i.h | 1 - 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index ef1af4c56..e0886aff8 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -729,9 +729,6 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - dict_attack_ctx->reuse_success = true; - } mf_classic_set_key_found( instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key); @@ -768,9 +765,6 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - dict_attack_ctx->reuse_success = true; - } mf_classic_set_key_found( instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key); @@ -1613,11 +1607,25 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - if(!(dict_attack_ctx->reuse_success)) { + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : + (dict_attack_ctx->nested_target_key / 16); + if(!(mf_classic_is_key_found(instance->data, target_sector, target_key_type))) { instance->state = MfClassicPollerStateNestedDictAttack; return command; + } else { + dict_attack_ctx->auth_passed = true; + furi_assert(dict_attack_ctx->nested_nonce.nonces); + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } - dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && (dict_attack_ctx->nested_target_key < dict_target_key_max)) { @@ -1633,14 +1641,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->auth_passed || dict_attack_ctx->reuse_success) { - if(dict_attack_ctx->reuse_success) { - furi_assert(dict_attack_ctx->nested_nonce.nonces); - free(dict_attack_ctx->nested_nonce.nonces); - dict_attack_ctx->nested_nonce.nonces = NULL; - dict_attack_ctx->nested_nonce.count = 0; - dict_attack_ctx->reuse_success = false; - } + } else if(dict_attack_ctx->auth_passed) { dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } @@ -1654,8 +1655,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { - // TODO: Ensure this works - // All keys have been collected, skip to reading blocks + // All keys have been collected FURI_LOG_E(TAG, "All keys collected and sectors read"); dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; @@ -1665,19 +1665,21 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedController; return command; } - // Check if the nested target key is a known key - MfClassicKeyType target_key_type = - (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || - ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : - (dict_attack_ctx->nested_target_key / 16); - if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->attempt_count = 0; - instance->state = MfClassicPollerStateNestedController; - return command; + if(dict_attack_ctx->attempt_count == 0) { + // Check if the nested target key is a known key + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : + (dict_attack_ctx->nested_target_key / 16); + if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + instance->state = MfClassicPollerStateNestedController; + return command; + } } if(dict_attack_ctx->attempt_count >= 3) { // Unpredictable, skip @@ -1757,8 +1759,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; } - // TODO: If we've recovered all keys, read blocks and go to complete - instance->state = MfClassicPollerStateKeyReuseStart; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; + instance->state = MfClassicPollerStateSuccess; return command; } 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 233101904..d9d1d31cc 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -134,7 +134,6 @@ typedef struct { MfClassicKey current_key; MfClassicKeyType current_key_type; bool auth_passed; - bool reuse_success; uint16_t current_block; uint8_t reuse_key_sector; // Enhanced dictionary attack and nested nonce collection From c1f01ce66af9f09c4a2c7b7fb84956157c8cd1ce Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 20 Aug 2024 18:04:07 -0400 Subject: [PATCH 22/63] Code cleanup --- .../protocols/mf_classic/mf_classic_poller.c | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index e0886aff8..8a874b086 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1574,6 +1574,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_known_key_sector = dict_attack_ctx->reuse_key_sector; dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } + bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { instance->state = MfClassicPollerStateNestedCollectNt; @@ -1601,13 +1602,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_nonce.count = 0; } dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + initial_dict_attack_iter = true; } - // Accelerated Nested dictionary attack + // Accelerated nested dictionary attack + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; MfClassicKeyType target_key_type = (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? @@ -1629,9 +1631,16 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && (dict_attack_ctx->nested_target_key < dict_target_key_max)) { - bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; bool is_last_iter_for_hard_key = ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); + if(initial_dict_attack_iter) { + uint8_t nested_target_key_offset = + (dict_attack_ctx->current_key_type == MfClassicKeyTypeA) ? 0 : 1; + dict_attack_ctx->nested_target_key = + (is_weak) ? + ((dict_attack_ctx->current_sector * 2) + nested_target_key_offset) : + ((dict_attack_ctx->current_sector * 16) + (nested_target_key_offset * 8)); + } if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { // Key reuse dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; @@ -1641,7 +1650,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->auth_passed) { + } else if(dict_attack_ctx->auth_passed && !(initial_dict_attack_iter)) { dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } @@ -1725,39 +1734,37 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } } - // Log collected nonces + // Collect and log nonces if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - if(((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (dict_attack_ctx->nested_nonce.count == 2)) || - ((dict_attack_ctx->prng_type == MfClassicPrngTypeHard) && - (dict_attack_ctx->nested_nonce.count > 0))) { + if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || + ((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) { instance->state = MfClassicPollerStateNestedLog; return command; } - } - // Target all sectors, key A and B, first and second nonce - // TODO: Hardnested nonces logic - if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { - if(!(dict_attack_ctx->auth_passed)) { - dict_attack_ctx->attempt_count++; - } else { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->attempt_count = 0; - } - dict_attack_ctx->auth_passed = true; - if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { - // Unpredictable, skip - FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); - if(dict_attack_ctx->nested_nonce.nonces) { - free(dict_attack_ctx->nested_nonce.nonces); - dict_attack_ctx->nested_nonce.nonces = NULL; - dict_attack_ctx->nested_nonce.count = 0; + // Target all sectors, key A and B, first and second nonce + // TODO: Hardnested nonces logic + if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { + if(!(dict_attack_ctx->auth_passed)) { + dict_attack_ctx->attempt_count++; + } else { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; } - dict_attack_ctx->nested_target_key += 2; - dict_attack_ctx->attempt_count = 0; + dict_attack_ctx->auth_passed = true; + if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { + // Unpredictable, skip + FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + dict_attack_ctx->nested_target_key += 2; + dict_attack_ctx->attempt_count = 0; + } + instance->state = MfClassicPollerStateNestedCollectNtEnc; + return command; } - instance->state = MfClassicPollerStateNestedCollectNtEnc; - return command; } dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; From 26845cbdc5b4a7844495b48e130bd4313b915c08 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 21 Aug 2024 00:50:27 -0400 Subject: [PATCH 23/63] Misc bugfixes --- .../protocols/mf_classic/mf_classic_poller.c | 85 ++++++++++++------- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 8a874b086..025046e35 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -711,6 +711,22 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) return command; } +NfcCommand mf_classic_poller_handler_key_reuse_start_no_offset(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + 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); + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + } else { + instance->state = MfClassicPollerStateKeyReuseAuthKeyB; + } + + return command; +} + NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -1324,11 +1340,12 @@ static MfClassicKey* search_dicts_for_nonce_key( for(int i = 0; i < 2; i++) { keys_dict_rewind(dicts[i]); while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { - if(is_resumed && !(found_resume_point) && - memcmp(dict_attack_ctx->current_key.data, stack_key.data, sizeof(MfClassicKey)) == - 0) { - found_resume_point = true; - } else { + if(is_resumed && !found_resume_point) { + found_resume_point = + (memcmp( + dict_attack_ctx->current_key.data, + stack_key.data, + sizeof(MfClassicKey)) == 0); continue; } bool full_match = true; @@ -1458,6 +1475,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); dict_attack_ctx->current_key = *key_candidate; dict_attack_ctx->reuse_key_sector = (target_block / 4); + dict_attack_ctx->current_key_type = target_key_type; free(key_candidate); break; } else { @@ -1570,8 +1588,15 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { dict_attack_ctx->auth_passed = true; dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; - dict_attack_ctx->nested_known_key_type = dict_attack_ctx->current_key_type; - dict_attack_ctx->nested_known_key_sector = dict_attack_ctx->reuse_key_sector; + for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { + for(uint8_t key_type = 0; key_type < 2; key_type++) { + if(mf_classic_is_key_found(instance->data, sector, key_type)) { + dict_attack_ctx->nested_known_key_sector = sector; + dict_attack_ctx->nested_known_key_type = key_type; + break; + } + } + } dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } bool initial_dict_attack_iter = false; @@ -1634,18 +1659,28 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance bool is_last_iter_for_hard_key = ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); if(initial_dict_attack_iter) { - uint8_t nested_target_key_offset = - (dict_attack_ctx->current_key_type == MfClassicKeyTypeA) ? 0 : 1; - dict_attack_ctx->nested_target_key = - (is_weak) ? - ((dict_attack_ctx->current_sector * 2) + nested_target_key_offset) : - ((dict_attack_ctx->current_sector * 16) + (nested_target_key_offset * 8)); + // Initialize dictionaries + // Note: System dict should always exist + bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); + bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); + if(system_dict_exists) { + dict_attack_ctx->mf_classic_system_dict = keys_dict_alloc( + MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + } + if(user_dict_exists) { + dict_attack_ctx->mf_classic_user_dict = keys_dict_alloc( + MF_CLASSIC_NESTED_USER_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + } } if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { // Key reuse dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; dict_attack_ctx->auth_passed = false; - instance->state = MfClassicPollerStateKeyReuseStart; + instance->state = MfClassicPollerStateKeyReuseStartNoOffset; return command; } if(!(dict_attack_ctx->auth_passed)) { @@ -1684,8 +1719,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : (dict_attack_ctx->nested_target_key / 16); if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->attempt_count = 0; + // Continue to next key instance->state = MfClassicPollerStateNestedController; return command; } @@ -1696,23 +1730,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } - if(dict_attack_ctx->nested_target_key == 0) { - // Note: System dict should always exist - bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); - bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); - if(system_dict_exists) { - dict_attack_ctx->mf_classic_system_dict = keys_dict_alloc( - MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); - } - if(user_dict_exists) { - dict_attack_ctx->mf_classic_user_dict = keys_dict_alloc( - MF_CLASSIC_NESTED_USER_DICT_PATH, - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); - } - } instance->state = MfClassicPollerStateNestedDictAttack; return command; } @@ -1807,6 +1824,8 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateAuthKeyB] = mf_classic_poller_handler_auth_b, [MfClassicPollerStateReadSector] = mf_classic_poller_handler_read_sector, [MfClassicPollerStateKeyReuseStart] = mf_classic_poller_handler_key_reuse_start, + [MfClassicPollerStateKeyReuseStartNoOffset] = + mf_classic_poller_handler_key_reuse_start_no_offset, [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, 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 d9d1d31cc..ab6426b38 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -99,6 +99,7 @@ typedef enum { MfClassicPollerStateAuthKeyA, MfClassicPollerStateAuthKeyB, MfClassicPollerStateKeyReuseStart, + MfClassicPollerStateKeyReuseStartNoOffset, MfClassicPollerStateKeyReuseAuthKeyA, MfClassicPollerStateKeyReuseAuthKeyB, MfClassicPollerStateKeyReuseReadSector, From 523559205487cf86de5a280d1651c66361965662 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 27 Aug 2024 06:32:00 -0400 Subject: [PATCH 24/63] Only use dicts in search_dicts_for_nonce_key if we have them --- .../protocols/mf_classic/mf_classic_poller.c | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 025046e35..c670ed9ed 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1338,6 +1338,7 @@ static MfClassicKey* search_dicts_for_nonce_key( bool found_resume_point = false; for(int i = 0; i < 2; i++) { + if(!dicts[i]) continue; keys_dict_rewind(dicts[i]); while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { if(is_resumed && !found_resume_point) { @@ -1661,20 +1662,21 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(initial_dict_attack_iter) { // Initialize dictionaries // Note: System dict should always exist - bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); - bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); - if(system_dict_exists) { - dict_attack_ctx->mf_classic_system_dict = keys_dict_alloc( - MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); - } - if(user_dict_exists) { - dict_attack_ctx->mf_classic_user_dict = keys_dict_alloc( - MF_CLASSIC_NESTED_USER_DICT_PATH, - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); - } + dict_attack_ctx->mf_classic_system_dict = + keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH) ? + keys_dict_alloc( + MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)) : + NULL; + + dict_attack_ctx->mf_classic_user_dict = + keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH) ? + keys_dict_alloc( + MF_CLASSIC_NESTED_USER_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)) : + NULL; } if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { // Key reuse From 0b33c85b8b6752dc4913cc43b96e358f659ab799 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 27 Aug 2024 16:16:38 -0400 Subject: [PATCH 25/63] Collect nonces again --- .../protocols/mf_classic/mf_classic_poller.c | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index c670ed9ed..8b690cf1b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1582,6 +1582,22 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { return command; } +bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance) { + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : + (dict_attack_ctx->nested_target_key / 16); + if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { + return true; + } + return false; +} + NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys NfcCommand command = NfcCommandContinue; @@ -1636,14 +1652,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - MfClassicKeyType target_key_type = - (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || - ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : - (dict_attack_ctx->nested_target_key / 16); - if(!(mf_classic_is_key_found(instance->data, target_sector, target_key_type))) { + if(!(mf_classic_nested_is_target_key_found(instance))) { instance->state = MfClassicPollerStateNestedDictAttack; return command; } else { @@ -1707,20 +1716,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateSuccess; return command; } - dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; instance->state = MfClassicPollerStateNestedController; return command; } if(dict_attack_ctx->attempt_count == 0) { // Check if the nested target key is a known key - MfClassicKeyType target_key_type = - (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || - ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : - (dict_attack_ctx->nested_target_key / 16); - if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { + if(mf_classic_nested_is_target_key_found(instance)) { // Continue to next key instance->state = MfClassicPollerStateNestedController; return command; @@ -1737,21 +1739,22 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Analyze tag for NXP/Fudan backdoor if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { - dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzeBackdoor) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; } // TODO: Need to think about how this works for NXP/Fudan backdoored tags. // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. // Calibration - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { - if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (!dict_attack_ctx->calibrated)) { + if(!(dict_attack_ctx->calibrated)) { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedCalibrate; return command; - } else { - dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { @@ -1760,9 +1763,9 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedLog; return command; } - // Target all sectors, key A and B, first and second nonce - // TODO: Hardnested nonces logic - if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { + // Target all remaining sectors, key A and B, first and second nonce + // TODO: Hardnested nonces logic, will likely be similar to dict attack has dict_target_key_max + if(is_weak && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 4))) { if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; } else { @@ -1770,6 +1773,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; + if(dict_attack_ctx->attempt_count == 0) { + // Check if the nested target key is a known key + if(mf_classic_nested_is_target_key_found(instance)) { + // Continue to next key + instance->state = MfClassicPollerStateNestedController; + return command; + } + } if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); From c0331ba2e262bf1bedd9714a0133835d5d3f03b5 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 27 Aug 2024 19:35:33 -0400 Subject: [PATCH 26/63] Should be detecting both backdoors now --- .../protocols/mf_classic/mf_classic_poller.c | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 8b690cf1b..04d92b401 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -916,7 +916,14 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in MfClassicAuthContext auth_ctx = {}; MfClassicNt nt = {}; - MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; + MfClassicKey auth_backdoor_key; + if(dict_attack_ctx->nested_target_key == 1) { + auth_backdoor_key = (MfClassicKey){ + .data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; // auth2 backdoor key, more common + } else { + auth_backdoor_key = + (MfClassicKey){.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; // auth1 backdoor key + } MfClassicError error; Iso14443_3aError iso_error; bool backdoor_found = false; @@ -961,13 +968,17 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in bit_buffer_write_bytes(instance->rx_plain_buffer, nt.data, sizeof(MfClassicNt)); uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); // Ensure the encrypted nt can be generated by the backdoor - uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, auth2_backdoor_key); + uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, auth_backdoor_key); backdoor_found = is_weak_prng_nonce(decrypted_nt_enc); } while(false); if(backdoor_found) { FURI_LOG_E(TAG, "Backdoor identified"); - dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; - } else { + if(dict_attack_ctx->nested_target_key == 1) { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; + } else { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; + } + } else if(dict_attack_ctx->nested_target_key == 0) { dict_attack_ctx->backdoor = MfClassicBackdoorNone; } instance->state = MfClassicPollerStateNestedController; @@ -1708,14 +1719,15 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); } - dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { // All keys have been collected FURI_LOG_E(TAG, "All keys collected and sectors read"); + dict_attack_ctx->nested_target_key = 0; dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; return command; } + dict_attack_ctx->nested_target_key = 2; // Backdoor keys dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; instance->state = MfClassicPollerStateNestedController; return command; @@ -1739,9 +1751,11 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Analyze tag for NXP/Fudan backdoor if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { + dict_attack_ctx->nested_target_key--; instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzeBackdoor) { + dict_attack_ctx->nested_target_key = 0; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; } // TODO: Need to think about how this works for NXP/Fudan backdoored tags. From 4c14594ebb6cfdee1a72f13fca7198842d84d1c5 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 28 Aug 2024 09:26:59 -0400 Subject: [PATCH 27/63] Relocate backdoor detection --- .../debug/unit_tests/tests/nfc/nfc_test.c | 2 +- .../protocols/mf_classic/mf_classic_poller.c | 196 +++++++----------- .../protocols/mf_classic/mf_classic_poller.h | 16 +- .../mf_classic/mf_classic_poller_i.c | 41 ++-- .../mf_classic/mf_classic_poller_i.h | 8 +- .../mf_classic/mf_classic_poller_sync.c | 18 +- targets/f7/api_symbols.csv | 10 +- 7 files changed, 144 insertions(+), 147 deletions(-) diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index 0898ac8ed..4ba934b6d 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -496,7 +496,7 @@ NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* MfClassicKey key = { .data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }; - error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL); + error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL, false); frame_test->state = (error == MfClassicErrorNone) ? NfcTestMfClassicSendFrameTestStateReadBlock : NfcTestMfClassicSendFrameTestStateFail; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 04d92b401..3e4fa35aa 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -8,6 +8,9 @@ #define MF_CLASSIC_MAX_BUFF_SIZE (64) +const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; +const MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; + typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance); MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { @@ -86,7 +89,8 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { iso14443_3a_copy( instance->data->iso14443_3a_data, iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); - MfClassicError error = mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL); + MfClassicError error = + mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType4k; instance->state = MfClassicPollerStateStart; @@ -96,7 +100,8 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { instance->current_type_check = MfClassicType1k; } } else if(instance->current_type_check == MfClassicType1k) { - MfClassicError error = mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL); + MfClassicError error = + mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType1k; FURI_LOG_D(TAG, "1K detected"); @@ -122,7 +127,7 @@ NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) { mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); - instance->state = MfClassicPollerStateRequestKey; + instance->state = MfClassicPollerStateAnalyzeBackdoor; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) { instance->state = MfClassicPollerStateRequestReadSector; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeWrite) { @@ -236,7 +241,7 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { do { // Authenticate to sector error = mf_classic_poller_auth( - instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL); + instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL, false); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block); instance->state = MfClassicPollerStateFail; @@ -294,7 +299,12 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { // Reauth if necessary if(write_ctx->need_halt_before_write) { error = mf_classic_poller_auth( - instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL); + instance, + write_ctx->current_block, + auth_key, + write_ctx->key_type_write, + NULL, + false); if(error != MfClassicErrorNone) { FURI_LOG_D( TAG, "Failed to auth to block %d for writing", write_ctx->current_block); @@ -403,8 +413,8 @@ NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance MfClassicKey* key = (auth_key_type == MfClassicKeyTypeA) ? &write_ctx->sec_tr.key_a : &write_ctx->sec_tr.key_b; - MfClassicError error = - mf_classic_poller_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); + MfClassicError error = mf_classic_poller_auth( + instance, write_ctx->current_block, key, auth_key_type, NULL, false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_value_cmd(instance, write_ctx->current_block, value_cmd, diff); @@ -468,7 +478,8 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* sec_read_ctx->current_block, &sec_read_ctx->key, sec_read_ctx->key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; sec_read_ctx->auth_passed = true; @@ -505,6 +516,42 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* return command; } +NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + bool current_key_is_auth1 = + memcmp(dict_attack_ctx->current_key.data, auth1_backdoor_key.data, sizeof(MfClassicKey)) == + 0; + bool current_key_is_auth2 = + memcmp(dict_attack_ctx->current_key.data, auth2_backdoor_key.data, sizeof(MfClassicKey)) == + 0; + + if(!current_key_is_auth1) { + dict_attack_ctx->current_key = auth2_backdoor_key; + } else if(current_key_is_auth2) { + dict_attack_ctx->current_key = auth1_backdoor_key; + } + + // Attempt backdoor authentication + MfClassicError error = mf_classic_poller_auth( + instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); + bool backdoor_found = (error == MfClassicErrorNone) ? true : false; + + if(backdoor_found) { + FURI_LOG_E(TAG, "Backdoor identified"); + if(!current_key_is_auth1) { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; + } else { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; + } + instance->state = MfClassicPollerStateRequestKey; + } else if(current_key_is_auth2) { + dict_attack_ctx->backdoor = MfClassicBackdoorNone; + instance->state = MfClassicPollerStateRequestKey; + } + return command; +} + NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -535,7 +582,7 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); mf_classic_set_key_found( @@ -574,7 +621,7 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); mf_classic_set_key_found( @@ -629,7 +676,8 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { block_num, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) { instance->state = MfClassicPollerStateNextSector; FURI_LOG_W(TAG, "Failed to re-auth. Go to next sector"); @@ -742,7 +790,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); mf_classic_set_key_found( @@ -778,7 +826,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); mf_classic_set_key_found( @@ -817,7 +865,8 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst block_num, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) { instance->state = MfClassicPollerStateKeyReuseStart; break; @@ -905,92 +954,12 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan return command; } -NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) { - // Can use on more than S variant as a free key for Nested - NfcCommand command = NfcCommandReset; - MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - - uint8_t block = - mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); - uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); - - MfClassicAuthContext auth_ctx = {}; - MfClassicNt nt = {}; - MfClassicKey auth_backdoor_key; - if(dict_attack_ctx->nested_target_key == 1) { - auth_backdoor_key = (MfClassicKey){ - .data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; // auth2 backdoor key, more common - } else { - auth_backdoor_key = - (MfClassicKey){.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; // auth1 backdoor key - } - MfClassicError error; - Iso14443_3aError iso_error; - bool backdoor_found = false; - - do { - // Step 1: Perform full authentication once - error = mf_classic_poller_auth( - instance, - block, - &dict_attack_ctx->nested_known_key, - dict_attack_ctx->nested_known_key_type, - &auth_ctx); - - if(error != MfClassicErrorNone) { - FURI_LOG_E(TAG, "Failed to perform full authentication"); - break; - } - - FURI_LOG_E(TAG, "Full authentication successful"); - - // Step 2: Attempt backdoor authentication - uint8_t auth_type = (dict_attack_ctx->nested_known_key_type == MfClassicKeyTypeB) ? - MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : - MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; - uint8_t auth_cmd[2] = {auth_type, block}; - bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); - iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); - crypto1_encrypt( - instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); - iso_error = iso14443_3a_poller_txrx_custom_parity( - instance->iso14443_3a_poller, - instance->tx_encrypted_buffer, - instance->rx_plain_buffer, - MF_CLASSIC_FWT_FC); - if(iso_error != Iso14443_3aErrorNone) { - FURI_LOG_E(TAG, "Error during nested authentication"); - break; - } - if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { - break; - } - bit_buffer_write_bytes(instance->rx_plain_buffer, nt.data, sizeof(MfClassicNt)); - uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); - // Ensure the encrypted nt can be generated by the backdoor - uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, auth_backdoor_key); - backdoor_found = is_weak_prng_nonce(decrypted_nt_enc); - } while(false); - if(backdoor_found) { - FURI_LOG_E(TAG, "Backdoor identified"); - if(dict_attack_ctx->nested_target_key == 1) { - dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; - } else { - dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; - } - } else if(dict_attack_ctx->nested_target_key == 0) { - dict_attack_ctx->backdoor = MfClassicBackdoorNone; - } - instance->state = MfClassicPollerStateNestedController; - 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); + MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt, false); if(error != MfClassicErrorNone) { dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; @@ -1021,8 +990,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // TODO: Check if we have already identified the tag as static encrypted NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - uint8_t nt_enc_calibration_cnt = 21; - uint32_t nt_enc_temp_arr[nt_enc_calibration_cnt]; + uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; dict_attack_ctx->d_min = UINT16_MAX; dict_attack_ctx->d_max = 0; @@ -1044,7 +1012,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) block, &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1057,7 +1026,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); // Step 2: Perform nested authentication multiple times - for(uint8_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; + for(uint8_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { error = mf_classic_poller_auth_nested( instance, @@ -1065,6 +1034,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, + false, false); if(error != MfClassicErrorNone) { @@ -1108,7 +1078,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); - for(uint32_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; + for(uint32_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc( @@ -1220,7 +1190,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst block, &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1242,6 +1213,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, + false, false); if(error != MfClassicErrorNone) { @@ -1259,6 +1231,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, target_key_type, &auth_ctx, + false, true); if(nt_enc_collected != (nt_enc_per_collection - 1)) { @@ -1422,7 +1395,8 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc block, &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1439,6 +1413,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc &dict_attack_ctx->nested_known_key, target_key_type, &auth_ctx, + false, true); if(error != MfClassicErrorNone) { @@ -1719,16 +1694,15 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); } + dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { // All keys have been collected FURI_LOG_E(TAG, "All keys collected and sectors read"); - dict_attack_ctx->nested_target_key = 0; dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; return command; } - dict_attack_ctx->nested_target_key = 2; // Backdoor keys - dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; instance->state = MfClassicPollerStateNestedController; return command; } @@ -1749,15 +1723,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedDictAttack; return command; } - // Analyze tag for NXP/Fudan backdoor - if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { - dict_attack_ctx->nested_target_key--; - instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; - return command; - } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzeBackdoor) { - dict_attack_ctx->nested_target_key = 0; - dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; - } // TODO: Need to think about how this works for NXP/Fudan backdoored tags. // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. // Calibration @@ -1843,6 +1808,7 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateWriteBlock] = mf_classic_poller_handler_write_block, [MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block, [MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector, + [MfClassicPollerStateAnalyzeBackdoor] = mf_classic_poller_handler_analyze_backdoor, [MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key, [MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector, [MfClassicPollerStateReadSectorBlocks] = @@ -1857,8 +1823,6 @@ static const MfClassicPollerReadHandler [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, [MfClassicPollerStateNestedCalibrate] = mf_classic_poller_handler_nested_calibrate, [MfClassicPollerStateNestedCollectNt] = mf_classic_poller_handler_nested_collect_nt, [MfClassicPollerStateNestedController] = mf_classic_poller_handler_nested_controller, diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 043424989..b26378d33 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -170,13 +170,15 @@ typedef struct { * @param[in] block_num block number for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_get_nt( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt); + MfClassicNt* nt, + bool backdoor_auth); /** * @brief Collect tag nonce during nested authentication. @@ -189,13 +191,15 @@ MfClassicError mf_classic_poller_get_nt( * @param[in] block_num block number for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_get_nt_nested( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt); + MfClassicNt* nt, + bool backdoor_auth); /** * @brief Perform authentication. @@ -210,6 +214,7 @@ MfClassicError mf_classic_poller_get_nt_nested( * @param[in] key key to be used for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_auth( @@ -217,20 +222,22 @@ MfClassicError mf_classic_poller_auth( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data); + MfClassicAuthContext* data, + bool backdoor_auth); /** * @brief Perform nested authentication. * * Must ONLY be used inside the callback function. * - * Perform nested authentication as specified in Mf Classic protocol. + * Perform nested authentication as specified in Mf Classic protocol. * * @param[in, out] instance pointer to the instance to be used in the transaction. * @param[in] block_num block number for authentication. * @param[in] key key to be used for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @param[in] early_ret return immediately after receiving encrypted nonce. * @return MfClassicErrorNone on success, an error code on failure. */ @@ -240,6 +247,7 @@ MfClassicError mf_classic_poller_auth_nested( MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, + bool backdoor_auth, bool early_ret); /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index d3a3882c1..aac05748f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -38,13 +38,20 @@ static MfClassicError mf_classic_poller_get_nt_common( uint8_t block_num, MfClassicKeyType key_type, MfClassicNt* nt, - bool is_nested) { + bool is_nested, + bool backdoor_auth) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; do { - uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : - MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_type; + if(!backdoor_auth) { + auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + } else { + auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : + MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; + } uint8_t auth_cmd[2] = {auth_type, block_num}; bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); @@ -89,29 +96,33 @@ MfClassicError mf_classic_poller_get_nt( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt) { + MfClassicNt* nt, + bool backdoor_auth) { furi_check(instance); - return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, false); + return mf_classic_poller_get_nt_common( + instance, block_num, key_type, nt, false, backdoor_auth); } MfClassicError mf_classic_poller_get_nt_nested( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt) { + MfClassicNt* nt, + bool backdoor_auth) { furi_check(instance); - return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, true); + return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, true, backdoor_auth); } -static MfClassicError mf_classic_poller_auth_common( +MfClassicError mf_classic_poller_auth_common( MfClassicPoller* instance, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, bool is_nested, + bool backdoor_auth, bool early_ret) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -123,9 +134,10 @@ static MfClassicError mf_classic_poller_auth_common( MfClassicNt nt = {}; if(is_nested) { - ret = mf_classic_poller_get_nt_nested(instance, block_num, key_type, &nt); + ret = + mf_classic_poller_get_nt_nested(instance, block_num, key_type, &nt, backdoor_auth); } else { - ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); + ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt, backdoor_auth); } if(ret != MfClassicErrorNone) break; if(data) { @@ -184,10 +196,12 @@ MfClassicError mf_classic_poller_auth( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data) { + MfClassicAuthContext* data, + bool backdoor_auth) { furi_check(instance); furi_check(key); - return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false, false); + return mf_classic_poller_auth_common( + instance, block_num, key, key_type, data, false, backdoor_auth, false); } MfClassicError mf_classic_poller_auth_nested( @@ -196,11 +210,12 @@ MfClassicError mf_classic_poller_auth_nested( MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, + bool backdoor_auth, bool early_ret) { furi_check(instance); furi_check(key); return mf_classic_poller_auth_common( - instance, block_num, key, key_type, data, true, early_ret); + instance, block_num, key, key_type, data, true, backdoor_auth, early_ret); } MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { 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 ab6426b38..df4f9b0f5 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -19,6 +19,7 @@ extern "C" { #define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) #define MF_CLASSIC_NESTED_HARD_MINIMUM (3) #define MF_CLASSIC_NESTED_RETRY_MAXIMUM (20) +#define MF_CLASSIC_NESTED_CALIBRATION_COUNT (21) #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" #define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" #define MF_CLASSIC_NESTED_USER_DICT_FILE_NAME "mf_classic_dict_user_nested.nfc" @@ -28,6 +29,9 @@ extern "C" { #define MF_CLASSIC_NESTED_USER_DICT_PATH \ (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_USER_DICT_FILE_NAME) +extern const MfClassicKey auth1_backdoor_key; +extern const MfClassicKey auth2_backdoor_key; + typedef enum { MfClassicAuthStateIdle, MfClassicAuthStatePassed, @@ -94,6 +98,7 @@ typedef enum { // Dict attack states MfClassicPollerStateNextSector, + MfClassicPollerStateAnalyzeBackdoor, MfClassicPollerStateRequestKey, MfClassicPollerStateReadSector, MfClassicPollerStateAuthKeyA, @@ -108,7 +113,6 @@ typedef enum { // Enhanced dictionary attack states MfClassicPollerStateNestedAnalyzePRNG, - MfClassicPollerStateNestedAnalyzeBackdoor, MfClassicPollerStateNestedCalibrate, MfClassicPollerStateNestedCollectNt, MfClassicPollerStateNestedController, @@ -137,6 +141,7 @@ typedef struct { bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; + MfClassicBackdoor backdoor; // Enhanced dictionary attack and nested nonce collection MfClassicNestedPhase nested_phase; MfClassicKey nested_known_key; @@ -145,7 +150,6 @@ typedef struct { uint16_t nested_target_key; MfClassicNestedNonceArray nested_nonce; MfClassicPrngType prng_type; - MfClassicBackdoor backdoor; bool static_encrypted; bool calibrated; uint16_t d_min; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c index cc6bc0908..13b51786f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c @@ -37,7 +37,8 @@ static MfClassicError mf_classic_poller_collect_nt_handler( poller, data->collect_nt_context.block, data->collect_nt_context.key_type, - &data->collect_nt_context.nt); + &data->collect_nt_context.nt, + false); } static MfClassicError @@ -47,7 +48,8 @@ static MfClassicError data->auth_context.block_num, &data->auth_context.key, data->auth_context.key_type, - &data->auth_context); + &data->auth_context, + false); } static MfClassicError mf_classic_poller_read_block_handler( @@ -61,7 +63,8 @@ static MfClassicError mf_classic_poller_read_block_handler( data->read_block_context.block_num, &data->read_block_context.key, data->read_block_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_read_block( @@ -87,7 +90,8 @@ static MfClassicError mf_classic_poller_write_block_handler( data->read_block_context.block_num, &data->read_block_context.key, data->read_block_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_write_block( @@ -113,7 +117,8 @@ static MfClassicError mf_classic_poller_read_value_handler( data->read_value_context.block_num, &data->read_value_context.key, data->read_value_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; MfClassicBlock block = {}; @@ -144,7 +149,8 @@ static MfClassicError mf_classic_poller_change_value_handler( data->change_value_context.block_num, &data->change_value_context.key, data->change_value_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_value_cmd( diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 829dcf5bf..c8e89f96e 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,73.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -2515,10 +2515,10 @@ Function,+,mf_classic_is_sector_read,_Bool,"const MfClassicData*, uint8_t" Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" -Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" -Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool" -Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" -Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool" +Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool, _Bool" +Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*, _Bool" +Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*, _Bool" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" Function,+,mf_classic_poller_send_custom_parity_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" From 90d0c3d095138b267d86bccccb57af3be4178452 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 1 Sep 2024 22:30:37 -0400 Subject: [PATCH 28/63] Hardnested support --- .../protocols/mf_classic/mf_classic_poller.c | 179 +++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 12 +- 2 files changed, 140 insertions(+), 51 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3e4fa35aa..c99e54dc1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -10,6 +10,8 @@ const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; const MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; +const uint16_t valid_sums[] = + {0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256}; typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance); @@ -940,7 +942,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan if(!is_weak_prng_nonce(nonce->nt)) hard_nt_count++; } - if(hard_nt_count >= MF_CLASSIC_NESTED_HARD_MINIMUM) { + if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) { dict_attack_ctx->prng_type = MfClassicPrngTypeHard; // FIXME: E -> D FURI_LOG_E(TAG, "Detected Hard PRNG"); @@ -1128,6 +1130,14 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) return command; } +static inline void set_byte_found(uint8_t* found, uint8_t byte) { + SET_PACKED_BIT(found, byte); +} + +static inline bool is_byte_found(uint8_t* found, uint8_t byte) { + return GET_PACKED_BIT(found, byte) != 0; +} + NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) // TODO: Look into using MfClassicNt more @@ -1135,13 +1145,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { - FURI_LOG_E(TAG, "Hard PRNG, skipping"); - // 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; - } - if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { @@ -1173,8 +1176,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; - uint8_t nt_enc_per_collection = (dict_attack_ctx->attempt_count + 2) + nonce_pair_index; + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0; + uint8_t nt_enc_per_collection = + is_weak ? ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : 1; MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; @@ -1245,29 +1250,41 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst for(int i = 0; i < 4; i++) { parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); } - // Decrypt the previous nonce - uint32_t nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - uint32_t decrypted_nt_prev = - decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); - // Find matching nt_enc plain at expected distance - uint32_t found_nt = 0; - uint8_t found_nt_cnt = 0; - uint16_t current_dist = dict_attack_ctx->d_min; - while(current_dist <= dict_attack_ctx->d_max) { - uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); - if(nonce_matches_encrypted_parity_bits(nth_successor, nth_successor ^ nt_enc, parity)) { - found_nt_cnt++; - if(found_nt_cnt > 1) { - FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt"); - break; + uint32_t nt_prev = 0, decrypted_nt_prev = 0, found_nt = 0; + if(is_weak) { + // Decrypt the previous nonce + nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; + decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); + + // Find matching nt_enc plain at expected distance + found_nt = 0; + uint8_t found_nt_cnt = 0; + uint16_t current_dist = dict_attack_ctx->d_min; + while(current_dist <= dict_attack_ctx->d_max) { + uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); + if(nonce_matches_encrypted_parity_bits( + nth_successor, nth_successor ^ nt_enc, parity)) { + found_nt_cnt++; + if(found_nt_cnt > 1) { + FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt"); + break; + } + found_nt = nth_successor; } - found_nt = nth_successor; + current_dist++; } - current_dist++; - } - if(found_nt_cnt != 1) { - break; + if(found_nt_cnt != 1) { + break; + } + } else { + // Hardnested + if(!is_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF)) { + set_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF); + // Add unique parity to sum + dict_attack_ctx->msb_par_sum += nfc_util_even_parity32(parity & 0x08); + } + parity ^= 0x0F; } // Add the nonce to the array @@ -1568,18 +1585,45 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { return command; } -bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance) { +bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) { MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; - MfClassicKeyType target_key_type = - (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || - ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : - (dict_attack_ctx->nested_target_key / 16); - if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { - return true; + uint8_t nested_target_key = dict_attack_ctx->nested_target_key; + + MfClassicKeyType target_key_type; + uint8_t target_sector; + + if(is_dict_attack) { + target_key_type = (((is_weak) && ((nested_target_key % 2) == 0)) || + ((!is_weak) && ((nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + target_sector = is_weak ? (nested_target_key / 2) : (nested_target_key / 16); + } else { + target_key_type = (((is_weak) && ((nested_target_key % 4) < 2)) || + ((!is_weak) && ((nested_target_key % 2) == 0))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + target_sector = is_weak ? (nested_target_key / 4) : (nested_target_key / 2); + } + + return mf_classic_is_key_found(instance->data, target_sector, target_key_type); +} + +bool found_all_nt_enc_msb(const MfClassicPollerDictAttackContext* dict_attack_ctx) { + for(int i = 0; i < 32; i++) { + if(dict_attack_ctx->nt_enc_msb[i] != 0xFF) { + return false; + } + } + return true; +} + +bool is_valid_sum(uint16_t sum) { + for(size_t i = 0; i < 19; i++) { + if(sum == valid_sums[i]) { + return true; + } } return false; } @@ -1638,7 +1682,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - if(!(mf_classic_nested_is_target_key_found(instance))) { + if(!(mf_classic_nested_is_target_key_found(instance, true))) { instance->state = MfClassicPollerStateNestedDictAttack; return command; } else { @@ -1708,7 +1752,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if(dict_attack_ctx->attempt_count == 0) { // Check if the nested target key is a known key - if(mf_classic_nested_is_target_key_found(instance)) { + if(mf_classic_nested_is_target_key_found(instance, true)) { // Continue to next key instance->state = MfClassicPollerStateNestedController; return command; @@ -1742,25 +1786,52 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedLog; return command; } - // Target all remaining sectors, key A and B, first and second nonce - // TODO: Hardnested nonces logic, will likely be similar to dict attack has dict_target_key_max - if(is_weak && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 4))) { + // Target all remaining sectors, key A and B + if((is_weak && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 4))) || + (!(is_weak) && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 2)))) { + if((!(is_weak)) && found_all_nt_enc_msb(dict_attack_ctx)) { + if(is_valid_sum(dict_attack_ctx->msb_par_sum)) { + // All Hardnested nonces collected + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + instance->state = MfClassicPollerStateNestedController; + } else { + // Nonces do not match an expected sum + dict_attack_ctx->attempt_count++; + instance->state = MfClassicPollerStateNestedCollectNtEnc; + } + dict_attack_ctx->msb_par_sum = 0; + memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); + return command; + } if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; } else { - dict_attack_ctx->nested_target_key++; + if(is_weak) { + dict_attack_ctx->nested_target_key++; + if(dict_attack_ctx->nested_target_key % 2 == 0) { + dict_attack_ctx->current_key_checked = false; + } + } dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; - if(dict_attack_ctx->attempt_count == 0) { + if(!(dict_attack_ctx->current_key_checked)) { // Check if the nested target key is a known key - if(mf_classic_nested_is_target_key_found(instance)) { + if(mf_classic_nested_is_target_key_found(instance, false)) { // Continue to next key + if(!(is_weak)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + } instance->state = MfClassicPollerStateNestedController; return command; } + dict_attack_ctx->current_key_checked = true; } - if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { + if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) || + (!(is_weak) && + (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); if(dict_attack_ctx->nested_nonce.nonces) { @@ -1768,7 +1839,15 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; } - dict_attack_ctx->nested_target_key += 2; + if(is_weak) { + dict_attack_ctx->nested_target_key += 2; + dict_attack_ctx->current_key_checked = false; + } else { + dict_attack_ctx->msb_par_sum = 0; + memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + } dict_attack_ctx->attempt_count = 0; } instance->state = MfClassicPollerStateNestedCollectNtEnc; 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 df4f9b0f5..9e4cfd728 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -8,6 +8,7 @@ #include #include #include "keys_dict.h" +#include "helpers/nfc_util.h" #ifdef __cplusplus extern "C" { @@ -17,8 +18,9 @@ extern "C" { #define NFC_FOLDER EXT_PATH("nfc") #define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") #define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) -#define MF_CLASSIC_NESTED_HARD_MINIMUM (3) +#define MF_CLASSIC_NESTED_NT_HARD_MINIMUM (3) #define MF_CLASSIC_NESTED_RETRY_MAXIMUM (20) +#define MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM (3) #define MF_CLASSIC_NESTED_CALIBRATION_COUNT (21) #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" #define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" @@ -28,9 +30,12 @@ extern "C" { (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME) #define MF_CLASSIC_NESTED_USER_DICT_PATH \ (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_USER_DICT_FILE_NAME) +#define SET_PACKED_BIT(arr, bit) ((arr)[(bit) / 8] |= (1 << ((bit) % 8))) +#define GET_PACKED_BIT(arr, bit) ((arr)[(bit) / 8] & (1 << ((bit) % 8))) extern const MfClassicKey auth1_backdoor_key; extern const MfClassicKey auth2_backdoor_key; +extern const uint16_t valid_sums[19]; typedef enum { MfClassicAuthStateIdle, @@ -146,6 +151,7 @@ typedef struct { MfClassicNestedPhase nested_phase; MfClassicKey nested_known_key; MfClassicKeyType nested_known_key_type; + bool current_key_checked; uint8_t nested_known_key_sector; uint16_t nested_target_key; MfClassicNestedNonceArray nested_nonce; @@ -157,6 +163,10 @@ typedef struct { uint8_t attempt_count; KeysDict* mf_classic_system_dict; KeysDict* mf_classic_user_dict; + // Hardnested + uint8_t nt_enc_msb + [32]; // Bit-packed array to track which unique most significant bytes have been seen (256 bits = 32 bytes) + uint16_t msb_par_sum; // Sum of parity bits for each unique most significant byte } MfClassicPollerDictAttackContext; typedef struct { From 2abeb071fdb2df9b385b4ed7582aaa4fdd5aba8e Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 2 Sep 2024 10:28:50 -0400 Subject: [PATCH 29/63] Fix regression for regular nested attack --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 10 +++++++++- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index c99e54dc1..7234cc65c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1770,13 +1770,20 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // TODO: Need to think about how this works for NXP/Fudan backdoored tags. // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. // Calibration + bool initial_collect_nt_enc_iter = false; if(!(dict_attack_ctx->calibrated)) { if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedCalibrate; return command; } + initial_collect_nt_enc_iter = true; + dict_attack_ctx->auth_passed = true; + dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { + initial_collect_nt_enc_iter = true; + dict_attack_ctx->auth_passed = true; + dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces @@ -1807,7 +1814,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; } else { - if(is_weak) { + if(is_weak && !(initial_collect_nt_enc_iter)) { dict_attack_ctx->nested_target_key++; if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; @@ -1850,6 +1857,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->attempt_count = 0; } + dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; } 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 9e4cfd728..4726aa5bc 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -19,7 +19,7 @@ extern "C" { #define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") #define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) #define MF_CLASSIC_NESTED_NT_HARD_MINIMUM (3) -#define MF_CLASSIC_NESTED_RETRY_MAXIMUM (20) +#define MF_CLASSIC_NESTED_RETRY_MAXIMUM (60) #define MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM (3) #define MF_CLASSIC_NESTED_CALIBRATION_COUNT (21) #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" From ccc4326574eb96db1087d42159e225899c087300 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 2 Sep 2024 12:45:39 -0400 Subject: [PATCH 30/63] Backdoor read --- .../protocols/mf_classic/mf_classic_poller.c | 88 ++++++++++++++++++- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 7234cc65c..b6de613f7 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,6 +6,9 @@ #define TAG "MfClassicPoller" +// TODO: Backdoor static nested +// TODO: Reflect status in NFC app + #define MF_CLASSIC_MAX_BUFF_SIZE (64) const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; @@ -546,7 +549,7 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) } else { dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; } - instance->state = MfClassicPollerStateRequestKey; + instance->state = MfClassicPollerStateBackdoorReadSector; } else if(current_key_is_auth2) { dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; @@ -554,6 +557,88 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) return command; } +NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + MfClassicError error = MfClassicErrorNone; + MfClassicBlock block = {}; + + uint8_t current_sector = mf_classic_get_sector_by_block(dict_attack_ctx->current_block); + uint8_t blocks_in_sector = mf_classic_get_blocks_num_in_sector(current_sector); + uint8_t first_block_in_sector = mf_classic_get_first_block_num_of_sector(current_sector); + + do { + if(dict_attack_ctx->current_block >= instance->sectors_total * 4) { + // We've read all blocks, reset current_block and move to next state + dict_attack_ctx->current_block = 0; + instance->state = MfClassicPollerStateNestedController; + break; + } + + // Use the appropriate backdoor key + const MfClassicKey* backdoor_key = (dict_attack_ctx->backdoor == MfClassicBackdoorAuth1) ? + &auth1_backdoor_key : + &auth2_backdoor_key; + + // Create a non-const copy of the backdoor key + MfClassicKey backdoor_key_copy; + memcpy(&backdoor_key_copy, backdoor_key, sizeof(MfClassicKey)); + + // Authenticate with the backdoor key + error = mf_classic_poller_auth( + instance, + first_block_in_sector, // Authenticate to the first block of the sector + &backdoor_key_copy, + MfClassicKeyTypeA, + NULL, + true); + + if(error != MfClassicErrorNone) { + FURI_LOG_E( + TAG, "Failed to authenticate with backdoor key for sector %d", current_sector); + break; + } + + // Read all blocks in the sector + for(uint8_t block_in_sector = 0; block_in_sector < blocks_in_sector; block_in_sector++) { + uint8_t block_to_read = first_block_in_sector + block_in_sector; + + error = mf_classic_poller_read_block(instance, block_to_read, &block); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to read block %d", block_to_read); + break; + } + + // Set the block as read in the data structure + mf_classic_set_block_read(instance->data, block_to_read, &block); + } + + if(error != MfClassicErrorNone) { + break; + } + + // Move to the next sector + current_sector++; + dict_attack_ctx->current_block = mf_classic_get_first_block_num_of_sector(current_sector); + + // Update blocks_in_sector and first_block_in_sector for the next sector + if(current_sector < instance->sectors_total) { + blocks_in_sector = mf_classic_get_blocks_num_in_sector(current_sector); + first_block_in_sector = mf_classic_get_first_block_num_of_sector(current_sector); + } + + // Halt the card after each sector to reset the authentication state + mf_classic_poller_halt(instance); + + // Send an event to the app that a sector has been read + command = mf_classic_poller_handle_data_update(instance); + + } while(false); + + return command; +} + NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -1896,6 +1981,7 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block, [MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector, [MfClassicPollerStateAnalyzeBackdoor] = mf_classic_poller_handler_analyze_backdoor, + [MfClassicPollerStateBackdoorReadSector] = mf_classic_poller_handler_backdoor_read_sector, [MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key, [MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector, [MfClassicPollerStateReadSectorBlocks] = 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 4726aa5bc..808a879f7 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -104,6 +104,7 @@ typedef enum { // Dict attack states MfClassicPollerStateNextSector, MfClassicPollerStateAnalyzeBackdoor, + MfClassicPollerStateBackdoorReadSector, MfClassicPollerStateRequestKey, MfClassicPollerStateReadSector, MfClassicPollerStateAuthKeyA, From 9c7120ec91338be00741cd9870f937ebc2ae0fcb Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 2 Sep 2024 18:53:39 -0400 Subject: [PATCH 31/63] Backdoor working up to calibration --- .../protocols/mf_classic/mf_classic_poller.c | 39 ++++++++++--------- 1 file changed, 20 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 b6de613f7..f8ec5ba5b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -575,20 +575,11 @@ NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* insta break; } - // Use the appropriate backdoor key - const MfClassicKey* backdoor_key = (dict_attack_ctx->backdoor == MfClassicBackdoorAuth1) ? - &auth1_backdoor_key : - &auth2_backdoor_key; - - // Create a non-const copy of the backdoor key - MfClassicKey backdoor_key_copy; - memcpy(&backdoor_key_copy, backdoor_key, sizeof(MfClassicKey)); - // Authenticate with the backdoor key error = mf_classic_poller_auth( instance, first_block_in_sector, // Authenticate to the first block of the sector - &backdoor_key_copy, + &(dict_attack_ctx->current_key), MfClassicKeyTypeA, NULL, true); @@ -1476,6 +1467,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc MfClassicAuthContext auth_ctx = {}; MfClassicError error; + bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; bool is_last_iter_for_hard_key = ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); @@ -1498,7 +1490,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false); + use_backdoor_for_initial_auth); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1717,21 +1709,30 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { dict_attack_ctx->auth_passed = true; dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; - for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { - for(uint8_t key_type = 0; key_type < 2; key_type++) { - if(mf_classic_is_key_found(instance->data, sector, key_type)) { - dict_attack_ctx->nested_known_key_sector = sector; - dict_attack_ctx->nested_known_key_type = key_type; - break; + bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); + if(!(backdoor_present)) { + for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { + for(uint8_t key_type = 0; key_type < 2; key_type++) { + if(mf_classic_is_key_found(instance->data, sector, key_type)) { + dict_attack_ctx->nested_known_key_sector = sector; + dict_attack_ctx->nested_known_key_type = key_type; + break; + } } } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; + } else { + dict_attack_ctx->nested_known_key_sector = 0; + dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; + dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + initial_dict_attack_iter = true; } - dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } - bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { instance->state = MfClassicPollerStateNestedCollectNt; From 2cb2f05ea98c40970fdb5a18760c86c3db7c3aa5 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 2 Sep 2024 22:34:44 -0400 Subject: [PATCH 32/63] Backdoor nested calibration --- .../protocols/mf_classic/mf_classic_poller.c | 57 ++++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index f8ec5ba5b..2505909c9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1064,8 +1064,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance } NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { - // TODO: Calibrate backdoored tags too - // TODO: Check if we have already identified the tag as static encrypted NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; @@ -1083,6 +1081,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; uint8_t nt_enc_collected = 0; + bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -1091,7 +1090,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false); + use_backdoor); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1103,6 +1102,39 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + if((dict_attack_ctx->static_encrypted) && + (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2)) { + uint8_t target_block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, + &auth_ctx, + use_backdoor, + false); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication for static encrypted tag"); + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Store the decrypted static encrypted nonce + dict_attack_ctx->static_encrypted_nonce = + decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); + + dict_attack_ctx->calibrated = true; + + FURI_LOG_E(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc); + + instance->state = MfClassicPollerStateNestedController; + return command; + } + + // Original calibration logic for non-static encrypted tags // Step 2: Perform nested authentication multiple times for(uint8_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { @@ -1112,7 +1144,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false, + use_backdoor, false); if(error != MfClassicErrorNone) { @@ -1140,17 +1172,9 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { - // TODO: Backdoor static nested attack calibration - dict_attack_ctx->calibrated = true; - instance->state = MfClassicPollerStateNestedController; - return command; - } else { - // TODO: Log these - dict_attack_ctx->calibrated = true; - instance->state = MfClassicPollerStateNestedController; - return command; - } + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + return command; } // Find the distance between each nonce @@ -1729,6 +1753,9 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_known_key_sector = 0; dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + dict_attack_ctx->static_encrypted = true; + } dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; initial_dict_attack_iter = true; } 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 808a879f7..41138144e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -158,6 +158,7 @@ typedef struct { MfClassicNestedNonceArray nested_nonce; MfClassicPrngType prng_type; bool static_encrypted; + uint32_t static_encrypted_nonce; bool calibrated; uint16_t d_min; uint16_t d_max; From 6e9fe1edd820ae936996bd9b6c0f1d5cbdce7cc9 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 05:54:59 -0400 Subject: [PATCH 33/63] Don't recalibrate hard PRNG tags --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 2505909c9..0b20586c2 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -8,6 +8,8 @@ // TODO: Backdoor static nested // TODO: Reflect status in NFC app +// TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches +// TODO: Test not resetting the tag during Hardnested (single initial auth, repeated failed nested) #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -816,6 +818,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; command = instance->callback(instance->general_event, instance->context); // Nested entrypoint + // TODO: Ensure nested attack isn't run if tag is fully read if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished) { instance->state = MfClassicPollerStateNestedController; @@ -1891,6 +1894,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } initial_collect_nt_enc_iter = true; dict_attack_ctx->auth_passed = true; + dict_attack_ctx->calibrated = true; dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { From 2e0cd320c790fad650be8014aeb73c1234aa24e5 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 09:26:14 -0400 Subject: [PATCH 34/63] Static encrypted nonce collection --- .../protocols/mf_classic/mf_classic_poller.c | 78 ++++++++++--------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 0b20586c2..6d784f9f0 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1107,6 +1107,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) if((dict_attack_ctx->static_encrypted) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2)) { + command = NfcCommandReset; uint8_t target_block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); error = mf_classic_poller_auth_nested( @@ -1248,30 +1249,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - if(dict_attack_ctx->static_encrypted) { - FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { - // TODO: Backdoor static nested attack with calibrated distance - break; - } else { - // TODO: If not present, just log nonces with parity bits, e.g. - /* - bool success = add_nested_nonce( - &result, - cuid, - dict_attack_ctx->nested_known_key_sector, - nt_prev, - nt_enc_prev, - parity, - UINT16_MAX); - if(!success) { - FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); - } - */ - break; - } - } - uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); @@ -1279,14 +1256,17 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; + bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0; uint8_t nt_enc_per_collection = - is_weak ? ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : 1; + (is_weak && !(dict_attack_ctx->static_encrypted)) ? + ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : + 1; MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; - // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? + // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? Match calibrated? uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; uint32_t nt_enc_temp_arr[nt_enc_per_collection]; uint8_t nt_enc_collected = 0; @@ -1299,7 +1279,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false); + use_backdoor_for_initial_auth); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1355,7 +1335,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } uint32_t nt_prev = 0, decrypted_nt_prev = 0, found_nt = 0; - if(is_weak) { + uint16_t dist = 0; + if(is_weak && !(dict_attack_ctx->static_encrypted)) { // Decrypt the previous nonce nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); @@ -1380,6 +1361,12 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst if(found_nt_cnt != 1) { break; } + } else if(dict_attack_ctx->static_encrypted) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + found_nt = dict_attack_ctx->static_encrypted_nonce; + } else { + dist = UINT16_MAX; + } } else { // Hardnested if(!is_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF)) { @@ -1398,7 +1385,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst found_nt, nt_enc, parity, - 0)) { + dist)) { dict_attack_ctx->auth_passed = true; } else { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); @@ -1619,9 +1606,10 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { Stream* stream = buffered_file_stream_alloc(storage); FuriString* temp_str = furi_string_alloc(); bool weak_prng = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + bool static_encrypted = dict_attack_ctx->static_encrypted; do { - if(weak_prng && (dict_attack_ctx->nested_nonce.count != 2)) { + if(weak_prng && (!(static_encrypted)) && (dict_attack_ctx->nested_nonce.count != 2)) { FURI_LOG_E( TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %u", @@ -1646,7 +1634,8 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { (nonce->key_idx / 4), ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', nonce->cuid); - for(uint8_t nt_idx = 0; nt_idx < (weak_prng ? 2 : 1); nt_idx++) { + for(uint8_t nt_idx = 0; nt_idx < ((weak_prng && (!(static_encrypted))) ? 2 : 1); + nt_idx++) { if(nt_idx == 1) { nonce = &dict_attack_ctx->nested_nonce.nonces[i + 1]; } @@ -1883,8 +1872,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedDictAttack; return command; } - // TODO: Need to think about how this works for NXP/Fudan backdoored tags. - // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. // Calibration bool initial_collect_nt_enc_iter = false; if(!(dict_attack_ctx->calibrated)) { @@ -1904,15 +1891,25 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces + // TODO: Calibrates too frequently for static encrypted backdoored tags if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || + ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + (dict_attack_ctx->nested_nonce.count == 1)) || ((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) { instance->state = MfClassicPollerStateNestedLog; return command; } + uint16_t nonce_collect_key_max; + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + nonce_collect_key_max = dict_attack_ctx->static_encrypted ? + ((instance->sectors_total * 4) - 2) : + (instance->sectors_total * 4); + } else { + nonce_collect_key_max = instance->sectors_total * 2; + } // Target all remaining sectors, key A and B - if((is_weak && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 4))) || - (!(is_weak) && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 2)))) { + if(dict_attack_ctx->nested_target_key < nonce_collect_key_max) { if((!(is_weak)) && found_all_nt_enc_msb(dict_attack_ctx)) { if(is_valid_sum(dict_attack_ctx->msb_par_sum)) { // All Hardnested nonces collected @@ -1932,10 +1929,19 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count++; } else { if(is_weak && !(initial_collect_nt_enc_iter)) { - dict_attack_ctx->nested_target_key++; + if(!(dict_attack_ctx->static_encrypted)) { + dict_attack_ctx->nested_target_key++; + } else { + dict_attack_ctx->nested_target_key += 2; + } if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; } + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + (dict_attack_ctx->nested_target_key % 4 == 0) && + (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { + dict_attack_ctx->calibrated = false; + } } dict_attack_ctx->attempt_count = 0; } From 3cb3eab1185a01b7cd17801302f7a4f78e365afb Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 11:36:19 -0400 Subject: [PATCH 35/63] Update TODO --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 6d784f9f0..33973afe6 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,10 +6,10 @@ #define TAG "MfClassicPoller" -// TODO: Backdoor static nested // TODO: Reflect status in NFC app // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Test not resetting the tag during Hardnested (single initial auth, repeated failed nested) +// TODO: Load dictionaries specific to a CUID to not clutter the user dictionary #define MF_CLASSIC_MAX_BUFF_SIZE (64) From 92122b2cdf50da2182bf6a89abe047f2e2bd7db2 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 15:19:12 -0400 Subject: [PATCH 36/63] NFC app UI updates, MVP --- applications/main/nfc/nfc_app_i.h | 3 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 22 +++-- applications/main/nfc/views/dict_attack.c | 84 ++++++++++++++++++- applications/main/nfc/views/dict_attack.h | 6 ++ .../protocols/mf_classic/mf_classic_poller.c | 6 +- .../protocols/mf_classic/mf_classic_poller.h | 3 + .../mf_classic/mf_classic_poller_i.h | 1 - 7 files changed, 115 insertions(+), 10 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 798c3a5a6..5310b8979 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -97,6 +97,9 @@ typedef struct { bool is_key_attack; uint8_t key_attack_current_sector; bool is_card_present; + uint8_t nested_phase; + uint8_t prng_type; + uint8_t backdoor; } NfcMfClassicDictAttackContext; struct NfcApp { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 5aee8ddd8..af329a67c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -5,6 +5,8 @@ #define TAG "NfcMfClassicDictAttack" +// TODO: Update progress bar with nested attacks + typedef enum { DictAttackStateUserDictInProgress, DictAttackStateSystemDictInProgress, @@ -58,6 +60,9 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) instance->nfc_dict_context.sectors_read = data_update->sectors_read; instance->nfc_dict_context.keys_found = data_update->keys_found; instance->nfc_dict_context.current_sector = data_update->current_sector; + instance->nfc_dict_context.nested_phase = data_update->nested_phase; + instance->nfc_dict_context.prng_type = data_update->prng_type; + instance->nfc_dict_context.backdoor = data_update->backdoor; view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { @@ -117,6 +122,9 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found); dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current); dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector); + dict_attack_set_nested_phase(instance->dict_attack, mfc_dict->nested_phase); + dict_attack_set_prng_type(instance->dict_attack, mfc_dict->prng_type); + dict_attack_set_backdoor(instance->dict_attack, mfc_dict->backdoor); } } @@ -125,6 +133,13 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(state == DictAttackStateUserDictInProgress) { do { + // TODO: Check for errors + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { state = DictAttackStateSystemDictInProgress; break; @@ -149,13 +164,6 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { } while(false); } if(state == DictAttackStateSystemDictInProgress) { - // TODO: Check for errors - storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); - storage_common_copy( - instance->storage, - NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, - NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); - instance->nfc_dict_context.dict = keys_dict_alloc( NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary"); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 14298a6aa..83cf9a4bf 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -10,6 +10,30 @@ struct DictAttack { void* context; }; +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + +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 + MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) +} MfClassicBackdoor; + typedef struct { FuriString* header; bool card_detected; @@ -21,6 +45,9 @@ typedef struct { size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; + MfClassicNestedPhase nested_phase; + MfClassicPrngType prng_type; + MfClassicBackdoor backdoor; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { @@ -34,8 +61,39 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { } else { char draw_str[32] = {}; canvas_set_font(canvas, FontSecondary); + + switch(m->nested_phase) { + case MfClassicNestedPhaseAnalyzePRNG: + furi_string_set(m->header, "PRNG Analysis"); + break; + case MfClassicNestedPhaseDictAttack: + case MfClassicNestedPhaseDictAttackResume: + furi_string_set(m->header, "Nested Dictionary"); + break; + case MfClassicNestedPhaseCalibrate: + furi_string_set(m->header, "Calibration"); + break; + case MfClassicNestedPhaseCollectNtEnc: + furi_string_set(m->header, "Nonce Collection"); + break; + default: + break; + } + + if(m->prng_type == MfClassicPrngTypeHard) { + furi_string_cat(m->header, " (Hard)"); + } + + if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { + if(m->nested_phase != MfClassicNestedPhaseNone) { + furi_string_cat(m->header, " (Backdoor)"); + } else { + furi_string_set(m->header, "Backdoor Read"); + } + } + canvas_draw_str_aligned( - canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); if(m->is_key_attack) { snprintf( draw_str, @@ -132,6 +190,9 @@ void dict_attack_reset(DictAttack* instance) { model->dict_keys_total = 0; model->dict_keys_current = 0; model->is_key_attack = false; + model->nested_phase = MfClassicNestedPhaseNone; + model->prng_type = MfClassicPrngTypeUnknown; + model->backdoor = MfClassicBackdoorUnknown; furi_string_reset(model->header); }, false); @@ -242,3 +303,24 @@ void dict_attack_reset_key_attack(DictAttack* instance) { with_view_model( instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true); } + +void dict_attack_set_nested_phase(DictAttack* instance, uint8_t nested_phase) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->nested_phase = nested_phase; }, true); +} + +void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->prng_type = prng_type; }, true); +} + +void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->backdoor = backdoor; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 30f3b3c44..9afbeaf84 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -45,6 +45,12 @@ void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector); void dict_attack_reset_key_attack(DictAttack* instance); +void dict_attack_set_nested_phase(DictAttack* instance, uint8_t nested_phase); + +void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type); + +void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 33973afe6..de258b4d5 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -68,6 +68,9 @@ static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance mf_classic_get_read_sectors_and_keys( instance->data, &data_update->sectors_read, &data_update->keys_found); data_update->current_sector = instance->mode_ctx.dict_attack_ctx.current_sector; + data_update->nested_phase = instance->mode_ctx.dict_attack_ctx.nested_phase; + data_update->prng_type = instance->mode_ctx.dict_attack_ctx.prng_type; + data_update->backdoor = instance->mode_ctx.dict_attack_ctx.backdoor; instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate; return instance->callback(instance->general_event, instance->context); } @@ -1723,7 +1726,8 @@ bool is_valid_sum(uint16_t sum) { NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys - NfcCommand command = NfcCommandContinue; + //NfcCommand command = NfcCommandContinue; + NfcCommand command = mf_classic_poller_handle_data_update(instance); MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index b26378d33..0501de21e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -77,6 +77,9 @@ typedef struct { uint8_t sectors_read; /**< Number of sectors read. */ uint8_t keys_found; /**< Number of keys found. */ uint8_t current_sector; /**< Current sector number. */ + uint8_t nested_phase; /**< Nested attack phase. */ + uint8_t prng_type; /**< PRNG (weak or hard). */ + uint8_t backdoor; /**< Backdoor type. */ } MfClassicPollerEventDataUpdate; /** 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 41138144e..e17e13d13 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -52,7 +52,6 @@ typedef enum { MfClassicNestedPhaseAnalyzePRNG, MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttackResume, - MfClassicNestedPhaseAnalyzeBackdoor, MfClassicNestedPhaseCalibrate, MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseFinished, From b09d5a0069e5c4d13c9ba434191320eec4837026 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 20:20:49 -0400 Subject: [PATCH 37/63] Bump f18 API version (all functions are NFC related) --- targets/f18/api_symbols.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 57cbd1d62..ecc3849c9 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,73.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, From cba58ed4372572c5214884b857889171059d247a Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 9 Sep 2024 20:50:15 -0400 Subject: [PATCH 38/63] Add new backdoor key, fix UI status update carrying over from previous read --- .../scenes/nfc_scene_mf_classic_dict_attack.c | 4 ++ applications/main/nfc/views/dict_attack.c | 24 ------- applications/main/nfc/views/dict_attack.h | 25 ++++++++ .../protocols/mf_classic/mf_classic_poller.c | 63 +++++++++++-------- .../mf_classic/mf_classic_poller_i.h | 12 +++- 5 files changed, 78 insertions(+), 50 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index af329a67c..e0050f4ae 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -6,6 +6,7 @@ #define TAG "NfcMfClassicDictAttack" // TODO: Update progress bar with nested attacks +// TODO: Zero the new values when skipping or stopping the attack typedef enum { DictAttackStateUserDictInProgress, @@ -297,6 +298,9 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.is_key_attack = false; instance->nfc_dict_context.key_attack_current_sector = 0; instance->nfc_dict_context.is_card_present = false; + instance->nfc_dict_context.nested_phase = MfClassicNestedPhaseNone; + instance->nfc_dict_context.prng_type = MfClassicPrngTypeUnknown; + instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown; nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 83cf9a4bf..2fae91694 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -10,30 +10,6 @@ struct DictAttack { void* context; }; -typedef enum { - MfClassicNestedPhaseNone, - MfClassicNestedPhaseAnalyzePRNG, - MfClassicNestedPhaseDictAttack, - MfClassicNestedPhaseDictAttackResume, - MfClassicNestedPhaseCalibrate, - MfClassicNestedPhaseCollectNtEnc, - MfClassicNestedPhaseFinished, -} MfClassicNestedPhase; - -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 - MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor - MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) -} MfClassicBackdoor; - typedef struct { FuriString* header; bool card_detected; diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 9afbeaf84..c2a7b1e68 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -9,6 +9,31 @@ extern "C" { typedef struct DictAttack DictAttack; +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + +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 + MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor + MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce) +} MfClassicBackdoor; + typedef enum { DictAttackEventSkipPressed, } DictAttackEvent; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index de258b4d5..0e48b5d9f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -13,8 +13,14 @@ #define MF_CLASSIC_MAX_BUFF_SIZE (64) -const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; -const MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; +// Ordered by frequency, labeled chronologically +const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[] = { + {{{0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}, MfClassicBackdoorAuth3}, // Fudan (static encrypted) + {{{0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}, MfClassicBackdoorAuth1}, // Fudan, Infineon, NXP + {{{0x51, 0x8b, 0x33, 0x54, 0xe7, 0x60}}, MfClassicBackdoorAuth2}, // Fudan +}; +const size_t mf_classic_backdoor_keys_count = + sizeof(mf_classic_backdoor_keys) / sizeof(mf_classic_backdoor_keys[0]); const uint16_t valid_sums[] = {0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256}; @@ -529,36 +535,43 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - bool current_key_is_auth1 = - memcmp(dict_attack_ctx->current_key.data, auth1_backdoor_key.data, sizeof(MfClassicKey)) == - 0; - bool current_key_is_auth2 = - memcmp(dict_attack_ctx->current_key.data, auth2_backdoor_key.data, sizeof(MfClassicKey)) == - 0; - if(!current_key_is_auth1) { - dict_attack_ctx->current_key = auth2_backdoor_key; - } else if(current_key_is_auth2) { - dict_attack_ctx->current_key = auth1_backdoor_key; + size_t current_key_index = + mf_classic_backdoor_keys_count - 1; // Default to the last valid index + + // Find the current key in the backdoor_keys array + for(size_t i = 0; i < mf_classic_backdoor_keys_count; i++) { + if(memcmp( + dict_attack_ctx->current_key.data, + mf_classic_backdoor_keys[i].key.data, + sizeof(MfClassicKey)) == 0) { + current_key_index = i; + break; + } } + // Choose the next key to try + size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count; + uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1; + + FURI_LOG_E(TAG, "Trying backdoor v%d", backdoor_version); + dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key; + // Attempt backdoor authentication MfClassicError error = mf_classic_poller_auth( instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); - bool backdoor_found = (error == MfClassicErrorNone) ? true : false; + bool backdoor_found = (error == MfClassicErrorNone); if(backdoor_found) { - FURI_LOG_E(TAG, "Backdoor identified"); - if(!current_key_is_auth1) { - dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; - } else { - dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; - } + FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version); + dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type; instance->state = MfClassicPollerStateBackdoorReadSector; - } else if(current_key_is_auth2) { + } else if(next_key_index == (mf_classic_backdoor_keys_count - 1)) { + // We've tried all backdoor keys dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; } + return command; } @@ -1109,7 +1122,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); if((dict_attack_ctx->static_encrypted) && - (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2)) { + (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3)) { command = NfcCommandReset; uint8_t target_block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); @@ -1365,7 +1378,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst break; } } else if(dict_attack_ctx->static_encrypted) { - if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { found_nt = dict_attack_ctx->static_encrypted_nonce; } else { dist = UINT16_MAX; @@ -1749,7 +1762,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_known_key_sector = 0; dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; - if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { dict_attack_ctx->static_encrypted = true; } dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; @@ -1898,7 +1911,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // TODO: Calibrates too frequently for static encrypted backdoored tags if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || - ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && (dict_attack_ctx->nested_nonce.count == 1)) || ((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) { instance->state = MfClassicPollerStateNestedLog; @@ -1941,7 +1954,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; } - if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && (dict_attack_ctx->nested_target_key % 4 == 0) && (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { dict_attack_ctx->calibrated = false; 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 e17e13d13..111ddf260 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -35,6 +35,7 @@ extern "C" { extern const MfClassicKey auth1_backdoor_key; extern const MfClassicKey auth2_backdoor_key; +extern const MfClassicKey auth3_backdoor_key; extern const uint16_t valid_sums[19]; typedef enum { @@ -68,9 +69,18 @@ typedef enum { MfClassicBackdoorUnknown, // Tag not yet tested MfClassicBackdoorNone, // No observed backdoor MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor - MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor + MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce) } MfClassicBackdoor; +typedef struct { + MfClassicKey key; + MfClassicBackdoor type; +} MfClassicBackdoorKeyPair; + +extern const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[]; +extern const size_t mf_classic_backdoor_keys_count; + typedef struct { uint32_t cuid; // Card UID uint8_t key_idx; // Key index From ab8bc3e21c4a2d641f588ecb6ede081fee7bd4cc Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 9 Sep 2024 20:51:51 -0400 Subject: [PATCH 39/63] Clear TODO line --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index e0050f4ae..b660520a3 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -6,7 +6,6 @@ #define TAG "NfcMfClassicDictAttack" // TODO: Update progress bar with nested attacks -// TODO: Zero the new values when skipping or stopping the attack typedef enum { DictAttackStateUserDictInProgress, From d7484ee84078265192e019eeb07de79fb461e164 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 10 Sep 2024 09:03:11 -0400 Subject: [PATCH 40/63] Fix v1/v2 backdoor nonce collection --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 0e48b5d9f..de0d645fd 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1272,7 +1272,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); + bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0; uint8_t nt_enc_per_collection = @@ -1295,7 +1295,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - use_backdoor_for_initial_auth); + use_backdoor); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1317,7 +1317,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false, + use_backdoor, false); if(error != MfClassicErrorNone) { From c43806b5dbd827c0c998338779a6b743864019a9 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 10 Sep 2024 14:10:09 -0400 Subject: [PATCH 41/63] Speed up backdoor detection, alert on new backdoor --- .../protocols/mf_classic/mf_classic_poller.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index de0d645fd..2b122679e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -560,16 +560,19 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) // Attempt backdoor authentication MfClassicError error = mf_classic_poller_auth( instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); - bool backdoor_found = (error == MfClassicErrorNone); - - if(backdoor_found) { + if((next_key_index == 0) && (error == MfClassicErrorProtocol)) { + FURI_LOG_E(TAG, "No backdoor identified"); + dict_attack_ctx->backdoor = MfClassicBackdoorNone; + instance->state = MfClassicPollerStateRequestKey; + } else if(error == MfClassicErrorNone) { FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version); dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type; instance->state = MfClassicPollerStateBackdoorReadSector; - } else if(next_key_index == (mf_classic_backdoor_keys_count - 1)) { - // We've tried all backdoor keys - dict_attack_ctx->backdoor = MfClassicBackdoorNone; - instance->state = MfClassicPollerStateRequestKey; + } else if( + (error == MfClassicErrorAuth) && + (next_key_index == (mf_classic_backdoor_keys_count - 1))) { + // We've tried all backdoor keys, this is a unique key and an important research finding + furi_crash("New backdoor: please report!"); } return command; @@ -1083,6 +1086,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance } NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { + // TODO: Discard outliers (e.g. greater than 3 standard deviations) NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; From ab0debba0213ebd91a19e4a151e820cb6a9d7769 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 10 Sep 2024 19:20:53 -0400 Subject: [PATCH 42/63] Add additional condition to backdoor check --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 2b122679e..7a068c4b8 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -560,7 +560,8 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) // Attempt backdoor authentication MfClassicError error = mf_classic_poller_auth( instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); - if((next_key_index == 0) && (error == MfClassicErrorProtocol)) { + if((next_key_index == 0) && + (error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) { FURI_LOG_E(TAG, "No backdoor identified"); dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; From 13411da449ec9632b12b1db48ee4dcceeb42c063 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 11 Sep 2024 16:22:52 -0400 Subject: [PATCH 43/63] I'll try freeing memory, that's a good trick! --- .../protocols/mf_classic/mf_classic_poller.c | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 7a068c4b8..88af4744b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -65,6 +65,26 @@ void mf_classic_poller_free(MfClassicPoller* instance) { bit_buffer_free(instance->tx_encrypted_buffer); bit_buffer_free(instance->rx_encrypted_buffer); + // Clean up resources in MfClassicPollerDictAttackContext + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + // Free the dictionaries + if(dict_attack_ctx->mf_classic_system_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + dict_attack_ctx->mf_classic_system_dict = NULL; + } + if(dict_attack_ctx->mf_classic_user_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + dict_attack_ctx->mf_classic_user_dict = NULL; + } + + // Free the nested nonce array if it exists + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + free(instance); } @@ -1861,9 +1881,11 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + dict_attack_ctx->mf_classic_system_dict = NULL; } if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + dict_attack_ctx->mf_classic_user_dict = NULL; } dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { From 8edafa3f39860df2642f1cda4fb50120a0bf9600 Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 12 Sep 2024 14:28:18 -0400 Subject: [PATCH 44/63] Do not enter nested attack if card is already finished --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 88af4744b..260c41221 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -858,9 +858,10 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; command = instance->callback(instance->general_event, instance->context); // Nested entrypoint - // TODO: Ensure nested attack isn't run if tag is fully read - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || - dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished) { + bool nested_active = dict_attack_ctx->nested_phase != MfClassicNestedPhaseNone; + if((nested_active && + (dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) || + (!(nested_active) && !(mf_classic_is_card_read(instance->data)))) { instance->state = MfClassicPollerStateNestedController; break; } From 18f8cfbef07ad8fd25f8e06e633176604ea82da1 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 16 Sep 2024 05:28:04 -0400 Subject: [PATCH 45/63] Do not reset the poller between collected nonces --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 260c41221..0934a7765 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,10 +6,10 @@ #define TAG "MfClassicPoller" -// TODO: Reflect status in NFC app +// TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches -// TODO: Test not resetting the tag during Hardnested (single initial auth, repeated failed nested) // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary +// TODO: Validate Hardnested is collecting nonces from the correct block #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -553,7 +553,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* } NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { - NfcCommand command = NfcCommandReset; + NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; size_t current_key_index = @@ -1287,7 +1287,7 @@ static inline bool is_byte_found(uint8_t* found, uint8_t byte) { NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) // TODO: Look into using MfClassicNt more - NfcCommand command = NfcCommandReset; + NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { @@ -1512,7 +1512,7 @@ static MfClassicKey* search_dicts_for_nonce_key( NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) // TODO: Look into using MfClassicNt more - NfcCommand command = NfcCommandReset; + NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { From 3ab752b7a019e743982f766e5d2ba7e893337ac4 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 17 Sep 2024 14:38:14 -0400 Subject: [PATCH 46/63] Clean up various issues --- applications/main/nfc/views/dict_attack.c | 1 + applications/main/nfc/views/dict_attack.h | 1 + .../protocols/mf_classic/mf_classic_poller.c | 74 ++++++++++++------- .../mf_classic/mf_classic_poller_i.h | 1 + 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 2fae91694..c0cc3802b 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -47,6 +47,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { furi_string_set(m->header, "Nested Dictionary"); break; case MfClassicNestedPhaseCalibrate: + case MfClassicNestedPhaseRecalibrate: furi_string_set(m->header, "Calibration"); break; case MfClassicNestedPhaseCollectNtEnc: diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index c2a7b1e68..d28188fbc 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -15,6 +15,7 @@ typedef enum { MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseRecalibrate, MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseFinished, } MfClassicNestedPhase; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 0934a7765..75139faf4 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -10,6 +10,7 @@ // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary // TODO: Validate Hardnested is collecting nonces from the correct block +// TODO: Nested entrypoint for cached keys #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -553,7 +554,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* } NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { - NfcCommand command = NfcCommandContinue; + NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; size_t current_key_index = @@ -1152,11 +1153,13 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) command = NfcCommandReset; uint8_t target_block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); + MfClassicKeyType target_key_type = + ((dict_attack_ctx->nested_target_key % 4) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; error = mf_classic_poller_auth_nested( instance, target_block, &dict_attack_ctx->nested_known_key, - dict_attack_ctx->nested_known_key_type, + target_key_type, &auth_ctx, use_backdoor, false); @@ -1231,7 +1234,6 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc( cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); - // TODO: Make sure we're not off-by-one here for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); if(nth_successor != decrypted_nt_enc) { @@ -1305,11 +1307,12 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst (is_weak && !(dict_attack_ctx->static_encrypted)) ? ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : 1; - MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? Match calibrated? - uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; + uint8_t target_sector = dict_attack_ctx->nested_target_key / (is_weak ? 4 : 2); + MfClassicKeyType target_key_type = + (dict_attack_ctx->nested_target_key % (is_weak ? 4 : 2) < (is_weak ? 2 : 1)) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = mf_classic_get_sector_trailer_num_by_sector(target_sector); uint32_t nt_enc_temp_arr[nt_enc_per_collection]; uint8_t nt_enc_collected = 0; uint8_t parity = 0; @@ -1510,7 +1513,7 @@ static MfClassicKey* search_dicts_for_nonce_key( } NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { - // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) + // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) // TODO: Look into using MfClassicNt more NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -1532,9 +1535,8 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? MfClassicKeyTypeA : MfClassicKeyTypeB; - // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? - uint8_t target_block = (is_weak) ? (4 * (dict_attack_ctx->nested_target_key / 2)) + 3 : - (4 * (dict_attack_ctx->nested_target_key / 16)) + 3; + uint8_t target_sector = dict_attack_ctx->nested_target_key / (is_weak ? 2 : 16); + uint8_t target_block = mf_classic_get_sector_trailer_num_by_sector(target_sector); uint8_t parity = 0; if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 0)) || @@ -1764,8 +1766,7 @@ bool is_valid_sum(uint16_t sum) { } NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { - // Iterate through keys - //NfcCommand command = NfcCommandContinue; + // This function guides the nested attack through its phases, and iterates over the target keys NfcCommand command = mf_classic_poller_handle_data_update(instance); MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool initial_dict_attack_iter = false; @@ -1795,6 +1796,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance initial_dict_attack_iter = true; } } + // Identify PRNG type if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { instance->state = MfClassicPollerStateNestedCollectNt; @@ -1896,6 +1898,10 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateSuccess; return command; } + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { + // Skip initial calibration for static encrypted backdoored tags + dict_attack_ctx->calibrated = true; + } dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; instance->state = MfClassicPollerStateNestedController; return command; @@ -1919,24 +1925,23 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Calibration bool initial_collect_nt_enc_iter = false; + bool recalibrated = false; if(!(dict_attack_ctx->calibrated)) { if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedCalibrate; return command; } initial_collect_nt_enc_iter = true; - dict_attack_ctx->auth_passed = true; dict_attack_ctx->calibrated = true; - dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { initial_collect_nt_enc_iter = true; - dict_attack_ctx->auth_passed = true; - dict_attack_ctx->current_key_checked = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseRecalibrate) { + recalibrated = true; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces - // TODO: Calibrates too frequently for static encrypted backdoored tags if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && @@ -1970,10 +1975,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); return command; } - if(!(dict_attack_ctx->auth_passed)) { + if(initial_collect_nt_enc_iter) { + dict_attack_ctx->current_key_checked = false; + } + if(!(dict_attack_ctx->auth_passed) && !(initial_collect_nt_enc_iter)) { dict_attack_ctx->attempt_count++; } else { - if(is_weak && !(initial_collect_nt_enc_iter)) { + if(is_weak && !(initial_collect_nt_enc_iter) && !(recalibrated)) { if(!(dict_attack_ctx->static_encrypted)) { dict_attack_ctx->nested_target_key++; } else { @@ -1982,28 +1990,36 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; } - if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && - (dict_attack_ctx->nested_target_key % 4 == 0) && - (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { - dict_attack_ctx->calibrated = false; - } } dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; if(!(dict_attack_ctx->current_key_checked)) { + dict_attack_ctx->current_key_checked = true; + // Check if the nested target key is a known key if(mf_classic_nested_is_target_key_found(instance, false)) { // Continue to next key - if(!(is_weak)) { + if(!(dict_attack_ctx->static_encrypted)) { dict_attack_ctx->nested_target_key++; dict_attack_ctx->current_key_checked = false; } instance->state = MfClassicPollerStateNestedController; return command; } - dict_attack_ctx->current_key_checked = true; + + // If it is not a known key, we'll need to calibrate for static encrypted backdoored tags + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && + (dict_attack_ctx->nested_target_key < nonce_collect_key_max) && + !(recalibrated)) { + dict_attack_ctx->calibrated = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate; + instance->state = MfClassicPollerStateNestedController; + return command; + } } + + // If we have tried to collect this nonce too many times, skip if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) || (!(is_weak) && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) { @@ -2025,6 +2041,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->attempt_count = 0; } + + // Collect a nonce dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; 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 111ddf260..71c972c1a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -54,6 +54,7 @@ typedef enum { MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseRecalibrate, MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseFinished, } MfClassicNestedPhase; From 8eae5c06082aa7cdd4e257652341165877633267 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 17 Sep 2024 17:07:31 -0400 Subject: [PATCH 47/63] Fix Hardnested sector/key type logging --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 75139faf4..7aab12b53 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -9,8 +9,6 @@ // TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary -// TODO: Validate Hardnested is collecting nonces from the correct block -// TODO: Nested entrypoint for cached keys #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -1672,11 +1670,16 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { bool params_write_success = true; for(size_t i = 0; i < nonce_pair_count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; + // TODO: Avoid repeating logic here + uint8_t nonce_sector = nonce->key_idx / (weak_prng ? 4 : 2); + MfClassicKeyType nonce_key_type = + (nonce->key_idx % (weak_prng ? 4 : 2) < (weak_prng ? 2 : 1)) ? MfClassicKeyTypeA : + MfClassicKeyTypeB; furi_string_printf( temp_str, "Sec %d key %c cuid %08lx", - (nonce->key_idx / 4), - ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', + nonce_sector, + (nonce_key_type == MfClassicKeyTypeA) ? 'A' : 'B', nonce->cuid); for(uint8_t nt_idx = 0; nt_idx < ((weak_prng && (!(static_encrypted))) ? 2 : 1); nt_idx++) { From d8864a490b75d121ee1fe1be652d542623043fba Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 17 Sep 2024 17:40:17 -0400 Subject: [PATCH 48/63] Add nested_target_key 64 to TODO --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 7aab12b53..aac9f228b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -9,6 +9,7 @@ // TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary +// TODO: Fix rare nested_target_key 64 bug #define MF_CLASSIC_MAX_BUFF_SIZE (64) From c1cdd491a6877bbf50a06eb955c1329e99d85848 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 18 Sep 2024 12:51:48 -0400 Subject: [PATCH 49/63] Implement progress bar for upgraded attacks in NFC app --- applications/main/nfc/nfc_app_i.h | 2 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 6 +- applications/main/nfc/views/dict_attack.c | 82 +++++++++++++++---- applications/main/nfc/views/dict_attack.h | 4 + .../protocols/mf_classic/mf_classic_poller.c | 17 ++-- .../protocols/mf_classic/mf_classic_poller.h | 3 + .../mf_classic/mf_classic_poller_i.h | 1 + 7 files changed, 88 insertions(+), 27 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 5310b8979..4aacdd19b 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -100,6 +100,8 @@ typedef struct { uint8_t nested_phase; uint8_t prng_type; uint8_t backdoor; + uint16_t nested_target_key; + uint16_t msb_count; } NfcMfClassicDictAttackContext; struct NfcApp { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index b660520a3..3d902fb58 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -5,7 +5,7 @@ #define TAG "NfcMfClassicDictAttack" -// TODO: Update progress bar with nested attacks +// TODO: Fix lag when leaving the dictionary attack view during Hardnested typedef enum { DictAttackStateUserDictInProgress, @@ -63,6 +63,8 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) instance->nfc_dict_context.nested_phase = data_update->nested_phase; instance->nfc_dict_context.prng_type = data_update->prng_type; instance->nfc_dict_context.backdoor = data_update->backdoor; + instance->nfc_dict_context.nested_target_key = data_update->nested_target_key; + instance->nfc_dict_context.msb_count = data_update->msb_count; view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { @@ -125,6 +127,8 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { dict_attack_set_nested_phase(instance->dict_attack, mfc_dict->nested_phase); dict_attack_set_prng_type(instance->dict_attack, mfc_dict->prng_type); dict_attack_set_backdoor(instance->dict_attack, mfc_dict->backdoor); + dict_attack_set_nested_target_key(instance->dict_attack, mfc_dict->nested_target_key); + dict_attack_set_msb_count(instance->dict_attack, mfc_dict->msb_count); } } diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index c0cc3802b..5138dd912 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -24,6 +24,8 @@ typedef struct { MfClassicNestedPhase nested_phase; MfClassicPrngType prng_type; MfClassicBackdoor backdoor; + uint16_t nested_target_key; + uint16_t msb_count; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { @@ -71,7 +73,12 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { canvas_draw_str_aligned( canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); - if(m->is_key_attack) { + if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + uint8_t nonce_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); + snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + } else if(m->is_key_attack) { snprintf( draw_str, sizeof(draw_str), @@ -81,21 +88,47 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); } canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - float dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); - float progress = m->sectors_total == 0 ? 0 : - ((float)(m->current_sector) + dict_progress) / - (float)(m->sectors_total); - if(progress > 1.0f) { - progress = 1.0f; - } - if(m->dict_keys_current == 0) { - // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + float dict_progress = 0; + if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || + m->nested_phase == MfClassicNestedPhaseDictAttack || + m->nested_phase == MfClassicNestedPhaseDictAttackResume) { + // Phase: Nested dictionary attack + uint8_t target_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else if( + m->nested_phase == MfClassicNestedPhaseCalibrate || + m->nested_phase == MfClassicNestedPhaseRecalibrate || + m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + // Phase: Nonce collection + if(m->prng_type == MfClassicPrngTypeWeak) { + uint8_t target_sector = m->nested_target_key / 4; + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else { + uint16_t max_msb = UINT8_MAX + 1; + dict_progress = (float)(m->msb_count) / (float)(max_msb); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); + } } else { - snprintf( - draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, + sizeof(draw_str), + "%zu/%zu", + m->dict_keys_current, + m->dict_keys_total); + } + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; } elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); canvas_set_font(canvas, FontSecondary); @@ -170,6 +203,8 @@ void dict_attack_reset(DictAttack* instance) { model->nested_phase = MfClassicNestedPhaseNone; model->prng_type = MfClassicPrngTypeUnknown; model->backdoor = MfClassicBackdoorUnknown; + model->nested_target_key = 0; + model->msb_count = 0; furi_string_reset(model->header); }, false); @@ -301,3 +336,20 @@ void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor) { with_view_model( instance->view, DictAttackViewModel * model, { model->backdoor = backdoor; }, true); } + +void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t nested_target_key) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->nested_target_key = nested_target_key; }, + true); +} + +void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index d28188fbc..8dc9b9708 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -77,6 +77,10 @@ void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type); void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor); +void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key); + +void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index aac9f228b..cf61db09f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,7 +6,6 @@ #define TAG "MfClassicPoller" -// TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary // TODO: Fix rare nested_target_key 64 bug @@ -97,6 +96,8 @@ static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance data_update->nested_phase = instance->mode_ctx.dict_attack_ctx.nested_phase; data_update->prng_type = instance->mode_ctx.dict_attack_ctx.prng_type; data_update->backdoor = instance->mode_ctx.dict_attack_ctx.backdoor; + data_update->nested_target_key = instance->mode_ctx.dict_attack_ctx.nested_target_key; + data_update->msb_count = instance->mode_ctx.dict_attack_ctx.msb_count; instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate; return instance->callback(instance->general_event, instance->context); } @@ -1415,6 +1416,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Hardnested if(!is_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF)) { set_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF); + dict_attack_ctx->msb_count++; // Add unique parity to sum dict_attack_ctx->msb_par_sum += nfc_util_even_parity32(parity & 0x08); } @@ -1751,15 +1753,6 @@ bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_di return mf_classic_is_key_found(instance->data, target_sector, target_key_type); } -bool found_all_nt_enc_msb(const MfClassicPollerDictAttackContext* dict_attack_ctx) { - for(int i = 0; i < 32; i++) { - if(dict_attack_ctx->nt_enc_msb[i] != 0xFF) { - return false; - } - } - return true; -} - bool is_valid_sum(uint16_t sum) { for(size_t i = 0; i < 19; i++) { if(sum == valid_sums[i]) { @@ -1964,7 +1957,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Target all remaining sectors, key A and B if(dict_attack_ctx->nested_target_key < nonce_collect_key_max) { - if((!(is_weak)) && found_all_nt_enc_msb(dict_attack_ctx)) { + if((!(is_weak)) && (dict_attack_ctx->msb_count == (UINT8_MAX + 1))) { if(is_valid_sum(dict_attack_ctx->msb_par_sum)) { // All Hardnested nonces collected dict_attack_ctx->nested_target_key++; @@ -1975,6 +1968,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count++; instance->state = MfClassicPollerStateNestedCollectNtEnc; } + dict_attack_ctx->msb_count = 0; dict_attack_ctx->msb_par_sum = 0; memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); return command; @@ -2038,6 +2032,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_target_key += 2; dict_attack_ctx->current_key_checked = false; } else { + dict_attack_ctx->msb_count = 0; dict_attack_ctx->msb_par_sum = 0; memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); dict_attack_ctx->nested_target_key++; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 0501de21e..818d19d0a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -80,6 +80,9 @@ typedef struct { uint8_t nested_phase; /**< Nested attack phase. */ uint8_t prng_type; /**< PRNG (weak or hard). */ uint8_t backdoor; /**< Backdoor type. */ + uint16_t nested_target_key; /**< Target key for nested attack. */ + uint16_t + msb_count; /**< Number of unique most significant bytes seen during Hardnested attack. */ } MfClassicPollerEventDataUpdate; /** 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 71c972c1a..7b9c1e73f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -179,6 +179,7 @@ typedef struct { uint8_t nt_enc_msb [32]; // Bit-packed array to track which unique most significant bytes have been seen (256 bits = 32 bytes) uint16_t msb_par_sum; // Sum of parity bits for each unique most significant byte + uint16_t msb_count; // Number of unique most significant bytes seen } MfClassicPollerDictAttackContext; typedef struct { From 96606dc36f5d567faede99d68e59fb6792a8d6c5 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 18 Sep 2024 12:52:22 -0400 Subject: [PATCH 50/63] Typo --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 3d902fb58..4ec313f95 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -5,7 +5,7 @@ #define TAG "NfcMfClassicDictAttack" -// TODO: Fix lag when leaving the dictionary attack view during Hardnested +// TODO: Fix lag when leaving the dictionary attack view after Hardnested typedef enum { DictAttackStateUserDictInProgress, From 6eccdc8f93cb9493ba5f8b8aa83b0f2dda2de93c Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 20 Sep 2024 11:53:04 -0400 Subject: [PATCH 51/63] Zero nested_target_key and msb_count on exit --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 4ec313f95..a430613ef 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -304,6 +304,8 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.nested_phase = MfClassicNestedPhaseNone; instance->nfc_dict_context.prng_type = MfClassicPrngTypeUnknown; instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown; + instance->nfc_dict_context.nested_target_key = 0; + instance->nfc_dict_context.msb_count = 0; nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); From c21b35951a08ffa3d270ca028521a6a0262fdbce Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 20 Sep 2024 13:09:04 -0400 Subject: [PATCH 52/63] Note TODO (malloc) --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index cf61db09f..538ff1c53 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -9,6 +9,7 @@ // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary // TODO: Fix rare nested_target_key 64 bug +// TODO: Dead code for malloc returning NULL? #define MF_CLASSIC_MAX_BUFF_SIZE (64) From 6a77ab77b07436cc79bc13d7612223f54f1122ff Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 14:26:19 -0400 Subject: [PATCH 53/63] Dismiss duplicate nonces --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 538ff1c53..61a85cf8b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1383,6 +1383,13 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst uint32_t nt_prev = 0, decrypted_nt_prev = 0, found_nt = 0; uint16_t dist = 0; if(is_weak && !(dict_attack_ctx->static_encrypted)) { + // Ensure this isn't the same nonce as the previous collection + if((dict_attack_ctx->nested_nonce.count == 1) && + (dict_attack_ctx->nested_nonce.nonces[0].nt_enc == nt_enc)) { + FURI_LOG_E(TAG, "Duplicate nonce, dismissing collection attempt"); + break; + } + // Decrypt the previous nonce nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); From 901bdf96921c4b2e9bc02e12c424d475a05bc9f2 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 15:54:50 -0400 Subject: [PATCH 54/63] Fix calibration (ensure values are within 3 standard deviations) --- .../protocols/mf_classic/mf_classic_poller.c | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 61a85cf8b..f254c8e06 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1110,10 +1110,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance } NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { - // TODO: Discard outliers (e.g. greater than 3 standard deviations) NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; + uint16_t distances[MF_CLASSIC_NESTED_CALIBRATION_COUNT - 1] = {0}; dict_attack_ctx->d_min = UINT16_MAX; dict_attack_ctx->d_max = 0; @@ -1230,25 +1230,22 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); - for(uint32_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; + uint8_t valid_distances = 0; + for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc( cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); - if(nth_successor != decrypted_nt_enc) { - continue; - } - if(collection_cycle > 0) { + if(nth_successor == decrypted_nt_enc) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); - if(i < dict_attack_ctx->d_min) dict_attack_ctx->d_min = i; - if(i > dict_attack_ctx->d_max) dict_attack_ctx->d_max = i; + distances[valid_distances++] = i; + nt_prev = nth_successor; + found = true; + break; } - nt_prev = nth_successor; - found = true; - break; } if(!found) { FURI_LOG_E( @@ -1260,9 +1257,49 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } } - // Some breathing room, doesn't account for overflows or static nested (FIXME) - dict_attack_ctx->d_min -= 3; - dict_attack_ctx->d_max += 3; + // Calculate median and standard deviation + if(valid_distances > 0) { + // Sort the distances array (bubble sort) + for(uint8_t i = 0; i < valid_distances - 1; i++) { + for(uint8_t j = 0; j < valid_distances - i - 1; j++) { + if(distances[j] > distances[j + 1]) { + uint16_t temp = distances[j]; + distances[j] = distances[j + 1]; + distances[j + 1] = temp; + } + } + } + + // Calculate median + uint16_t median = + (valid_distances % 2 == 0) ? + (distances[valid_distances / 2 - 1] + distances[valid_distances / 2]) / 2 : + distances[valid_distances / 2]; + + // Calculate standard deviation + float sum = 0, sum_sq = 0; + for(uint8_t i = 0; i < valid_distances; i++) { + sum += distances[i]; + sum_sq += (float)distances[i] * distances[i]; + } + float mean = sum / valid_distances; + float variance = (sum_sq / valid_distances) - (mean * mean); + float std_dev = sqrtf(variance); + + // Filter out values over 3 standard deviations away from the median + dict_attack_ctx->d_min = UINT16_MAX; + dict_attack_ctx->d_max = 0; + for(uint8_t i = 0; i < valid_distances; i++) { + if(fabsf((float)distances[i] - median) <= 3 * std_dev) { + if(distances[i] < dict_attack_ctx->d_min) dict_attack_ctx->d_min = distances[i]; + if(distances[i] > dict_attack_ctx->d_max) dict_attack_ctx->d_max = distances[i]; + } + } + + // Some breathing room + dict_attack_ctx->d_min = (dict_attack_ctx->d_min > 3) ? dict_attack_ctx->d_min - 3 : 0; + dict_attack_ctx->d_max += 3; + } furi_assert(dict_attack_ctx->d_min <= dict_attack_ctx->d_max); dict_attack_ctx->calibrated = true; From 4eb0f2a21b17b8beba51082fa42ebdc32d17c311 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 18:03:15 -0400 Subject: [PATCH 55/63] Log static --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index f254c8e06..d7d8ee904 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1306,12 +1306,13 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); + uint16_t d_dist = dict_attack_ctx->d_max - dict_attack_ctx->d_min; FURI_LOG_E( TAG, "Calibration completed: min=%u max=%u static=%s", dict_attack_ctx->d_min, dict_attack_ctx->d_max, - (dict_attack_ctx->d_min == dict_attack_ctx->d_max) ? "true" : "false"); + ((d_dist >= 3) && (d_dist <= 6)) ? "true" : "false"); return command; } From 6ae950673e36244deb25e39f5949125c0eeb1f16 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 19:06:08 -0400 Subject: [PATCH 56/63] No nested dictionary attack re-entry --- .../main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index a430613ef..434680d54 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -219,7 +219,9 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventDictAttackComplete) { - if(state == DictAttackStateUserDictInProgress) { + bool ran_nested_dict = instance->nfc_dict_context.nested_phase != + MfClassicNestedPhaseNone; + if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->nfc_dict_context.dict); @@ -248,7 +250,9 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent } else if(event.event == NfcCustomEventDictAttackSkip) { const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); - if(state == DictAttackStateUserDictInProgress) { + bool ran_nested_dict = instance->nfc_dict_context.nested_phase != + MfClassicNestedPhaseNone; + if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { if(instance->nfc_dict_context.is_card_present) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); @@ -266,7 +270,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent dolphin_deed(DolphinDeedNfcReadSuccess); } consumed = true; - } else if(state == DictAttackStateSystemDictInProgress) { + } else { nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); From cd76926c746f746162cf9f0093f6df029bb79f96 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 19:13:20 -0400 Subject: [PATCH 57/63] Note minor inefficiency --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 434680d54..6340eebf5 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -6,6 +6,7 @@ #define TAG "NfcMfClassicDictAttack" // TODO: Fix lag when leaving the dictionary attack view after Hardnested +// TODO: Re-enters backdoor detection between user and system dictionary if no backdoor is found typedef enum { DictAttackStateUserDictInProgress, From 0ba8ac4ed01e459a79f608e8b49439b1c283c254 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 19:44:08 -0400 Subject: [PATCH 58/63] Uniformly use crypto1_ prefix for symbols in Crypto1 API --- lib/nfc/helpers/crypto1.c | 17 ++++++++------- lib/nfc/helpers/crypto1.h | 10 ++++----- .../mf_classic/mf_classic_listener.c | 9 +++++--- .../protocols/mf_classic/mf_classic_poller.c | 21 ++++++++++--------- targets/f7/api_symbols.csv | 10 ++++----- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index e59657a40..0f2b48e4e 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -82,7 +82,7 @@ uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { return out; } -uint32_t prng_successor(uint32_t x, uint32_t n) { +uint32_t crypto1_prng_successor(uint32_t x, uint32_t n) { SWAPENDIAN(x); while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; @@ -169,9 +169,9 @@ void crypto1_encrypt_reader_nonce( nr[i] = byte; } - nt_num = prng_successor(nt_num, 32); + nt_num = crypto1_prng_successor(nt_num, 32); for(size_t i = 4; i < 8; i++) { - nt_num = prng_successor(nt_num, 8); + nt_num = crypto1_prng_successor(nt_num, 8); uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num); bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01); bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); @@ -198,7 +198,7 @@ static uint8_t lfsr_rollback_bit(Crypto1* crypto1, uint32_t in, int fb) { return ret; } -uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { +uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { uint32_t ret = 0; for(int i = 31; i >= 0; i--) { ret |= lfsr_rollback_bit(crypto1, BEBIT(in, i), fb) << (24 ^ i); @@ -206,7 +206,7 @@ uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { return ret; } -bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { +bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { return (nfc_util_even_parity8((nt >> 24) & 0xFF) == (((nt_par_enc >> 3) & 1) ^ FURI_BIT(ks, 16))) && (nfc_util_even_parity8((nt >> 16) & 0xFF) == @@ -215,7 +215,7 @@ bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_pa (((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0))); } -bool is_weak_prng_nonce(uint32_t nonce) { +bool crypto1_is_weak_prng_nonce(uint32_t nonce) { if(nonce == 0) return false; uint16_t x = nonce >> 16; x = (x & 0xff) << 8 | x >> 8; @@ -226,11 +226,12 @@ bool is_weak_prng_nonce(uint32_t nonce) { return x == (nonce & 0xFFFF); } -uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { +uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { uint64_t known_key_int = bit_lib_bytes_to_num_be(known_key.data, 6); Crypto1 crypto_temp; crypto1_init(&crypto_temp, known_key_int); crypto1_word(&crypto_temp, nt_enc ^ cuid, 1); - uint32_t decrypted_nt_enc = (nt_enc ^ lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); + uint32_t decrypted_nt_enc = + (nt_enc ^ crypto1_lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); return decrypted_nt_enc; } diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index 26862ab49..a0ab77f66 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -39,15 +39,15 @@ void crypto1_encrypt_reader_nonce( BitBuffer* out, bool is_nested); -uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); +uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); -bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); +bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); -bool is_weak_prng_nonce(uint32_t nonce); +bool crypto1_is_weak_prng_nonce(uint32_t nonce); -uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); +uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); -uint32_t prng_successor(uint32_t x, uint32_t n); +uint32_t crypto1_prng_successor(uint32_t x, uint32_t n); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index 7e4f4725b..ef571117a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -157,14 +157,17 @@ static MfClassicListenerCommand uint32_t nt_num = bit_lib_bytes_to_num_be(instance->auth_context.nt.data, sizeof(MfClassicNt)); uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); - if(secret_poller != prng_successor(nt_num, 64)) { + if(secret_poller != crypto1_prng_successor(nt_num, 64)) { FURI_LOG_T( - TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64)); + TAG, + "Wrong reader key: %08lX != %08lX", + secret_poller, + crypto1_prng_successor(nt_num, 64)); command = MfClassicListenerCommandSleep; break; } - uint32_t at_num = prng_successor(nt_num, 96); + uint32_t at_num = crypto1_prng_successor(nt_num, 96); bit_lib_num_to_bytes_be(at_num, sizeof(uint32_t), instance->auth_context.at.data); bit_buffer_copy_bytes( instance->tx_plain_buffer, instance->auth_context.at.data, sizeof(MfClassicAr)); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index d7d8ee904..be08b0698 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1061,7 +1061,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan for(uint8_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; - if(!is_weak_prng_nonce(nonce->nt)) hard_nt_count++; + if(!crypto1_is_weak_prng_nonce(nonce->nt)) hard_nt_count++; } if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) { @@ -1174,7 +1174,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); // Store the decrypted static encrypted nonce dict_attack_ctx->static_encrypted_nonce = - decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); + crypto1_decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); dict_attack_ctx->calibrated = true; @@ -1234,10 +1234,10 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { bool found = false; - uint32_t decrypted_nt_enc = decrypt_nt_enc( + uint32_t decrypted_nt_enc = crypto1_decrypt_nt_enc( cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); for(int i = 0; i < 65535; i++) { - uint32_t nth_successor = prng_successor(nt_prev, i); + uint32_t nth_successor = crypto1_prng_successor(nt_prev, i); if(nth_successor == decrypted_nt_enc) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); @@ -1430,15 +1430,16 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Decrypt the previous nonce nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); + decrypted_nt_prev = + crypto1_decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); // Find matching nt_enc plain at expected distance found_nt = 0; uint8_t found_nt_cnt = 0; uint16_t current_dist = dict_attack_ctx->d_min; while(current_dist <= dict_attack_ctx->d_max) { - uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); - if(nonce_matches_encrypted_parity_bits( + uint32_t nth_successor = crypto1_prng_successor(decrypted_nt_prev, current_dist); + if(crypto1_nonce_matches_encrypted_parity_bits( nth_successor, nth_successor ^ nt_enc, parity)) { found_nt_cnt++; if(found_nt_cnt > 1) { @@ -1535,13 +1536,13 @@ static MfClassicKey* search_dicts_for_nonce_key( bool full_match = true; for(uint8_t j = 0; j < nonce_array->count; j++) { // Verify nonce matches encrypted parity bits for all nonces - uint32_t nt_enc_plain = decrypt_nt_enc( + uint32_t nt_enc_plain = crypto1_decrypt_nt_enc( nonce_array->nonces[j].cuid, nonce_array->nonces[j].nt_enc, stack_key); if(is_weak) { - full_match &= is_weak_prng_nonce(nt_enc_plain); + full_match &= crypto1_is_weak_prng_nonce(nt_enc_plain); if(!full_match) break; } - full_match &= nonce_matches_encrypted_parity_bits( + full_match &= crypto1_nonce_matches_encrypted_parity_bits( nt_enc_plain, nt_enc_plain ^ nonce_array->nonces[j].nt_enc, nonce_array->nonces[j].par); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index cafd1f150..942fbec7c 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -889,10 +889,15 @@ Function,+,crypto1_alloc,Crypto1*, Function,+,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" Function,+,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" Function,+,crypto1_decrypt,void,"Crypto1*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,crypto1_encrypt,void,"Crypto1*, uint8_t*, const BitBuffer*, BitBuffer*" Function,+,crypto1_encrypt_reader_nonce,void,"Crypto1*, uint64_t, uint32_t, uint8_t*, uint8_t*, BitBuffer*, _Bool" Function,+,crypto1_free,void,Crypto1* Function,+,crypto1_init,void,"Crypto1*, uint64_t" +Function,+,crypto1_is_weak_prng_nonce,_Bool,uint32_t +Function,+,crypto1_lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" +Function,+,crypto1_nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" +Function,+,crypto1_prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,crypto1_reset,void,Crypto1* Function,+,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* @@ -903,7 +908,6 @@ Function,+,datetime_get_days_per_year,uint16_t,uint16_t Function,+,datetime_is_leap_year,_Bool,uint16_t Function,+,datetime_timestamp_to_datetime,void,"uint32_t, DateTime*" Function,+,datetime_validate_datetime,_Bool,DateTime* -Function,+,decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -2041,7 +2045,6 @@ Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType Function,-,iprintf,int,"const char*, ..." -Function,+,is_weak_prng_nonce,_Bool,uint32_t Function,-,isalnum,int,int Function,-,isalnum_l,int,"int, locale_t" Function,-,isalpha,int,int @@ -2225,7 +2228,6 @@ Function,+,lfrfid_worker_start_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_stop,void,LFRFIDWorker* Function,+,lfrfid_worker_stop_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_write_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" -Function,+,lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,lgamma,double,double Function,-,lgamma_r,double,"double, int*" Function,-,lgammaf,float,float @@ -2822,7 +2824,6 @@ Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_even_parity8,uint8_t,uint8_t Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t -Function,+,nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -2935,7 +2936,6 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." -Function,+,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" From 61e24fcb13eb7680490c3ac344f701625b1ba715 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 21:55:39 -0400 Subject: [PATCH 59/63] Fix include paths --- lib/nfc/helpers/crypto1.h | 2 +- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index a0ab77f66..0e358581a 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -1,6 +1,6 @@ #pragma once -#include "protocols/mf_classic/mf_classic.h" +#include #include #ifdef __cplusplus 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 7b9c1e73f..f0175b25b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,12 +3,12 @@ #include "mf_classic_poller.h" #include #include -#include "nfc/helpers/iso14443_crc.h" +#include #include #include #include #include "keys_dict.h" -#include "helpers/nfc_util.h" +#include #ifdef __cplusplus extern "C" { From 099bb4071a8196858a6304d2a963d91ea3b60009 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 24 Sep 2024 22:21:15 -0400 Subject: [PATCH 60/63] Fix include paths cont --- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f0175b25b..4056ba30c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -7,7 +7,7 @@ #include #include #include -#include "keys_dict.h" +#include #include #ifdef __cplusplus From ba672e775f14b5dbcc25b5a7c97c24b245162d68 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 25 Sep 2024 10:27:32 -0400 Subject: [PATCH 61/63] Support CUID dictionary --- applications/main/nfc/nfc_app_i.h | 1 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 74 +++++++++++++++++-- .../protocols/mf_classic/mf_classic_poller.c | 15 ++-- .../protocols/mf_classic/mf_classic_poller.h | 3 +- .../mf_classic/mf_classic_poller_i.h | 1 + 5 files changed, 83 insertions(+), 11 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 4aacdd19b..9656eae11 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -102,6 +102,7 @@ typedef struct { uint8_t backdoor; uint16_t nested_target_key; uint16_t msb_count; + bool enhanced_dict; } NfcMfClassicDictAttackContext; struct NfcApp { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 6340eebf5..4c0459e74 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -1,5 +1,6 @@ #include "../nfc_app_i.h" +#include #include #include @@ -9,6 +10,7 @@ // TODO: Re-enters backdoor detection between user and system dictionary if no backdoor is found typedef enum { + DictAttackStateCUIDDictInProgress, DictAttackStateUserDictInProgress, DictAttackStateSystemDictInProgress, } DictAttackState; @@ -32,7 +34,9 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { const MfClassicData* mfc_data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); - mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack; + mfc_event->data->poller_mode.mode = (instance->nfc_dict_context.enhanced_dict) ? + MfClassicPollerModeDictAttackEnhanced : + MfClassicPollerModeDictAttackStandard; mfc_event->data->poller_mode.data = mfc_data; instance->nfc_dict_context.sectors_total = mf_classic_get_total_sectors_num(mfc_data->type); @@ -136,8 +140,37 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); + if(state == DictAttackStateCUIDDictInProgress) { + do { + size_t cuid_len = 0; + const uint8_t* cuid = nfc_device_get_uid(instance->nfc_device, &cuid_len); + FuriString* cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", + EXT_PATH("nfc/assets"), + (uint32_t)bit_lib_bytes_to_num_be(cuid + (cuid_len - 4), 4)); + + if(!keys_dict_check_presence(furi_string_get_cstr(cuid_dict_path))) { + state = DictAttackStateUserDictInProgress; + break; + } + + instance->nfc_dict_context.dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { + keys_dict_free(instance->nfc_dict_context.dict); + state = DictAttackStateUserDictInProgress; + break; + } + + dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary"); + } while(false); + } if(state == DictAttackStateUserDictInProgress) { do { + instance->nfc_dict_context.enhanced_dict = true; + // TODO: Check for errors storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); storage_common_copy( @@ -191,7 +224,7 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { NfcApp* instance = context; scene_manager_set_scene_state( - instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateCUIDDictInProgress); nfc_scene_mf_classic_dict_attack_prepare_view(instance); dict_attack_set_card_state(instance->dict_attack, true); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); @@ -222,7 +255,19 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent if(event.event == NfcCustomEventDictAttackComplete) { bool ran_nested_dict = instance->nfc_dict_context.nested_phase != MfClassicNestedPhaseNone; - if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { + if(state == DictAttackStateCUIDDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + consumed = true; + } else if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->nfc_dict_context.dict); @@ -253,7 +298,25 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); bool ran_nested_dict = instance->nfc_dict_context.nested_phase != MfClassicNestedPhaseNone; - if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { + if(state == DictAttackStateCUIDDictInProgress) { + if(instance->nfc_dict_context.is_card_present) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + } else { + nfc_scene_mf_classic_dict_attack_notify_read(instance); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } else if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { if(instance->nfc_dict_context.is_card_present) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); @@ -293,7 +356,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { dict_attack_reset(instance->dict_attack); scene_manager_set_scene_state( - instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateCUIDDictInProgress); keys_dict_free(instance->nfc_dict_context.dict); @@ -311,6 +374,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown; instance->nfc_dict_context.nested_target_key = 0; instance->nfc_dict_context.msb_count = 0; + instance->nfc_dict_context.enhanced_dict = false; nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index be08b0698..322526dfe 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -7,7 +7,7 @@ #define TAG "MfClassicPoller" // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches -// TODO: Load dictionaries specific to a CUID to not clutter the user dictionary +// TODO: Store target key in CUID dictionary // TODO: Fix rare nested_target_key 64 bug // TODO: Dead code for malloc returning NULL? @@ -163,7 +163,10 @@ NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { instance->mfc_event.type = MfClassicPollerEventTypeRequestMode; command = instance->callback(instance->general_event, instance->context); - if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) { + if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackStandard) { + mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); + instance->state = MfClassicPollerStateRequestKey; + } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackEnhanced) { mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); instance->state = MfClassicPollerStateAnalyzeBackdoor; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) { @@ -557,6 +560,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + instance->mode_ctx.dict_attack_ctx.enhanced_dict = true; size_t current_key_index = mf_classic_backdoor_keys_count - 1; // Default to the last valid index @@ -861,9 +865,10 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) command = instance->callback(instance->general_event, instance->context); // Nested entrypoint bool nested_active = dict_attack_ctx->nested_phase != MfClassicNestedPhaseNone; - if((nested_active && - (dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) || - (!(nested_active) && !(mf_classic_is_card_read(instance->data)))) { + if((dict_attack_ctx->enhanced_dict) && + ((nested_active && + (dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) || + (!(nested_active) && !(mf_classic_is_card_read(instance->data))))) { instance->state = MfClassicPollerStateNestedController; break; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 818d19d0a..5c2550b7e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -44,7 +44,8 @@ typedef enum { typedef enum { MfClassicPollerModeRead, /**< Poller reading mode. */ MfClassicPollerModeWrite, /**< Poller writing mode. */ - MfClassicPollerModeDictAttack, /**< Poller dictionary attack mode. */ + MfClassicPollerModeDictAttackStandard, /**< Poller dictionary attack mode. */ + MfClassicPollerModeDictAttackEnhanced, /**< Poller enhanced dictionary attack mode. */ } MfClassicPollerMode; /** 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 4056ba30c..7b05ce240 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -159,6 +159,7 @@ typedef struct { uint8_t reuse_key_sector; MfClassicBackdoor backdoor; // Enhanced dictionary attack and nested nonce collection + bool enhanced_dict; MfClassicNestedPhase nested_phase; MfClassicKey nested_known_key; MfClassicKeyType nested_known_key_type; From 00f356469efddfcbe347cf3a709918cd287be3b2 Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 3 Oct 2024 19:27:20 -0400 Subject: [PATCH 62/63] Fix log levels --- .../protocols/mf_classic/mf_classic_poller.c | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 322526dfe..1a2e43dee 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -580,7 +580,7 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count; uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1; - FURI_LOG_E(TAG, "Trying backdoor v%d", backdoor_version); + FURI_LOG_D(TAG, "Trying backdoor v%d", backdoor_version); dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key; // Attempt backdoor authentication @@ -588,11 +588,11 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); if((next_key_index == 0) && (error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) { - FURI_LOG_E(TAG, "No backdoor identified"); + FURI_LOG_D(TAG, "No backdoor identified"); dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; } else if(error == MfClassicErrorNone) { - FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version); + FURI_LOG_I(TAG, "Backdoor identified: v%d", backdoor_version); dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type; instance->state = MfClassicPollerStateBackdoorReadSector; } else if( @@ -1071,12 +1071,10 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) { dict_attack_ctx->prng_type = MfClassicPrngTypeHard; - // FIXME: E -> D - FURI_LOG_E(TAG, "Detected Hard PRNG"); + FURI_LOG_D(TAG, "Detected Hard PRNG"); } else { dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; - // FIXME: E -> D - FURI_LOG_E(TAG, "Detected Weak PRNG"); + FURI_LOG_D(TAG, "Detected Weak PRNG"); } instance->state = MfClassicPollerStateNestedController; @@ -1092,11 +1090,9 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance if(error != MfClassicErrorNone) { 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]); + FURI_LOG_T(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)); if(!add_nested_nonce( &dict_attack_ctx->nested_nonce, @@ -1150,7 +1146,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) return command; } - FURI_LOG_E(TAG, "Full authentication successful"); + FURI_LOG_D(TAG, "Full authentication successful"); nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); @@ -1183,7 +1179,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) dict_attack_ctx->calibrated = true; - FURI_LOG_E(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc); + FURI_LOG_D(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc); instance->state = MfClassicPollerStateNestedController; return command; @@ -1226,14 +1222,14 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } if(dict_attack_ctx->static_encrypted) { - FURI_LOG_E(TAG, "Static encrypted nonce detected"); + FURI_LOG_D(TAG, "Static encrypted nonce detected"); dict_attack_ctx->calibrated = true; instance->state = MfClassicPollerStateNestedController; return command; } // Find the distance between each nonce - FURI_LOG_E(TAG, "Calculating distance between nonces"); + FURI_LOG_D(TAG, "Calculating distance between nonces"); uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); uint8_t valid_distances = 0; for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; @@ -1244,8 +1240,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) for(int i = 0; i < 65535; i++) { uint32_t nth_successor = crypto1_prng_successor(nt_prev, i); if(nth_successor == decrypted_nt_enc) { - FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); - FURI_LOG_E(TAG, "dist from nt prev: %i", i); + FURI_LOG_D(TAG, "nt_enc (plain) %08lx", nth_successor); + FURI_LOG_D(TAG, "dist from nt prev: %i", i); distances[valid_distances++] = i; nt_prev = nth_successor; found = true; @@ -1312,7 +1308,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) mf_classic_poller_halt(instance); uint16_t d_dist = dict_attack_ctx->d_max - dict_attack_ctx->d_min; - FURI_LOG_E( + FURI_LOG_D( TAG, "Calibration completed: min=%u max=%u static=%s", dict_attack_ctx->d_min, @@ -1375,7 +1371,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst break; } - FURI_LOG_E(TAG, "Full authentication successful"); + FURI_LOG_D(TAG, "Full authentication successful"); // Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset // eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the @@ -1429,7 +1425,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Ensure this isn't the same nonce as the previous collection if((dict_attack_ctx->nested_nonce.count == 1) && (dict_attack_ctx->nested_nonce.nonces[0].nt_enc == nt_enc)) { - FURI_LOG_E(TAG, "Duplicate nonce, dismissing collection attempt"); + FURI_LOG_D(TAG, "Duplicate nonce, dismissing collection attempt"); break; } @@ -1448,7 +1444,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst nth_successor, nth_successor ^ nt_enc, parity)) { found_nt_cnt++; if(found_nt_cnt > 1) { - FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt"); + FURI_LOG_D(TAG, "Ambiguous nonce, dismissing collection attempt"); break; } found_nt = nth_successor; @@ -1489,24 +1485,24 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } - FURI_LOG_E( + FURI_LOG_D( TAG, "Target: %u (nonce pair %u, key type %s, block %u)", dict_attack_ctx->nested_target_key, nonce_pair_index, (target_key_type == MfClassicKeyTypeA) ? "A" : "B", target_block); - FURI_LOG_E(TAG, "cuid: %08lx", cuid); - FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); - FURI_LOG_E( + FURI_LOG_T(TAG, "cuid: %08lx", cuid); + FURI_LOG_T(TAG, "nt_enc: %08lx", nt_enc); + FURI_LOG_T( TAG, "parity: %u%u%u%u", ((parity >> 3) & 1), ((parity >> 2) & 1), ((parity >> 1) & 1), (parity & 1)); - FURI_LOG_E(TAG, "nt_enc prev: %08lx", nt_prev); - FURI_LOG_E(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); + FURI_LOG_T(TAG, "nt_enc prev: %08lx", nt_prev); + FURI_LOG_T(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); } while(false); instance->state = MfClassicPollerStateNestedController; @@ -1609,7 +1605,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc break; } - FURI_LOG_E(TAG, "Full authentication successful"); + FURI_LOG_D(TAG, "Full authentication successful"); // Step 2: Collect nested nt and parity error = mf_classic_poller_auth_nested( @@ -1661,7 +1657,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc dict_attack_ctx->mf_classic_user_dict, is_weak); if(key_candidate != NULL) { - FURI_LOG_E( + FURI_LOG_I( TAG, "Found key candidate %06llx", bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); @@ -1677,7 +1673,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc } } - FURI_LOG_E( + FURI_LOG_D( TAG, "Target: %u (key type %s, block %u) cuid: %08lx", dict_attack_ctx->nested_target_key, @@ -1942,7 +1938,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { // All keys have been collected - FURI_LOG_E(TAG, "All keys collected and sectors read"); + FURI_LOG_D(TAG, "All keys collected and sectors read"); dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; return command; @@ -2074,7 +2070,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (!(is_weak) && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) { // Unpredictable, skip - FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); + FURI_LOG_W(TAG, "Failed to collect nonce, skipping key"); if(dict_attack_ctx->nested_nonce.nonces) { free(dict_attack_ctx->nested_nonce.nonces); dict_attack_ctx->nested_nonce.nonces = NULL; From 4f722a00c06c7eb4816840fbe4e10a857e8db3dc Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 4 Oct 2024 18:41:29 -0400 Subject: [PATCH 63/63] Avoid storage errors, clean up temporary files --- .../scenes/nfc_scene_mf_classic_dict_attack.c | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 4c0459e74..2691999b0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -171,20 +171,25 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { do { instance->nfc_dict_context.enhanced_dict = true; - // TODO: Check for errors - storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); - storage_common_copy( - instance->storage, - NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, - NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) { + storage_common_remove( + instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH)) { + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { state = DictAttackStateSystemDictInProgress; break; } - // TODO: Check for errors - storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + } storage_common_copy( instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_PATH, @@ -376,6 +381,14 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.msb_count = 0; instance->nfc_dict_context.enhanced_dict = false; + // Clean up temporary files used for nested dictionary attack + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + } + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } + nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); }