mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-02 12:13:16 +03:00
Refactor to nested dictionary attack
This commit is contained in:
parent
79bc887f95
commit
0af28fb221
@ -681,8 +681,9 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance)
|
|||||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if(!dict_attack_ctx->prng_type) {
|
// Nested entrypoint
|
||||||
instance->state = MfClassicPollerStateNestedAnalyzePRNG;
|
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) {
|
||||||
|
instance->state = MfClassicPollerStateNestedController;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -840,13 +841,17 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst
|
|||||||
static bool add_nested_nonce(
|
static bool add_nested_nonce(
|
||||||
MfClassicNestedNonceArray* array,
|
MfClassicNestedNonceArray* array,
|
||||||
uint32_t cuid,
|
uint32_t cuid,
|
||||||
uint8_t key_idx,
|
uint16_t key_idx,
|
||||||
uint32_t nt,
|
uint32_t nt,
|
||||||
uint32_t nt_enc,
|
uint32_t nt_enc,
|
||||||
uint8_t par,
|
uint8_t par,
|
||||||
uint16_t dist) {
|
uint16_t dist) {
|
||||||
MfClassicNestedNonce* new_nonces =
|
MfClassicNestedNonce* new_nonces;
|
||||||
realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce));
|
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;
|
if(new_nonces == NULL) return false;
|
||||||
|
|
||||||
array->nonces = new_nonces;
|
array->nonces = new_nonces;
|
||||||
@ -860,23 +865,34 @@ static bool add_nested_nonce(
|
|||||||
return true;
|
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 mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instance) {
|
||||||
NfcCommand command = NfcCommandContinue;
|
NfcCommand command = NfcCommandContinue;
|
||||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||||
|
uint8_t hard_nt_count = 0;
|
||||||
|
|
||||||
// Analyze PRNG by collecting nt
|
for(uint8_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) {
|
||||||
uint8_t nonce_limit = 5;
|
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 > 0) {
|
|
||||||
if(!is_weak_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dict_attack_ctx->nt_count < nonce_limit) {
|
if(hard_nt_count >= MF_CLASSIC_NESTED_HARD_MINIMUM) {
|
||||||
instance->state = MfClassicPollerStateNestedCollectNt;
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dict_attack_ctx->hard_nt_count >= 3) {
|
|
||||||
dict_attack_ctx->prng_type = MfClassicPrngTypeHard;
|
dict_attack_ctx->prng_type = MfClassicPrngTypeHard;
|
||||||
// FIXME: E -> D
|
// FIXME: E -> D
|
||||||
FURI_LOG_E(TAG, "Detected Hard PRNG");
|
FURI_LOG_E(TAG, "Detected Hard PRNG");
|
||||||
@ -900,7 +916,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in
|
|||||||
|
|
||||||
MfClassicAuthContext auth_ctx = {};
|
MfClassicAuthContext auth_ctx = {};
|
||||||
MfClassicNt nt = {};
|
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;
|
MfClassicError error;
|
||||||
Iso14443_3aError iso_error;
|
Iso14443_3aError iso_error;
|
||||||
bool backdoor_found = false;
|
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));
|
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));
|
uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt));
|
||||||
// Ensure the encrypted nt can be generated by the backdoor
|
// 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);
|
backdoor_found = is_weak_prng_nonce(decrypted_nt_enc);
|
||||||
} while(false);
|
} while(false);
|
||||||
if(backdoor_found) {
|
if(backdoor_found) {
|
||||||
FURI_LOG_E(TAG, "Backdoor identified");
|
FURI_LOG_E(TAG, "Backdoor identified");
|
||||||
dict_attack_ctx->backdoor = MfClassicBackdoorFM11RF08S;
|
dict_attack_ctx->backdoor = MfClassicBackdoorAuth2;
|
||||||
} else {
|
} else {
|
||||||
dict_attack_ctx->backdoor = MfClassicBackdoorNone;
|
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);
|
MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt);
|
||||||
|
|
||||||
if(error != MfClassicErrorNone) {
|
if(error != MfClassicErrorNone) {
|
||||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
|
||||||
dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag;
|
dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag;
|
||||||
// FIXME: E -> D
|
// FIXME: E -> D
|
||||||
FURI_LOG_E(TAG, "Failed to collect nt");
|
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
|
// 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_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));
|
uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt));
|
||||||
dict_attack_ctx->nt_prev = nt_data;
|
if(!add_nested_nonce(
|
||||||
dict_attack_ctx->nt_count++;
|
&dict_attack_ctx->nested_nonce,
|
||||||
instance->state = MfClassicPollerStateNestedAnalyzePRNG;
|
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;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1057,7 +1080,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance)
|
|||||||
|
|
||||||
if(dict_attack_ctx->static_encrypted) {
|
if(dict_attack_ctx->static_encrypted) {
|
||||||
FURI_LOG_E(TAG, "Static encrypted nonce detected");
|
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
|
// TODO: Backdoor static nested attack calibration
|
||||||
dict_attack_ctx->calibrated = true;
|
dict_attack_ctx->calibrated = true;
|
||||||
instance->state = MfClassicPollerStateNestedController;
|
instance->state = MfClassicPollerStateNestedController;
|
||||||
@ -1139,7 +1162,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
|
|||||||
|
|
||||||
if(dict_attack_ctx->static_encrypted) {
|
if(dict_attack_ctx->static_encrypted) {
|
||||||
FURI_LOG_E(TAG, "Static encrypted nonce detected");
|
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
|
// TODO: Backdoor static nested attack with calibrated distance
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
@ -1300,46 +1323,50 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
|
|||||||
return command;
|
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* system_dict,
|
||||||
KeysDict* user_dict,
|
KeysDict* user_dict,
|
||||||
uint32_t cuid,
|
bool is_weak) {
|
||||||
uint32_t nt_enc) {
|
|
||||||
MfClassicKey stack_key;
|
MfClassicKey stack_key;
|
||||||
KeysDict* dicts[] = {system_dict, user_dict};
|
KeysDict* dicts[] = {user_dict, system_dict};
|
||||||
|
|
||||||
for(int i = 0; i < 2; i++) {
|
for(int i = 0; i < 2; i++) {
|
||||||
keys_dict_rewind(dicts[i]);
|
keys_dict_rewind(dicts[i]);
|
||||||
while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) {
|
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))) {
|
bool full_match = true;
|
||||||
MfClassicKey* heap_key = malloc(sizeof(MfClassicKey));
|
for(uint8_t j = 0; j < nonce_array->count; j++) {
|
||||||
if(heap_key) {
|
// Verify nonce matches encrypted parity bits for all nonces
|
||||||
memcpy(heap_key, &stack_key, sizeof(MfClassicKey));
|
uint32_t nt_enc_plain = decrypt_nt_enc(
|
||||||
return heap_key;
|
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) {
|
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_dict_target_key)
|
||||||
// TODO: Look into using MfClassicNt more
|
// TODO: Look into using MfClassicNt more
|
||||||
|
// TODO: A method to try the key candidates when we've collected sufficient nonces
|
||||||
NfcCommand command = NfcCommandReset;
|
NfcCommand command = NfcCommandReset;
|
||||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||||
|
|
||||||
do {
|
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 =
|
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->reuse_key_sector);
|
||||||
uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
|
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 = {};
|
MfClassicAuthContext auth_ctx = {};
|
||||||
MfClassicError error;
|
MfClassicError error;
|
||||||
|
|
||||||
MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_dict_target_key % 2) == 0) ?
|
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
|
||||||
MfClassicKeyTypeA :
|
bool is_last_iter_for_hard_key =
|
||||||
MfClassicKeyTypeB;
|
((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7));
|
||||||
uint8_t target_block = (4 * (dict_attack_ctx->nested_dict_target_key / 2)) + 3;
|
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;
|
uint8_t parity = 0;
|
||||||
|
|
||||||
// Step 1: Perform full authentication once
|
if(((is_weak) && (dict_attack_ctx->nested_key_candidates.count == 0)) ||
|
||||||
error = mf_classic_poller_auth(
|
((!is_weak) && (!is_last_iter_for_hard_key))) {
|
||||||
instance,
|
// Step 1: Perform full authentication once
|
||||||
block,
|
error = mf_classic_poller_auth(
|
||||||
&dict_attack_ctx->current_key,
|
instance,
|
||||||
dict_attack_ctx->current_key_type,
|
block,
|
||||||
&auth_ctx);
|
&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 full authentication");
|
FURI_LOG_E(TAG, "Failed to perform full authentication");
|
||||||
dict_attack_ctx->nested_state = MfClassicNestedStateFailed;
|
dict_attack_ctx->nested_state = MfClassicNestedStateFailed;
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// If we have sufficient nonces, search the dictionaries for the key
|
||||||
FURI_LOG_E(TAG, "Full authentication successful");
|
if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) ||
|
||||||
|
(is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) {
|
||||||
// Step 2: Collect nested nt and parity
|
// Identify candidate keys (there may be multiple) and validate them to the currently tested sector
|
||||||
error = mf_classic_poller_auth_nested(
|
// stopping on the first valid key
|
||||||
instance,
|
search_dicts_for_nonce_key(
|
||||||
target_block,
|
&dict_attack_ctx->nested_key_candidates,
|
||||||
&dict_attack_ctx->current_key,
|
&dict_attack_ctx->nested_nonce,
|
||||||
target_key_type,
|
dict_attack_ctx->mf_classic_system_dict,
|
||||||
&auth_ctx,
|
dict_attack_ctx->mf_classic_user_dict,
|
||||||
true);
|
is_weak);
|
||||||
|
for(uint8_t i = 0; i < dict_attack_ctx->nested_key_candidates.count; i++) {
|
||||||
// TODO: Check error? If there is one, return MfClassicNestedStateFailed
|
FURI_LOG_E(
|
||||||
|
TAG,
|
||||||
uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
"Found key candidate %06llx",
|
||||||
// Collect parity bits
|
bit_lib_bytes_to_num_be(
|
||||||
const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer);
|
dict_attack_ctx->nested_key_candidates.key_candidates[i].data,
|
||||||
for(int i = 0; i < 4; i++) {
|
sizeof(MfClassicKey)));
|
||||||
parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01);
|
// TODO: Add to found keys in dictionary attack struct ONCE AUTH IS VALIDATED
|
||||||
}
|
}
|
||||||
MfClassicKey* found_key = search_dicts_for_weak_nonce_key(
|
// FIXME
|
||||||
dict_attack_ctx->mf_classic_system_dict,
|
free(dict_attack_ctx->nested_key_candidates.key_candidates);
|
||||||
dict_attack_ctx->mf_classic_user_dict,
|
dict_attack_ctx->nested_key_candidates.key_candidates = NULL;
|
||||||
cuid,
|
dict_attack_ctx->nested_key_candidates.count = 0;
|
||||||
nt_enc);
|
free(dict_attack_ctx->nested_nonce.nonces);
|
||||||
if(found_key) {
|
dict_attack_ctx->nested_nonce.nonces = NULL;
|
||||||
uint64_t k = bit_lib_bytes_to_num_be(found_key->data, sizeof(MfClassicKey));
|
dict_attack_ctx->nested_nonce.count = 0;
|
||||||
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(
|
FURI_LOG_E(
|
||||||
TAG,
|
TAG,
|
||||||
"Target: %u (key type %s, block %u)",
|
"Target: %u (key type %s, block %u) cuid: %08lx",
|
||||||
dict_attack_ctx->nested_target_key,
|
dict_attack_ctx->nested_target_key,
|
||||||
(target_key_type == MfClassicKeyTypeA) ? "A" : "B",
|
(target_key_type == MfClassicKeyTypeA) ? "A" : "B",
|
||||||
target_block);
|
target_block,
|
||||||
FURI_LOG_E(TAG, "cuid: %08lx", cuid);
|
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);
|
} while(false);
|
||||||
|
|
||||||
dict_attack_ctx->nested_state = MfClassicNestedStatePassed;
|
dict_attack_ctx->nested_state = MfClassicNestedStatePassed;
|
||||||
@ -1493,6 +1562,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) {
|
|||||||
|
|
||||||
furi_assert(params_saved);
|
furi_assert(params_saved);
|
||||||
free(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_nonce.count = 0;
|
||||||
furi_string_free(temp_str);
|
furi_string_free(temp_str);
|
||||||
buffered_file_stream_close(stream);
|
buffered_file_stream_close(stream);
|
||||||
@ -1502,53 +1572,95 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) {
|
|||||||
return command;
|
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) {
|
NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) {
|
||||||
// Iterate through keys
|
// Iterate through keys
|
||||||
NfcCommand command = NfcCommandContinue;
|
NfcCommand command = NfcCommandContinue;
|
||||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||||
if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) &&
|
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) {
|
||||||
(dict_attack_ctx->nested_nonce.count == 2)) {
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG;
|
||||||
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) {
|
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) {
|
||||||
instance->state = MfClassicPollerStateNestedAnalyzeBackdoor;
|
if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) {
|
||||||
return command;
|
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
|
// Accelerated Nested dictionary attack
|
||||||
if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) &&
|
uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ?
|
||||||
(dict_attack_ctx->nested_dict_target_key <= (instance->sectors_total * 2))) {
|
(instance->sectors_total * 2) :
|
||||||
if(dict_attack_ctx->nested_dict_target_key == (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) {
|
if(dict_attack_ctx->mf_classic_system_dict) {
|
||||||
keys_dict_free(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) {
|
if(dict_attack_ctx->mf_classic_user_dict) {
|
||||||
keys_dict_free(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;
|
instance->state = MfClassicPollerStateNestedController;
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) {
|
if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) {
|
||||||
dict_attack_ctx->attempt_count++;
|
dict_attack_ctx->attempt_count++;
|
||||||
} else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) {
|
} 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->attempt_count = 0;
|
||||||
}
|
}
|
||||||
dict_attack_ctx->nested_state = MfClassicNestedStateNone;
|
dict_attack_ctx->nested_state = MfClassicNestedStateNone;
|
||||||
if(dict_attack_ctx->attempt_count >= 3) {
|
if(dict_attack_ctx->attempt_count >= 3) {
|
||||||
// Unpredictable, skip
|
// Unpredictable, skip
|
||||||
FURI_LOG_E(TAG, "Failed to collect nonce, skipping key");
|
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;
|
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
|
// Note: System dict should always exist
|
||||||
bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH);
|
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);
|
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;
|
instance->state = MfClassicPollerStateNestedDictAttack;
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
// TODO: Skip all remaining phases if we have collected all keys
|
// Analyze tag for NXP/Fudan backdoor
|
||||||
// TODO: Need to think about how this works for Fudan backdoored tags.
|
if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) {
|
||||||
// We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too.
|
dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor;
|
||||||
if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak && !dict_attack_ctx->calibrated) {
|
instance->state = MfClassicPollerStateNestedAnalyzeBackdoor;
|
||||||
instance->state = MfClassicPollerStateNestedCalibrate;
|
|
||||||
return command;
|
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
|
// 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_target_key < (instance->sectors_total * 4)) {
|
||||||
if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) {
|
if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) {
|
||||||
dict_attack_ctx->attempt_count++;
|
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->attempt_count = 0;
|
||||||
}
|
}
|
||||||
dict_attack_ctx->nested_state = MfClassicNestedStateNone;
|
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
|
// Unpredictable, skip
|
||||||
FURI_LOG_E(TAG, "Failed to collect nonce, skipping key");
|
FURI_LOG_E(TAG, "Failed to collect nonce, skipping key");
|
||||||
if(dict_attack_ctx->nested_nonce.nonces) {
|
if(dict_attack_ctx->nested_nonce.nonces) {
|
||||||
free(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_nonce.count = 0;
|
||||||
}
|
}
|
||||||
dict_attack_ctx->nested_target_key += 2;
|
dict_attack_ctx->nested_target_key += 2;
|
||||||
|
@ -16,6 +16,9 @@ extern "C" {
|
|||||||
#define MF_CLASSIC_FWT_FC (60000)
|
#define MF_CLASSIC_FWT_FC (60000)
|
||||||
#define NFC_FOLDER EXT_PATH("nfc")
|
#define NFC_FOLDER EXT_PATH("nfc")
|
||||||
#define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets")
|
#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_LOGS_FILE_NAME ".nested.log"
|
||||||
#define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc"
|
#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_USER_DICT_FILE_NAME "mf_classic_dict_user_nested.nfc"
|
||||||
@ -41,6 +44,16 @@ typedef enum {
|
|||||||
MfClassicNestedStatePassed,
|
MfClassicNestedStatePassed,
|
||||||
} MfClassicNestedState;
|
} MfClassicNestedState;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MfClassicNestedPhaseNone,
|
||||||
|
MfClassicNestedPhaseAnalyzePRNG,
|
||||||
|
MfClassicNestedPhaseDictAttack,
|
||||||
|
MfClassicNestedPhaseAnalyzeBackdoor,
|
||||||
|
MfClassicNestedPhaseCalibrate,
|
||||||
|
MfClassicNestedPhaseCollectNtEnc,
|
||||||
|
MfClassicNestedPhaseFinished,
|
||||||
|
} MfClassicNestedPhase;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
MfClassicPrngTypeUnknown, // Tag not yet tested
|
MfClassicPrngTypeUnknown, // Tag not yet tested
|
||||||
MfClassicPrngTypeNoTag, // No tag detected during test
|
MfClassicPrngTypeNoTag, // No tag detected during test
|
||||||
@ -51,7 +64,8 @@ typedef enum {
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
MfClassicBackdoorUnknown, // Tag not yet tested
|
MfClassicBackdoorUnknown, // Tag not yet tested
|
||||||
MfClassicBackdoorNone, // No observed backdoor
|
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;
|
} MfClassicBackdoor;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -125,7 +139,6 @@ typedef struct {
|
|||||||
MfClassicBlock tag_block;
|
MfClassicBlock tag_block;
|
||||||
} MfClassicPollerWriteContext;
|
} MfClassicPollerWriteContext;
|
||||||
|
|
||||||
// TODO: Investigate reducing the number of members of this struct by moving into a separate struct dedicated to nested dict attack
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t current_sector;
|
uint8_t current_sector;
|
||||||
MfClassicKey current_key;
|
MfClassicKey current_key;
|
||||||
@ -134,14 +147,11 @@ typedef struct {
|
|||||||
uint16_t current_block;
|
uint16_t current_block;
|
||||||
uint8_t reuse_key_sector;
|
uint8_t reuse_key_sector;
|
||||||
// Enhanced dictionary attack and nested nonce collection
|
// Enhanced dictionary attack and nested nonce collection
|
||||||
|
MfClassicNestedPhase nested_phase;
|
||||||
MfClassicPrngType prng_type;
|
MfClassicPrngType prng_type;
|
||||||
MfClassicBackdoor backdoor;
|
MfClassicBackdoor backdoor;
|
||||||
uint32_t nt_prev;
|
uint16_t nested_target_key;
|
||||||
uint32_t nt_next;
|
MfClassicNestedKeyCandidateArray nested_key_candidates;
|
||||||
uint8_t nt_count;
|
|
||||||
uint8_t hard_nt_count;
|
|
||||||
uint8_t nested_dict_target_key;
|
|
||||||
uint8_t nested_target_key;
|
|
||||||
MfClassicNestedNonceArray nested_nonce;
|
MfClassicNestedNonceArray nested_nonce;
|
||||||
bool static_encrypted;
|
bool static_encrypted;
|
||||||
bool calibrated;
|
bool calibrated;
|
||||||
|
Loading…
Reference in New Issue
Block a user