Merge branch 'nestednonces' into ofw-3822-nestednonces

This commit is contained in:
Willy-JL
2024-09-18 02:12:43 +01:00
4 changed files with 86 additions and 37 deletions

View File

@@ -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: Fix rare nested_target_key 64 bug
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
@@ -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);
}
@@ -560,7 +580,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;
@@ -837,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;
}
@@ -1130,11 +1152,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);
@@ -1209,7 +1233,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) {
@@ -1265,7 +1288,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 {
@@ -1283,11 +1306,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;
@@ -1488,9 +1512,9 @@ 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 = NfcCommandReset;
NfcCommand command = NfcCommandContinue;
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
do {
@@ -1510,9 +1534,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)) ||
@@ -1648,11 +1671,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++) {
@@ -1742,8 +1770,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;
@@ -1773,6 +1800,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;
@@ -1860,9 +1888,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)) {
@@ -1872,6 +1902,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;
@@ -1895,24 +1929,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) &&
@@ -1946,10 +1979,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 {
@@ -1958,28 +1994,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))) {
@@ -2001,6 +2045,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;

View File

@@ -54,6 +54,7 @@ typedef enum {
MfClassicNestedPhaseDictAttack,
MfClassicNestedPhaseDictAttackResume,
MfClassicNestedPhaseCalibrate,
MfClassicNestedPhaseRecalibrate,
MfClassicNestedPhaseCollectNtEnc,
MfClassicNestedPhaseFinished,
} MfClassicNestedPhase;