mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
nfc mf classic upgrades
by noproto
This commit is contained in:
@@ -7,6 +7,8 @@
|
|||||||
* SubGHz: Add IL-100 Smart support for Add manually
|
* SubGHz: Add IL-100 Smart support for Add manually
|
||||||
* SubGHz: Add **experimental counter overflow mode** (OFEX), replicates how some key duplicators work, DO NOT USE if you don't know what you are doing, it will reset your counter value! (accesible with debug on in radio settings - counter incr.)
|
* SubGHz: Add **experimental counter overflow mode** (OFEX), replicates how some key duplicators work, DO NOT USE if you don't know what you are doing, it will reset your counter value! (accesible with debug on in radio settings - counter incr.)
|
||||||
* SubGHz: **Return Honeywell Sec** with fixes and improvements (by htotoo & LiQuiDz & xMasterX)
|
* SubGHz: **Return Honeywell Sec** with fixes and improvements (by htotoo & LiQuiDz & xMasterX)
|
||||||
|
* NFC: Keys found in key cache are now used in Nested attacks, deleting key cache is no longer required (by @noproto)
|
||||||
|
* NFC: MFKey 4.0, MIFARE Classic Static Encrypted Nested attacks run 10x faster (by @noproto)
|
||||||
* NFC: **Add Saflok MFUL Parser Support** (by @aaronjamt)
|
* NFC: **Add Saflok MFUL Parser Support** (by @aaronjamt)
|
||||||
* NFC: **Add MFUL counters to Info page** (by @aaronjamt)
|
* NFC: **Add MFUL counters to Info page** (by @aaronjamt)
|
||||||
* OFW: Fix Felica standard loading from nfc file
|
* OFW: Fix Felica standard loading from nfc file
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ typedef struct {
|
|||||||
uint16_t nested_target_key;
|
uint16_t nested_target_key;
|
||||||
uint16_t msb_count;
|
uint16_t msb_count;
|
||||||
bool enhanced_dict;
|
bool enhanced_dict;
|
||||||
|
uint16_t current_key_idx; // Current key index for CUID dictionary mode
|
||||||
|
uint8_t*
|
||||||
|
cuid_key_indices_bitmap; // Bitmap of key indices present in CUID dictionary (256 bits = 32 bytes)
|
||||||
} NfcMfClassicDictAttackContext;
|
} NfcMfClassicDictAttackContext;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -2,12 +2,22 @@
|
|||||||
|
|
||||||
#include <bit_lib/bit_lib.h>
|
#include <bit_lib/bit_lib.h>
|
||||||
#include <dolphin/dolphin.h>
|
#include <dolphin/dolphin.h>
|
||||||
|
#include <toolbox/stream/buffered_file_stream.h>
|
||||||
|
|
||||||
#define TAG "NfcMfClassicDictAttack"
|
#define TAG "NfcMfClassicDictAttack"
|
||||||
|
#define BIT(x, n) ((x) >> (n) & 1)
|
||||||
|
|
||||||
// TODO FL-3926: Fix lag when leaving the dictionary attack view after Hardnested
|
// TODO FL-3926: Fix lag when leaving the dictionary attack view after Hardnested
|
||||||
// TODO FL-3926: Re-enters backdoor detection between user and system dictionary if no backdoor is found
|
// TODO FL-3926: Re-enters backdoor detection between user and system dictionary if no backdoor is found
|
||||||
|
|
||||||
|
// KeysDict structure definition for inline CUID dictionary allocation
|
||||||
|
struct KeysDict {
|
||||||
|
Stream* stream;
|
||||||
|
size_t key_size;
|
||||||
|
size_t key_size_symbols;
|
||||||
|
size_t total_keys;
|
||||||
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
DictAttackStateCUIDDictInProgress,
|
DictAttackStateCUIDDictInProgress,
|
||||||
DictAttackStateUserDictInProgress,
|
DictAttackStateUserDictInProgress,
|
||||||
@@ -31,11 +41,22 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context)
|
|||||||
instance->nfc_dict_context.is_card_present = false;
|
instance->nfc_dict_context.is_card_present = false;
|
||||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost);
|
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost);
|
||||||
} else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
|
} else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
|
||||||
|
uint32_t state =
|
||||||
|
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||||
|
bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress);
|
||||||
|
|
||||||
const MfClassicData* mfc_data =
|
const MfClassicData* mfc_data =
|
||||||
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
|
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
|
||||||
mfc_event->data->poller_mode.mode = (instance->nfc_dict_context.enhanced_dict) ?
|
|
||||||
MfClassicPollerModeDictAttackEnhanced :
|
// Select mode based on dictionary type
|
||||||
MfClassicPollerModeDictAttackStandard;
|
if(is_cuid_dict) {
|
||||||
|
mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackCUID;
|
||||||
|
} else if(instance->nfc_dict_context.enhanced_dict) {
|
||||||
|
mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackEnhanced;
|
||||||
|
} else {
|
||||||
|
mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttackStandard;
|
||||||
|
}
|
||||||
|
|
||||||
mfc_event->data->poller_mode.data = mfc_data;
|
mfc_event->data->poller_mode.data = mfc_data;
|
||||||
instance->nfc_dict_context.sectors_total =
|
instance->nfc_dict_context.sectors_total =
|
||||||
mf_classic_get_total_sectors_num(mfc_data->type);
|
mf_classic_get_total_sectors_num(mfc_data->type);
|
||||||
@@ -46,12 +67,57 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context)
|
|||||||
view_dispatcher_send_custom_event(
|
view_dispatcher_send_custom_event(
|
||||||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
||||||
} else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) {
|
} else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) {
|
||||||
|
uint32_t state =
|
||||||
|
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||||
|
bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress);
|
||||||
|
|
||||||
MfClassicKey key = {};
|
MfClassicKey key = {};
|
||||||
if(keys_dict_get_next_key(
|
bool key_found = false;
|
||||||
instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) {
|
|
||||||
|
if(is_cuid_dict) {
|
||||||
|
// CUID dictionary: read 7 bytes (1 byte key_idx + 6 bytes key) and filter by exact key_idx
|
||||||
|
uint16_t target_key_idx = instance->nfc_dict_context.current_key_idx;
|
||||||
|
|
||||||
|
// Check if this key index exists in the bitmap (only valid for 0-255)
|
||||||
|
if(target_key_idx < 256 &&
|
||||||
|
BIT(instance->nfc_dict_context.cuid_key_indices_bitmap[target_key_idx / 8],
|
||||||
|
target_key_idx % 8)) {
|
||||||
|
uint8_t key_with_idx[sizeof(MfClassicKey) + 1];
|
||||||
|
|
||||||
|
while(keys_dict_get_next_key(
|
||||||
|
instance->nfc_dict_context.dict, key_with_idx, sizeof(MfClassicKey) + 1)) {
|
||||||
|
// Extract key_idx from first byte
|
||||||
|
uint8_t key_idx = key_with_idx[0];
|
||||||
|
|
||||||
|
instance->nfc_dict_context.dict_keys_current++;
|
||||||
|
|
||||||
|
// Only use key if it matches the exact current key index
|
||||||
|
if(key_idx == (uint8_t)target_key_idx) {
|
||||||
|
// Copy the actual key (starts at byte 1)
|
||||||
|
memcpy(key.data, &key_with_idx[1], sizeof(MfClassicKey));
|
||||||
|
key_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Standard dictionary: read 12 bytes
|
||||||
|
if(keys_dict_get_next_key(
|
||||||
|
instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) {
|
||||||
|
key_found = true;
|
||||||
|
instance->nfc_dict_context.dict_keys_current++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key_found) {
|
||||||
mfc_event->data->key_request_data.key = key;
|
mfc_event->data->key_request_data.key = key;
|
||||||
|
// In CUID mode, set key_type based on key_idx (odd = B, even = A)
|
||||||
|
if(is_cuid_dict) {
|
||||||
|
uint16_t target_key_idx = instance->nfc_dict_context.current_key_idx;
|
||||||
|
mfc_event->data->key_request_data.key_type =
|
||||||
|
(target_key_idx % 2 == 0) ? MfClassicKeyTypeA : MfClassicKeyTypeB;
|
||||||
|
}
|
||||||
mfc_event->data->key_request_data.key_provided = true;
|
mfc_event->data->key_request_data.key_provided = true;
|
||||||
instance->nfc_dict_context.dict_keys_current++;
|
|
||||||
if(instance->nfc_dict_context.dict_keys_current % 10 == 0) {
|
if(instance->nfc_dict_context.dict_keys_current % 10 == 0) {
|
||||||
view_dispatcher_send_custom_event(
|
view_dispatcher_send_custom_event(
|
||||||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
||||||
@@ -72,10 +138,27 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context)
|
|||||||
view_dispatcher_send_custom_event(
|
view_dispatcher_send_custom_event(
|
||||||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
||||||
} else if(mfc_event->type == MfClassicPollerEventTypeNextSector) {
|
} else if(mfc_event->type == MfClassicPollerEventTypeNextSector) {
|
||||||
|
uint32_t state =
|
||||||
|
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||||
|
bool is_cuid_dict = (state == DictAttackStateCUIDDictInProgress);
|
||||||
|
|
||||||
keys_dict_rewind(instance->nfc_dict_context.dict);
|
keys_dict_rewind(instance->nfc_dict_context.dict);
|
||||||
instance->nfc_dict_context.dict_keys_current = 0;
|
instance->nfc_dict_context.dict_keys_current = 0;
|
||||||
instance->nfc_dict_context.current_sector =
|
|
||||||
mfc_event->data->next_sector_data.current_sector;
|
// In CUID mode, increment the key index and calculate sector from it
|
||||||
|
if(is_cuid_dict) {
|
||||||
|
instance->nfc_dict_context.current_key_idx++;
|
||||||
|
// Calculate sector from key_idx (each sector has 2 keys: A and B)
|
||||||
|
instance->nfc_dict_context.current_sector =
|
||||||
|
instance->nfc_dict_context.current_key_idx / 2;
|
||||||
|
// Write back to event data so poller can read it
|
||||||
|
mfc_event->data->next_sector_data.current_sector =
|
||||||
|
instance->nfc_dict_context.current_sector;
|
||||||
|
} else {
|
||||||
|
instance->nfc_dict_context.current_sector =
|
||||||
|
mfc_event->data->next_sector_data.current_sector;
|
||||||
|
}
|
||||||
|
|
||||||
view_dispatcher_send_custom_event(
|
view_dispatcher_send_custom_event(
|
||||||
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
|
||||||
} else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) {
|
} else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) {
|
||||||
@@ -153,18 +236,51 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance->nfc_dict_context.dict = keys_dict_alloc(
|
// Manually create KeysDict and scan once to count + populate bitmap
|
||||||
furi_string_get_cstr(cuid_dict_path),
|
KeysDict* dict = malloc(sizeof(KeysDict));
|
||||||
KeysDictModeOpenExisting,
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
sizeof(MfClassicKey));
|
dict->stream = buffered_file_stream_alloc(storage);
|
||||||
|
dict->key_size = sizeof(MfClassicKey) + 1;
|
||||||
|
dict->key_size_symbols = dict->key_size * 2 + 1;
|
||||||
|
dict->total_keys = 0;
|
||||||
|
|
||||||
if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) {
|
if(!buffered_file_stream_open(
|
||||||
keys_dict_free(instance->nfc_dict_context.dict);
|
dict->stream,
|
||||||
|
furi_string_get_cstr(cuid_dict_path),
|
||||||
|
FSAM_READ_WRITE,
|
||||||
|
FSOM_OPEN_EXISTING)) {
|
||||||
|
buffered_file_stream_close(dict->stream);
|
||||||
|
free(dict);
|
||||||
state = DictAttackStateUserDictInProgress;
|
state = DictAttackStateUserDictInProgress;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allocate and populate bitmap of key indices present in CUID dictionary
|
||||||
|
instance->nfc_dict_context.cuid_key_indices_bitmap = malloc(32);
|
||||||
|
memset(instance->nfc_dict_context.cuid_key_indices_bitmap, 0, 32);
|
||||||
|
|
||||||
|
// Scan dictionary once to count keys and populate bitmap
|
||||||
|
uint8_t key_with_idx[dict->key_size];
|
||||||
|
while(keys_dict_get_next_key(dict, key_with_idx, dict->key_size)) {
|
||||||
|
uint8_t key_idx = key_with_idx[0];
|
||||||
|
// Set bit for this key index
|
||||||
|
instance->nfc_dict_context.cuid_key_indices_bitmap[key_idx / 8] |=
|
||||||
|
(1 << (key_idx % 8));
|
||||||
|
dict->total_keys++;
|
||||||
|
}
|
||||||
|
keys_dict_rewind(dict);
|
||||||
|
|
||||||
|
if(dict->total_keys == 0) {
|
||||||
|
keys_dict_free(dict);
|
||||||
|
free(instance->nfc_dict_context.cuid_key_indices_bitmap);
|
||||||
|
instance->nfc_dict_context.cuid_key_indices_bitmap = NULL;
|
||||||
|
state = DictAttackStateUserDictInProgress;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->nfc_dict_context.dict = dict;
|
||||||
dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary");
|
dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary");
|
||||||
|
instance->nfc_dict_context.current_key_idx = 0; // Initialize key index for CUID mode
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
furi_string_free(cuid_dict_path);
|
furi_string_free(cuid_dict_path);
|
||||||
@@ -265,6 +381,10 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent
|
|||||||
nfc_poller_stop(instance->poller);
|
nfc_poller_stop(instance->poller);
|
||||||
nfc_poller_free(instance->poller);
|
nfc_poller_free(instance->poller);
|
||||||
keys_dict_free(instance->nfc_dict_context.dict);
|
keys_dict_free(instance->nfc_dict_context.dict);
|
||||||
|
if(instance->nfc_dict_context.cuid_key_indices_bitmap) {
|
||||||
|
free(instance->nfc_dict_context.cuid_key_indices_bitmap);
|
||||||
|
instance->nfc_dict_context.cuid_key_indices_bitmap = NULL;
|
||||||
|
}
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
instance->scene_manager,
|
instance->scene_manager,
|
||||||
NfcSceneMfClassicDictAttack,
|
NfcSceneMfClassicDictAttack,
|
||||||
@@ -309,6 +429,10 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent
|
|||||||
nfc_poller_stop(instance->poller);
|
nfc_poller_stop(instance->poller);
|
||||||
nfc_poller_free(instance->poller);
|
nfc_poller_free(instance->poller);
|
||||||
keys_dict_free(instance->nfc_dict_context.dict);
|
keys_dict_free(instance->nfc_dict_context.dict);
|
||||||
|
if(instance->nfc_dict_context.cuid_key_indices_bitmap) {
|
||||||
|
free(instance->nfc_dict_context.cuid_key_indices_bitmap);
|
||||||
|
instance->nfc_dict_context.cuid_key_indices_bitmap = NULL;
|
||||||
|
}
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
instance->scene_manager,
|
instance->scene_manager,
|
||||||
NfcSceneMfClassicDictAttack,
|
NfcSceneMfClassicDictAttack,
|
||||||
@@ -366,6 +490,12 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) {
|
|||||||
|
|
||||||
keys_dict_free(instance->nfc_dict_context.dict);
|
keys_dict_free(instance->nfc_dict_context.dict);
|
||||||
|
|
||||||
|
// Free CUID bitmap if allocated
|
||||||
|
if(instance->nfc_dict_context.cuid_key_indices_bitmap) {
|
||||||
|
free(instance->nfc_dict_context.cuid_key_indices_bitmap);
|
||||||
|
instance->nfc_dict_context.cuid_key_indices_bitmap = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
instance->nfc_dict_context.current_sector = 0;
|
instance->nfc_dict_context.current_sector = 0;
|
||||||
instance->nfc_dict_context.sectors_total = 0;
|
instance->nfc_dict_context.sectors_total = 0;
|
||||||
instance->nfc_dict_context.sectors_read = 0;
|
instance->nfc_dict_context.sectors_read = 0;
|
||||||
@@ -381,6 +511,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) {
|
|||||||
instance->nfc_dict_context.nested_target_key = 0;
|
instance->nfc_dict_context.nested_target_key = 0;
|
||||||
instance->nfc_dict_context.msb_count = 0;
|
instance->nfc_dict_context.msb_count = 0;
|
||||||
instance->nfc_dict_context.enhanced_dict = false;
|
instance->nfc_dict_context.enhanced_dict = false;
|
||||||
|
instance->nfc_dict_context.current_key_idx = 0;
|
||||||
|
|
||||||
// Clean up temporary files used for nested dictionary attack
|
// Clean up temporary files used for nested dictionary attack
|
||||||
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) {
|
if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ App(
|
|||||||
fap_icon_assets="images",
|
fap_icon_assets="images",
|
||||||
fap_weburl="https://github.com/noproto/FlipperMfkey",
|
fap_weburl="https://github.com/noproto/FlipperMfkey",
|
||||||
fap_description="MIFARE Classic key recovery tool",
|
fap_description="MIFARE Classic key recovery tool",
|
||||||
fap_version="3.1",
|
fap_version="4.0",
|
||||||
)
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
|
|||||||
@@ -251,10 +251,14 @@ bool load_nested_nonces(
|
|||||||
MfClassicNonce res = {0};
|
MfClassicNonce res = {0};
|
||||||
res.attack = static_encrypted;
|
res.attack = static_encrypted;
|
||||||
|
|
||||||
|
int sector_num = 0;
|
||||||
|
char key_type = 'A';
|
||||||
int parsed = sscanf(
|
int parsed = sscanf(
|
||||||
line,
|
line,
|
||||||
"Sec %*d key %*c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32
|
"Sec %d key %c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32
|
||||||
" par0 %4[01] nt1 %" PRIx32 " ks1 %" PRIx32 " par1 %4[01]",
|
" par0 %4[01] nt1 %" PRIx32 " ks1 %" PRIx32 " par1 %4[01]",
|
||||||
|
§or_num,
|
||||||
|
&key_type,
|
||||||
&res.uid,
|
&res.uid,
|
||||||
&res.nt0,
|
&res.nt0,
|
||||||
&res.ks1_1_enc,
|
&res.ks1_1_enc,
|
||||||
@@ -263,11 +267,14 @@ bool load_nested_nonces(
|
|||||||
&res.ks1_2_enc,
|
&res.ks1_2_enc,
|
||||||
res.par_2_str);
|
res.par_2_str);
|
||||||
|
|
||||||
if(parsed >= 4) { // At least one nonce is present
|
// Calculate key_idx from sector and key type (for static encrypted: key_idx = sector * 2 + key_offset)
|
||||||
|
res.key_idx = (uint8_t)(sector_num * 2 + (key_type == 'B' ? 1 : 0));
|
||||||
|
|
||||||
|
if(parsed >= 6) { // At least one nonce is present (sector, key, uid, nt0, ks0, par0)
|
||||||
res.par_1 = binaryStringToInt(res.par_1_str);
|
res.par_1 = binaryStringToInt(res.par_1_str);
|
||||||
res.uid_xor_nt0 = res.uid ^ res.nt0;
|
res.uid_xor_nt0 = res.uid ^ res.nt0;
|
||||||
|
|
||||||
if(parsed == 7) { // Both nonces are present
|
if(parsed == 9) { // Both nonces are present
|
||||||
res.attack = static_nested;
|
res.attack = static_nested;
|
||||||
res.par_2 = binaryStringToInt(res.par_2_str);
|
res.par_2 = binaryStringToInt(res.par_2_str);
|
||||||
res.uid_xor_nt1 = res.uid ^ res.nt1;
|
res.uid_xor_nt1 = res.uid ^ res.nt1;
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ static uint8_t MSB_LIMIT = 16;
|
|||||||
static inline void flush_key_buffer(ProgramState* program_state) {
|
static inline void flush_key_buffer(ProgramState* program_state) {
|
||||||
if(program_state->key_buffer && program_state->key_buffer_count > 0 &&
|
if(program_state->key_buffer && program_state->key_buffer_count > 0 &&
|
||||||
program_state->cuid_dict) {
|
program_state->cuid_dict) {
|
||||||
// Pre-allocate exact size needed: 12 hex chars + 1 newline per key
|
// Pre-allocate exact size needed: 2 hex chars (key_idx) + 12 hex chars (key) + 1 newline per key
|
||||||
size_t total_size = program_state->key_buffer_count * 13;
|
size_t total_size = program_state->key_buffer_count * 15;
|
||||||
//FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count);
|
//FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count);
|
||||||
//FURI_LOG_I(TAG, "Total size: %d bytes", total_size);
|
//FURI_LOG_I(TAG, "Total size: %d bytes", total_size);
|
||||||
char* batch_buffer = malloc(total_size + 1); // +1 for null terminator
|
char* batch_buffer = malloc(total_size + 1); // +1 for null terminator
|
||||||
@@ -77,6 +77,11 @@ static inline void flush_key_buffer(ProgramState* program_state) {
|
|||||||
const char hex_chars[] = "0123456789ABCDEF";
|
const char hex_chars[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
for(size_t i = 0; i < program_state->key_buffer_count; i++) {
|
for(size_t i = 0; i < program_state->key_buffer_count; i++) {
|
||||||
|
// Write key_idx as 2 hex chars
|
||||||
|
uint8_t key_idx = program_state->key_idx_buffer[i];
|
||||||
|
*ptr++ = hex_chars[key_idx >> 4];
|
||||||
|
*ptr++ = hex_chars[key_idx & 0x0F];
|
||||||
|
|
||||||
// Convert key to hex string directly into buffer
|
// Convert key to hex string directly into buffer
|
||||||
for(size_t j = 0; j < sizeof(MfClassicKey); j++) {
|
for(size_t j = 0; j < sizeof(MfClassicKey); j++) {
|
||||||
uint8_t byte = program_state->key_buffer[i].data[j];
|
uint8_t byte = program_state->key_buffer[i].data[j];
|
||||||
@@ -144,6 +149,7 @@ static inline int
|
|||||||
|
|
||||||
// Use key buffer - buffer is guaranteed to be available for static_encrypted
|
// Use key buffer - buffer is guaranteed to be available for static_encrypted
|
||||||
program_state->key_buffer[program_state->key_buffer_count] = n->key;
|
program_state->key_buffer[program_state->key_buffer_count] = n->key;
|
||||||
|
program_state->key_idx_buffer[program_state->key_buffer_count] = n->key_idx;
|
||||||
program_state->key_buffer_count++;
|
program_state->key_buffer_count++;
|
||||||
|
|
||||||
// Flush buffer when full
|
// Flush buffer when full
|
||||||
@@ -659,17 +665,20 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_
|
|||||||
// Allocate key buffer for static encrypted nonces
|
// Allocate key buffer for static encrypted nonces
|
||||||
if(n->attack == static_encrypted) {
|
if(n->attack == static_encrypted) {
|
||||||
size_t available_ram = memmgr_heap_get_max_free_block();
|
size_t available_ram = memmgr_heap_get_max_free_block();
|
||||||
// Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string
|
// Each key becomes 2 hex chars (key_idx) + 12 hex chars (key) + 1 newline = 15 bytes in the batch string
|
||||||
// Plus original 6 bytes in buffer = 19 bytes total per key
|
// Plus original 6 bytes (key) + 1 byte (key_idx) in buffer = 22 bytes total per key
|
||||||
// Add extra safety margin for string overhead and other allocations
|
// Add extra safety margin for string overhead and other allocations
|
||||||
const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD;
|
const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD;
|
||||||
const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation
|
const size_t bytes_per_key =
|
||||||
|
sizeof(MfClassicKey) + sizeof(uint8_t) + 15; // buffer + string representation
|
||||||
if(available_ram > safety_threshold) {
|
if(available_ram > safety_threshold) {
|
||||||
program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key;
|
program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key;
|
||||||
program_state->key_buffer =
|
program_state->key_buffer =
|
||||||
malloc(program_state->key_buffer_size * sizeof(MfClassicKey));
|
malloc(program_state->key_buffer_size * sizeof(MfClassicKey));
|
||||||
|
program_state->key_idx_buffer =
|
||||||
|
malloc(program_state->key_buffer_size * sizeof(uint8_t));
|
||||||
program_state->key_buffer_count = 0;
|
program_state->key_buffer_count = 0;
|
||||||
if(!program_state->key_buffer) {
|
if(!program_state->key_buffer || !program_state->key_idx_buffer) {
|
||||||
// Free the allocated blocks before returning
|
// Free the allocated blocks before returning
|
||||||
for(int i = 0; i < num_blocks; i++) {
|
for(int i = 0; i < num_blocks; i++) {
|
||||||
free(block_pointers[i]);
|
free(block_pointers[i]);
|
||||||
@@ -691,6 +700,7 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
program_state->key_buffer = NULL;
|
program_state->key_buffer = NULL;
|
||||||
|
program_state->key_idx_buffer = NULL;
|
||||||
program_state->key_buffer_size = 0;
|
program_state->key_buffer_size = 0;
|
||||||
program_state->key_buffer_count = 0;
|
program_state->key_buffer_count = 0;
|
||||||
}
|
}
|
||||||
@@ -736,7 +746,9 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_
|
|||||||
if(n->attack == static_encrypted && program_state->key_buffer) {
|
if(n->attack == static_encrypted && program_state->key_buffer) {
|
||||||
flush_key_buffer(program_state);
|
flush_key_buffer(program_state);
|
||||||
free(program_state->key_buffer);
|
free(program_state->key_buffer);
|
||||||
|
free(program_state->key_idx_buffer);
|
||||||
program_state->key_buffer = NULL;
|
program_state->key_buffer = NULL;
|
||||||
|
program_state->key_idx_buffer = NULL;
|
||||||
program_state->key_buffer_size = 0;
|
program_state->key_buffer_size = 0;
|
||||||
program_state->key_buffer_count = 0;
|
program_state->key_buffer_count = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ typedef struct {
|
|||||||
FuriThread* mfkeythread;
|
FuriThread* mfkeythread;
|
||||||
KeysDict* cuid_dict;
|
KeysDict* cuid_dict;
|
||||||
MfClassicKey* key_buffer;
|
MfClassicKey* key_buffer;
|
||||||
|
uint8_t* key_idx_buffer;
|
||||||
size_t key_buffer_size;
|
size_t key_buffer_size;
|
||||||
size_t key_buffer_count;
|
size_t key_buffer_count;
|
||||||
} ProgramState;
|
} ProgramState;
|
||||||
@@ -72,6 +73,7 @@ typedef struct {
|
|||||||
uint32_t nt1; // tag challenge second
|
uint32_t nt1; // tag challenge second
|
||||||
uint32_t uid_xor_nt0; // uid ^ nt0
|
uint32_t uid_xor_nt0; // uid ^ nt0
|
||||||
uint32_t uid_xor_nt1; // uid ^ nt1
|
uint32_t uid_xor_nt1; // uid ^ nt1
|
||||||
|
uint8_t key_idx; // key index (for static encrypted nonces)
|
||||||
union {
|
union {
|
||||||
// Mfkey32
|
// Mfkey32
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
@@ -163,11 +163,14 @@ NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) {
|
|||||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestMode;
|
instance->mfc_event.type = MfClassicPollerEventTypeRequestMode;
|
||||||
command = instance->callback(instance->general_event, instance->context);
|
command = instance->callback(instance->general_event, instance->context);
|
||||||
|
|
||||||
if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackStandard) {
|
if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackStandard ||
|
||||||
|
instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackCUID) {
|
||||||
mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data);
|
mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data);
|
||||||
|
instance->mode_ctx.dict_attack_ctx.mode = instance->mfc_event_data.poller_mode.mode;
|
||||||
instance->state = MfClassicPollerStateRequestKey;
|
instance->state = MfClassicPollerStateRequestKey;
|
||||||
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackEnhanced) {
|
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackEnhanced) {
|
||||||
mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data);
|
mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data);
|
||||||
|
instance->mode_ctx.dict_attack_ctx.mode = instance->mfc_event_data.poller_mode.mode;
|
||||||
instance->state = MfClassicPollerStateAnalyzeBackdoor;
|
instance->state = MfClassicPollerStateAnalyzeBackdoor;
|
||||||
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) {
|
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) {
|
||||||
instance->state = MfClassicPollerStateRequestReadSector;
|
instance->state = MfClassicPollerStateRequestReadSector;
|
||||||
@@ -590,7 +593,22 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance)
|
|||||||
(error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) {
|
(error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) {
|
||||||
FURI_LOG_D(TAG, "No backdoor identified");
|
FURI_LOG_D(TAG, "No backdoor identified");
|
||||||
dict_attack_ctx->backdoor = MfClassicBackdoorNone;
|
dict_attack_ctx->backdoor = MfClassicBackdoorNone;
|
||||||
instance->state = MfClassicPollerStateRequestKey;
|
|
||||||
|
// Check if any keys were cached - if so, go directly to nested attack
|
||||||
|
bool has_cached_keys = false;
|
||||||
|
for(uint8_t sector = 0; sector < instance->sectors_total; sector++) {
|
||||||
|
if(mf_classic_is_key_found(instance->data, sector, MfClassicKeyTypeA) ||
|
||||||
|
mf_classic_is_key_found(instance->data, sector, MfClassicKeyTypeB)) {
|
||||||
|
has_cached_keys = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(has_cached_keys) {
|
||||||
|
instance->state = MfClassicPollerStateNestedController;
|
||||||
|
} else {
|
||||||
|
instance->state = MfClassicPollerStateRequestKey;
|
||||||
|
}
|
||||||
} else if(error == MfClassicErrorNone) {
|
} else if(error == MfClassicErrorNone) {
|
||||||
FURI_LOG_I(TAG, "Backdoor identified: v%d", backdoor_version);
|
FURI_LOG_I(TAG, "Backdoor identified: v%d", backdoor_version);
|
||||||
dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type;
|
dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type;
|
||||||
@@ -687,7 +705,15 @@ NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) {
|
|||||||
command = instance->callback(instance->general_event, instance->context);
|
command = instance->callback(instance->general_event, instance->context);
|
||||||
if(instance->mfc_event_data.key_request_data.key_provided) {
|
if(instance->mfc_event_data.key_request_data.key_provided) {
|
||||||
dict_attack_ctx->current_key = instance->mfc_event_data.key_request_data.key;
|
dict_attack_ctx->current_key = instance->mfc_event_data.key_request_data.key;
|
||||||
instance->state = MfClassicPollerStateAuthKeyA;
|
dict_attack_ctx->requested_key_type = instance->mfc_event_data.key_request_data.key_type;
|
||||||
|
|
||||||
|
// In CUID mode, go directly to the appropriate Auth state based on key_type
|
||||||
|
if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID &&
|
||||||
|
dict_attack_ctx->requested_key_type == MfClassicKeyTypeB) {
|
||||||
|
instance->state = MfClassicPollerStateAuthKeyB;
|
||||||
|
} else {
|
||||||
|
instance->state = MfClassicPollerStateAuthKeyA;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
instance->state = MfClassicPollerStateNextSector;
|
instance->state = MfClassicPollerStateNextSector;
|
||||||
}
|
}
|
||||||
@@ -701,7 +727,12 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) {
|
|||||||
|
|
||||||
if(mf_classic_is_key_found(
|
if(mf_classic_is_key_found(
|
||||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
||||||
instance->state = MfClassicPollerStateAuthKeyB;
|
// In CUID mode, skip directly to RequestKey since we test keys by specific type
|
||||||
|
if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID) {
|
||||||
|
instance->state = MfClassicPollerStateRequestKey;
|
||||||
|
} else {
|
||||||
|
instance->state = MfClassicPollerStateAuthKeyB;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector);
|
uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector);
|
||||||
uint64_t key =
|
uint64_t key =
|
||||||
@@ -722,7 +753,12 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) {
|
|||||||
instance->state = MfClassicPollerStateReadSector;
|
instance->state = MfClassicPollerStateReadSector;
|
||||||
} else {
|
} else {
|
||||||
mf_classic_poller_halt(instance);
|
mf_classic_poller_halt(instance);
|
||||||
instance->state = MfClassicPollerStateAuthKeyB;
|
// In CUID mode, skip directly to RequestKey since we test keys by specific type
|
||||||
|
if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID) {
|
||||||
|
instance->state = MfClassicPollerStateRequestKey;
|
||||||
|
} else {
|
||||||
|
instance->state = MfClassicPollerStateAuthKeyB;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,8 +771,11 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) {
|
|||||||
|
|
||||||
if(mf_classic_is_key_found(
|
if(mf_classic_is_key_found(
|
||||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB)) {
|
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB)) {
|
||||||
if(mf_classic_is_key_found(
|
// In CUID mode, just request next key since we iterate by key_idx
|
||||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID) {
|
||||||
|
instance->state = MfClassicPollerStateRequestKey;
|
||||||
|
} else if(mf_classic_is_key_found(
|
||||||
|
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
||||||
instance->state = MfClassicPollerStateNextSector;
|
instance->state = MfClassicPollerStateNextSector;
|
||||||
} else {
|
} else {
|
||||||
instance->state = MfClassicPollerStateRequestKey;
|
instance->state = MfClassicPollerStateRequestKey;
|
||||||
@@ -774,12 +813,20 @@ NfcCommand mf_classic_poller_handler_next_sector(MfClassicPoller* instance) {
|
|||||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||||
|
|
||||||
dict_attack_ctx->current_sector++;
|
dict_attack_ctx->current_sector++;
|
||||||
|
|
||||||
if(dict_attack_ctx->current_sector == instance->sectors_total) {
|
if(dict_attack_ctx->current_sector == instance->sectors_total) {
|
||||||
instance->state = MfClassicPollerStateSuccess;
|
instance->state = MfClassicPollerStateSuccess;
|
||||||
} else {
|
} else {
|
||||||
instance->mfc_event.type = MfClassicPollerEventTypeNextSector;
|
instance->mfc_event.type = MfClassicPollerEventTypeNextSector;
|
||||||
instance->mfc_event_data.next_sector_data.current_sector = dict_attack_ctx->current_sector;
|
instance->mfc_event_data.next_sector_data.current_sector = dict_attack_ctx->current_sector;
|
||||||
command = instance->callback(instance->general_event, instance->context);
|
command = instance->callback(instance->general_event, instance->context);
|
||||||
|
|
||||||
|
// In CUID mode, NFC app manages sector based on key_idx - read it back
|
||||||
|
if(dict_attack_ctx->mode == MfClassicPollerModeDictAttackCUID) {
|
||||||
|
dict_attack_ctx->current_sector =
|
||||||
|
instance->mfc_event_data.next_sector_data.current_sector;
|
||||||
|
}
|
||||||
|
|
||||||
instance->state = MfClassicPollerStateRequestKey;
|
instance->state = MfClassicPollerStateRequestKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ typedef enum {
|
|||||||
MfClassicPollerModeRead, /**< Poller reading mode. */
|
MfClassicPollerModeRead, /**< Poller reading mode. */
|
||||||
MfClassicPollerModeWrite, /**< Poller writing mode. */
|
MfClassicPollerModeWrite, /**< Poller writing mode. */
|
||||||
MfClassicPollerModeDictAttackStandard, /**< Poller dictionary attack mode. */
|
MfClassicPollerModeDictAttackStandard, /**< Poller dictionary attack mode. */
|
||||||
|
MfClassicPollerModeDictAttackCUID, /**< Poller CUID dictionary attack mode. */
|
||||||
MfClassicPollerModeDictAttackEnhanced, /**< Poller enhanced dictionary attack mode. */
|
MfClassicPollerModeDictAttackEnhanced, /**< Poller enhanced dictionary attack mode. */
|
||||||
} MfClassicPollerMode;
|
} MfClassicPollerMode;
|
||||||
|
|
||||||
@@ -129,6 +130,7 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MfClassicKey key; /**< Key to be used by poller. */
|
MfClassicKey key; /**< Key to be used by poller. */
|
||||||
|
MfClassicKeyType key_type; /**< Key type (A or B) for CUID dict attack mode. */
|
||||||
bool key_provided; /**< Flag indicating if key is provided. */
|
bool key_provided; /**< Flag indicating if key is provided. */
|
||||||
} MfClassicPollerEventDataKeyRequest;
|
} MfClassicPollerEventDataKeyRequest;
|
||||||
|
|
||||||
|
|||||||
@@ -128,10 +128,12 @@ typedef struct {
|
|||||||
uint8_t current_sector;
|
uint8_t current_sector;
|
||||||
MfClassicKey current_key;
|
MfClassicKey current_key;
|
||||||
MfClassicKeyType current_key_type;
|
MfClassicKeyType current_key_type;
|
||||||
|
MfClassicKeyType requested_key_type; // Key type requested from app (for CUID mode)
|
||||||
bool auth_passed;
|
bool auth_passed;
|
||||||
uint16_t current_block;
|
uint16_t current_block;
|
||||||
uint8_t reuse_key_sector;
|
uint8_t reuse_key_sector;
|
||||||
MfClassicBackdoor backdoor;
|
MfClassicBackdoor backdoor;
|
||||||
|
MfClassicPollerMode mode; // Current attack mode
|
||||||
// Enhanced dictionary attack and nested nonce collection
|
// Enhanced dictionary attack and nested nonce collection
|
||||||
bool enhanced_dict;
|
bool enhanced_dict;
|
||||||
MfClassicNestedPhase nested_phase;
|
MfClassicNestedPhase nested_phase;
|
||||||
|
|||||||
Reference in New Issue
Block a user