flipperzero-firmware/applications/debug_tools/bad_usb.c
Nikolay Minaylov e6642b332c
[FL-1930] USB HID (#751)
* [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>
2021-10-13 19:38:24 +03:00

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;
}