Skip to content

Commit

Permalink
bad_usb: add new keyboard layout file format
Browse files Browse the repository at this point in the history
The existing .kl file format is quite limited. It only supports ASCII
characters, and it assumes all characters can be pressed using a single
key on the user's keyboard.

The latter assumption is false even when just considering ASCII
characters. For example, the US-International keyboard layout, which
is the de facto standard keyboard layout in the Netherlands, uses dead
keys for the following characters: ` ~ ^ ' ". To type any of these
characters, one needs to press the same key as in the ANSI QWERTY
layout, but followed by a space. This is not possible to describe in the
current keyboard layout, thus making Dutch computers immune to Bad USB.

The new file format makes this possible, by mapping characters to a
sequence of keys, rather than just a single key. It also expands the
format to support any Unicode codepoint, thus unlocking Bad USB to a
much wider audience. Characters composed of multiple codepoints are not
supported though, thus while this does make it possible to support
é when encoded as U+00E9 LATIN SMALL LETTER E WITH ACUTE, it does not
support é when encoded as U+0065 LATIN SMALL LETTER E + U+0301 COMBINING
ACUTE ACCENT.

The new file format is implemented in a fully backwards compatible way,
allowing users to keep using existing .kl files they may have added.

The documentation of the new file format can be found in
documentation/file_formats/BadUsbKeyboardFormat.md. This commit also
adds the keyboard layout en-US-intl.kl, which implements the
US-International keyboard layout.

Signed-off-by: Guacamolie <[email protected]>
  • Loading branch information
vanguacamolie committed Mar 4, 2024
1 parent 1070064 commit 7ce3f5b
Show file tree
Hide file tree
Showing 10 changed files with 542 additions and 45 deletions.
65 changes: 21 additions & 44 deletions applications/main/bad_usb/helpers/ducky_script.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@
#include "ducky_script.h"
#include "ducky_script_i.h"
#include <dolphin/dolphin.h>
#include "helpers/keyboard.h"

#define TAG "BadUsb"
#define WORKER_TAG TAG "Worker"

#define BADUSB_ASCII_TO_KEY(script, x) \
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)

typedef enum {
WorkerEvtStartStop = (1 << 0),
WorkerEvtPauseResume = (1 << 1),
Expand Down Expand Up @@ -57,7 +55,7 @@ uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept
}

if((accept_chars) && (strlen(param) > 0)) {
return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF);
return (bad_usb_keyboard_get_key(bad_usb->layout, param[0]) & 0xFF);
}
return 0;
}
Expand Down Expand Up @@ -134,44 +132,27 @@ int32_t ducky_error(BadUsbScript* bad_usb, const char* text, ...) {
}

bool ducky_string(BadUsbScript* bad_usb, const char* param) {
uint32_t i = 0;
bad_usb_keyboard_type_string(bad_usb->layout, param);

while(param[i] != '\0') {
if(param[i] != '\n') {
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
if(keycode != HID_KEYBOARD_NONE) {
furi_hal_hid_kb_press(keycode);
furi_hal_hid_kb_release(keycode);
}
} else {
furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
}
i++;
}
bad_usb->stringdelay = 0;
return true;
}

static bool ducky_string_next(BadUsbScript* bad_usb) {
if(bad_usb->string_print_pos >= furi_string_size(bad_usb->string_print)) {
return true;
}
FuriStringUTF8State utf8state = FuriStringUTF8StateStarting;
FuriStringUnicodeValue codepoint;
do {
if(bad_usb->string_print_pos >= furi_string_size(bad_usb->string_print)) {
return true;
}

char print_char = furi_string_get_char(bad_usb->string_print, bad_usb->string_print_pos);
char print_char = furi_string_get_char(bad_usb->string_print, bad_usb->string_print_pos);
furi_string_utf8_decode(print_char, &utf8state, &codepoint);

if(print_char != '\n') {
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, print_char);
if(keycode != HID_KEYBOARD_NONE) {
furi_hal_hid_kb_press(keycode);
furi_hal_hid_kb_release(keycode);
}
} else {
furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
}
bad_usb->string_print_pos++;
} while(utf8state != FuriStringUTF8StateStarting);

bad_usb->string_print_pos++;
bad_usb_keyboard_type_codepoint(bad_usb->layout, codepoint);

return false;
}
Expand Down Expand Up @@ -637,19 +618,13 @@ static int32_t bad_usb_worker(void* context) {
return 0;
}

static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) {
furi_assert(bad_usb);
memset(bad_usb->layout, HID_KEYBOARD_NONE, sizeof(bad_usb->layout));
memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
}

BadUsbScript* bad_usb_script_open(FuriString* file_path) {
furi_assert(file_path);

BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
bad_usb->file_path = furi_string_alloc();
furi_string_set(bad_usb->file_path, file_path);
bad_usb_script_set_default_keyboard_layout(bad_usb);
bad_usb->layout = bad_usb_keyboard_alloc_default();

bad_usb->st.state = BadUsbStateInit;
bad_usb->st.error[0] = '\0';
Expand Down Expand Up @@ -680,14 +655,16 @@ void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layou
if(!furi_string_empty(layout_path)) { //-V1051
if(storage_file_open(
layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
uint16_t layout[128];
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
memcpy(bad_usb->layout, layout, sizeof(layout));
bad_usb_keyboard_free(bad_usb->layout);
bad_usb->layout = bad_usb_keyboard_alloc_read(layout_file);
if(!bad_usb->layout) {
bad_usb->layout = bad_usb_keyboard_alloc_default();
}
}
storage_file_close(layout_file);
} else {
bad_usb_script_set_default_keyboard_layout(bad_usb);
bad_usb_keyboard_free(bad_usb->layout);
bad_usb->layout = bad_usb_keyboard_alloc_default();
}
storage_file_free(layout_file);
}
Expand Down
3 changes: 2 additions & 1 deletion applications/main/bad_usb/helpers/ducky_script_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern "C" {
#include <furi.h>
#include <furi_hal.h>
#include "ducky_script.h"
#include "keyboard.h"

#define SCRIPT_STATE_ERROR (-1)
#define SCRIPT_STATE_END (-2)
Expand All @@ -30,7 +31,7 @@ struct BadUsbScript {

uint32_t defdelay;
uint32_t stringdelay;
uint16_t layout[128];
BadUsbKeyboard* layout;

FuriString* line;
FuriString* line_prev;
Expand Down
97 changes: 97 additions & 0 deletions applications/main/bad_usb/helpers/keyboard.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include <furi_hal.h>
#include <stdint.h>
#include <storage/storage.h>
#include "keyboard.h"
#include "keyboard_i.h"

const uint8_t header_magic[3] = {0xFF, 'K', 'L'};

static int determine_version(File* layout_file) {
struct SharedHeader shared_header = {0};
storage_file_read(layout_file, &shared_header, sizeof(shared_header));
storage_file_seek(layout_file, 0, true);

if(memcmp(
shared_header.magic,
header_magic,
MIN(sizeof(shared_header.magic), sizeof(header_magic))) != 0) {
// Backwards compat with v1
return 1;
}
return shared_header.version;
}

BadUsbKeyboard* bad_usb_keyboard_alloc_read(File* layout_file) {
switch(determine_version(layout_file)) {
case 1:
return bad_usb_keyboard_v1_alloc_read(layout_file);
case 2:
return bad_usb_keyboard_v2_alloc_read(layout_file);
default:
return NULL;
}
}

BadUsbKeyboard* bad_usb_keyboard_alloc(void* data, const struct BadUsbKeyboardVTable* vtable) {
// vtable->get_key_sequence is optional
furi_assert(vtable);
furi_assert(vtable->get_key);
furi_assert(vtable->free);

BadUsbKeyboard* kb = malloc(sizeof(BadUsbKeyboard));
kb->data = data;
kb->vtable = vtable;
return kb;
}

void bad_usb_keyboard_free(BadUsbKeyboard* keyboard) {
furi_assert(keyboard);

keyboard->vtable->free(keyboard->data);
free(keyboard);
}

void bad_usb_keyboard_type_codepoint(BadUsbKeyboard* keyboard, FuriStringUnicodeValue codepoint) {
furi_assert(keyboard);

if(keyboard->vtable->get_key_sequence) {
uint16_t* key_sequence = keyboard->vtable->get_key_sequence(keyboard->data, codepoint);

if(key_sequence == NULL) {
return;
}

for(size_t i = 0; key_sequence[i] != HID_KEYBOARD_NONE; i++) {
furi_hal_hid_kb_press(key_sequence[i]);
furi_hal_hid_kb_release(key_sequence[i]);
}
} else {
uint16_t key = keyboard->vtable->get_key(keyboard->data, codepoint);
if(key != HID_KEYBOARD_NONE) {
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release(key);
}
}
}

void bad_usb_keyboard_type_string(BadUsbKeyboard* keyboard, const char* string) {
furi_assert(keyboard);

FuriStringUTF8State utf8_state = FuriStringUTF8StateStarting;
FuriStringUnicodeValue codepoint;

for(size_t i = 0; string[i] != '\0'; i++) {
furi_string_utf8_decode(string[i], &utf8_state, &codepoint);
if(utf8_state != FuriStringUTF8StateStarting) {
continue;
}

bad_usb_keyboard_type_codepoint(keyboard, codepoint);
}
}

uint16_t bad_usb_keyboard_get_key(BadUsbKeyboard* keyboard, FuriStringUnicodeValue codepoint) {
furi_assert(keyboard);

return keyboard->vtable->get_key(keyboard->data, codepoint);
}
23 changes: 23 additions & 0 deletions applications/main/bad_usb/helpers/keyboard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

#include <furi.h>
#include <storage/storage.h>

typedef struct BadUsbKeyboard BadUsbKeyboard;

BadUsbKeyboard* bad_usb_keyboard_alloc_default();
BadUsbKeyboard* bad_usb_keyboard_alloc_read(File* layout_file);
void bad_usb_keyboard_free(BadUsbKeyboard* keyboard);

void bad_usb_keyboard_type_string(BadUsbKeyboard* keyboard, const char* string);
void bad_usb_keyboard_type_codepoint(BadUsbKeyboard* keyboard, FuriStringUnicodeValue codepoint);

uint16_t bad_usb_keyboard_get_key(BadUsbKeyboard* keyboard, FuriStringUnicodeValue codepoint);

#ifdef __cplusplus
}
#endif
25 changes: 25 additions & 0 deletions applications/main/bad_usb/helpers/keyboard_default.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <furi_hal.h>
#include <stdint.h>
#include <storage/storage.h>
#include "keyboard_i.h"

static uint16_t get_key(void*, FuriStringUnicodeValue codepoint) {
if(codepoint >= sizeof(hid_asciimap) / sizeof(*hid_asciimap)) {
return HID_KEYBOARD_NONE;
}

return hid_asciimap[codepoint];
}

static void free_data(void*) {
// No-op.
}

static const struct BadUsbKeyboardVTable vtable = {
.get_key = get_key,
.free = free_data,
};

BadUsbKeyboard* bad_usb_keyboard_alloc_default() {
return bad_usb_keyboard_alloc(NULL, &vtable);
}
69 changes: 69 additions & 0 deletions applications/main/bad_usb/helpers/keyboard_i.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#pragma once

#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif

#include <storage/storage.h>
#include "keyboard.h"

struct BadUsbKeyboardVTable {
/**
* @brief Gets the key associated the codepoint.
*
* This gets the key that needs to be pressed to enter the character
* associated with codepoint. The key is represented by a 2 byte value with
* the USB HID Keyboard/Keypad Page Usage ID in the low byte and the
* modifier keys in the high byte. It is suitable for passing to
* furi_hal_hid_kb_press.
*
* This function is required to be implemented.
*
* @returns The keycode if the codepoint can meaningfully be entered by
* pressing a single key. Otherwise returns HID_KEYBOARD_NONE.
*/
uint16_t (*get_key)(void* data, FuriStringUnicodeValue codepoint);

/**
* @brief Gets the sequence of keys associated with the codepoint.
*
* This gets the key sequence that needs to be pressed to enter the
* character associated with the codepoint. For example, to enter the
* codepoint U+00E9 “é” LATIN SMALL LETTER E WITH ACUTE on the
* US-International keyboard layout, one first needs to type
* HID_KEYBOARD_APOSTROPHE (0x34), followed by HID_KEYBOARD_E (0x08).
*
* This function is optional. It may be set to NULL, in which case get_key
* will be used instead.
*
* @returns A pointer to a sequence of keycodes, terminated by
* HID_KEYBOARD_NONE. NULL may be returned if no sequence could be found.
*/
uint16_t* (*get_key_sequence)(void* data, FuriStringUnicodeValue codepoint);

/**
* @brief Called when the data structure needs to be freed.
*/
void (*free)(void* data);
};

struct BadUsbKeyboard {
const struct BadUsbKeyboardVTable* vtable;
void* data;
};

BadUsbKeyboard* bad_usb_keyboard_alloc(void* data, const struct BadUsbKeyboardVTable* vtable);

BadUsbKeyboard* bad_usb_keyboard_layout_default_alloc();
BadUsbKeyboard* bad_usb_keyboard_v1_alloc_read(File* layout);
BadUsbKeyboard* bad_usb_keyboard_v2_alloc_read(File* layout);

struct SharedHeader {
uint8_t magic[3];
uint8_t version;
} FURI_PACKED;

#ifdef __cplusplus
}
#endif
33 changes: 33 additions & 0 deletions applications/main/bad_usb/helpers/keyboard_v1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <furi_hal.h>
#include <storage/storage.h>
#include "keyboard.h"
#include "keyboard_i.h"

struct Layout {
uint16_t keycodes[128];
};

static uint16_t get_key(void* data, FuriStringUnicodeValue codepoint) {
struct Layout* layout = data;

if(codepoint >= sizeof(layout->keycodes) / sizeof(*layout->keycodes)) {
return HID_KEYBOARD_NONE;
}

return layout->keycodes[codepoint];
}

static const struct BadUsbKeyboardVTable imp_v1 = {
.get_key = get_key,
.free = free,
};

BadUsbKeyboard* bad_usb_keyboard_v1_alloc_read(File* layout) {
struct Layout* data = malloc(sizeof(*data));
if(storage_file_read(layout, data, sizeof(*data)) != sizeof(*data)) {
free(data);
return NULL;
}

return bad_usb_keyboard_alloc(data, &imp_v1);
}
Loading

0 comments on commit 7ce3f5b

Please sign in to comment.