Skip to content

Commit 987f74f

Browse files
committed
feat(ducky_parser): ducky parser for BT
1 parent 3728ed7 commit 987f74f

2 files changed

Lines changed: 173 additions & 7 deletions

File tree

components/Applications/bad_usb/ducky_parser.c

Lines changed: 162 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "ducky_parser.h"
1616
#include "bad_usb.h"
1717
#include "esp_log.h"
18+
// Assuming ble_hid_keyboard.h is available in include path via CMake
19+
#include "ble_hid_keyboard.h"
1820
#include "freertos/FreeRTOS.h"
1921
#include "freertos/task.h"
2022
#include "class/hid/hid_device.h"
@@ -29,6 +31,12 @@ static const char *TAG = "DUCKY_PARSER";
2931
static volatile bool s_abort_flag = false;
3032
static ducky_progress_cb_t s_progress_cb = NULL;
3133
static ducky_layout_t s_layout = DUCKY_LAYOUT_US;
34+
static ducky_output_mode_t s_output_mode = DUCKY_OUTPUT_USB;
35+
36+
void ducky_set_output_mode(ducky_output_mode_t mode) {
37+
s_output_mode = mode;
38+
ESP_LOGI(TAG, "Output mode set to: %s", mode == DUCKY_OUTPUT_USB ? "USB" : "BLUETOOTH");
39+
}
3240

3341
void ducky_set_progress_callback(ducky_progress_cb_t cb) {
3442
s_progress_cb = cb;
@@ -134,6 +142,14 @@ static bool is_modifier(const char* word, uint8_t* current_mod) {
134142
return false;
135143
}
136144

145+
static void press_key_wrapper(uint8_t keycode, uint8_t modifiers) {
146+
if (s_output_mode == DUCKY_OUTPUT_USB) {
147+
bad_usb_press_key(keycode, modifiers);
148+
} else {
149+
ble_hid_send_key(keycode, modifiers);
150+
}
151+
}
152+
137153
static void process_line(char* line) {
138154
if (strlen(line) < 2 || strncmp(line, "REM", 3) == 0) return;
139155

@@ -152,10 +168,152 @@ static void process_line(char* line) {
152168
else if (strcmp(cmd, "STRING") == 0) {
153169
char* next_token = strtok_r(NULL, "", &saveptr); // Get rest of string
154170
if (next_token) {
155-
if (s_layout == DUCKY_LAYOUT_ABNT2) {
156-
type_string_abnt2(next_token);
171+
// Temporarily, we need to adapt type_string functions or manually iterate here
172+
// Ideally type_string_XX should take a callback, but for now we will loop here
173+
// if mode is BT, or rely on BadUSB logic if USB.
174+
175+
// Simpler approach: Iterate char by char here using the wrapper?
176+
// No, type_string logic is complex (ABNT2).
177+
// Best approach: If USB, call type_string. If BT, implementing a simple typer loop or refactor type_string.
178+
// Let's refactor type_string usage by checking mode inside type_string functions?
179+
// No, bad_usb.c owns type_string.
180+
181+
// HACK: For now, if BT mode, we only support basic ASCII typing via simple map or
182+
// we force USB mode functions to use a callback?
183+
// Since I cannot easily modify bad_usb.c to call BLE without circular dependency,
184+
// I will implement a basic BT typer here.
185+
186+
if (s_output_mode == DUCKY_OUTPUT_USB) {
187+
if (s_layout == DUCKY_LAYOUT_ABNT2) {
188+
type_string_abnt2(next_token);
189+
} else {
190+
type_string_us(next_token);
191+
}
157192
} else {
158-
type_string_us(next_token);
193+
// BLE Typing
194+
if (s_layout == DUCKY_LAYOUT_US) {
195+
for (int i = 0; next_token[i] != 0; i++) {
196+
char c = next_token[i];
197+
if (c >= 'a' && c <= 'z') press_key_wrapper(HID_KEY_A + (c - 'a'), 0);
198+
else if (c >= 'A' && c <= 'Z') press_key_wrapper(HID_KEY_A + (c - 'A'), KEYBOARD_MODIFIER_LEFTSHIFT);
199+
else if (c >= '1' && c <= '9') press_key_wrapper(HID_KEY_1 + (c - '1'), 0);
200+
else if (c == '0') press_key_wrapper(HID_KEY_0, 0);
201+
else {
202+
switch (c) {
203+
case ' ': press_key_wrapper(HID_KEY_SPACE, 0); break;
204+
case '!': press_key_wrapper(HID_KEY_1, KEYBOARD_MODIFIER_LEFTSHIFT); break;
205+
case '@': press_key_wrapper(HID_KEY_2, KEYBOARD_MODIFIER_LEFTSHIFT); break;
206+
case '#': press_key_wrapper(HID_KEY_3, KEYBOARD_MODIFIER_LEFTSHIFT); break;
207+
case '$': press_key_wrapper(HID_KEY_4, KEYBOARD_MODIFIER_LEFTSHIFT); break;
208+
case '%': press_key_wrapper(HID_KEY_5, KEYBOARD_MODIFIER_LEFTSHIFT); break;
209+
case '^': press_key_wrapper(HID_KEY_6, KEYBOARD_MODIFIER_LEFTSHIFT); break;
210+
case '&': press_key_wrapper(HID_KEY_7, KEYBOARD_MODIFIER_LEFTSHIFT); break;
211+
case '*': press_key_wrapper(HID_KEY_8, KEYBOARD_MODIFIER_LEFTSHIFT); break;
212+
case '(': press_key_wrapper(HID_KEY_9, KEYBOARD_MODIFIER_LEFTSHIFT); break;
213+
case ')': press_key_wrapper(HID_KEY_0, KEYBOARD_MODIFIER_LEFTSHIFT); break;
214+
case '-': press_key_wrapper(HID_KEY_MINUS, 0); break;
215+
case '_': press_key_wrapper(HID_KEY_MINUS, KEYBOARD_MODIFIER_LEFTSHIFT); break;
216+
case '=': press_key_wrapper(HID_KEY_EQUAL, 0); break;
217+
case '+': press_key_wrapper(HID_KEY_EQUAL, KEYBOARD_MODIFIER_LEFTSHIFT); break;
218+
case '[': press_key_wrapper(HID_KEY_BRACKET_LEFT, 0); break;
219+
case '{': press_key_wrapper(HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT); break;
220+
case ']': press_key_wrapper(HID_KEY_BRACKET_RIGHT, 0); break;
221+
case '}': press_key_wrapper(HID_KEY_BRACKET_RIGHT, KEYBOARD_MODIFIER_LEFTSHIFT); break;
222+
case '\\': press_key_wrapper(HID_KEY_BACKSLASH, 0); break;
223+
case '|': press_key_wrapper(HID_KEY_BACKSLASH, KEYBOARD_MODIFIER_LEFTSHIFT); break;
224+
case ';': press_key_wrapper(HID_KEY_SEMICOLON, 0); break;
225+
case ':': press_key_wrapper(HID_KEY_SEMICOLON, KEYBOARD_MODIFIER_LEFTSHIFT); break;
226+
case '\'': press_key_wrapper(HID_KEY_APOSTROPHE, 0); break;
227+
case '"': press_key_wrapper(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT); break;
228+
case ',': press_key_wrapper(HID_KEY_COMMA, 0); break;
229+
case '<': press_key_wrapper(HID_KEY_COMMA, KEYBOARD_MODIFIER_LEFTSHIFT); break;
230+
case '.': press_key_wrapper(HID_KEY_PERIOD, 0); break;
231+
case '>': press_key_wrapper(HID_KEY_PERIOD, KEYBOARD_MODIFIER_LEFTSHIFT); break;
232+
case '/': press_key_wrapper(HID_KEY_SLASH, 0); break;
233+
case '?': press_key_wrapper(HID_KEY_SLASH, KEYBOARD_MODIFIER_LEFTSHIFT); break;
234+
case '`': press_key_wrapper(HID_KEY_GRAVE, 0); break;
235+
case '~': press_key_wrapper(HID_KEY_GRAVE, KEYBOARD_MODIFIER_LEFTSHIFT); break;
236+
case '\n': press_key_wrapper(HID_KEY_ENTER, 0); break;
237+
case '\t': press_key_wrapper(HID_KEY_TAB, 0); break;
238+
}
239+
}
240+
}
241+
} else {
242+
// BLE Typing (ABNT2 Layout)
243+
// Mirroring logic from bad_usb.c type_string_abnt2
244+
for (size_t i = 0; next_token[i] != '\0'; ++i) {
245+
uint8_t c1 = (uint8_t)next_token[i];
246+
uint8_t c2 = (uint8_t)next_token[i+1];
247+
248+
// Special cases for ABNT2 quotes
249+
if (c1 == '\'') { press_key_wrapper(HID_KEY_BRACKET_LEFT, 0); press_key_wrapper(HID_KEY_SPACE, 0); continue; }
250+
if (c1 == '"') { press_key_wrapper(HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT); continue; }
251+
252+
// UTF-8 Handling for Accents
253+
if ((c1 & 0xE0) == 0xC0 && c2 != '\0') {
254+
bool char_processed = true;
255+
if (c1 == 0xC3 && c2 == 0xA7) { press_key_wrapper(HID_KEY_SEMICOLON, 0); } // ç
256+
else if (c1 == 0xC3 && c2 == 0x87) { press_key_wrapper(HID_KEY_SEMICOLON, KEYBOARD_MODIFIER_LEFTSHIFT); } // Ç
257+
else if (c1 == 0xC3 && c2 == 0xA1) { press_key_wrapper(HID_KEY_BRACKET_LEFT, 0); press_key_wrapper(HID_KEY_A, 0); } // á
258+
else if (c1 == 0xC3 && c2 == 0xA9) { press_key_wrapper(HID_KEY_BRACKET_LEFT, 0); press_key_wrapper(HID_KEY_E, 0); } // é
259+
else if (c1 == 0xC3 && c2 == 0xAD) { press_key_wrapper(HID_KEY_BRACKET_LEFT, 0); press_key_wrapper(HID_KEY_I, 0); } // í
260+
else if (c1 == 0xC3 && c2 == 0xB3) { press_key_wrapper(HID_KEY_BRACKET_LEFT, 0); press_key_wrapper(HID_KEY_O, 0); } // ó
261+
else if (c1 == 0xC3 && c2 == 0xBA) { press_key_wrapper(HID_KEY_BRACKET_LEFT, 0); press_key_wrapper(HID_KEY_U, 0); } // ú
262+
else if (c1 == 0xC3 && c2 == 0xA2) { press_key_wrapper(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT); press_key_wrapper(HID_KEY_A, 0); } // â
263+
else if (c1 == 0xC3 && c2 == 0xAA) { press_key_wrapper(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT); press_key_wrapper(HID_KEY_E, 0); } // ê
264+
else if (c1 == 0xC3 && c2 == 0xB4) { press_key_wrapper(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT); press_key_wrapper(HID_KEY_O, 0); } // ô
265+
else if (c1 == 0xC3 && c2 == 0xA3) { press_key_wrapper(HID_KEY_APOSTROPHE, 0); press_key_wrapper(HID_KEY_A, 0); } // ã
266+
else if (c1 == 0xC3 && c2 == 0xB5) { press_key_wrapper(HID_KEY_APOSTROPHE, 0); press_key_wrapper(HID_KEY_O, 0); } // õ
267+
else if (c1 == 0xC3 && c2 == 0xA0) { press_key_wrapper(HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT); press_key_wrapper(HID_KEY_A, 0); } // à
268+
else { char_processed = false; }
269+
270+
if (char_processed) { i++; continue; }
271+
}
272+
273+
// Standard ASCII in ABNT2
274+
uint8_t keycode = 0;
275+
uint8_t modifier = 0;
276+
277+
if (c1 >= 'a' && c1 <= 'z') keycode = HID_KEY_A + (c1 - 'a');
278+
else if (c1 >= 'A' && c1 <= 'Z') { modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_A + (c1 - 'A'); }
279+
else if (c1 >= '1' && c1 <= '9') keycode = HID_KEY_1 + (c1 - '1');
280+
else if (c1 == '0') keycode = HID_KEY_0;
281+
else {
282+
switch (c1) {
283+
case '!': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_1; break;
284+
case '@': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_2; break;
285+
case '#': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_3; break;
286+
case '$': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_4; break;
287+
case '%': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_5; break;
288+
case '&': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_7; break;
289+
case '*': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_8; break;
290+
case '(': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_9; break;
291+
case ')': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_0; break;
292+
case ' ': keycode = HID_KEY_SPACE; break;
293+
case '\n': keycode = HID_KEY_ENTER; break;
294+
case '\t': keycode = HID_KEY_TAB; break;
295+
case '-': keycode = HID_KEY_MINUS; break;
296+
case '=': keycode = HID_KEY_EQUAL; break;
297+
case '_': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_MINUS; break;
298+
case '+': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_EQUAL; break;
299+
case '.': keycode = HID_KEY_PERIOD; break;
300+
case ',': keycode = HID_KEY_COMMA; break;
301+
case ';': keycode = HID_KEY_SLASH; break;
302+
case ':': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_SLASH; break;
303+
// ABNT2 / and ? are on International 1 key (0x87)
304+
case '/': keycode = 0x87; break;
305+
case '?': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = 0x87; break;
306+
case '[': modifier = KEYBOARD_MODIFIER_RIGHTALT; keycode = HID_KEY_BRACKET_LEFT; break;
307+
case '{': modifier = KEYBOARD_MODIFIER_RIGHTALT; keycode = HID_KEY_BRACKET_LEFT; modifier |= KEYBOARD_MODIFIER_LEFTSHIFT; break;
308+
case ']': modifier = KEYBOARD_MODIFIER_RIGHTALT; keycode = HID_KEY_BRACKET_RIGHT; break;
309+
case '}': modifier = KEYBOARD_MODIFIER_RIGHTALT; keycode = HID_KEY_BRACKET_RIGHT; modifier |= KEYBOARD_MODIFIER_LEFTSHIFT; break;
310+
case '\\': keycode = HID_KEY_BACKSLASH; break;
311+
case '|': modifier = KEYBOARD_MODIFIER_LEFTSHIFT; keycode = HID_KEY_BACKSLASH; break;
312+
}
313+
}
314+
if (keycode != 0) press_key_wrapper(keycode, modifier);
315+
}
316+
}
159317
}
160318
}
161319
}
@@ -172,13 +330,10 @@ static void process_line(char* line) {
172330
}
173331
} else {
174332
keycode = find_key_code(cmd);
175-
176-
// Check for potential following modifiers or keys?
177-
// Standard DuckyScript usually puts modifiers first or implies single key press.
178333
}
179334

180335
if (keycode != 0 || modifiers != 0) {
181-
bad_usb_press_key(keycode, modifiers);
336+
press_key_wrapper(keycode, modifiers);
182337
}
183338
}
184339
}

components/Applications/bad_usb/include/ducky_parser.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ typedef enum {
3030
DUCKY_LAYOUT_ABNT2 = 1
3131
} ducky_layout_t;
3232

33+
typedef enum {
34+
DUCKY_OUTPUT_USB = 0,
35+
DUCKY_OUTPUT_BLUETOOTH = 1
36+
} ducky_output_mode_t;
37+
38+
/**
39+
* @brief Sets the output mode for the script execution (USB or Bluetooth).
40+
* @param mode DUCKY_OUTPUT_USB or DUCKY_OUTPUT_BLUETOOTH.
41+
*/
42+
void ducky_set_output_mode(ducky_output_mode_t mode);
43+
3344
/**
3445
* @brief Parses and executes a DuckyScript string.
3546
*

0 commit comments

Comments
 (0)