spheeere98 пре 2 година
родитељ
комит
ffdb210a83

+ 25 - 1
README.md

@@ -1 +1,25 @@
-# mifare_fuzzer
+# Flipperzero Mifare Fuzzer
+
+### What
+This app allows you to fake Mifare UIDs.  
+It emulates only the UID of the card, it does not emulate the full card, but it seems to be enough...
+
+Currently it does support this kinds of card:
+- Classic 1k
+- Classic 4k
+- Ultralight
+
+### Install
+To compile you must be familiar with the Flipperzero firmware.
+1. Checkout the Flipperzero firmware
+2. go to `applications/plugins/`
+3. `git clone` this repo inside directory
+4. return to main firmware directory with `cd ../..`
+5. run `fbt fap_mifare_fuzzer` to compile
+
+### License
+
+https://en.wikipedia.org/wiki/WTFPL
+
+    DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+    Version 2, December 2004

+ 11 - 0
TODO.md

@@ -0,0 +1,11 @@
+
+# Flipperzero Mifare Fuzzer - TODO
+
+- Create a list of manufacturer codes for 7byte UIDs. _(Now it's fixed to: 0x04 = NXP Semiconductors Germany)_
+    - https://github.com/Proxmark/proxmark3/blob/master/client/taginfo.c
+    - https://stackoverflow.com/questions/37837730/mifare-cards-distinguish-between-4-byte-and-7-byte-uids
+    - https://stackoverflow.com/questions/31233652/how-to-detect-manufacturer-from-nfc-tag-using-android
+
+- Add saving option
+
+- Emulate a full card and not only the UID

+ 15 - 0
application.fam

@@ -0,0 +1,15 @@
+App(
+    appid="mifare_fuzzer",
+    name="Mifare Fuzzer",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="mifare_fuzzer_app",
+    requires=[
+        "storage",
+        "gui",
+    ],
+    stack_size=4 * 1024,
+    order=30,
+    fap_icon="./images/mifare_fuzzer_10px.png",
+    fap_category="Tools",
+    fap_icon_assets="images",
+)

+ 9 - 0
example_uids04.txt

@@ -0,0 +1,9 @@
+# One UID per line
+# Keep an empty line at the end
+01020304
+02030405
+03040506
+04050607
+05060708
+06070809
+

+ 10 - 0
example_uids07.txt

@@ -0,0 +1,10 @@
+# One UID per line
+# Keep an empty line at the end
+01020304050607
+02030405060701
+03040506070102
+04050607010203
+05060701020304
+06070102030405
+07010203040506
+

BIN
images/mifare_fuzzer_10px.png


+ 165 - 0
mifare_fuzzer.c

@@ -0,0 +1,165 @@
+#include "mifare_fuzzer_i.h"
+
+/// @brief mifare_fuzzer_custom_event_callback()
+/// @param context 
+/// @param event 
+/// @return 
+static bool mifare_fuzzer_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    MifareFuzzerApp* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+/// @brief mifare_fuzzer_back_event_callback()
+/// @param context 
+/// @return 
+static bool mifare_fuzzer_back_event_callback(void* context) {
+    furi_assert(context);
+    MifareFuzzerApp* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+/// @brief mifare_fuzzer_tick_event_callback()
+/// @param context 
+static void mifare_fuzzer_tick_event_callback(void* context){
+    furi_assert(context);
+    MifareFuzzerApp* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+/// @brief mifare_fuzzer_alloc()
+/// @return 
+MifareFuzzerApp* mifare_fuzzer_alloc() {
+    MifareFuzzerApp* app = malloc(sizeof(MifareFuzzerApp));
+
+    app->view_dispatcher = view_dispatcher_alloc();
+    app->scene_manager = scene_manager_alloc(&mifare_fuzzer_scene_handlers, app);
+    view_dispatcher_enable_queue(app->view_dispatcher);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, mifare_fuzzer_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(app->view_dispatcher, mifare_fuzzer_back_event_callback);
+
+    // 1000 ticks are about 1 sec
+    view_dispatcher_set_tick_event_callback(app->view_dispatcher, mifare_fuzzer_tick_event_callback, MIFARE_FUZZER_TICK_PERIOD);
+
+    // Open GUI record
+    app->gui = furi_record_open(RECORD_GUI);
+    view_dispatcher_attach_to_gui(
+        app->view_dispatcher,
+        app->gui,
+        ViewDispatcherTypeFullscreen
+    );
+
+    // view: select card type
+    app->submenu_card = submenu_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        MifareFuzzerViewSelectCard,
+        submenu_get_view(app->submenu_card)
+    );
+
+    // view: select attack type
+    app->submenu_attack = submenu_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        MifareFuzzerViewSelectAttack,
+        submenu_get_view(app->submenu_attack)
+    );
+
+    // view: emulator
+    app->emulator_view = mifare_fuzzer_emulator_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        MifareFuzzerViewEmulator,
+        mifare_fuzzer_emulator_get_view(app->emulator_view)
+    );
+
+    // worker
+    app->worker = mifare_fuzzer_worker_alloc();
+
+    // storage
+    app->storage = furi_record_open(RECORD_STORAGE);
+    if(!storage_simply_mkdir(app->storage, MIFARE_FUZZER_APP_FOLDER)) {
+        FURI_LOG_E(TAG, "Could not create folder: %s", MIFARE_FUZZER_APP_FOLDER);
+    }
+
+    // dialog
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    // furi strings
+    app->uid_str = furi_string_alloc();
+    app->file_path = furi_string_alloc();
+    app->app_folder = furi_string_alloc_set(MIFARE_FUZZER_APP_FOLDER);
+
+    return app;
+}
+
+/// @brief mifare_fuzzer_free()
+/// @param app 
+void mifare_fuzzer_free(MifareFuzzerApp* app) {
+    furi_assert(app);
+
+    // Views
+    //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: Views");
+    view_dispatcher_remove_view(app->view_dispatcher, MifareFuzzerViewSelectCard);
+    view_dispatcher_remove_view(app->view_dispatcher, MifareFuzzerViewSelectAttack);
+
+    // Submenus
+    //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: Submenus");
+    submenu_free(app->submenu_card);
+    submenu_free(app->submenu_attack);
+
+    // View Dispatcher
+    //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: View Dispatcher");
+    view_dispatcher_free(app->view_dispatcher);
+
+    // Scene Manager
+    //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: Scene Manager");
+    scene_manager_free(app->scene_manager);
+
+    // GUI
+    //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: GUI");
+    furi_record_close(RECORD_GUI);
+    app->gui = NULL;
+
+    // Worker
+    //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: Worker");
+    mifare_fuzzer_worker_free(app->worker);
+
+    // storage
+    furi_record_close(RECORD_STORAGE);
+    app->storage = NULL;
+
+    // dialog
+    furi_record_close(RECORD_DIALOGS);
+    app->dialogs = NULL;
+
+    // furi strings
+    furi_string_free(app->uid_str);
+    furi_string_free(app->file_path);
+    furi_string_free(app->app_folder);
+
+    // App
+    //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: App");
+    free(app);
+}
+
+/// @brief mifare_fuzzer_app (ENTRYPOINT)
+/// @param p 
+/// @return 
+int32_t mifare_fuzzer_app(void* p) {
+    UNUSED(p);
+    //FURI_LOG_D(TAG, "mifare_fuzzer_app()");
+
+    MifareFuzzerApp* app = mifare_fuzzer_alloc();
+    // init some defaults
+    scene_manager_set_scene_state(app->scene_manager, MifareFuzzerSceneStart, 0);
+    scene_manager_set_scene_state(app->scene_manager, MifareFuzzerSceneAttack, 0);
+    // open scene
+    scene_manager_next_scene(app->scene_manager, MifareFuzzerSceneStart);
+    view_dispatcher_run(app->view_dispatcher);
+    // free
+    mifare_fuzzer_free(app);
+
+    return 0;
+}

+ 3 - 0
mifare_fuzzer.h

@@ -0,0 +1,3 @@
+#pragma once
+
+typedef struct MifareFuzzerApp MifareFuzzerApp;

+ 14 - 0
mifare_fuzzer_custom_events.h

@@ -0,0 +1,14 @@
+#pragma once
+
+typedef enum MifareFuzzerEvent {
+    MifareFuzzerEventClassic1k = 1,
+    MifareFuzzerEventClassic4k,
+    MifareFuzzerEventUltralight,
+    MifareFuzzerEventTestValueAttack,
+    MifareFuzzerEventRandomValuesAttack,
+    MifareFuzzerEventLoadUIDsFromFileAttack,
+    MifareFuzzerEventStartAttack,
+    MifareFuzzerEventStopAttack,
+    MifareFuzzerEventIncrementTicks,
+    MifareFuzzerEventDecrementTicks,
+} MifareFuzzerEvent;

+ 78 - 0
mifare_fuzzer_i.h

@@ -0,0 +1,78 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+
+#include <gui/modules/submenu.h>
+
+#include <dialogs/dialogs.h>
+
+#include <input/input.h>
+
+#include <toolbox/stream/stream.h>
+//#include <toolbox/stream/string_stream.h>
+//#include <toolbox/stream/file_stream.h>
+#include <toolbox/stream/buffered_file_stream.h>
+
+#include "mifare_fuzzer.h"
+
+#include "scenes/mifare_fuzzer_scene.h"
+#include "views/mifare_fuzzer_emulator.h"
+
+#include "mifare_fuzzer_worker.h"
+
+#define TAG "MifareFuzzerApp"
+
+#define MIFARE_FUZZER_APP_FOLDER EXT_PATH("mifare_fuzzer")
+#define MIFARE_FUZZER_FILE_EXT ".txt"
+
+#define MIFARE_FUZZER_TICK_PERIOD 200
+#define MIFARE_FUZZER_DEFAULT_TICKS_BETWEEN_CARDS 10
+#define MIFARE_FUZZER_MIN_TICKS_BETWEEN_CARDS 5
+#define MIFARE_FUZZER_MAX_TICKS_BETWEEN_CARDS 50
+
+typedef enum MifareFuzzerSceneState {
+    MifareFuzzerSceneStateClassic1k,
+    MifareFuzzerSceneStateClassic4k,
+    MifareFuzzerSceneStateUltralight,
+} MifareFuzzerSceneState;
+
+typedef enum {
+    MifareFuzzerViewSelectCard,
+    MifareFuzzerViewSelectAttack,
+    MifareFuzzerViewEmulator,
+} MifareFuzzerView;
+
+struct MifareFuzzerApp {
+
+    Gui* gui;
+
+    ViewDispatcher* view_dispatcher;
+
+    SceneManager* scene_manager;
+
+    DialogsApp* dialogs;
+
+    Storage* storage;
+
+    // Common Views
+    Submenu* submenu_card;
+    Submenu* submenu_attack;
+
+    MifareFuzzerEmulator* emulator_view;
+
+    MifareFuzzerWorker* worker;
+
+    MifareCard card;
+    MifareFuzzerAttack attack;
+    FuriHalNfcDevData nfc_dev_data;
+    FuriString* app_folder;
+    FuriString* file_path;
+    FuriString* uid_str;
+    Stream* uids_stream;
+
+};

+ 91 - 0
mifare_fuzzer_worker.c

@@ -0,0 +1,91 @@
+
+#include "mifare_fuzzer_worker.h"
+
+/// @brief mifare_fuzzer_worker_alloc()
+/// @return 
+MifareFuzzerWorker* mifare_fuzzer_worker_alloc() {
+    MifareFuzzerWorker* mifare_fuzzer_worker = malloc(sizeof(MifareFuzzerWorker));
+    // Worker thread attributes
+    mifare_fuzzer_worker->thread = furi_thread_alloc_ex("MifareFuzzerWorker", 8192, mifare_fuzzer_worker_task, mifare_fuzzer_worker);
+    mifare_fuzzer_worker->state = MifareFuzzerWorkerStateStop;
+    return mifare_fuzzer_worker;
+}
+
+/// @brief mifare_fuzzer_worker_free()
+/// @param mifare_fuzzer_worker 
+void mifare_fuzzer_worker_free(MifareFuzzerWorker* mifare_fuzzer_worker) {
+    furi_assert(mifare_fuzzer_worker);
+    furi_thread_free(mifare_fuzzer_worker->thread);
+    free(mifare_fuzzer_worker);
+}
+
+/// @brief mifare_fuzzer_worker_stop()
+/// @param mifare_fuzzer_worker 
+void mifare_fuzzer_worker_stop(MifareFuzzerWorker* mifare_fuzzer_worker) {
+    furi_assert(mifare_fuzzer_worker);
+    if (mifare_fuzzer_worker->state != MifareFuzzerWorkerStateStop) {
+        mifare_fuzzer_worker->state = MifareFuzzerWorkerStateStop;
+        furi_thread_join(mifare_fuzzer_worker->thread);
+    }
+}
+
+/// @brief mifare_fuzzer_worker_start()
+/// @param mifare_fuzzer_worker 
+void mifare_fuzzer_worker_start(MifareFuzzerWorker* mifare_fuzzer_worker) {
+    furi_assert(mifare_fuzzer_worker);
+    mifare_fuzzer_worker->state = MifareFuzzerWorkerStateEmulate;
+    furi_thread_start(mifare_fuzzer_worker->thread);
+}
+
+/// @brief mifare_fuzzer_worker_task()
+/// @param context 
+/// @return 
+int32_t mifare_fuzzer_worker_task(void* context) {
+    MifareFuzzerWorker* mifare_fuzzer_worker = context;
+
+    if(mifare_fuzzer_worker->state == MifareFuzzerWorkerStateEmulate) {
+
+        FuriHalNfcDevData params = mifare_fuzzer_worker->nfc_dev_data;
+
+        furi_hal_nfc_exit_sleep();
+        while(mifare_fuzzer_worker->state == MifareFuzzerWorkerStateEmulate) {
+            furi_hal_nfc_listen(
+                params.uid,
+                params.uid_len,
+                params.atqa,
+                params.sak, false, 500
+            );
+            furi_delay_ms(50);
+        }
+        furi_hal_nfc_sleep();
+
+    }
+
+    mifare_fuzzer_worker->state = MifareFuzzerWorkerStateStop;
+
+    return 0;
+}
+
+/// @brief mifare_fuzzer_worker_is_emulating()
+/// @param mifare_fuzzer_worker 
+/// @return 
+bool mifare_fuzzer_worker_is_emulating(MifareFuzzerWorker* mifare_fuzzer_worker) {
+    if (mifare_fuzzer_worker->state == MifareFuzzerWorkerStateEmulate) {
+        return true;
+    }
+    return false;
+}
+
+/// @brief mifare_fuzzer_worker_set_nfc_dev_data()
+/// @param mifare_fuzzer_worker 
+/// @param nfc_dev_data 
+void mifare_fuzzer_worker_set_nfc_dev_data(MifareFuzzerWorker* mifare_fuzzer_worker, FuriHalNfcDevData nfc_dev_data) {
+    mifare_fuzzer_worker->nfc_dev_data = nfc_dev_data;
+}
+
+/// @brief mifare_fuzzer_worker_get_nfc_dev_data()
+/// @param mifare_fuzzer_worker 
+/// @return 
+FuriHalNfcDevData mifare_fuzzer_worker_get_nfc_dev_data(MifareFuzzerWorker* mifare_fuzzer_worker) {
+    return mifare_fuzzer_worker->nfc_dev_data;
+}

+ 29 - 0
mifare_fuzzer_worker.h

@@ -0,0 +1,29 @@
+#pragma once
+#include <furi.h>
+#include <furi_hal.h>
+
+typedef enum MifareFuzzerWorkerState {
+    MifareFuzzerWorkerStateEmulate,
+    MifareFuzzerWorkerStateStop,
+} MifareFuzzerWorkerState;
+
+#define UID_LEN 7
+#define ATQA_LEN 2
+
+typedef struct MifareFuzzerWorker {
+    FuriThread* thread;
+    MifareFuzzerWorkerState state;
+    FuriHalNfcDevData nfc_dev_data;
+} MifareFuzzerWorker;
+
+// worker
+MifareFuzzerWorker* mifare_fuzzer_worker_alloc();
+void mifare_fuzzer_worker_free(MifareFuzzerWorker* mifare_fuzzer_worker);
+void mifare_fuzzer_worker_stop(MifareFuzzerWorker* mifare_fuzzer_worker);
+void mifare_fuzzer_worker_start(MifareFuzzerWorker* mifare_fuzzer_worker);
+// task
+int32_t mifare_fuzzer_worker_task(void* context);
+// 
+bool mifare_fuzzer_worker_is_emulating(MifareFuzzerWorker* mifare_fuzzer_worker);
+void mifare_fuzzer_worker_set_nfc_dev_data(MifareFuzzerWorker* mifare_fuzzer_worker, FuriHalNfcDevData nfc_dev_data);
+FuriHalNfcDevData mifare_fuzzer_worker_get_nfc_dev_data(MifareFuzzerWorker* mifare_fuzzer_worker);

+ 30 - 0
scenes/mifare_fuzzer_scene.c

@@ -0,0 +1,30 @@
+#include "mifare_fuzzer_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const mifare_fuzzer_on_enter_handlers[])(void*) = {
+#include "mifare_fuzzer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const mifare_fuzzer_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "mifare_fuzzer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const mifare_fuzzer_on_exit_handlers[])(void* context) = {
+#include "mifare_fuzzer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers mifare_fuzzer_scene_handlers = {
+    .on_enter_handlers = mifare_fuzzer_on_enter_handlers,
+    .on_event_handlers = mifare_fuzzer_on_event_handlers,
+    .on_exit_handlers = mifare_fuzzer_on_exit_handlers,
+    .scene_num = MifareFuzzerSceneNum,
+};

+ 29 - 0
scenes/mifare_fuzzer_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) MifareFuzzerScene##id,
+typedef enum {
+#include "mifare_fuzzer_scene_config.h"
+    MifareFuzzerSceneNum,
+} MifareFuzzerScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers mifare_fuzzer_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "mifare_fuzzer_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "mifare_fuzzer_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "mifare_fuzzer_scene_config.h"
+#undef ADD_SCENE

+ 149 - 0
scenes/mifare_fuzzer_scene_attack.c

@@ -0,0 +1,149 @@
+#include "../mifare_fuzzer_i.h"
+#include "../mifare_fuzzer_custom_events.h"
+
+enum SubmenuIndex {
+    SubmenuIndexTestValue,
+    SubmenuIndexRandomValuesAttack,
+    SubmenuIndexLoadUIDsFromFile,
+};
+
+/// @brief mifare_fuzzer_scene_attack_submenu_callback()
+/// @param context 
+/// @param index 
+void mifare_fuzzer_scene_attack_submenu_callback(void* context, uint32_t index) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_attack_submenu_callback() :: index = %ld", index);
+    MifareFuzzerApp* app = context;
+    uint8_t custom_event = 255;
+    switch(index){
+    case SubmenuIndexTestValue:
+        custom_event = MifareFuzzerEventTestValueAttack;
+        break;
+    case SubmenuIndexRandomValuesAttack:
+        custom_event = MifareFuzzerEventRandomValuesAttack;
+        break;
+    case SubmenuIndexLoadUIDsFromFile:
+        custom_event = MifareFuzzerEventLoadUIDsFromFileAttack;
+        break;
+    default:
+        return;
+    }
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_attack_submenu_callback() :: custom_event = %d", custom_event);
+    view_dispatcher_send_custom_event(app->view_dispatcher, custom_event);
+}
+
+/// @brief mifare_fuzzer_scene_attack_on_enter()
+/// @param context 
+void mifare_fuzzer_scene_attack_on_enter(void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_attack_on_enter()");
+    MifareFuzzerApp* app = context;
+
+    Submenu* submenu_attack = app->submenu_attack;
+    submenu_set_header(submenu_attack, "Mifare Fuzzer (attack)");
+    submenu_add_item(
+        submenu_attack,
+        "Test Values",
+        SubmenuIndexTestValue,
+        mifare_fuzzer_scene_attack_submenu_callback,
+        app
+    );
+    submenu_add_item(
+        submenu_attack,
+        "Random Values",
+        SubmenuIndexRandomValuesAttack,
+        mifare_fuzzer_scene_attack_submenu_callback,
+        app
+    );
+    submenu_add_item(
+        submenu_attack,
+        "Load UIDs from file",
+        SubmenuIndexLoadUIDsFromFile,
+        mifare_fuzzer_scene_attack_submenu_callback,
+        app
+    );
+
+    // set selected menu
+    submenu_set_selected_item(submenu_attack,
+        scene_manager_get_scene_state(
+            app->scene_manager,
+            MifareFuzzerSceneAttack
+        )
+    );
+
+    view_dispatcher_switch_to_view(
+        app->view_dispatcher,
+        MifareFuzzerViewSelectAttack
+    );
+}
+
+/// @brief mifare_fuzzer_scene_attack_on_event()
+/// @param context 
+/// @param event 
+/// @return 
+bool mifare_fuzzer_scene_attack_on_event(void* context, SceneManagerEvent event) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_attack_on_event()");
+    MifareFuzzerApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        //FURI_LOG_D(TAG, "mifare_fuzzer_scene_attack_on_event() :: event.event = %ld", event.event);
+        if(event.event == MifareFuzzerEventTestValueAttack) {
+            // save selected item
+            scene_manager_set_scene_state(app->scene_manager, MifareFuzzerSceneAttack, SubmenuIndexTestValue);
+            // set emulator attack
+            app->attack = MifareFuzzerAttackTestValues;
+            mifare_fuzzer_emulator_set_attack(app->emulator_view, app->attack);
+            // open next scene
+            scene_manager_next_scene(app->scene_manager, MifareFuzzerSceneEmulator);
+            consumed = true;
+        } else if(event.event == MifareFuzzerEventRandomValuesAttack) {
+            // save selected item
+            scene_manager_set_scene_state(app->scene_manager, MifareFuzzerSceneAttack, SubmenuIndexRandomValuesAttack);
+            // set emulator attack
+            app->attack = MifareFuzzerAttackRandomValues;
+            mifare_fuzzer_emulator_set_attack(app->emulator_view, app->attack);
+            // open next scene
+            scene_manager_next_scene(app->scene_manager, MifareFuzzerSceneEmulator);
+            consumed = true;
+        } else if(event.event == MifareFuzzerEventLoadUIDsFromFileAttack) {
+            // save selected item
+            scene_manager_set_scene_state(app->scene_manager, MifareFuzzerSceneAttack, SubmenuIndexLoadUIDsFromFile);
+            // set emulator attack
+            app->attack = MifareFuzzerAttackLoadUidsFromFile;
+            mifare_fuzzer_emulator_set_attack(app->emulator_view, app->attack);
+            // open dialog file
+            DialogsFileBrowserOptions browser_options;
+            dialog_file_browser_set_basic_options(&browser_options, MIFARE_FUZZER_FILE_EXT, NULL);
+            browser_options.hide_ext = false;
+            bool res = dialog_file_browser_show(
+                app->dialogs,
+                app->file_path,
+                app->app_folder, &browser_options);
+            if(res) {
+                app->uids_stream = buffered_file_stream_alloc(app->storage);
+                    res = buffered_file_stream_open(
+                        app->uids_stream,
+                        furi_string_get_cstr(app->file_path), FSAM_READ, FSOM_OPEN_EXISTING);
+                if(res) {
+                    // open next scene
+                    scene_manager_next_scene(app->scene_manager, MifareFuzzerSceneEmulator);
+                } else {
+                    buffered_file_stream_close(app->uids_stream);
+                }
+            }
+            consumed = true;
+        }
+    } else if (event.type == SceneManagerEventTypeTick) {
+        //FURI_LOG_D(TAG, "mifare_fuzzer_scene_attack_on_event() :: SceneManagerEventTypeTick");
+        //consumed = true;
+    }
+
+    return consumed;
+}
+
+/// @brief mifare_fuzzer_scene_attack_on_exit()
+/// @param context 
+void mifare_fuzzer_scene_attack_on_exit(void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_attack_on_exit()");
+    MifareFuzzerApp* app = context;
+    submenu_reset(app->submenu_attack);
+}

+ 3 - 0
scenes/mifare_fuzzer_scene_config.h

@@ -0,0 +1,3 @@
+ADD_SCENE(mifare_fuzzer, start, Start)
+ADD_SCENE(mifare_fuzzer, attack, Attack)
+ADD_SCENE(mifare_fuzzer, emulator, Emulator)

+ 241 - 0
scenes/mifare_fuzzer_scene_emulator.c

@@ -0,0 +1,241 @@
+#include "../mifare_fuzzer_i.h"
+
+uint8_t tick_counter = 0;
+uint8_t attack_step = 0;
+
+uint8_t id_uid_test[9][7] = {
+    {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
+    {0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28},
+    {0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39},
+    {0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a},
+    {0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b},
+    {0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c},
+    {0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d},
+    {0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e},
+    {0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f},
+};
+
+/// @brief mifare_fuzzer_scene_emulator_callback()
+/// @param event 
+/// @param context 
+static void mifare_fuzzer_scene_emulator_callback(MifareFuzzerEvent event, void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_callback()");
+    furi_assert(context);
+    MifareFuzzerApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+/// @brief mifare_fuzzer_scene_emulator_on_enter()
+/// @param context 
+void mifare_fuzzer_scene_emulator_on_enter(void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_enter()");
+    MifareFuzzerApp* app = context;
+    MifareFuzzerEmulator* emulator = app->emulator_view;
+
+    // init callback
+    mifare_fuzzer_emulator_set_callback(emulator, mifare_fuzzer_scene_emulator_callback, app);
+    // init ticks
+    tick_counter = 0;
+    mifare_fuzzer_emulator_set_tick_num(app->emulator_view, tick_counter);
+    emulator->ticks_between_cards = MIFARE_FUZZER_DEFAULT_TICKS_BETWEEN_CARDS;
+    mifare_fuzzer_emulator_set_ticks_between_cards(app->emulator_view, emulator->ticks_between_cards);
+    // init default card data
+    FuriHalNfcDevData nfc_dev_data;
+    nfc_dev_data.atqa[0] = 0x00;
+    nfc_dev_data.atqa[1] = 0x00;
+    nfc_dev_data.sak = 0x00;
+    if (app->card == MifareCardUltralight) {
+        nfc_dev_data.uid_len = 0x07;
+    } else {
+        nfc_dev_data.uid_len = 0x04;
+    }
+    for(uint32_t i = 0; i < nfc_dev_data.uid_len; i++) {
+        nfc_dev_data.uid[i] = 0x00;
+    }
+    mifare_fuzzer_emulator_set_nfc_dev_data(app->emulator_view, nfc_dev_data);
+    // init other vars
+    attack_step = 0;
+
+    // switch to view
+    view_dispatcher_switch_to_view(
+        app->view_dispatcher,
+        MifareFuzzerViewEmulator
+    );
+}
+
+/// @brief mifare_fuzzer_scene_emulator_on_event()
+/// @param context 
+/// @param event 
+/// @return 
+bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent event) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_event()");
+    FuriHalNfcDevData nfc_dev_data;
+
+    MifareFuzzerApp* app = context;
+    MifareFuzzerEmulator* emulator = app->emulator_view;
+
+    bool consumed = false;
+
+    if (event.type == SceneManagerEventTypeCustom) {
+        if (event.event == MifareFuzzerEventStartAttack) {
+            //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_event() :: MifareFuzzerEventStartAttack");
+
+            // Stop worker
+            mifare_fuzzer_worker_stop(app->worker);
+
+            // Set card type
+            // TODO: Move somewhere else, I do not like this to be there
+            if (app->card == MifareCardClassic1k) {
+                nfc_dev_data.atqa[0] = 0x04;
+                nfc_dev_data.atqa[1] = 0x00;
+                nfc_dev_data.sak = 0x08;
+                nfc_dev_data.uid_len = 0x04;
+            } else if (app->card == MifareCardClassic4k) {
+                nfc_dev_data.atqa[0] = 0x02;
+                nfc_dev_data.atqa[1] = 0x00;
+                nfc_dev_data.sak = 0x18;
+                nfc_dev_data.uid_len = 0x04;
+            } else if (app->card == MifareCardUltralight) {
+                nfc_dev_data.atqa[0] = 0x44;
+                nfc_dev_data.atqa[1] = 0x00;
+                nfc_dev_data.sak = 0x00;
+                nfc_dev_data.uid_len = 0x07;
+            }
+
+            // Set UIDs
+            if (app->attack == MifareFuzzerAttackTestValues) {
+                // Load test UIDs
+                for(uint8_t i = 0; i < nfc_dev_data.uid_len; i++) {
+                    nfc_dev_data.uid[i] = id_uid_test[attack_step][i];
+                }
+                // Next UIDs on next loop
+                if (attack_step >= 8) {
+                    attack_step = 0;
+                } else {
+                    attack_step++;
+                }
+            } else if (app->attack == MifareFuzzerAttackRandomValues) {
+                if (app->card == MifareCardUltralight) {
+                    // First byte of a 7 byte UID is the manufacturer-code
+                    // https://github.com/Proxmark/proxmark3/blob/master/client/taginfo.c
+                    // https://stackoverflow.com/questions/37837730/mifare-cards-distinguish-between-4-byte-and-7-byte-uids
+                    // https://stackoverflow.com/questions/31233652/how-to-detect-manufacturer-from-nfc-tag-using-android
+
+                    // TODO: Manufacture-code must be selectable from a list
+                    // use a fixed manufacture-code for now: 0x04 = NXP Semiconductors Germany
+                    nfc_dev_data.uid[0] = 0x04;
+                    for(uint8_t i = 1; i < nfc_dev_data.uid_len; i++) {
+                        nfc_dev_data.uid[i] = (furi_hal_random_get() & 0xFF);
+                    }
+                } else {
+                    for(uint8_t i = 0; i < nfc_dev_data.uid_len; i++) {
+                        nfc_dev_data.uid[i] = (furi_hal_random_get() & 0xFF);
+                    }
+                }
+            } else if (app->attack == MifareFuzzerAttackLoadUidsFromFile) {
+                //bool end_of_list = false;
+                // read stream
+                while(true){
+                    furi_string_reset(app->uid_str);
+                    if(!stream_read_line(app->uids_stream, app->uid_str)) {
+                        // restart from beginning on empty line
+                        stream_rewind(app->uids_stream);
+                        continue;
+                        //end_of_list = true;
+                    }
+                    // Skip comments
+                    if(furi_string_get_char(app->uid_str, 0) == '#') continue;
+                    // Skip lines with invalid length
+                    if((furi_string_size(app->uid_str) != 9) && (furi_string_size(app->uid_str) != 15)) continue;
+                    break;
+                }
+
+                // TODO: stop on end of list?
+                //if(end_of_list) break;
+
+                // parse string to UID
+                // TODO: a better validation on input?
+                for(uint8_t i = 0; i < nfc_dev_data.uid_len; i++) {
+                    if (i <= ((furi_string_size(app->uid_str) - 1) / 2)) {
+                        char temp_str[3];
+                        temp_str[0] = furi_string_get_cstr(app->uid_str)[i * 2];
+                        temp_str[1] = furi_string_get_cstr(app->uid_str)[i * 2 + 1];
+                        temp_str[2] = '\0';
+                        nfc_dev_data.uid[i] = (uint8_t)strtol(temp_str, NULL, 16);
+                    } else {
+                        nfc_dev_data.uid[i] = 0x00;
+                    }
+                }
+
+            }
+
+            mifare_fuzzer_worker_set_nfc_dev_data(app->worker, nfc_dev_data);
+            mifare_fuzzer_emulator_set_nfc_dev_data(app->emulator_view, nfc_dev_data);
+
+            // Reset tick_counter
+            tick_counter = 0;
+            mifare_fuzzer_emulator_set_tick_num(app->emulator_view, tick_counter);
+
+            // Start worker
+            mifare_fuzzer_worker_start(app->worker);
+
+        } else if (event.event == MifareFuzzerEventStopAttack) {
+            //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_event() :: MifareFuzzerEventStopAttack");
+            // Stop worker
+            mifare_fuzzer_worker_stop(app->worker);
+        } else if (event.event == MifareFuzzerEventIncrementTicks) {
+            if (!emulator->is_attacking) {
+                if (emulator->ticks_between_cards < MIFARE_FUZZER_MAX_TICKS_BETWEEN_CARDS) {
+                    emulator->ticks_between_cards++;
+                    mifare_fuzzer_emulator_set_ticks_between_cards(app->emulator_view, emulator->ticks_between_cards);
+                };
+            };
+        } else if (event.event == MifareFuzzerEventDecrementTicks) {
+            if (!emulator->is_attacking) {
+                if (emulator->ticks_between_cards > MIFARE_FUZZER_MIN_TICKS_BETWEEN_CARDS) {
+                    emulator->ticks_between_cards--;
+                    mifare_fuzzer_emulator_set_ticks_between_cards(app->emulator_view, emulator->ticks_between_cards);
+                };
+            };
+        }
+        consumed = true;
+    } else if (event.type == SceneManagerEventTypeTick) {
+        //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_event() :: SceneManagerEventTypeTick");
+
+        // Used to check tick length (not perfect but enough)
+        //FuriHalRtcDateTime curr_dt;
+        //furi_hal_rtc_get_datetime(&curr_dt);
+        //FURI_LOG_D(TAG, "Time is: %.2d:%.2d:%.2d", curr_dt.hour, curr_dt.minute, curr_dt.second);
+
+        // If emulator is attacking
+        if (emulator->is_attacking) {
+            // increment tick_counter
+            tick_counter++;
+            mifare_fuzzer_emulator_set_tick_num(app->emulator_view, tick_counter);
+            //FURI_LOG_D(TAG, "tick_counter is: %.2d", tick_counter);
+            if (tick_counter >= emulator->ticks_between_cards) {
+                // Queue event for changing UID
+                view_dispatcher_send_custom_event(app->view_dispatcher, MifareFuzzerEventStartAttack);
+            }
+        }
+
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+/// @brief mifare_fuzzer_scene_emulator_on_exit()
+/// @param context 
+void mifare_fuzzer_scene_emulator_on_exit(void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_exit()");
+    MifareFuzzerApp* app = context;
+    mifare_fuzzer_worker_stop(app->worker);
+
+    if(app->attack == MifareFuzzerAttackLoadUidsFromFile) {
+        furi_string_reset(app->uid_str);
+        stream_rewind(app->uids_stream);
+        buffered_file_stream_close(app->uids_stream);
+    }
+
+}

+ 131 - 0
scenes/mifare_fuzzer_scene_start.c

@@ -0,0 +1,131 @@
+#include "../mifare_fuzzer_i.h"
+#include "../mifare_fuzzer_custom_events.h"
+
+enum SubmenuIndex {
+    SubmenuIndexClassic1k,
+    SubmenuIndexClassic4k,
+    SubmenuIndexUltralight,
+};
+
+/// @brief mifare_fuzzer_scene_start_submenu_callback()
+/// @param context 
+/// @param index 
+void mifare_fuzzer_scene_start_submenu_callback(void* context, uint32_t index) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_start_submenu_callback() :: index = %ld", index);
+    MifareFuzzerApp* app = context;
+    uint8_t custom_event = 255;
+    switch(index){
+    case SubmenuIndexClassic1k:
+        custom_event = MifareFuzzerEventClassic1k;
+        break;
+    case SubmenuIndexClassic4k:
+        custom_event = MifareFuzzerEventClassic4k;
+        break;
+    case SubmenuIndexUltralight:
+        custom_event = MifareFuzzerEventUltralight;
+        break;
+    default:
+        return;
+    }
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_start_submenu_callback() :: custom_event = %d", custom_event);
+    view_dispatcher_send_custom_event(app->view_dispatcher, custom_event);
+}
+
+/// @brief mifare_fuzzer_scene_start_on_enter()
+/// @param context 
+void mifare_fuzzer_scene_start_on_enter(void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_start_on_enter()");
+    MifareFuzzerApp* app = context;
+
+    Submenu* submenu_card = app->submenu_card;
+    submenu_set_header(submenu_card, "Mifare Fuzzer (card)");
+    submenu_add_item(
+        submenu_card,
+        "Classic 1k",
+        SubmenuIndexClassic1k,
+        mifare_fuzzer_scene_start_submenu_callback,
+        app
+    );
+    submenu_add_item(
+        submenu_card,
+        "Classic 4k",
+        SubmenuIndexClassic4k,
+        mifare_fuzzer_scene_start_submenu_callback,
+        app
+    );
+    submenu_add_item(
+        submenu_card,
+        "Ultralight",
+        SubmenuIndexUltralight,
+        mifare_fuzzer_scene_start_submenu_callback,
+        app
+    );
+
+    // set selected menu
+    submenu_set_selected_item(submenu_card,
+        scene_manager_get_scene_state(
+            app->scene_manager,
+            MifareFuzzerSceneStart
+        )
+    );
+
+    view_dispatcher_switch_to_view(
+        app->view_dispatcher,
+        MifareFuzzerViewSelectCard
+    );
+}
+
+/// @brief mifare_fuzzer_scene_start_on_event()
+/// @param context 
+/// @param event 
+/// @return 
+bool mifare_fuzzer_scene_start_on_event(void* context, SceneManagerEvent event) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_start_on_event()");
+    MifareFuzzerApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        //FURI_LOG_D(TAG, "mifare_fuzzer_scene_start_on_event() :: event.event = %ld", event.event);
+        if(event.event == MifareFuzzerEventClassic1k) {
+            // save selected item
+            scene_manager_set_scene_state(app->scene_manager, MifareFuzzerSceneStart, SubmenuIndexClassic1k);
+            // set emulator card
+            app->card = MifareCardClassic1k;
+            mifare_fuzzer_emulator_set_card(app->emulator_view, app->card);
+            // open next scene
+            scene_manager_next_scene(app->scene_manager, MifareFuzzerSceneAttack);
+            consumed = true;
+        } else if(event.event == MifareFuzzerEventClassic4k) {
+            // save selected item
+            scene_manager_set_scene_state(app->scene_manager, MifareFuzzerSceneStart, SubmenuIndexClassic4k);
+            // set emulator card
+            app->card = MifareCardClassic4k;
+            mifare_fuzzer_emulator_set_card(app->emulator_view, app->card);
+            // open next scene
+            scene_manager_next_scene(app->scene_manager, MifareFuzzerSceneAttack);
+            consumed = true;
+        } else if(event.event == MifareFuzzerEventUltralight) {
+            // save selected item
+            scene_manager_set_scene_state(app->scene_manager, MifareFuzzerSceneStart, SubmenuIndexUltralight);
+            // set emulator card
+            app->card = MifareCardUltralight;
+            mifare_fuzzer_emulator_set_card(app->emulator_view, app->card);
+            // open next scene
+            scene_manager_next_scene(app->scene_manager, MifareFuzzerSceneAttack);
+            consumed = true;
+        }
+    } else if (event.type == SceneManagerEventTypeTick) {
+        //FURI_LOG_D(TAG, "mifare_fuzzer_scene_start_on_event() :: SceneManagerEventTypeTick");
+        //consumed = true;
+    }
+
+    return consumed;
+}
+
+/// @brief mifare_fuzzer_scene_start_on_exit()
+/// @param context 
+void mifare_fuzzer_scene_start_on_exit(void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_scene_start_on_exit()");
+    MifareFuzzerApp* app = context;
+    submenu_reset(app->submenu_card);
+}

+ 307 - 0
views/mifare_fuzzer_emulator.c

@@ -0,0 +1,307 @@
+#include "mifare_fuzzer_emulator.h"
+
+#define TAG "MifareFuzzerApp_emulator_view"
+
+// Screen is 128 × 64 pixels
+
+/// @brief mifare_fuzzer_emulator_set_callback
+/// @param mifare_fuzzer_emulator 
+/// @param callback 
+/// @param context 
+void mifare_fuzzer_emulator_set_callback(
+    MifareFuzzerEmulator* mifare_fuzzer_emulator,
+    MifareFuzzerEmulatorCallback callback,
+    void* context) {
+    furi_assert(mifare_fuzzer_emulator);
+    furi_assert(callback);
+
+    mifare_fuzzer_emulator->callback = callback;
+    mifare_fuzzer_emulator->context = context;
+}
+
+/// @brief mifare_fuzzer_emulator_draw_callback
+/// @param canvas 
+/// @param _model 
+static void mifare_fuzzer_emulator_draw_callback(Canvas* canvas, void* _model) {
+    MifareFuzzerEmulatorModel* model = _model;
+    FuriString* furi_string = furi_string_alloc();
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    // Primary font
+    canvas_set_font(canvas, FontPrimary);
+    // Title
+    canvas_draw_str(canvas, 4, 11, model->title);
+
+    // Emulated UID
+    uint8_t cpos;
+    char uid[25];
+    char uid_char[3];
+    cpos = 0;
+    for(uint8_t i = 0; i < model->nfc_dev_data.uid_len; i++) {
+        if (i > 0) {
+            uid[cpos] = ':';
+            cpos++;
+        }
+        snprintf(uid_char, sizeof(uid_char), "%02X", model->nfc_dev_data.uid[i]);
+        uid[cpos] = uid_char[0];
+        cpos++;
+        uid[cpos] = uid_char[1];
+        cpos++;
+    }
+    uid[cpos] = 0x00;
+    canvas_draw_str_aligned(canvas, 128 / 2, 43, AlignCenter, AlignCenter, uid);
+
+    // Secondary font
+    canvas_set_font(canvas, FontSecondary);
+    // Card
+    canvas_draw_str(canvas,   4, 22, "c:");
+    canvas_draw_str(canvas,  15, 22, model->mifare_card_dsc);
+    // Timing
+    furi_string_printf(furi_string, "%d", model->ticks_between_cards);
+    canvas_draw_str(canvas,  90, 22, "t:");
+    canvas_draw_str(canvas, 100, 22, furi_string_get_cstr(furi_string));
+    // Attack
+    canvas_draw_str(canvas,   4, 33, "a:");
+    canvas_draw_str(canvas,  15, 33, model->attack_dsc);
+
+    if (!model->is_attacking) {
+        elements_button_left(canvas, "t-1");
+        elements_button_center(canvas, "Start");
+        elements_button_right(canvas, "t+1");
+    } else {
+        canvas_draw_line(canvas, 1, 49, (128 * model->tick_num / model->ticks_between_cards), 49);
+        elements_button_center(canvas, "Stop");
+    }
+
+    // Free temp string
+    furi_string_free(furi_string);
+}
+
+/// @brief mifare_fuzzer_emulator_input_callback
+/// @param event 
+/// @param context 
+/// @return 
+static bool mifare_fuzzer_emulator_input_callback(InputEvent* event, void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_emulator_input_callback()");
+    furi_assert(context);
+    MifareFuzzerEmulator* mifare_fuzzer_emulator = context;
+    bool consumed = false;
+
+    if(event->type == InputTypeShort) {
+        if(event->key == InputKeyRight) {
+            if (!mifare_fuzzer_emulator->is_attacking) {
+                mifare_fuzzer_emulator->callback(MifareFuzzerEventIncrementTicks, mifare_fuzzer_emulator->context);
+            };
+            consumed = true;
+        } else if(event->key == InputKeyLeft) {
+            if (!mifare_fuzzer_emulator->is_attacking) {
+                mifare_fuzzer_emulator->callback(MifareFuzzerEventDecrementTicks, mifare_fuzzer_emulator->context);
+            };
+            consumed = true;
+        } else if(event->key == InputKeyUp) {
+            consumed = true;
+        } else if(event->key == InputKeyDown) {
+            consumed = true;
+        } else if(event->key == InputKeyOk) {
+
+            // Toggle attack
+            if (mifare_fuzzer_emulator->is_attacking) {
+                mifare_fuzzer_emulator->is_attacking = false;
+                mifare_fuzzer_emulator->callback(MifareFuzzerEventStopAttack, mifare_fuzzer_emulator->context);
+            } else {
+                mifare_fuzzer_emulator->is_attacking = true;
+                mifare_fuzzer_emulator->callback(MifareFuzzerEventStartAttack, mifare_fuzzer_emulator->context);
+            }
+
+            with_view_model(
+                mifare_fuzzer_emulator->view,
+                MifareFuzzerEmulatorModel* model,
+                {
+                    model->is_attacking = mifare_fuzzer_emulator->is_attacking;
+                }, true
+            );
+
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+/// @brief mifare_fuzzer_emulator_enter_callback
+/// @param context 
+static void mifare_fuzzer_emulator_enter_callback(void* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_emulator_enter_callback()");
+    furi_assert(context);
+    MifareFuzzerEmulator* mifare_fuzzer_emulator = context;
+
+    //UNUSED(mifare_fuzzer_emulator);
+    mifare_fuzzer_emulator->is_attacking = false;
+    with_view_model(
+        mifare_fuzzer_emulator->view,
+        MifareFuzzerEmulatorModel* model,
+        {
+            model->is_attacking = false;
+        },
+        true
+    );
+
+}
+
+/// @brief mifare_fuzzer_emulator_alloc
+/// @return 
+MifareFuzzerEmulator* mifare_fuzzer_emulator_alloc() {
+    MifareFuzzerEmulator* mifare_fuzzer_emulator = malloc(sizeof(MifareFuzzerEmulator));
+    mifare_fuzzer_emulator->view = view_alloc();
+    view_set_context(mifare_fuzzer_emulator->view, mifare_fuzzer_emulator);
+    view_allocate_model(mifare_fuzzer_emulator->view, ViewModelTypeLocking, sizeof(MifareFuzzerEmulatorModel));
+    view_set_draw_callback(mifare_fuzzer_emulator->view, mifare_fuzzer_emulator_draw_callback);
+    view_set_input_callback(mifare_fuzzer_emulator->view, mifare_fuzzer_emulator_input_callback);
+    view_set_enter_callback(mifare_fuzzer_emulator->view, mifare_fuzzer_emulator_enter_callback);
+
+    with_view_model(
+        mifare_fuzzer_emulator->view,
+        MifareFuzzerEmulatorModel* model,
+        {
+            model->title = "Mifare Fuzzer (emulator)";
+        },
+        true
+    );
+
+    return mifare_fuzzer_emulator;
+}
+
+/// @brief mifare_fuzzer_emulator_free
+/// @param context 
+void mifare_fuzzer_emulator_free(MifareFuzzerEmulator* context) {
+    //FURI_LOG_D(TAG, "mifare_fuzzer_emulator_free()");
+    furi_assert(context);
+    MifareFuzzerEmulator* mifare_fuzzer_emulator = context;
+    /*
+    with_view_model(
+        mifare_fuzzer_emulator->view,
+        MifareFuzzerEmulatorModel* model,
+        {
+            UNUSED(model);
+        },
+        true
+    );
+    */
+
+    view_free(mifare_fuzzer_emulator->view);
+    free(mifare_fuzzer_emulator);
+}
+
+/// @brief mifare_fuzzer_emulator_get_view
+/// @param mifare_fuzzer_emulator 
+/// @return 
+View* mifare_fuzzer_emulator_get_view(MifareFuzzerEmulator* mifare_fuzzer_emulator) {
+    furi_assert(mifare_fuzzer_emulator);
+    return mifare_fuzzer_emulator->view;
+}
+
+/// @brief Set card type
+/// @param mifare_fuzzer_emulator 
+/// @param mifare_card 
+void mifare_fuzzer_emulator_set_card(MifareFuzzerEmulator* mifare_fuzzer_emulator, MifareCard mifare_card) {
+    furi_assert(mifare_fuzzer_emulator);
+    furi_assert(mifare_card);
+
+    with_view_model(
+        mifare_fuzzer_emulator->view,
+        MifareFuzzerEmulatorModel* model,
+        {
+            model->mifare_card = mifare_card;
+            switch(mifare_card) {
+            case MifareCardClassic1k:
+                model->mifare_card_dsc = "Classic 1k";
+                break;
+            case MifareCardClassic4k:
+                model->mifare_card_dsc = "Classic 4k";
+                break;
+            case MifareCardUltralight:
+                model->mifare_card_dsc = "Ultralight";
+                break;
+            }
+        },
+        true
+    );
+
+}
+
+/// @brief Set attack type
+/// @param mifare_fuzzer_emulator 
+/// @param mifare_attack 
+void mifare_fuzzer_emulator_set_attack(MifareFuzzerEmulator* mifare_fuzzer_emulator, MifareFuzzerAttack mifare_attack) {
+    furi_assert(mifare_fuzzer_emulator);
+    furi_assert(mifare_attack);
+
+    with_view_model(
+        mifare_fuzzer_emulator->view,
+        MifareFuzzerEmulatorModel* model,
+        {
+            model->attack = mifare_attack;
+            switch(mifare_attack) {
+            case MifareFuzzerAttackTestValues:
+                model->attack_dsc = "Test values";
+                break;
+            case MifareFuzzerAttackRandomValues:
+                model->attack_dsc = "Random values";
+                break;
+            case MifareFuzzerAttackLoadUidsFromFile:
+                model->attack_dsc = "Load Uids From File";
+                break;
+            }
+        },
+        true
+    );
+
+}
+
+/// @brief mifare_fuzzer_emulator_set_nfc_dev_data
+/// @param mifare_fuzzer_emulator 
+/// @param nfc_dev_data 
+void mifare_fuzzer_emulator_set_nfc_dev_data(MifareFuzzerEmulator* mifare_fuzzer_emulator, FuriHalNfcDevData nfc_dev_data) {
+    furi_assert(mifare_fuzzer_emulator);
+
+    with_view_model(
+        mifare_fuzzer_emulator->view,
+        MifareFuzzerEmulatorModel* model,
+        {
+            model->nfc_dev_data = nfc_dev_data;
+        }, true
+    );
+}
+
+
+/// @brief mifare_fuzzer_emulator_set_ticks_between_cards
+/// @param mifare_fuzzer_emulator 
+/// @param ticks 
+void mifare_fuzzer_emulator_set_ticks_between_cards(MifareFuzzerEmulator* mifare_fuzzer_emulator, uint8_t ticks) {
+    furi_assert(mifare_fuzzer_emulator);
+
+    with_view_model(
+        mifare_fuzzer_emulator->view,
+        MifareFuzzerEmulatorModel* model,
+        {
+            model->ticks_between_cards = ticks;
+        }, true
+    );
+}
+
+/// @brief mifare_fuzzer_emulator_set_tick_num
+/// @param mifare_fuzzer_emulator 
+/// @param tick_num 
+void mifare_fuzzer_emulator_set_tick_num(MifareFuzzerEmulator* mifare_fuzzer_emulator, uint8_t tick_num) {
+    furi_assert(mifare_fuzzer_emulator);
+
+    with_view_model(
+        mifare_fuzzer_emulator->view,
+        MifareFuzzerEmulatorModel* model,
+        {
+            model->tick_num = tick_num;
+        }, true
+    );
+}

+ 70 - 0
views/mifare_fuzzer_emulator.h

@@ -0,0 +1,70 @@
+#pragma once
+#include "../mifare_fuzzer_custom_events.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/view.h>
+#include <gui/elements.h>
+
+typedef void (*MifareFuzzerEmulatorCallback)(MifareFuzzerEvent event, void* context);
+
+typedef enum MifareCard {
+    MifareCardClassic1k = 1,
+    MifareCardClassic4k,
+    MifareCardUltralight,
+} MifareCard;
+
+typedef enum MifareFuzzerAttack {
+    MifareFuzzerAttackTestValues = 1,
+    MifareFuzzerAttackRandomValues,
+    MifareFuzzerAttackLoadUidsFromFile,
+} MifareFuzzerAttack;
+
+typedef struct MifareFuzzerEmulator {
+    View* view;
+    MifareFuzzerEmulatorCallback callback;
+    void* context;
+    bool is_attacking;
+    uint8_t ticks_between_cards;
+} MifareFuzzerEmulator;
+
+typedef struct MifareFuzzerEmulatorModel {
+    const char* title;
+    MifareCard mifare_card;
+    const char* mifare_card_dsc;
+    MifareFuzzerAttack attack;
+    const char* attack_dsc;
+    FuriHalNfcDevData nfc_dev_data;
+    bool is_attacking;
+    uint8_t tick_num;
+    uint8_t ticks_between_cards;
+} MifareFuzzerEmulatorModel;
+
+MifareFuzzerEmulator* mifare_fuzzer_emulator_alloc();
+
+void mifare_fuzzer_emulator_free(MifareFuzzerEmulator* context);
+
+View* mifare_fuzzer_emulator_get_view(MifareFuzzerEmulator* context);
+
+void mifare_fuzzer_emulator_set_card(MifareFuzzerEmulator* mifare_fuzzer_emulator, MifareCard mifare_card);
+void mifare_fuzzer_emulator_set_attack(MifareFuzzerEmulator* mifare_fuzzer_emulator, MifareFuzzerAttack mifare_attack);
+
+void mifare_fuzzer_emulator_set_callback(
+    MifareFuzzerEmulator* mifare_fuzzer_emulator,
+    MifareFuzzerEmulatorCallback callback,
+    void* context
+);
+
+void mifare_fuzzer_emulator_set_nfc_dev_data(
+    MifareFuzzerEmulator* mifare_fuzzer_emulator,
+    FuriHalNfcDevData nfc_dev_data
+);
+
+void mifare_fuzzer_emulator_set_ticks_between_cards(
+    MifareFuzzerEmulator* mifare_fuzzer_emulator,
+    uint8_t ticks
+);
+
+void mifare_fuzzer_emulator_set_tick_num(
+    MifareFuzzerEmulator* mifare_fuzzer_emulator,
+    uint8_t tick_num
+);