diff --git a/mfkey/.catalog/README.md b/mfkey/.catalog/README.md index 7cf89be..7f130b4 100644 --- a/mfkey/.catalog/README.md +++ b/mfkey/.catalog/README.md @@ -1,12 +1,12 @@ # Flipper Zero MFKey -This application allows you to calculate the keys of MIFARE Classic cards using the Mfkey32 and Nested algorithms directly on your Flipper Zero. After collecting the nonces using the Detect Reader feature of the NFC app, they can be used to calculate the keys to the card in the MFKey app. +This application allows you to calculate the keys of MIFARE Classic cards using the Mfkey32 and Nested algorithms directly on your Flipper Zero. After collecting the nonces using the Extract MF Keys feature of the NFC app, they can be used to calculate the keys to the card in the MFKey app. ## Usage -After collecting nonces using the Detect Reader option, press the Start button in the MFKey app and wait for it to finish. The calculation can take more than 10 minutes, so you'll have to be patient. After the calculation is complete, the keys will be saved to the user key dictionary. +After collecting nonces using the Extract MF Keys option, press the Start button in the MFKey app and wait for it to finish. The calculation can take more than 10 minutes, so you'll have to be patient. After the calculation is complete, the keys will be saved to the user key dictionary. ## Credits -Developers: noproto, AG -Thanks: bettse +Developers: noproto, AG, Flipper Devices, WillyJL +Thanks: AloneLiberty, Foxushka, bettse, Equip diff --git a/mfkey/.catalog/changelog.md b/mfkey/.catalog/changelog.md index cce42b7..2b05351 100644 --- a/mfkey/.catalog/changelog.md +++ b/mfkey/.catalog/changelog.md @@ -1,3 +1,5 @@ +## 3.0 + - Added Static Encrypted Nested key recovery, added NFC app support, dropped FlipperNested support ## 2.7 - Mfkey32 recovery is 30% faster, fix UI and slowdown bugs ## 2.6 diff --git a/mfkey/application.fam b/mfkey/application.fam index 6f83eb7..dfd5138 100644 --- a/mfkey/application.fam +++ b/mfkey/application.fam @@ -15,7 +15,7 @@ App( fap_icon_assets="images", fap_weburl="https://github.com/noproto/FlipperMfkey", fap_description="MIFARE Classic key recovery tool", - fap_version="2.7", + fap_version="3.0", ) App( diff --git a/mfkey/crypto1.h b/mfkey/crypto1.h index 299de9f..9caf5b3 100644 --- a/mfkey/crypto1.h +++ b/mfkey/crypto1.h @@ -3,6 +3,7 @@ #include #include "mfkey.h" +#include #include #define LF_POLY_ODD (0x29CE5C) @@ -20,6 +21,12 @@ void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); static inline uint32_t crypt_word(struct Crypto1State* s); static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); +static uint32_t crypt_word_par( + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits); static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); @@ -131,6 +138,48 @@ static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x return ret; } +static uint8_t get_nth_byte(uint32_t value, int n) { + if(n < 0 || n > 3) { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; +} + +static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; +} + +static inline uint32_t crypt_word_par( + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits) { + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits + + for(int i = 0; i < 32; i++) { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if((i + 1) % 8 == 0) { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; +} + static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { uint8_t ret; uint32_t feedin, t, next_in; diff --git a/mfkey/init_plugin.c b/mfkey/init_plugin.c index 751efee..8540a8f 100644 --- a/mfkey/init_plugin.c +++ b/mfkey/init_plugin.c @@ -9,12 +9,11 @@ #include "plugin_interface.h" #include +#define TAG "MFKey" + // TODO: Remove defines that are not needed -#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") #define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") -#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested") -#define TAG "MFKey" +#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested.log") #define MAX_NAME_LEN 32 #define MAX_PATH_LEN 64 @@ -30,6 +29,7 @@ ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) bool key_already_found_for_nonce_in_dict(KeysDict* dict, MfClassicNonce* nonce) { + // This function must not be passed the CUID dictionary bool found = false; uint8_t key_bytes[sizeof(MfClassicKey)]; keys_dict_rewind(dict); @@ -47,7 +47,7 @@ bool key_already_found_for_nonce_in_dict(KeysDict* dict, MfClassicNonce* nonce) found = true; break; } - } else if(nonce->attack == static_nested) { + } else if(nonce->attack == static_nested || nonce->attack == static_encrypted) { uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); if(nonce->ks1_1_enc == expected_ks1) { found = true; @@ -68,59 +68,30 @@ bool napi_mf_classic_mfkey32_nonces_check_presence() { return nonces_present; } -bool distance_in_nonces_file(const char* file_path, const char* file_name) { - char full_path[MAX_PATH_LEN]; - snprintf(full_path, sizeof(full_path), "%s/%s", file_path, file_name); - bool distance_present = false; - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* file_stream = buffered_file_stream_alloc(storage); - FuriString* line_str; - line_str = furi_string_alloc(); - - if(buffered_file_stream_open(file_stream, full_path, FSAM_READ, FSOM_OPEN_EXISTING)) { - while(true) { - if(!stream_read_line(file_stream, line_str)) break; - if(furi_string_search_str(line_str, "distance") != FURI_STRING_FAILURE) { - distance_present = true; - break; - } - } - } - - buffered_file_stream_close(file_stream); - stream_free(file_stream); - furi_string_free(line_str); - furi_record_close(RECORD_STORAGE); - - return distance_present; -} - bool napi_mf_classic_nested_nonces_check_presence() { Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + bool nonces_present = false; + FuriString* line = furi_string_alloc(); - if(!(storage_dir_exists(storage, MF_CLASSIC_NESTED_NONCE_PATH))) { - furi_record_close(RECORD_STORAGE); - return false; - } + do { + if(!buffered_file_stream_open( + stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + break; + } - bool nonces_present = false; - File* dir = storage_file_alloc(storage); - char filename_buffer[MAX_NAME_LEN]; - FileInfo file_info; - - if(storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) { - while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) { - // We only care about Static Nested files - if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") && - !(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) { + while(stream_read_line(stream, line)) { + if(furi_string_search_str(line, "dist 0") != FURI_STRING_FAILURE) { nonces_present = true; break; } } - } - storage_dir_close(dir); - storage_file_free(dir); + } while(false); + + furi_string_free(line); + buffered_file_stream_close(stream); + stream_free(stream); furi_record_close(RECORD_STORAGE); return nonces_present; @@ -247,7 +218,6 @@ bool load_mfkey32_nonces( } furi_string_free(next_line); buffered_file_stream_close(nonce_array->stream); - //stream_free(nonce_array->stream); array_loaded = true; //FURI_LOG_I(TAG, "Loaded %lu Mfkey32 nonces", nonce_array->total_nonces); @@ -262,89 +232,70 @@ bool load_nested_nonces( KeysDict* system_dict, bool system_dict_exists, KeysDict* user_dict) { - Storage* storage = furi_record_open(RECORD_STORAGE); - File* dir = storage_file_alloc(storage); - char filename_buffer[MAX_NAME_LEN]; - FileInfo file_info; - FuriString* next_line = furi_string_alloc(); - - if(!storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) { - storage_dir_close(dir); - storage_file_free(dir); - furi_record_close(RECORD_STORAGE); - furi_string_free(next_line); + if(!buffered_file_stream_open( + nonce_array->stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { return false; } - while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) { - if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") && - !(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) { - char full_path[MAX_PATH_LEN]; - snprintf( - full_path, - sizeof(full_path), - "%s/%s", - MF_CLASSIC_NESTED_NONCE_PATH, - filename_buffer); - - // TODO: We should only need READ_WRITE here if we plan on adding a newline to the end of the file if has none - if(!buffered_file_stream_open( - nonce_array->stream, full_path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(nonce_array->stream); - continue; - } + FuriString* next_line = furi_string_alloc(); + bool array_loaded = false; - while(stream_read_line(nonce_array->stream, next_line)) { - if(furi_string_search_str(next_line, "Nested:") != FURI_STRING_FAILURE) { - MfClassicNonce res = {0}; - res.attack = static_nested; - int parsed = sscanf( - furi_string_get_cstr(next_line), - "Nested: %*s %*s cuid 0x%" PRIx32 " nt0 0x%" PRIx32 " ks0 0x%" PRIx32 - " par0 %4[01] nt1 0x%" PRIx32 " ks1 0x%" PRIx32 " par1 %4[01]", - &res.uid, - &res.nt0, - &res.ks1_1_enc, - res.par_1_str, - &res.nt1, - &res.ks1_2_enc, - res.par_2_str); - - if(parsed != 7) continue; - res.par_1 = binaryStringToInt(res.par_1_str); - res.par_2 = binaryStringToInt(res.par_2_str); - res.uid_xor_nt0 = res.uid ^ res.nt0; - res.uid_xor_nt1 = res.uid ^ res.nt1; - - (program_state->total)++; - if((system_dict_exists && - key_already_found_for_nonce_in_dict(system_dict, &res)) || - (key_already_found_for_nonce_in_dict(user_dict, &res))) { - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } + while(stream_read_line(nonce_array->stream, next_line)) { + const char* line = furi_string_get_cstr(next_line); - nonce_array->remaining_nonce_array = realloc( - nonce_array->remaining_nonce_array, - sizeof(MfClassicNonce) * (nonce_array->remaining_nonces + 1)); - nonce_array->remaining_nonce_array[nonce_array->remaining_nonces] = res; - nonce_array->remaining_nonces++; - nonce_array->total_nonces++; - } + // Only process lines ending with "dist 0" + if(!strstr(line, "dist 0")) { + continue; + } + + MfClassicNonce res = {0}; + res.attack = static_encrypted; + + int parsed = sscanf( + line, + "Sec %*d key %*c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32 + " par0 %4[01] nt1 %" PRIx32 " ks1 %" PRIx32 " par1 %4[01]", + &res.uid, + &res.nt0, + &res.ks1_1_enc, + res.par_1_str, + &res.nt1, + &res.ks1_2_enc, + res.par_2_str); + + if(parsed >= 4) { // At least one nonce is present + res.par_1 = binaryStringToInt(res.par_1_str); + res.uid_xor_nt0 = res.uid ^ res.nt0; + + if(parsed == 7) { // Both nonces are present + res.attack = static_nested; + res.par_2 = binaryStringToInt(res.par_2_str); + res.uid_xor_nt1 = res.uid ^ res.nt1; } - buffered_file_stream_close(nonce_array->stream); + (program_state->total)++; + if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) || + (key_already_found_for_nonce_in_dict(user_dict, &res))) { + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + + nonce_array->remaining_nonce_array = realloc( + nonce_array->remaining_nonce_array, + sizeof(MfClassicNonce) * (nonce_array->remaining_nonces + 1)); + nonce_array->remaining_nonce_array[nonce_array->remaining_nonces] = res; + nonce_array->remaining_nonces++; + nonce_array->total_nonces++; + array_loaded = true; } } - storage_dir_close(dir); - storage_file_free(dir); - furi_record_close(RECORD_STORAGE); furi_string_free(next_line); + buffered_file_stream_close(nonce_array->stream); //FURI_LOG_I(TAG, "Loaded %lu Static Nested nonces", nonce_array->total_nonces); - return true; + return array_loaded; } MfClassicNonceArray* napi_mf_classic_nonce_array_alloc( @@ -359,21 +310,13 @@ MfClassicNonceArray* napi_mf_classic_nonce_array_alloc( nonce_array->stream = buffered_file_stream_alloc(storage); furi_record_close(RECORD_STORAGE); - bool array_loaded = false; - if(program_state->mfkey32_present) { - array_loaded = load_mfkey32_nonces( + load_mfkey32_nonces( nonce_array, program_state, system_dict, system_dict_exists, user_dict); } if(program_state->nested_present) { - array_loaded |= load_nested_nonces( - nonce_array, program_state, system_dict, system_dict_exists, user_dict); - } - - if(!array_loaded) { - free(nonce_array); - nonce_array = NULL; + load_nested_nonces(nonce_array, program_state, system_dict, system_dict_exists, user_dict); } return nonce_array; @@ -384,6 +327,7 @@ void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) { furi_assert(nonce_array); furi_assert(nonce_array->stream); + // TODO: Already closed? buffered_file_stream_close(nonce_array->stream); stream_free(nonce_array->stream); free(nonce_array); diff --git a/mfkey/mfkey.c b/mfkey/mfkey.c index f95c079..ae5cc90 100644 --- a/mfkey/mfkey.c +++ b/mfkey/mfkey.c @@ -1,7 +1,6 @@ #pragma GCC optimize("O3") #pragma GCC optimize("-funroll-all-loops") -// TODO: Add keys to top of the user dictionary, not the bottom // TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? // (a cache for key_already_found_for_nonce_in_dict) // TODO: Selectively unroll loops to reduce binary size @@ -9,8 +8,11 @@ // TODO: Why different sscanf between Mfkey32 and Nested? // TODO: "Read tag again with NFC app" message upon completion, "Complete. Keys added: " // TODO: Separate Mfkey32 and Nested functions where possible to reduce branch statements -// TODO: More accurate timing for Nested // TODO: Find ~1 KB memory leak +// TODO: Use seednt16 to reduce static encrypted key candidates: https://gist.github.com/noproto/8102f8f32546564cd674256e62ff76ea +// https://eprint.iacr.org/2024/1275.pdf section X +// TODO: Static Encrypted: Minimum RAM for adding to keys dict (avoid crashes) +// TODO: Static Encrypted: Optimize KeysDict or buffer keys to write in chunks #include #include @@ -31,14 +33,13 @@ #include #include +#define TAG "MFKey" + // TODO: Remove defines that are not needed -#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") -#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested") -#define TAG "MFKey" -#define MAX_NAME_LEN 32 -#define MAX_PATH_LEN 64 +#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) @@ -57,7 +58,8 @@ static int eta_total_time = 705; // MSB_LIMIT: Chunk size (out of 256) static int MSB_LIMIT = 16; -int check_state(struct Crypto1State* t, MfClassicNonce* n) { +static inline int + check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { if(!(t->odd | t->even)) return 0; if(n->attack == mfkey32) { uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); @@ -73,7 +75,6 @@ int check_state(struct Crypto1State* t, MfClassicNonce* n) { crypto1_get_lfsr(&temp, &(n->key)); return 1; } - return 0; } else if(n->attack == static_nested) { struct Crypto1State temp = {t->odd, t->even}; rollback_word_noret(t, n->uid_xor_nt1, 0); @@ -82,7 +83,21 @@ int check_state(struct Crypto1State* t, MfClassicNonce* n) { crypto1_get_lfsr(&temp, &(n->key)); return 1; } - return 0; + } else if(n->attack == static_encrypted) { + // TODO: Parity bits from rollback_word? + if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); + } + } } return 0; } @@ -208,7 +223,8 @@ int old_recover( int s, MfClassicNonce* n, unsigned int in, - int first_run) { + int first_run, + ProgramState* program_state) { int o, e, i; if(rem == -1) { for(e = e_head; e <= e_tail; ++e) { @@ -217,7 +233,7 @@ int old_recover( struct Crypto1State temp = {0, 0}; temp.even = odd[o]; temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, n)) { + if(check_state(&temp, n, program_state)) { return -1; } } @@ -245,7 +261,20 @@ int old_recover( o_tail = binsearch(odd, o_head, o = o_tail); e_tail = binsearch(even, e_head, e = e_tail); s = old_recover( - odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, n, in, first_run); + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); if(s == -1) { break; } @@ -381,7 +410,8 @@ int calculate_msb_tables( 0, n, in >> 16, - 1); + 1, + program_state); if(res == -1) { return 1; } @@ -436,6 +466,15 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_ return false; } } + // Adjust estimates for static encrypted attacks + if(n->attack == static_encrypted) { + eta_round_time *= 4; + eta_total_time *= 4; + if(is_full_speed()) { + eta_round_time *= 4; + eta_total_time *= 4; + } + } struct Msb* odd_msbs = block_pointers[0]; struct Msb* even_msbs = block_pointers[1]; unsigned int* temp_states_odd = block_pointers[2]; @@ -523,7 +562,8 @@ static void finished_beep() { } void mfkey(ProgramState* program_state) { - MfClassicKey found_key; // recovered key + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key size_t keyarray_size = 0; MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); uint32_t i = 0, j = 0; @@ -587,6 +627,7 @@ void mfkey(ProgramState* program_state) { // TODO: Track free state at the time this is called to ensure double free does not happen furi_assert(nonce_arr); furi_assert(nonce_arr->stream); + // TODO: Already closed? buffered_file_stream_close(nonce_arr->stream); stream_free(nonce_arr->stream); //FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); @@ -601,28 +642,39 @@ void mfkey(ProgramState* program_state) { continue; } //FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); - if(next_nonce.attack == mfkey32) { - if(!recover(&next_nonce, next_nonce.ar0_enc ^ next_nonce.p64, 0, program_state)) { - if(program_state->close_thread_please) { - break; - } - // No key found in recover() - (program_state->num_completed)++; - continue; + FuriString* cuid_dict_path; + switch(next_nonce.attack) { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + break; + } + + if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); } - } else if(next_nonce.attack == static_nested) { - if(!recover( - &next_nonce, - next_nonce.ks1_2_enc, - next_nonce.nt1 ^ next_nonce.uid, - program_state)) { - if(program_state->close_thread_please) { - break; - } - // No key found in recover() - (program_state->num_completed)++; - continue; + if(program_state->close_thread_please) { + break; } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + continue; } (program_state->cracked)++; (program_state->num_completed)++; @@ -677,27 +729,24 @@ static void render_callback(Canvas* const canvas, void* ctx) { canvas_draw_frame(canvas, 0, 0, 128, 64); canvas_draw_frame(canvas, 0, 15, 128, 64); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + // FontSecondary by default, title is drawn at the end snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); - canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if(program_state->is_thread_running && program_state->mfkey_state == MFKeyAttack) { + if(program_state->mfkey_state == MFKeyAttack) { float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); float progress = (float)program_state->num_completed / (float)program_state->total; - if(eta_round < 0) { + if(eta_round < 0 || eta_round > 1) { // Round ETA miscalculated eta_round = 1; program_state->eta_round = 0; } - if(eta_total < 0) { + if(eta_total < 0 || eta_round > 1) { // Total ETA miscalculated eta_total = 1; program_state->eta_total = 0; } - canvas_set_font(canvas, FontSecondary); snprintf( draw_str, sizeof(draw_str), @@ -715,8 +764,7 @@ static void render_callback(Canvas* const canvas, void* ctx) { elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } else if(program_state->is_thread_running && program_state->mfkey_state == DictionaryAttack) { - canvas_set_font(canvas, FontSecondary); + } else if(program_state->mfkey_state == DictionaryAttack) { snprintf( draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); @@ -725,29 +773,32 @@ static void render_callback(Canvas* const canvas, void* ctx) { } else if(program_state->mfkey_state == Complete) { // TODO: Scrollable list view to see cracked keys if user presses down elements_progress_bar(canvas, 5, 18, 118, 1); - canvas_set_font(canvas, FontSecondary); - snprintf(draw_str, sizeof(draw_str), "Complete"); - canvas_draw_str_aligned(canvas, 40, 31, AlignLeft, AlignTop, draw_str); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); snprintf( draw_str, sizeof(draw_str), "Keys added to user dict: %d", program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 10, 41, AlignLeft, AlignTop, draw_str); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if(program_state->num_candidates > 0) { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } } else if(program_state->mfkey_state == Ready) { - canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); elements_button_center(canvas, "Start"); elements_button_right(canvas, "Help"); } else if(program_state->mfkey_state == Help) { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces using Detect"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "Reader or FlipperNested."); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "Devs: noproto, AG, ALiberty"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "Thanks: bettse, Foxushka"); + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); } else if(program_state->mfkey_state == Error) { canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - canvas_set_font(canvas, FontSecondary); if(program_state->err == MissingNonces) { canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); } else if(program_state->err == ZeroNonces) { @@ -760,6 +811,9 @@ static void render_callback(Canvas* const canvas, void* ctx) { } else { // Unhandled program state } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); furi_mutex_release(program_state->mutex); } @@ -769,11 +823,11 @@ static void input_callback(InputEvent* input_event, void* event_queue) { } static void mfkey_state_init(ProgramState* program_state) { - program_state->is_thread_running = false; program_state->mfkey_state = Ready; program_state->cracked = 0; program_state->unique_cracked = 0; program_state->num_completed = 0; + program_state->num_candidates = 0; program_state->total = 0; program_state->dict_count = 0; } @@ -781,11 +835,8 @@ static void mfkey_state_init(ProgramState* program_state) { // Entrypoint for worker thread static int32_t mfkey_worker_thread(void* ctx) { ProgramState* program_state = ctx; - program_state->is_thread_running = true; program_state->mfkey_state = Initializing; - //FURI_LOG_I(TAG, "Hello from the mfkey worker thread"); // DEBUG mfkey(program_state); - program_state->is_thread_running = false; return 0; } @@ -807,11 +858,8 @@ int32_t mfkey_main() { Gui* gui = furi_record_open(RECORD_GUI); gui_add_view_port(gui, view_port, GuiLayerFullscreen); - program_state->mfkeythread = furi_thread_alloc(); - furi_thread_set_name(program_state->mfkeythread, "MFKeyWorker"); - furi_thread_set_stack_size(program_state->mfkeythread, 2048); - furi_thread_set_context(program_state->mfkeythread, program_state); - furi_thread_set_callback(program_state->mfkeythread, mfkey_worker_thread); + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); InputEvent input_event; for(bool main_loop = true; main_loop;) { @@ -823,25 +871,22 @@ int32_t mfkey_main() { if(input_event.type == InputTypePress) { switch(input_event.key) { case InputKeyRight: - if(!program_state->is_thread_running && program_state->mfkey_state == Ready) { + if(program_state->mfkey_state == Ready) { program_state->mfkey_state = Help; } break; case InputKeyOk: - if(!program_state->is_thread_running && program_state->mfkey_state == Ready) { + if(program_state->mfkey_state == Ready) { furi_thread_start(program_state->mfkeythread); } break; case InputKeyBack: - if(!program_state->is_thread_running && program_state->mfkey_state == Help) { + if(program_state->mfkey_state == Help) { program_state->mfkey_state = Ready; } else { program_state->close_thread_please = true; - if(program_state->is_thread_running) { - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - } - program_state->close_thread_please = false; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); main_loop = false; } break; @@ -855,6 +900,7 @@ int32_t mfkey_main() { view_port_update(view_port); } + // Thread joined in back event handler furi_thread_free(program_state->mfkeythread); view_port_enabled_set(view_port, false); gui_remove_view_port(gui, view_port); diff --git a/mfkey/mfkey.h b/mfkey/mfkey.h index 9d7e043..4a7ab34 100644 --- a/mfkey/mfkey.h +++ b/mfkey/mfkey.h @@ -41,6 +41,7 @@ typedef struct { int cracked; int unique_cracked; int num_completed; + int num_candidates; int total; int dict_count; int search; @@ -49,14 +50,15 @@ typedef struct { int eta_round; bool mfkey32_present; bool nested_present; - bool is_thread_running; bool close_thread_please; FuriThread* mfkeythread; + KeysDict* cuid_dict; } ProgramState; typedef enum { mfkey32, - static_nested + static_nested, + static_encrypted } AttackType; typedef struct { @@ -67,20 +69,26 @@ typedef struct { uint32_t nt1; // tag challenge second uint32_t uid_xor_nt0; // uid ^ nt0 uint32_t uid_xor_nt1; // uid ^ nt1 - // Mfkey32 - uint32_t p64; // 64th successor of nt0 - uint32_t p64b; // 64th successor of nt1 - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response - // Nested - uint32_t ks1_1_enc; // first encrypted keystream - uint32_t ks1_2_enc; // second encrypted keystream - char par_1_str[5]; // first parity bits (string representation) - char par_2_str[5]; // second parity bits (string representation) - uint8_t par_1; // first parity bits - uint8_t par_2; // second parity bits + union { + // Mfkey32 + struct { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; } MfClassicNonce; typedef struct {