mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-19 20:34:19 -07:00
Initial structure for nonce collection
This commit is contained in:
@@ -680,25 +680,32 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance)
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
||||
instance->state = MfClassicPollerStateKeyReuseAuthKeyB;
|
||||
} else {
|
||||
dict_attack_ctx->reuse_key_sector++;
|
||||
if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
} else {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
||||
instance->mfc_event_data.key_attack_data.current_sector =
|
||||
dict_attack_ctx->reuse_key_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
||||
instance->state = MfClassicPollerStateKeyReuseAuthKeyA;
|
||||
do {
|
||||
if(!dict_attack_ctx->prng_type) {
|
||||
instance->state = MfClassicPollerStateNestedAnalyzePRNG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
||||
instance->state = MfClassicPollerStateKeyReuseAuthKeyB;
|
||||
} else {
|
||||
dict_attack_ctx->reuse_key_sector++;
|
||||
if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
} else {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
||||
instance->mfc_event_data.key_attack_data.current_sector =
|
||||
dict_attack_ctx->reuse_key_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
||||
instance->state = MfClassicPollerStateKeyReuseAuthKeyA;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
@@ -829,6 +836,294 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst
|
||||
return command;
|
||||
}
|
||||
|
||||
void nonce_distance(uint32_t* msb, uint32_t* lsb) {
|
||||
uint16_t x = 1, pos;
|
||||
uint8_t calc_ok = 0;
|
||||
|
||||
for(uint16_t i = 1; i; ++i) {
|
||||
pos = (x & 0xff) << 8 | x >> 8;
|
||||
|
||||
if((pos == *msb) & !(calc_ok >> 0 & 0x01)) {
|
||||
*msb = i;
|
||||
calc_ok |= 0x01;
|
||||
}
|
||||
|
||||
if((pos == *lsb) & !(calc_ok >> 1 & 0x01)) {
|
||||
*lsb = i;
|
||||
calc_ok |= 0x02;
|
||||
}
|
||||
|
||||
if(calc_ok == 0x03) {
|
||||
return;
|
||||
}
|
||||
|
||||
x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Faster? https://github.com/RfidResearchGroup/proxmark3/commit/bd3e8db852186d4ab9d5dda890d1cd52389b1254
|
||||
bool validate_prng_nonce(uint32_t nonce) {
|
||||
uint32_t msb = nonce >> 16;
|
||||
uint32_t lsb = nonce & 0xffff;
|
||||
nonce_distance(&msb, &lsb);
|
||||
return ((65535 - msb + lsb) % 65535) == 16;
|
||||
}
|
||||
|
||||
// Helper function to add a nonce to the array
|
||||
static bool add_nested_nonce(MfClassicNestedNonceArray* array, uint32_t cuid, uint8_t key_idx, uint32_t nt, uint32_t nt_enc, uint8_t par, uint16_t dist) {
|
||||
MfClassicNestedNonce* new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce));
|
||||
if (new_nonces == NULL) return false;
|
||||
|
||||
array->nonces = new_nonces;
|
||||
array->nonces[array->count].cuid = cuid;
|
||||
array->nonces[array->count].key_idx = key_idx;
|
||||
array->nonces[array->count].nt = nt;
|
||||
array->nonces[array->count].nt_enc = nt_enc;
|
||||
array->nonces[array->count].par = par;
|
||||
array->nonces[array->count].dist = dist;
|
||||
array->count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
// Analyze PRNG by collecting nt
|
||||
uint8_t nonce_limit = 10;
|
||||
|
||||
if (dict_attack_ctx->nt_count > 0) {
|
||||
if(!validate_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++;
|
||||
}
|
||||
|
||||
if(dict_attack_ctx->nt_count <= nonce_limit) {
|
||||
instance->state = MfClassicPollerStateNestedCollectNt;
|
||||
return command;
|
||||
}
|
||||
|
||||
if(dict_attack_ctx->hard_nt_count > 3) {
|
||||
dict_attack_ctx->prng_type = MfClassicPrngTypeHard;
|
||||
// FIXME: E -> D
|
||||
FURI_LOG_E(TAG, "Detected Hard PRNG");
|
||||
} else {
|
||||
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
|
||||
// FIXME: E -> D
|
||||
FURI_LOG_E(TAG, "Detected Weak PRNG");
|
||||
}
|
||||
|
||||
instance->state = MfClassicPollerStateNestedController;
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) {
|
||||
// Stub
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandReset;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
MfClassicNt nt = {};
|
||||
MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt);
|
||||
|
||||
if(error != MfClassicErrorNone) {
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag;
|
||||
// FIXME: E -> D
|
||||
FURI_LOG_E(TAG, "Failed to collect nt");
|
||||
} else {
|
||||
// FIXME: E -> D
|
||||
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));
|
||||
dict_attack_ctx->nt_prev = nt_data;
|
||||
dict_attack_ctx->nt_count++;
|
||||
instance->state = MfClassicPollerStateNestedAnalyzePRNG;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) {
|
||||
// TODO: Collect parity
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
MfClassicNestedNonceArray result = {NULL, 0};
|
||||
|
||||
do {
|
||||
if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) {
|
||||
FURI_LOG_E(TAG, "Hard PRNG, skipping calibration");
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
uint32_t davg = 0, dmin = 0, dmax = 0;
|
||||
uint32_t collected = 0;
|
||||
const uint32_t rounds = 10;
|
||||
|
||||
uint32_t nt_enc_arr[rounds];
|
||||
uint32_t nt_prev = 0;
|
||||
uint32_t nt_enc_prev = 0;
|
||||
uint32_t same_nt_enc_cnt = 0;
|
||||
uint32_t dist = 0;
|
||||
bool static_encrypted = false;
|
||||
|
||||
// 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");
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_E(TAG, "Full authentication successful");
|
||||
|
||||
nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
||||
|
||||
// Step 2: Perform nested authentication multiple times
|
||||
for (uint32_t round_no = 0; round_no < rounds; round_no++) {
|
||||
error = mf_classic_poller_auth_nested(
|
||||
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 nested authentication %lu", round_no);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
|
||||
if (nt_enc == nt_enc_prev) {
|
||||
same_nt_enc_cnt++;
|
||||
if (same_nt_enc_cnt > 3) {
|
||||
static_encrypted = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
same_nt_enc_cnt = 0;
|
||||
nt_enc_prev = nt_enc;
|
||||
}
|
||||
nt_enc_arr[round_no] = nt_enc;
|
||||
}
|
||||
|
||||
if (static_encrypted) {
|
||||
FURI_LOG_E(TAG, "Static encrypted nonce detected");
|
||||
if (dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) {
|
||||
// TODO: Backdoor static nested attack
|
||||
break;
|
||||
} else {
|
||||
// TODO: If not present, just log nonces with parity bits
|
||||
bool success = add_nested_nonce(&result, cuid, dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, 0, 0);
|
||||
if (!success) {
|
||||
FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the distance between each nonce
|
||||
// TODO: Distance calculation
|
||||
uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6);
|
||||
Crypto1 crypto;
|
||||
crypto1_init(&crypto, known_key);
|
||||
for (uint32_t rtr = 0; rtr < rounds; rtr++) {
|
||||
bool found = false;
|
||||
for (int i = 0; i < 65535; i++) {
|
||||
Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even};
|
||||
uint32_t nth_successor = prng_successor(nt_prev, i);
|
||||
if ((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) == nt_enc_arr[rtr]) {
|
||||
FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor);
|
||||
FURI_LOG_E(TAG, "dist from nt prev: %i", i);
|
||||
nt_prev = nth_successor;
|
||||
found = true;
|
||||
/*
|
||||
dist_a = i;
|
||||
found_dist++;
|
||||
printf("ks: %08x\n", nth_successor ^ nt_enc);
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
FURI_LOG_E(TAG, "Failed to find distance for nt_enc %08lx", nt_enc_arr[rtr]);
|
||||
FURI_LOG_E(TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev);
|
||||
}
|
||||
}
|
||||
|
||||
if (collected > 0) {
|
||||
davg /= collected;
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Calibration completed: min=%lu max=%lu avg=%lu collected=%lu",
|
||||
dmin,
|
||||
dmax,
|
||||
davg,
|
||||
collected);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to collect any valid nonce distances");
|
||||
}
|
||||
} while(false);
|
||||
|
||||
instance->state = MfClassicPollerStateNestedController;
|
||||
|
||||
mf_classic_poller_halt(instance);
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) {
|
||||
// Log collected nonces to /ext/nfc/.nested.log
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
size_t nonce_idx = dict_attack_ctx->nested_nonce.count;
|
||||
while (nonce_idx > 0) {
|
||||
// TODO: Make sure we're not off by one here
|
||||
FURI_LOG_E(TAG, "nt: %08lx", dict_attack_ctx->nested_nonce.nonces[nonce_idx].nt);
|
||||
nonce_idx--;
|
||||
}
|
||||
free(dict_attack_ctx->nested_nonce.nonces);
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) {
|
||||
// Iterate through keys
|
||||
// To run an accelerated dictionary attack, first we need static nested nonces.
|
||||
// If the PRNG is hard, we can't run an accelerated dictionary attack.
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
if (dict_attack_ctx->nested_nonce.count > 0) {
|
||||
instance->state = MfClassicPollerStateNestedLog;
|
||||
return command;
|
||||
}
|
||||
if (dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) {
|
||||
instance->state = MfClassicPollerStateNestedAnalyzeBackdoor;
|
||||
return command;
|
||||
}
|
||||
// Target all sectors, key A and B
|
||||
for (uint8_t key_idx = 0; key_idx <= (instance->sectors_total * 2); key_idx++) {
|
||||
dict_attack_ctx->nested_target_key = key_idx;
|
||||
instance->state = MfClassicPollerStateNestedCollectNtEnc;
|
||||
return command;
|
||||
}
|
||||
// TODO: If we've recovered all keys, read blocks and go to complete
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeSuccess;
|
||||
@@ -868,6 +1163,15 @@ static const MfClassicPollerReadHandler
|
||||
[MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a,
|
||||
[MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b,
|
||||
[MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector,
|
||||
[MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng,
|
||||
[MfClassicPollerStateNestedAnalyzeBackdoor] = mf_classic_poller_handler_nested_analyze_backdoor,
|
||||
[MfClassicPollerStateNestedCollectNt] =
|
||||
mf_classic_poller_handler_nested_collect_nt,
|
||||
[MfClassicPollerStateNestedController] =
|
||||
mf_classic_poller_handler_nested_controller,
|
||||
[MfClassicPollerStateNestedCollectNtEnc] =
|
||||
mf_classic_poller_handler_nested_collect_nt_enc,
|
||||
[MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log,
|
||||
[MfClassicPollerStateSuccess] = mf_classic_poller_handler_success,
|
||||
[MfClassicPollerStateFail] = mf_classic_poller_handler_fail,
|
||||
};
|
||||
@@ -953,4 +1257,4 @@ const NfcPollerBase mf_classic_poller = {
|
||||
.run = (NfcPollerRun)mf_classic_poller_run,
|
||||
.detect = (NfcPollerDetect)mf_classic_poller_detect,
|
||||
.get_data = (NfcPollerGetData)mf_classic_poller_get_data,
|
||||
};
|
||||
};
|
||||
@@ -21,6 +21,33 @@ typedef enum {
|
||||
MfClassicCardStateLost,
|
||||
} MfClassicCardState;
|
||||
|
||||
typedef enum {
|
||||
MfClassicPrngTypeUnknown, // Tag not yet tested
|
||||
MfClassicPrngTypeNoTag, // No tag detected during test
|
||||
MfClassicPrngTypeWeak, // Weak PRNG, standard Nested
|
||||
MfClassicPrngTypeHard, // Hard PRNG, Hardnested
|
||||
} MfClassicPrngType;
|
||||
|
||||
typedef enum {
|
||||
MfClassicBackdoorUnknown, // Tag not yet tested
|
||||
MfClassicBackdoorNone, // No observed backdoor
|
||||
MfClassicBackdoorFM11RF08S, // Tag responds to Fudan FM11RF08S backdoor (static encrypted nonce tags)
|
||||
} MfClassicBackdoor;
|
||||
|
||||
typedef struct {
|
||||
uint32_t cuid; // Card UID
|
||||
uint8_t key_idx; // Key index
|
||||
uint32_t nt; // Nonce
|
||||
uint32_t nt_enc; // Encrypted nonce
|
||||
uint8_t par; // Parity
|
||||
uint16_t dist; // Distance
|
||||
} MfClassicNestedNonce;
|
||||
|
||||
typedef struct {
|
||||
MfClassicNestedNonce* nonces;
|
||||
size_t count;
|
||||
} MfClassicNestedNonceArray;
|
||||
|
||||
typedef enum {
|
||||
MfClassicPollerStateDetectType,
|
||||
MfClassicPollerStateStart,
|
||||
@@ -49,6 +76,14 @@ typedef enum {
|
||||
MfClassicPollerStateSuccess,
|
||||
MfClassicPollerStateFail,
|
||||
|
||||
// Enhanced dictionary attack states
|
||||
MfClassicPollerStateNestedAnalyzePRNG,
|
||||
MfClassicPollerStateNestedAnalyzeBackdoor,
|
||||
MfClassicPollerStateNestedCollectNt,
|
||||
MfClassicPollerStateNestedController,
|
||||
MfClassicPollerStateNestedCollectNtEnc,
|
||||
MfClassicPollerStateNestedLog,
|
||||
|
||||
MfClassicPollerStateNum,
|
||||
} MfClassicPollerState;
|
||||
|
||||
@@ -63,6 +98,7 @@ typedef struct {
|
||||
MfClassicBlock tag_block;
|
||||
} MfClassicPollerWriteContext;
|
||||
|
||||
// TODO: Investigate reducing the number of members of this struct
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
MfClassicKey current_key;
|
||||
@@ -70,6 +106,15 @@ typedef struct {
|
||||
bool auth_passed;
|
||||
uint16_t current_block;
|
||||
uint8_t reuse_key_sector;
|
||||
// Enhanced dictionary attack
|
||||
MfClassicPrngType prng_type;
|
||||
MfClassicBackdoor backdoor;
|
||||
uint32_t nt_prev;
|
||||
uint32_t nt_next;
|
||||
uint8_t nt_count;
|
||||
uint8_t hard_nt_count;
|
||||
uint8_t nested_target_key;
|
||||
MfClassicNestedNonceArray nested_nonce;
|
||||
} MfClassicPollerDictAttackContext;
|
||||
|
||||
typedef struct {
|
||||
|
||||
Reference in New Issue
Block a user