mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-14 23:48:35 -07:00
Initial accelerated dictionary attack for weak PRNGs
This commit is contained in:
@@ -74,8 +74,12 @@
|
|||||||
#define NFC_APP_MFKEY32_LOGS_FILE_NAME ".mfkey32.log"
|
#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_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_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 {
|
typedef enum {
|
||||||
NfcRpcStateIdle,
|
NfcRpcStateIdle,
|
||||||
|
|||||||
@@ -130,6 +130,13 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) {
|
|||||||
break;
|
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(
|
instance->nfc_dict_context.dict = keys_dict_alloc(
|
||||||
NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey));
|
NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey));
|
||||||
if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) {
|
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);
|
} while(false);
|
||||||
}
|
}
|
||||||
if(state == DictAttackStateSystemDictInProgress) {
|
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(
|
instance->nfc_dict_context.dict = keys_dict_alloc(
|
||||||
NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey));
|
NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey));
|
||||||
dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary");
|
dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary");
|
||||||
|
|||||||
@@ -1300,11 +1300,125 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
|
|||||||
return command;
|
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 mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) {
|
||||||
NfcCommand command = NfcCommandContinue;
|
// TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key)
|
||||||
//MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
// TODO: Look into using MfClassicNt more
|
||||||
// TODO: Nested dictionary attack with ks1
|
NfcCommand command = NfcCommandReset;
|
||||||
instance->state = MfClassicPollerStateNestedLog;
|
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;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1407,6 +1521,54 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
|
|||||||
instance->state = MfClassicPollerStateNestedAnalyzeBackdoor;
|
instance->state = MfClassicPollerStateNestedAnalyzeBackdoor;
|
||||||
return command;
|
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.
|
// 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.
|
// 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) {
|
if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak && !dict_attack_ctx->calibrated) {
|
||||||
|
|||||||
@@ -7,15 +7,23 @@
|
|||||||
#include <nfc/helpers/crypto1.h>
|
#include <nfc/helpers/crypto1.h>
|
||||||
#include <stream/stream.h>
|
#include <stream/stream.h>
|
||||||
#include <stream/buffered_file_stream.h>
|
#include <stream/buffered_file_stream.h>
|
||||||
|
#include "keys_dict.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#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 MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log"
|
#define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets")
|
||||||
#define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME)
|
#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 {
|
typedef enum {
|
||||||
MfClassicAuthStateIdle,
|
MfClassicAuthStateIdle,
|
||||||
@@ -55,6 +63,11 @@ typedef struct {
|
|||||||
uint16_t dist; // Distance
|
uint16_t dist; // Distance
|
||||||
} MfClassicNestedNonce;
|
} MfClassicNestedNonce;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MfClassicKey* key_candidates;
|
||||||
|
size_t count;
|
||||||
|
} MfClassicNestedKeyCandidateArray;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MfClassicNestedNonce* nonces;
|
MfClassicNestedNonce* nonces;
|
||||||
size_t count;
|
size_t count;
|
||||||
@@ -112,7 +125,7 @@ typedef struct {
|
|||||||
MfClassicBlock tag_block;
|
MfClassicBlock tag_block;
|
||||||
} MfClassicPollerWriteContext;
|
} 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 {
|
typedef struct {
|
||||||
uint8_t current_sector;
|
uint8_t current_sector;
|
||||||
MfClassicKey current_key;
|
MfClassicKey current_key;
|
||||||
@@ -120,13 +133,14 @@ typedef struct {
|
|||||||
bool auth_passed;
|
bool auth_passed;
|
||||||
uint16_t current_block;
|
uint16_t current_block;
|
||||||
uint8_t reuse_key_sector;
|
uint8_t reuse_key_sector;
|
||||||
// Enhanced dictionary attack
|
// Enhanced dictionary attack and nested nonce collection
|
||||||
MfClassicPrngType prng_type;
|
MfClassicPrngType prng_type;
|
||||||
MfClassicBackdoor backdoor;
|
MfClassicBackdoor backdoor;
|
||||||
uint32_t nt_prev;
|
uint32_t nt_prev;
|
||||||
uint32_t nt_next;
|
uint32_t nt_next;
|
||||||
uint8_t nt_count;
|
uint8_t nt_count;
|
||||||
uint8_t hard_nt_count;
|
uint8_t hard_nt_count;
|
||||||
|
uint8_t nested_dict_target_key;
|
||||||
uint8_t nested_target_key;
|
uint8_t nested_target_key;
|
||||||
MfClassicNestedNonceArray nested_nonce;
|
MfClassicNestedNonceArray nested_nonce;
|
||||||
bool static_encrypted;
|
bool static_encrypted;
|
||||||
@@ -135,6 +149,8 @@ typedef struct {
|
|||||||
uint16_t d_max;
|
uint16_t d_max;
|
||||||
uint8_t attempt_count;
|
uint8_t attempt_count;
|
||||||
MfClassicNestedState nested_state;
|
MfClassicNestedState nested_state;
|
||||||
|
KeysDict* mf_classic_system_dict;
|
||||||
|
KeysDict* mf_classic_user_dict;
|
||||||
} MfClassicPollerDictAttackContext;
|
} MfClassicPollerDictAttackContext;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user