Hardnested support

This commit is contained in:
noproto
2024-09-01 22:30:37 -04:00
parent 144424e04a
commit 90d0c3d095
2 changed files with 140 additions and 51 deletions
+129 -50
View File
@@ -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;
@@ -8,6 +8,7 @@
#include <stream/stream.h>
#include <stream/buffered_file_stream.h>
#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 {