noproto 1 год назад
Родитель
Сommit
45b22f0d8a
4 измененных файлов с 150 добавлено и 38 удалено
  1. 49 0
      crypto1.h
  2. 4 2
      init_plugin.c
  3. 93 35
      mfkey.c
  4. 4 1
      mfkey.h

+ 49 - 0
crypto1.h

@@ -3,6 +3,7 @@
 
 #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)
@@ -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;

+ 4 - 2
init_plugin.c

@@ -28,6 +28,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);
@@ -45,7 +46,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;
@@ -248,7 +249,7 @@ bool load_nested_nonces(
         }
 
         MfClassicNonce res = {0};
-        res.attack = static_nested;
+        res.attack = static_encrypted;
 
         int parsed = sscanf(
             line,
@@ -267,6 +268,7 @@ bool load_nested_nonces(
             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;
             }

+ 93 - 35
mfkey.c

@@ -9,6 +9,10 @@
 // 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: 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>
@@ -53,7 +57,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);
@@ -69,10 +74,7 @@ int check_state(struct Crypto1State* t, MfClassicNonce* n) {
             crypto1_get_lfsr(&temp, &(n->key));
             return 1;
         }
-        return 0;
     } else if(n->attack == static_nested) {
-        // TODO: Needs to be revised to save all candidate keys for static encrypted when all properties produce a match
-        // A static encrypted MfClassicNonce has no nonce pair
         struct Crypto1State temp = {t->odd, t->even};
         rollback_word_noret(t, n->uid_xor_nt1, 0);
         if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) {
@@ -80,7 +82,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;
 }
@@ -206,7 +222,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) {
@@ -215,7 +232,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;
                 }
             }
@@ -243,7 +260,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;
             }
@@ -379,7 +409,8 @@ int calculate_msb_tables(
             0,
             n,
             in >> 16,
-            1);
+            1,
+            program_state);
         if(res == -1) {
             return 1;
         }
@@ -434,6 +465,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];
@@ -521,7 +561,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;
@@ -599,28 +640,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)++;
@@ -725,15 +777,21 @@ static void render_callback(Canvas* const canvas, void* ctx) {
         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);
-        // Mfkey32
+        canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, draw_str);
         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);
-        // TODO: Keys added to UID dict (Static Encrypted)
+        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");
@@ -774,6 +832,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;
 }
@@ -783,7 +842,6 @@ 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;

+ 4 - 1
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;
@@ -52,11 +53,13 @@ typedef struct {
     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 {