Static encrypted performance fixed

This commit is contained in:
noproto
2025-08-24 19:00:10 -04:00
parent 2cfe6f6130
commit 2125e210c8
4 changed files with 164 additions and 32 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 <furi.h>
#include <furi_hal.h>
@@ -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)++;

View File

@@ -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