Merge remote-tracking branch 'noproto/dev' into ulcdict

This commit is contained in:
noproto
2024-10-28 21:24:23 -04:00
71 changed files with 701 additions and 3012 deletions

View File

@@ -543,6 +543,22 @@ void mf_classic_set_key_not_found(
}
}
MfClassicKey
mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type) {
furi_check(data);
furi_check(sector_num < mf_classic_get_total_sectors_num(data->type));
furi_check(key_type == MfClassicKeyTypeA || key_type == MfClassicKeyTypeB);
const MfClassicSectorTrailer* sector_trailer =
mf_classic_get_sector_trailer_by_sector(data, sector_num);
if(key_type == MfClassicKeyTypeA) {
return sector_trailer->key_a;
} else {
return sector_trailer->key_b;
}
}
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) {
furi_check(data);

View File

@@ -213,6 +213,9 @@ void mf_classic_set_key_not_found(
uint8_t sector_num,
MfClassicKeyType key_type);
MfClassicKey
mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type);
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num);
void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);

View File

@@ -8,9 +8,9 @@
// TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches
// TODO: Store target key in CUID dictionary
// TODO: Fix rare nested_target_key 64 bug
// TODO: Dead code for malloc returning NULL?
// TODO: Auth1 static encrypted exists (rare)
// TODO: Use keys found by NFC plugins, cached keys
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
@@ -1663,7 +1663,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc
"Found key candidate %06llx",
bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey)));
dict_attack_ctx->current_key = *key_candidate;
dict_attack_ctx->reuse_key_sector = (target_block / 4);
dict_attack_ctx->reuse_key_sector = target_sector;
dict_attack_ctx->current_key_type = target_key_type;
free(key_candidate);
break;
@@ -1780,7 +1780,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) {
bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) {
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
uint8_t nested_target_key = dict_attack_ctx->nested_target_key;
uint16_t nested_target_key = dict_attack_ctx->nested_target_key;
MfClassicKeyType target_key_type;
uint8_t target_sector;
@@ -1818,12 +1818,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
bool initial_dict_attack_iter = false;
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) {
dict_attack_ctx->auth_passed = true;
dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key;
bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone);
if(!(backdoor_present)) {
for(uint8_t sector = 0; sector < instance->sectors_total; sector++) {
for(uint8_t key_type = 0; key_type < 2; key_type++) {
if(mf_classic_is_key_found(instance->data, sector, key_type)) {
dict_attack_ctx->nested_known_key =
mf_classic_get_key(instance->data, sector, key_type);
dict_attack_ctx->nested_known_key_sector = sector;
dict_attack_ctx->nested_known_key_type = key_type;
break;
@@ -1832,6 +1833,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
}
dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG;
} else {
dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key;
dict_attack_ctx->nested_known_key_sector = 0;
dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA;
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
@@ -1879,9 +1881,10 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ?
(instance->sectors_total * 2) :
(instance->sectors_total * 16);
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) {
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackVerify) {
if(!(mf_classic_nested_is_target_key_found(instance, true)) &&
(dict_attack_ctx->nested_nonce.count > 0)) {
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume;
instance->state = MfClassicPollerStateNestedDictAttack;
return command;
} else {
@@ -1896,7 +1899,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
}
}
if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) &&
if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack ||
dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) &&
(dict_attack_ctx->nested_target_key < dict_target_key_max)) {
bool is_last_iter_for_hard_key =
((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7));
@@ -1920,11 +1924,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
NULL;
}
if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) {
// Key reuse
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume;
// Key verify and reuse
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify;
dict_attack_ctx->auth_passed = false;
instance->state = MfClassicPollerStateKeyReuseStartNoOffset;
return command;
} else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) {
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
dict_attack_ctx->auth_passed = true;
}
if(!(dict_attack_ctx->auth_passed)) {
dict_attack_ctx->attempt_count++;
@@ -2004,9 +2011,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
}
uint16_t nonce_collect_key_max;
if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) {
nonce_collect_key_max = dict_attack_ctx->static_encrypted ?
((instance->sectors_total * 4) - 2) :
(instance->sectors_total * 4);
nonce_collect_key_max = instance->sectors_total * 4;
} else {
nonce_collect_key_max = instance->sectors_total * 2;
}
@@ -2047,30 +2052,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
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(!(dict_attack_ctx->static_encrypted)) {
dict_attack_ctx->nested_target_key++;
dict_attack_ctx->current_key_checked = false;
}
instance->state = MfClassicPollerStateNestedController;
return command;
}
// 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)) ||
@@ -2096,12 +2077,52 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->attempt_count = 0;
}
FURI_LOG_D(
TAG,
"Nested target key: %u (max: %u)",
dict_attack_ctx->nested_target_key,
nonce_collect_key_max);
if(!(dict_attack_ctx->current_key_checked)) {
if(dict_attack_ctx->nested_target_key == nonce_collect_key_max) {
// All nonces have been collected
FURI_LOG_D(TAG, "All nonces collected");
instance->state = MfClassicPollerStateNestedController;
return command;
}
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(!(dict_attack_ctx->static_encrypted)) {
dict_attack_ctx->nested_target_key++;
dict_attack_ctx->current_key_checked = false;
}
instance->state = MfClassicPollerStateNestedController;
return command;
}
// 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;
}
}
FURI_LOG_T(TAG, "Collecting a nonce");
// Collect a nonce
dict_attack_ctx->auth_passed = false;
instance->state = MfClassicPollerStateNestedCollectNtEnc;
return command;
}
}
dict_attack_ctx->nested_target_key = 0;
dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished;
instance->state = MfClassicPollerStateSuccess;
return command;

View File

@@ -52,14 +52,15 @@ typedef enum {
* @brief MfClassic poller nested attack phase.
*/
typedef enum {
MfClassicNestedPhaseNone,
MfClassicNestedPhaseAnalyzePRNG,
MfClassicNestedPhaseDictAttack,
MfClassicNestedPhaseDictAttackResume,
MfClassicNestedPhaseCalibrate,
MfClassicNestedPhaseRecalibrate,
MfClassicNestedPhaseCollectNtEnc,
MfClassicNestedPhaseFinished,
MfClassicNestedPhaseNone, /**< No nested attack has taken place yet. */
MfClassicNestedPhaseAnalyzePRNG, /**< Analyze nonces produced by the PRNG to determine if they fit a weak PRNG */
MfClassicNestedPhaseDictAttack, /**< Search keys which match the expected PRNG properties and parity for collected nonces */
MfClassicNestedPhaseDictAttackVerify, /**< Verify candidate keys by authenticating to the sector with the key */
MfClassicNestedPhaseDictAttackResume, /**< Resume nested dictionary attack from the last tested (invalid) key */
MfClassicNestedPhaseCalibrate, /**< Perform necessary calculations to recover the plaintext nonce during later collection phase (weak PRNG tags only) */
MfClassicNestedPhaseRecalibrate, /**< Collect the next plaintext static encrypted nonce for backdoor static encrypted nonce nested attack */
MfClassicNestedPhaseCollectNtEnc, /**< Log nonces collected during nested authentication for key recovery */
MfClassicNestedPhaseFinished, /**< Nested attack has finished */
} MfClassicNestedPhase;
/**

View File

@@ -5,7 +5,7 @@
#include <nfc/helpers/iso14443_crc.h>
#define TAG "MfCLassicPoller"
#define TAG "MfClassicPoller"
MfClassicError mf_classic_process_error(Iso14443_3aError error) {
MfClassicError ret = MfClassicErrorNone;