Initial structure for nonce collection

This commit is contained in:
noproto
2024-08-01 09:10:02 -04:00
parent bec6bd381f
commit cf6d6bb9af
2 changed files with 368 additions and 19 deletions
+323 -19
View File
@@ -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 {