MX 1 год назад
Родитель
Сommit
eb82afdd15
7 измененных файлов с 299 добавлено и 275 удалено
  1. 1 1
      application.fam
  2. 1 1
      crypto1.c
  3. 57 6
      crypto1.h
  4. 85 141
      init_plugin.c
  5. 145 111
      mfkey.c
  6. 8 13
      mfkey.h
  7. 2 2
      plugin_interface.h

+ 1 - 1
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(

+ 1 - 1
crypto1.c

@@ -19,4 +19,4 @@ void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr) {
     for(i = 0; i < 6; ++i) {
         lfsr->data[i] = (lfsr_value >> ((5 - i) * 8)) & 0xFF;
     }
-}
+}

+ 57 - 6
crypto1.h

@@ -3,12 +3,13 @@
 
 #include <inttypes.h>
 #include "mfkey.h"
+#include <nfc/helpers/nfc_util.h>
 #include <nfc/protocols/mf_classic/mf_classic.h>
 
-#define LF_POLY_ODD (0x29CE5C)
+#define LF_POLY_ODD  (0x29CE5C)
 #define LF_POLY_EVEN (0x870804)
-#define BIT(x, n) ((x) >> (n) & 1)
-#define BEBIT(x, n) BIT(x, (n) ^ 24)
+#define BIT(x, n)    ((x) >> (n) & 1)
+#define BEBIT(x, n)  BIT(x, (n) ^ 24)
 #define SWAPENDIAN(x) \
     ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
 
@@ -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;
@@ -192,14 +241,16 @@ uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) {
 uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) {
     int i;
     uint32_t ret = 0;
-    for(i = 31; i >= 0; --i) ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24);
+    for(i = 31; i >= 0; --i)
+        ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24);
     return ret;
 }
 
 static inline uint32_t prng_successor(uint32_t x, uint32_t n) {
     SWAPENDIAN(x);
-    while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
+    while(n--)
+        x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
     return SWAPENDIAN(x);
 }
 
-#endif // CRYPTO1_H
+#endif // CRYPTO1_H

+ 85 - 141
init_plugin.c

@@ -9,27 +9,27 @@
 #include "plugin_interface.h"
 #include <flipper_application/flipper_application.h>
 
-// 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 LF_POLY_ODD (0x29CE5C)
+// TODO: Remove defines that are not needed
+#define MF_CLASSIC_NONCE_PATH        EXT_PATH("nfc/.mfkey32.log")
+#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested.log")
+#define MAX_NAME_LEN                 32
+#define MAX_PATH_LEN                 64
+
+#define LF_POLY_ODD  (0x29CE5C)
 #define LF_POLY_EVEN (0x870804)
-#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1)
-#define CONST_M2_1 (LF_POLY_ODD << 1)
-#define CONST_M1_2 (LF_POLY_ODD)
-#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1)
-#define BIT(x, n) ((x) >> (n) & 1)
-#define BEBIT(x, n) BIT(x, (n) ^ 24)
+#define CONST_M1_1   (LF_POLY_EVEN << 1 | 1)
+#define CONST_M2_1   (LF_POLY_ODD << 1)
+#define CONST_M1_2   (LF_POLY_ODD)
+#define CONST_M2_2   (LF_POLY_EVEN << 1 | 1)
+#define BIT(x, n)    ((x) >> (n) & 1)
+#define BEBIT(x, n)  BIT(x, (n) ^ 24)
 #define SWAPENDIAN(x) \
     ((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);
@@ -409,4 +353,4 @@ static const FlipperAppPluginDescriptor init_plugin_descriptor = {
 /* Plugin entry point - must return a pointer to const descriptor  */
 const FlipperAppPluginDescriptor* init_plugin_ep() {
     return &init_plugin_descriptor;
-}
+}

+ 145 - 111
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: <n>"
 // 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 <furi.h>
 #include <furi_hal.h>
@@ -31,23 +33,22 @@
 #include <loader/firmware_api/firmware_api.h>
 #include <storage/storage.h>
 
+#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_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_ODD  (0x29CE5C)
 #define LF_POLY_EVEN (0x870804)
-#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1)
-#define CONST_M2_1 (LF_POLY_ODD << 1)
-#define CONST_M1_2 (LF_POLY_ODD)
-#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1)
-#define BIT(x, n) ((x) >> (n) & 1)
-#define BEBIT(x, n) BIT(x, (n) ^ 24)
+#define CONST_M1_1   (LF_POLY_EVEN << 1 | 1)
+#define CONST_M2_1   (LF_POLY_ODD << 1)
+#define CONST_M1_2   (LF_POLY_ODD)
+#define CONST_M2_2   (LF_POLY_EVEN << 1 | 1)
+#define BIT(x, n)    ((x) >> (n) & 1)
+#define BEBIT(x, n)  BIT(x, (n) ^ 24)
 #define SWAPENDIAN(x) \
     ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
 //#define SIZEOF(arr) sizeof(arr) / sizeof(*arr)
@@ -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;
         }
@@ -394,9 +424,6 @@ int calculate_msb_tables(
 
 void** allocate_blocks(const size_t* block_sizes, int num_blocks) {
     void** block_pointers = malloc(num_blocks * sizeof(void*));
-    if(block_pointers == NULL) {
-        return NULL;
-    }
 
     for(int i = 0; i < num_blocks; i++) {
         if(memmgr_heap_get_max_free_block() < block_sizes[i]) {
@@ -409,14 +436,6 @@ void** allocate_blocks(const size_t* block_sizes, int num_blocks) {
         }
 
         block_pointers[i] = malloc(block_sizes[i]);
-        if(block_pointers[i] == NULL) {
-            // Allocation failed, free previously allocated blocks
-            for(int j = 0; j < i; j++) {
-                free(block_pointers[j]);
-            }
-            free(block_pointers);
-            return NULL;
-        }
     }
 
     return block_pointers;
@@ -447,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];
@@ -534,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;
@@ -598,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());
@@ -612,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)++;
@@ -684,9 +725,11 @@ static void render_callback(Canvas* const canvas, void* ctx) {
     ProgramState* program_state = ctx;
     furi_mutex_acquire(program_state->mutex, FuriWaitForever);
     char draw_str[44] = {};
-    canvas_clear(canvas);
+
     canvas_draw_frame(canvas, 0, 0, 128, 64);
     canvas_draw_frame(canvas, 0, 15, 128, 64);
+
+    // FontSecondary by default, title is drawn at the end
     snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap());
     canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str);
     canvas_draw_icon(canvas, 114, 4, &I_mfkey);
@@ -699,7 +742,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
             eta_round = 1;
             program_state->eta_round = 0;
         }
-        if(eta_total < 0 || eta_total > 1) {
+        if(eta_total < 0 || eta_round > 1) {
             // Total ETA miscalculated
             eta_total = 1;
             program_state->eta_total = 0;
@@ -730,24 +773,30 @@ 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_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_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");
         if(program_state->err == MissingNonces) {
@@ -762,6 +811,7 @@ 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,9 +819,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
 
 static void input_callback(InputEvent* input_event, void* event_queue) {
     furi_assert(event_queue);
-
-    PluginEvent event = {.type = EventTypeKey, .input = *input_event};
-    furi_message_queue_put((FuriMessageQueue*)event_queue, &event, FuriWaitForever);
+    furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever);
 }
 
 static void mfkey_state_init(ProgramState* program_state) {
@@ -779,6 +827,7 @@ static void mfkey_state_init(ProgramState* program_state) {
     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;
 }
@@ -787,24 +836,18 @@ static void mfkey_state_init(ProgramState* program_state) {
 static int32_t mfkey_worker_thread(void* ctx) {
     ProgramState* program_state = ctx;
     program_state->mfkey_state = Initializing;
-    //FURI_LOG_I(TAG, "Hello from the mfkey worker thread"); // DEBUG
     mfkey(program_state);
     return 0;
 }
 
 int32_t mfkey_main() {
-    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
 
     ProgramState* program_state = malloc(sizeof(ProgramState));
 
     mfkey_state_init(program_state);
 
     program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
-    if(!program_state->mutex) {
-        //FURI_LOG_E(TAG, "cannot create mutex\r\n");
-        free(program_state);
-        return 255;
-    }
 
     // Set system callbacks
     ViewPort* view_port = view_port_alloc();
@@ -816,48 +859,39 @@ int32_t mfkey_main() {
     gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 
     program_state->mfkeythread =
-        furi_thread_alloc_ex("MFKey Worker", 2048, mfkey_worker_thread, program_state);
+        furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state);
 
-    PluginEvent event;
+    InputEvent input_event;
     for(bool main_loop = true; main_loop;) {
-        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+        FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100);
 
         furi_mutex_acquire(program_state->mutex, FuriWaitForever);
 
         if(event_status == FuriStatusOk) {
-            // press events
-            if(event.type == EventTypeKey) {
-                if(event.input.type == InputTypePress) {
-                    switch(event.input.key) {
-                    case InputKeyUp:
-                        break;
-                    case InputKeyDown:
-                        break;
-                    case InputKeyRight:
-                        if(program_state->mfkey_state == Ready) {
-                            program_state->mfkey_state = Help;
-                        }
-                        break;
-                    case InputKeyLeft:
-                        break;
-                    case InputKeyOk:
-                        if(program_state->mfkey_state == Ready) {
-                            furi_thread_start(program_state->mfkeythread);
-                        }
-                        break;
-                    case InputKeyBack:
-                        if(program_state->mfkey_state == Help) {
-                            program_state->mfkey_state = Ready;
-                        } else {
-                            program_state->close_thread_please = true;
-                            // Wait until thread is finished
-                            furi_thread_join(program_state->mfkeythread);
-                            main_loop = false;
-                        }
-                        break;
-                    default:
-                        break;
+            if(input_event.type == InputTypePress) {
+                switch(input_event.key) {
+                case InputKeyRight:
+                    if(program_state->mfkey_state == Ready) {
+                        program_state->mfkey_state = Help;
+                    }
+                    break;
+                case InputKeyOk:
+                    if(program_state->mfkey_state == Ready) {
+                        furi_thread_start(program_state->mfkeythread);
+                    }
+                    break;
+                case InputKeyBack:
+                    if(program_state->mfkey_state == Help) {
+                        program_state->mfkey_state = Ready;
+                    } else {
+                        program_state->close_thread_please = true;
+                        // Wait until thread is finished
+                        furi_thread_join(program_state->mfkeythread);
+                        main_loop = false;
                     }
+                    break;
+                default:
+                    break;
                 }
             }
         }
@@ -870,7 +904,7 @@ int32_t mfkey_main() {
     furi_thread_free(program_state->mfkeythread);
     view_port_enabled_set(view_port, false);
     gui_remove_view_port(gui, view_port);
-    furi_record_close("gui");
+    furi_record_close(RECORD_GUI);
     view_port_free(view_port);
     furi_message_queue_free(event_queue);
     furi_mutex_free(program_state->mutex);
@@ -878,4 +912,4 @@ int32_t mfkey_main() {
 
     return 0;
 }
-#pragma GCC pop_options
+#pragma GCC pop_options

+ 8 - 13
mfkey.h

@@ -17,16 +17,6 @@ struct Msb {
     uint32_t states[768];
 };
 
-typedef enum {
-    EventTypeTick,
-    EventTypeKey,
-} EventType;
-
-typedef struct {
-    EventType type;
-    InputEvent input;
-} PluginEvent;
-
 typedef enum {
     MissingNonces,
     ZeroNonces,
@@ -51,6 +41,7 @@ typedef struct {
     int cracked;
     int unique_cracked;
     int num_completed;
+    int num_candidates;
     int total;
     int dict_count;
     int search;
@@ -59,12 +50,16 @@ 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 } AttackType;
+typedef enum {
+    mfkey32,
+    static_nested,
+    static_encrypted
+} AttackType;
 
 typedef struct {
     AttackType attack;
@@ -110,4 +105,4 @@ struct KeysDict {
     size_t total_keys;
 };
 
-#endif // MFKEY_H
+#endif // MFKEY_H

+ 2 - 2
plugin_interface.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#define PLUGIN_APP_ID "mfkey"
+#define PLUGIN_APP_ID      "mfkey"
 #define PLUGIN_API_VERSION 1
 
 typedef struct {
@@ -10,4 +10,4 @@ typedef struct {
     MfClassicNonceArray* (
         *napi_mf_classic_nonce_array_alloc)(KeysDict*, bool, KeysDict*, ProgramState*);
     void (*napi_mf_classic_nonce_array_free)(MfClassicNonceArray*);
-} MfkeyPlugin;
+} MfkeyPlugin;