diff --git a/applications/system/mfkey/.catalog/README.md b/applications/system/mfkey/.catalog/README.md index 22ff43aa6..770e46880 100644 --- a/applications/system/mfkey/.catalog/README.md +++ b/applications/system/mfkey/.catalog/README.md @@ -8,5 +8,5 @@ After collecting nonces using the Extract MF Keys option, press the Start button ## Credits -Developers: noproto, AG, Flipper Devices, WillyJL, CavallUwU +Developers: noproto, AG, Flipper Devices, WillyJL, CavallUwU, Ivisayan Thanks: AloneLiberty, Foxushka, bettse, Equip diff --git a/applications/system/mfkey/.catalog/changelog.md b/applications/system/mfkey/.catalog/changelog.md index c93c6e8f8..7b5be62b2 100644 --- a/applications/system/mfkey/.catalog/changelog.md +++ b/applications/system/mfkey/.catalog/changelog.md @@ -1,5 +1,5 @@ ## 3.1 - - Key recovery is 20% faster, added write buffering of Static Encrypted Nested key candidates + - Key recovery is 20% faster, new write buffering of Static Encrypted Nested key candidates performs recovery 70x faster ## 3.0 - Added Static Encrypted Nested key recovery, added NFC app support, dropped FlipperNested support ## 2.7 diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index 0b68e31e0..e49b96263 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -11,8 +11,6 @@ // 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 @@ -40,6 +38,7 @@ #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 STATIC_ENCRYPTED_RAM_THRESHOLD 4096 #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) @@ -66,6 +65,50 @@ static int16_t eta_total_time = 705; // MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value static uint8_t MSB_LIMIT = 16; +static inline void flush_key_buffer(ProgramState *program_state) +{ + if (program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) + { + // Pre-allocate exact size needed: 12 hex chars + 1 newline per key + size_t total_size = program_state->key_buffer_count * 13; + //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); + //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); + char* batch_buffer = malloc(total_size + 1); // +1 for null terminator + + char* ptr = batch_buffer; + const char hex_chars[] = "0123456789ABCDEF"; + + for (size_t i = 0; i < program_state->key_buffer_count; i++) + { + // Convert key to hex string directly into buffer + for (size_t j = 0; j < sizeof(MfClassicKey); j++) + { + uint8_t byte = program_state->key_buffer[i].data[j]; + *ptr++ = hex_chars[byte >> 4]; + *ptr++ = hex_chars[byte & 0x0F]; + } + *ptr++ = '\n'; + } + *ptr = '\0'; + + // Write all keys at once by directly accessing the stream + Stream* stream = program_state->cuid_dict->stream; + uint32_t actual_pos = stream_tell(stream); + + if (stream_seek(stream, 0, StreamOffsetFromEnd) && + stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) + { + // Update total key count + program_state->cuid_dict->total_keys += program_state->key_buffer_count; + } + + // May not be needed + stream_seek(stream, actual_pos, StreamOffsetFromStart); + free(batch_buffer); + program_state->key_buffer_count = 0; + } +} + static inline int check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) { @@ -115,7 +158,16 @@ check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_sta // 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)); + + // 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_count++; + + // Flush buffer when full + if (program_state->key_buffer_count >= program_state->key_buffer_size) + { + flush_key_buffer(program_state); + } } } } @@ -680,53 +732,102 @@ void **allocate_blocks(const size_t *block_sizes, int num_blocks) return block_pointers; } -// Inline function for checking if we're at full speed -static inline bool is_full_speed() -{ - return MSB_LIMIT == 16; -} - bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) { bool found = false; const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - void **block_pointers = allocate_blocks(block_sizes, num_blocks); - if (block_pointers == NULL) + // Reset globals each nonce + eta_round_time = 44; + eta_total_time = 705; + MSB_LIMIT = 16; + + // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys + bool use_half_speed = (n->attack == static_encrypted); + if (use_half_speed) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - if (is_full_speed()) + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } + + void **block_pointers = allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); + if (block_pointers == NULL) { + if (n->attack != static_encrypted) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed // eta_round_time *= 2; eta_total_time *= 2; MSB_LIMIT /= 2; - } - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if (block_pointers == NULL) + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further program_state->err = InsufficientRAM; program_state->mfkey_state = Error; 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]; unsigned int *temp_states_even = block_pointers[3]; unsigned int *states_buffer = block_pointers[4]; + + // Allocate key buffer for static encrypted nonces + if (n->attack == static_encrypted) + { + size_t available_ram = memmgr_heap_get_max_free_block(); + // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string + // Plus original 6 bytes in buffer = 19 bytes total per key + // Add extra safety margin for string overhead and other allocations + const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; + const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation + if (available_ram > safety_threshold) + { + program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; + program_state->key_buffer = malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); + program_state->key_buffer_count = 0; + if (!program_state->key_buffer) + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + int oks = 0, eks = 0; int i = 0, msb = 0; for (i = 31; i >= 0; i -= 2) @@ -768,6 +869,17 @@ bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_ break; } } + + // Final flush and cleanup for key buffer + if (n->attack == static_encrypted && program_state->key_buffer) + { + flush_key_buffer(program_state); + free(program_state->key_buffer); + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + // Free the allocated blocks for (int i = 0; i < num_blocks; i++) { @@ -946,16 +1058,33 @@ void mfkey(ProgramState *program_state) if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + // Check for non-recoverable errors and break the loop + if (program_state->mfkey_state == Error) { - keys_dict_free(program_state->cuid_dict); + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; } if (program_state->close_thread_please) { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } break; } // No key found in recover() or static encrypted (program_state->num_completed)++; + // Free CUID dictionary after each static_encrypted nonce processing + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } continue; } (program_state->cracked)++; diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 74274edc8..7b5be5c5a 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -58,6 +58,9 @@ typedef struct bool close_thread_please; FuriThread *mfkeythread; KeysDict *cuid_dict; + MfClassicKey *key_buffer; + size_t key_buffer_size; + size_t key_buffer_count; } ProgramState; typedef enum