e6642b332c
* [FL-1930] USB HID keyboard test * [FL-1930] HID mouse demo app * [FL-1930] BadUSB: RubberDucky script parser. BadUSB test app Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
365 lines
12 KiB
C
365 lines
12 KiB
C
#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;
|
|
}
|