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 {