Merge branch 'nestednonces' into ofw-3822-nestednonces

This commit is contained in:
Willy-JL
2024-09-10 20:34:33 +02:00
5 changed files with 87 additions and 56 deletions

View File

@@ -296,6 +296,9 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) {
instance->nfc_dict_context.is_key_attack = false; instance->nfc_dict_context.is_key_attack = false;
instance->nfc_dict_context.key_attack_current_sector = 0; instance->nfc_dict_context.key_attack_current_sector = 0;
instance->nfc_dict_context.is_card_present = false; instance->nfc_dict_context.is_card_present = false;
instance->nfc_dict_context.nested_phase = MfClassicNestedPhaseNone;
instance->nfc_dict_context.prng_type = MfClassicPrngTypeUnknown;
instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown;
nfc_blink_stop(instance); nfc_blink_stop(instance);
} }

View File

@@ -10,30 +10,6 @@ struct DictAttack {
void* context; void* context;
}; };
typedef enum {
MfClassicNestedPhaseNone,
MfClassicNestedPhaseAnalyzePRNG,
MfClassicNestedPhaseDictAttack,
MfClassicNestedPhaseDictAttackResume,
MfClassicNestedPhaseCalibrate,
MfClassicNestedPhaseCollectNtEnc,
MfClassicNestedPhaseFinished,
} MfClassicNestedPhase;
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
MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor
MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce)
} MfClassicBackdoor;
typedef struct { typedef struct {
FuriString* header; FuriString* header;
bool card_detected; bool card_detected;

View File

@@ -9,6 +9,31 @@ extern "C" {
typedef struct DictAttack DictAttack; typedef struct DictAttack DictAttack;
typedef enum {
MfClassicNestedPhaseNone,
MfClassicNestedPhaseAnalyzePRNG,
MfClassicNestedPhaseDictAttack,
MfClassicNestedPhaseDictAttackResume,
MfClassicNestedPhaseCalibrate,
MfClassicNestedPhaseCollectNtEnc,
MfClassicNestedPhaseFinished,
} MfClassicNestedPhase;
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
MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor
MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor
MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce)
} MfClassicBackdoor;
typedef enum { typedef enum {
DictAttackEventSkipPressed, DictAttackEventSkipPressed,
} DictAttackEvent; } DictAttackEvent;

View File

@@ -13,8 +13,14 @@
#define MF_CLASSIC_MAX_BUFF_SIZE (64) #define MF_CLASSIC_MAX_BUFF_SIZE (64)
const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; // Ordered by frequency, labeled chronologically
const MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[] = {
{{{0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}, MfClassicBackdoorAuth3}, // Fudan (static encrypted)
{{{0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}, MfClassicBackdoorAuth1}, // Fudan, Infineon, NXP
{{{0x51, 0x8b, 0x33, 0x54, 0xe7, 0x60}}, MfClassicBackdoorAuth2}, // Fudan
};
const size_t mf_classic_backdoor_keys_count =
sizeof(mf_classic_backdoor_keys) / sizeof(mf_classic_backdoor_keys[0]);
const uint16_t valid_sums[] = const uint16_t valid_sums[] =
{0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256}; {0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256};
@@ -529,36 +535,46 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller*
NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) {
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;
bool current_key_is_auth1 =
memcmp(dict_attack_ctx->current_key.data, auth1_backdoor_key.data, sizeof(MfClassicKey)) ==
0;
bool current_key_is_auth2 =
memcmp(dict_attack_ctx->current_key.data, auth2_backdoor_key.data, sizeof(MfClassicKey)) ==
0;
if(!current_key_is_auth1) { size_t current_key_index =
dict_attack_ctx->current_key = auth2_backdoor_key; mf_classic_backdoor_keys_count - 1; // Default to the last valid index
} else if(current_key_is_auth2) {
dict_attack_ctx->current_key = auth1_backdoor_key; // Find the current key in the backdoor_keys array
for(size_t i = 0; i < mf_classic_backdoor_keys_count; i++) {
if(memcmp(
dict_attack_ctx->current_key.data,
mf_classic_backdoor_keys[i].key.data,
sizeof(MfClassicKey)) == 0) {
current_key_index = i;
break;
}
} }
// Choose the next key to try
size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count;
uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1;
FURI_LOG_E(TAG, "Trying backdoor v%d", backdoor_version);
dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key;
// Attempt backdoor authentication // Attempt backdoor authentication
MfClassicError error = mf_classic_poller_auth( MfClassicError error = mf_classic_poller_auth(
instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true);
bool backdoor_found = (error == MfClassicErrorNone) ? true : false; if((next_key_index == 0) && (error == MfClassicErrorProtocol)) {
FURI_LOG_E(TAG, "No backdoor identified");
if(backdoor_found) {
FURI_LOG_E(TAG, "Backdoor identified");
if(!current_key_is_auth1) {
dict_attack_ctx->backdoor = MfClassicBackdoorAuth2;
} else {
dict_attack_ctx->backdoor = MfClassicBackdoorAuth1;
}
instance->state = MfClassicPollerStateBackdoorReadSector;
} else if(current_key_is_auth2) {
dict_attack_ctx->backdoor = MfClassicBackdoorNone; dict_attack_ctx->backdoor = MfClassicBackdoorNone;
instance->state = MfClassicPollerStateRequestKey; instance->state = MfClassicPollerStateRequestKey;
} else if(error == MfClassicErrorNone) {
FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version);
dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type;
instance->state = MfClassicPollerStateBackdoorReadSector;
} else if(
(error == MfClassicErrorAuth) &&
(next_key_index == (mf_classic_backdoor_keys_count - 1))) {
// We've tried all backdoor keys, this is a unique key and an important research finding
furi_crash("New backdoor: please report!");
} }
return command; return command;
} }
@@ -1070,6 +1086,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance
} }
NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) {
// TODO: Discard outliers (e.g. greater than 3 standard deviations)
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;
uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT];
@@ -1109,7 +1126,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance)
nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt));
if((dict_attack_ctx->static_encrypted) && if((dict_attack_ctx->static_encrypted) &&
(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2)) { (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3)) {
command = NfcCommandReset; command = NfcCommandReset;
uint8_t target_block = uint8_t target_block =
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4);
@@ -1259,7 +1276,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
MfClassicAuthContext auth_ctx = {}; MfClassicAuthContext auth_ctx = {};
MfClassicError error; MfClassicError error;
bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone);
bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak;
uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0; uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0;
uint8_t nt_enc_per_collection = uint8_t nt_enc_per_collection =
@@ -1282,7 +1299,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
&dict_attack_ctx->nested_known_key, &dict_attack_ctx->nested_known_key,
dict_attack_ctx->nested_known_key_type, dict_attack_ctx->nested_known_key_type,
&auth_ctx, &auth_ctx,
use_backdoor_for_initial_auth); use_backdoor);
if(error != MfClassicErrorNone) { if(error != MfClassicErrorNone) {
FURI_LOG_E(TAG, "Failed to perform full authentication"); FURI_LOG_E(TAG, "Failed to perform full authentication");
@@ -1304,7 +1321,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
&dict_attack_ctx->nested_known_key, &dict_attack_ctx->nested_known_key,
dict_attack_ctx->nested_known_key_type, dict_attack_ctx->nested_known_key_type,
&auth_ctx, &auth_ctx,
false, use_backdoor,
false); false);
if(error != MfClassicErrorNone) { if(error != MfClassicErrorNone) {
@@ -1365,7 +1382,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst
break; break;
} }
} else if(dict_attack_ctx->static_encrypted) { } else if(dict_attack_ctx->static_encrypted) {
if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) {
found_nt = dict_attack_ctx->static_encrypted_nonce; found_nt = dict_attack_ctx->static_encrypted_nonce;
} else { } else {
dist = UINT16_MAX; dist = UINT16_MAX;
@@ -1749,7 +1766,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
dict_attack_ctx->nested_known_key_sector = 0; dict_attack_ctx->nested_known_key_sector = 0;
dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA;
dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; dict_attack_ctx->prng_type = MfClassicPrngTypeWeak;
if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) {
dict_attack_ctx->static_encrypted = true; dict_attack_ctx->static_encrypted = true;
} }
dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack;
@@ -1898,7 +1915,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
// TODO: Calibrates too frequently for static encrypted backdoored tags // TODO: Calibrates too frequently for static encrypted backdoored tags
if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) {
if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) ||
((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) &&
(dict_attack_ctx->nested_nonce.count == 1)) || (dict_attack_ctx->nested_nonce.count == 1)) ||
((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) { ((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) {
instance->state = MfClassicPollerStateNestedLog; instance->state = MfClassicPollerStateNestedLog;
@@ -1941,7 +1958,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance
if(dict_attack_ctx->nested_target_key % 2 == 0) { if(dict_attack_ctx->nested_target_key % 2 == 0) {
dict_attack_ctx->current_key_checked = false; dict_attack_ctx->current_key_checked = false;
} }
if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) &&
(dict_attack_ctx->nested_target_key % 4 == 0) && (dict_attack_ctx->nested_target_key % 4 == 0) &&
(dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) {
dict_attack_ctx->calibrated = false; dict_attack_ctx->calibrated = false;

View File

@@ -35,6 +35,7 @@ extern "C" {
extern const MfClassicKey auth1_backdoor_key; extern const MfClassicKey auth1_backdoor_key;
extern const MfClassicKey auth2_backdoor_key; extern const MfClassicKey auth2_backdoor_key;
extern const MfClassicKey auth3_backdoor_key;
extern const uint16_t valid_sums[19]; extern const uint16_t valid_sums[19];
typedef enum { typedef enum {
@@ -68,9 +69,18 @@ typedef enum {
MfClassicBackdoorUnknown, // Tag not yet tested MfClassicBackdoorUnknown, // Tag not yet tested
MfClassicBackdoorNone, // No observed backdoor MfClassicBackdoorNone, // No observed backdoor
MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor
MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor
MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce)
} MfClassicBackdoor; } MfClassicBackdoor;
typedef struct {
MfClassicKey key;
MfClassicBackdoor type;
} MfClassicBackdoorKeyPair;
extern const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[];
extern const size_t mf_classic_backdoor_keys_count;
typedef struct { typedef struct {
uint32_t cuid; // Card UID uint32_t cuid; // Card UID
uint8_t key_idx; // Key index uint8_t key_idx; // Key index