|
|
@@ -0,0 +1,364 @@
|
|
|
+#include <furi.h>
|
|
|
+#include <furi-hal.h>
|
|
|
+#include <gui/gui.h>
|
|
|
+#include <input/input.h>
|
|
|
+#include <lib/toolbox/args.h>
|
|
|
+#include <furi-hal-usb-hid.h>
|
|
|
+#include <storage/storage.h>
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ EventTypeInput,
|
|
|
+ EventTypeWorkerState,
|
|
|
+} EventType;
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ WorkerStateDone,
|
|
|
+ WorkerStateNoFile,
|
|
|
+ WorkerStateScriptError,
|
|
|
+ WorkerStateDisconnected,
|
|
|
+} WorkerState;
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ AppStateWait,
|
|
|
+ AppStateRunning,
|
|
|
+ AppStateError,
|
|
|
+ AppStateExit,
|
|
|
+} AppState;
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ WorkerCmdStart = (1 << 0),
|
|
|
+ WorkerCmdStop = (1 << 1),
|
|
|
+} WorkerCommandFlags;
|
|
|
+
|
|
|
+// Event message from worker
|
|
|
+typedef struct {
|
|
|
+ WorkerState state;
|
|
|
+ uint16_t line;
|
|
|
+} BadUsbWorkerState;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ union {
|
|
|
+ InputEvent input;
|
|
|
+ BadUsbWorkerState worker;
|
|
|
+ };
|
|
|
+ EventType type;
|
|
|
+} BadUsbEvent;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ uint32_t defdelay;
|
|
|
+ char msg_text[32];
|
|
|
+ osThreadAttr_t thread_attr;
|
|
|
+ osThreadId_t thread;
|
|
|
+ osMessageQueueId_t event_queue;
|
|
|
+} BadUsbParams;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ char* name;
|
|
|
+ uint16_t keycode;
|
|
|
+} DuckyKey;
|
|
|
+
|
|
|
+static const DuckyKey ducky_keys[] = {
|
|
|
+ {"CTRL", KEY_MOD_LEFT_CTRL},
|
|
|
+ {"CONTROL", KEY_MOD_LEFT_CTRL},
|
|
|
+ {"SHIFT", KEY_MOD_LEFT_SHIFT},
|
|
|
+ {"ALT", KEY_MOD_LEFT_ALT},
|
|
|
+ {"GUI", KEY_MOD_LEFT_GUI},
|
|
|
+ {"WINDOWS", KEY_MOD_LEFT_GUI},
|
|
|
+
|
|
|
+ {"DOWNARROW", KEY_DOWN_ARROW},
|
|
|
+ {"DOWN", KEY_DOWN_ARROW},
|
|
|
+ {"LEFTARROW", KEY_LEFT_ARROW},
|
|
|
+ {"LEFT", KEY_LEFT_ARROW},
|
|
|
+ {"RIGHTARROW", KEY_RIGHT_ARROW},
|
|
|
+ {"RIGHT", KEY_RIGHT_ARROW},
|
|
|
+ {"UPARROW", KEY_UP_ARROW},
|
|
|
+ {"UP", KEY_UP_ARROW},
|
|
|
+
|
|
|
+ {"ENTER", KEY_ENTER},
|
|
|
+ {"BREAK", KEY_PAUSE},
|
|
|
+ {"PAUSE", KEY_PAUSE},
|
|
|
+ {"CAPSLOCK", KEY_CAPS_LOCK},
|
|
|
+ {"DELETE", KEY_DELETE},
|
|
|
+ {"BACKSPACE", KEY_BACKSPACE},
|
|
|
+ {"END", KEY_END},
|
|
|
+ {"ESC", KEY_ESC},
|
|
|
+ {"ESCAPE", KEY_ESC},
|
|
|
+ {"HOME", KEY_HOME},
|
|
|
+ {"INSERT", KEY_INSERT},
|
|
|
+ {"NUMLOCK", KEY_NUM_LOCK},
|
|
|
+ {"PAGEUP", KEY_PAGE_UP},
|
|
|
+ {"PAGEDOWN", KEY_PAGE_DOWN},
|
|
|
+ {"PRINTSCREEN", KEY_PRINT},
|
|
|
+ {"SCROLLOCK", KEY_SCROLL_LOCK},
|
|
|
+ {"SPACE", KEY_SPACE},
|
|
|
+ {"TAB", KEY_TAB},
|
|
|
+ {"MENU", KEY_APPLICATION},
|
|
|
+ {"APP", KEY_APPLICATION},
|
|
|
+};
|
|
|
+
|
|
|
+static const char ducky_cmd_comment[] = {"REM"};
|
|
|
+static const char ducky_cmd_delay[] = {"DELAY"};
|
|
|
+static const char ducky_cmd_string[] = {"STRING"};
|
|
|
+static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY"};
|
|
|
+static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY"};
|
|
|
+
|
|
|
+static bool ducky_get_delay_val(char* param, uint32_t* val) {
|
|
|
+ uint32_t delay_val = 0;
|
|
|
+ if(sscanf(param, "%lu", &delay_val) == 1) {
|
|
|
+ *val = delay_val;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static bool ducky_string(char* param) {
|
|
|
+ uint32_t i = 0;
|
|
|
+ while(param[i] != '\0') {
|
|
|
+ furi_hal_hid_kb_press(HID_ASCII_TO_KEY(param[i]));
|
|
|
+ furi_hal_hid_kb_release(HID_ASCII_TO_KEY(param[i]));
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static uint16_t ducky_get_keycode(char* param, bool accept_chars) {
|
|
|
+ for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
|
|
|
+ if(strncmp(param, ducky_keys[i].name, strlen(ducky_keys[i].name)) == 0)
|
|
|
+ return ducky_keys[i].keycode;
|
|
|
+ }
|
|
|
+ if((accept_chars) && (strlen(param) > 0)) {
|
|
|
+ return (HID_ASCII_TO_KEY(param[0]) & 0xFF);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static bool ducky_parse_line(string_t line, BadUsbParams* app) {
|
|
|
+ //uint32_t line_len = string_size(line);
|
|
|
+ char* line_t = (char*)string_get_cstr(line);
|
|
|
+ bool state = false;
|
|
|
+
|
|
|
+ // General commands
|
|
|
+ if(strncmp(line_t, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
|
|
|
+ // REM - comment line
|
|
|
+ return true;
|
|
|
+ } else if(strncmp(line_t, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
|
|
|
+ // DELAY
|
|
|
+ line_t = &line_t[args_get_first_word_length(line) + 1];
|
|
|
+ uint32_t delay_val = 0;
|
|
|
+ state = ducky_get_delay_val(line_t, &delay_val);
|
|
|
+ if((state) && (delay_val > 0)) {
|
|
|
+ // Using ThreadFlagsWait as delay function allows exiting task on WorkerCmdStop command
|
|
|
+ if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, delay_val) ==
|
|
|
+ WorkerCmdStop)
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return state;
|
|
|
+ } else if(
|
|
|
+ (strncmp(line_t, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) ||
|
|
|
+ (strncmp(line_t, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) {
|
|
|
+ // DEFAULT_DELAY
|
|
|
+ line_t = &line_t[args_get_first_word_length(line) + 1];
|
|
|
+ return ducky_get_delay_val(line_t, &app->defdelay);
|
|
|
+ } else if(strncmp(line_t, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
|
|
|
+ // STRING
|
|
|
+ if(app->defdelay > 0) {
|
|
|
+ if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, app->defdelay) ==
|
|
|
+ WorkerCmdStop)
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ line_t = &line_t[args_get_first_word_length(line) + 1];
|
|
|
+ return ducky_string(line_t);
|
|
|
+ } else {
|
|
|
+ // Special keys + modifiers
|
|
|
+ uint16_t key = ducky_get_keycode(line_t, false);
|
|
|
+ if(key == KEY_NONE) return false;
|
|
|
+ if((key & 0xFF00) != 0) {
|
|
|
+ // It's a modifier key
|
|
|
+ line_t = &line_t[args_get_first_word_length(line) + 1];
|
|
|
+ key |= ducky_get_keycode(line_t, true);
|
|
|
+ }
|
|
|
+ if(app->defdelay > 0) {
|
|
|
+ if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, app->defdelay) ==
|
|
|
+ WorkerCmdStop)
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ furi_hal_hid_kb_press(key);
|
|
|
+ furi_hal_hid_kb_release(key);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void badusb_worker(void* context) {
|
|
|
+ BadUsbParams* app = context;
|
|
|
+ FURI_LOG_I("BadUSB worker", "Init");
|
|
|
+ File* script_file = storage_file_alloc(furi_record_open("storage"));
|
|
|
+ BadUsbEvent evt;
|
|
|
+ string_t line;
|
|
|
+ uint32_t line_cnt = 0;
|
|
|
+ string_init(line);
|
|
|
+ if(storage_file_open(script_file, "/ext/badusb.txt", FSAM_READ, FSOM_OPEN_EXISTING)) {
|
|
|
+ char buffer[16];
|
|
|
+ uint16_t ret;
|
|
|
+ uint32_t flags =
|
|
|
+ osThreadFlagsWait(WorkerCmdStart | WorkerCmdStop, osFlagsWaitAny, osWaitForever);
|
|
|
+ if(flags & WorkerCmdStart) {
|
|
|
+ FURI_LOG_I("BadUSB worker", "Start");
|
|
|
+ do {
|
|
|
+ ret = storage_file_read(script_file, buffer, 16);
|
|
|
+ for(uint16_t i = 0; i < ret; i++) {
|
|
|
+ if(buffer[i] == '\n' && string_size(line) > 0) {
|
|
|
+ line_cnt++;
|
|
|
+ if(ducky_parse_line(line, app) == false) {
|
|
|
+ ret = 0;
|
|
|
+ FURI_LOG_E("BadUSB worker", "Unknown command at line %lu", line_cnt);
|
|
|
+ evt.type = EventTypeWorkerState;
|
|
|
+ evt.worker.state = WorkerStateScriptError;
|
|
|
+ evt.worker.line = line_cnt;
|
|
|
+ osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ flags = osThreadFlagsGet();
|
|
|
+ if(flags == WorkerCmdStop) {
|
|
|
+ ret = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ string_clean(line);
|
|
|
+ } else {
|
|
|
+ string_push_back(line, buffer[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } while(ret > 0);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ FURI_LOG_E("BadUSB worker", "Script file open error");
|
|
|
+ evt.type = EventTypeWorkerState;
|
|
|
+ evt.worker.state = WorkerStateNoFile;
|
|
|
+ osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever);
|
|
|
+ }
|
|
|
+ string_clean(line);
|
|
|
+ string_clear(line);
|
|
|
+
|
|
|
+ furi_hal_hid_kb_release_all();
|
|
|
+ storage_file_close(script_file);
|
|
|
+ storage_file_free(script_file);
|
|
|
+
|
|
|
+ FURI_LOG_I("BadUSB worker", "End");
|
|
|
+ evt.type = EventTypeWorkerState;
|
|
|
+ evt.worker.state = WorkerStateDone;
|
|
|
+ osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever);
|
|
|
+
|
|
|
+ osThreadExit();
|
|
|
+}
|
|
|
+
|
|
|
+static void bad_usb_render_callback(Canvas* canvas, void* ctx) {
|
|
|
+ BadUsbParams* app = (BadUsbParams*)ctx;
|
|
|
+
|
|
|
+ canvas_clear(canvas);
|
|
|
+
|
|
|
+ canvas_set_font(canvas, FontPrimary);
|
|
|
+ canvas_draw_str(canvas, 0, 10, "Bad USB test");
|
|
|
+
|
|
|
+ if(strlen(app->msg_text) > 0) {
|
|
|
+ canvas_set_font(canvas, FontSecondary);
|
|
|
+ canvas_draw_str(canvas, 0, 62, app->msg_text);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void bad_usb_input_callback(InputEvent* input_event, void* ctx) {
|
|
|
+ osMessageQueueId_t event_queue = ctx;
|
|
|
+
|
|
|
+ BadUsbEvent event;
|
|
|
+ event.type = EventTypeInput;
|
|
|
+ event.input = *input_event;
|
|
|
+ osMessageQueuePut(event_queue, &event, 0, osWaitForever);
|
|
|
+}
|
|
|
+
|
|
|
+int32_t bad_usb_app(void* p) {
|
|
|
+ BadUsbParams* app = furi_alloc(sizeof(BadUsbParams));
|
|
|
+ app->event_queue = osMessageQueueNew(8, sizeof(BadUsbEvent), NULL);
|
|
|
+ furi_check(app->event_queue);
|
|
|
+ ViewPort* view_port = view_port_alloc();
|
|
|
+
|
|
|
+ UsbMode usb_mode_prev = furi_hal_usb_get_config();
|
|
|
+ furi_hal_usb_set_config(UsbModeHid);
|
|
|
+
|
|
|
+ view_port_draw_callback_set(view_port, bad_usb_render_callback, app);
|
|
|
+ view_port_input_callback_set(view_port, bad_usb_input_callback, app->event_queue);
|
|
|
+
|
|
|
+ // Open GUI and register view_port
|
|
|
+ Gui* gui = furi_record_open("gui");
|
|
|
+ gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
+
|
|
|
+ app->thread = NULL;
|
|
|
+ app->thread_attr.name = "bad_usb_worker";
|
|
|
+ app->thread_attr.stack_size = 2048;
|
|
|
+ app->thread = osThreadNew(badusb_worker, app, &app->thread_attr);
|
|
|
+ bool worker_running = true;
|
|
|
+ AppState app_state = AppStateWait;
|
|
|
+ snprintf(app->msg_text, sizeof(app->msg_text), "Press [OK] to start");
|
|
|
+ view_port_update(view_port);
|
|
|
+
|
|
|
+ BadUsbEvent event;
|
|
|
+ while(1) {
|
|
|
+ osStatus_t event_status = osMessageQueueGet(app->event_queue, &event, NULL, osWaitForever);
|
|
|
+
|
|
|
+ if(event_status == osOK) {
|
|
|
+ if(event.type == EventTypeInput) {
|
|
|
+ if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
|
|
|
+ if(worker_running) {
|
|
|
+ osThreadFlagsSet(app->thread, WorkerCmdStop);
|
|
|
+ app_state = AppStateExit;
|
|
|
+ } else
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(event.input.type == InputTypeShort && event.input.key == InputKeyOk) {
|
|
|
+ if(worker_running) {
|
|
|
+ app_state = AppStateRunning;
|
|
|
+ osThreadFlagsSet(app->thread, WorkerCmdStart);
|
|
|
+ snprintf(app->msg_text, sizeof(app->msg_text), "Running...");
|
|
|
+ view_port_update(view_port);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if(event.type == EventTypeWorkerState) {
|
|
|
+ FURI_LOG_I("BadUSB app", "ev: %d", event.worker.state);
|
|
|
+ if(event.worker.state == WorkerStateDone) {
|
|
|
+ worker_running = false;
|
|
|
+ if(app_state == AppStateExit)
|
|
|
+ break;
|
|
|
+ else if(app_state == AppStateRunning) {
|
|
|
+ //done
|
|
|
+ app->thread = osThreadNew(badusb_worker, app, &app->thread_attr);
|
|
|
+ worker_running = true;
|
|
|
+ app_state = AppStateWait;
|
|
|
+ snprintf(app->msg_text, sizeof(app->msg_text), "Press [OK] to start");
|
|
|
+ view_port_update(view_port);
|
|
|
+ }
|
|
|
+ } else if(event.worker.state == WorkerStateNoFile) {
|
|
|
+ app_state = AppStateError;
|
|
|
+ snprintf(app->msg_text, sizeof(app->msg_text), "File not found!");
|
|
|
+ view_port_update(view_port);
|
|
|
+ } else if(event.worker.state == WorkerStateScriptError) {
|
|
|
+ app_state = AppStateError;
|
|
|
+ snprintf(
|
|
|
+ app->msg_text,
|
|
|
+ sizeof(app->msg_text),
|
|
|
+ "Error at line %u",
|
|
|
+ event.worker.line);
|
|
|
+ view_port_update(view_port);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ furi_hal_usb_set_config(usb_mode_prev);
|
|
|
+
|
|
|
+ // remove & free all stuff created by app
|
|
|
+ gui_remove_view_port(gui, view_port);
|
|
|
+ view_port_free(view_port);
|
|
|
+
|
|
|
+ osMessageQueueDelete(app->event_queue);
|
|
|
+ free(app);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|