diff --git a/non_catalog_apps/bad_duck3/README.md b/non_catalog_apps/bad_duck3/README.md new file mode 100644 index 00000000..6ddcf3f2 --- /dev/null +++ b/non_catalog_apps/bad_duck3/README.md @@ -0,0 +1,163 @@ +# Bad Duck3 - DuckyScript 3.0 for Flipper Zero + +The first DuckyScript 3.0 implementation for Flipper Zero with USB and Bluetooth HID support. + +## Features + +### DuckyScript 3.0 Language + +Full implementation of DuckyScript 3.0 control flow: + +- **Variables**: `VAR $name = value` with integers and strings +- **Loops**: `LOOP N`/`END_LOOP`, `WHILE (condition)`/`END_WHILE` +- **Conditionals**: `IF (condition)`/`ELSE`/`END_IF` +- **Control**: `BREAK`, `CONTINUE` +- **Built-in Variables**: `$_RANDOM`, `$_LINE` +- **Operators**: `+ - * / %` (arithmetic), `< > <= >= == !=` (comparison), `&& ||` (logical) + +### Dual HID Interface + +Switch between USB and Bluetooth HID modes: + +| Interface | Description | +|-----------|-------------| +| **USB** | Standard USB HID - plug and play | +| **BLE** | Bluetooth Low Energy HID - wireless | + +Toggle with the **Right** button on the main screen when idle. + +### Full Configuration + +Access configuration with the **Left** button: + +**Keyboard Layout** +- Select from 15+ keyboard layouts +- Proper character mapping for international keyboards + +**BLE Settings** +- **Device Name**: Custom Bluetooth device name +- **MAC Address**: Spoof BLE MAC address +- **Pairing Mode**: None, PIN code, PIN + confirm +- **Remove Pairing**: Clear bonded devices + +**USB Settings** +- **VID/PID**: Custom Vendor/Product IDs +- **Manufacturer**: Custom manufacturer string +- **Product Name**: Custom product string + +## Usage + +1. Place `.txt` DuckyScript files in `/ext/badusb/` on your Flipper +2. Open **Bad Duck3** from Apps > USB +3. Select a script file +4. Configure interface (USB/BLE) and settings as needed +5. Press **OK** to run + +### Controls + +| Button | Idle State | Running State | Paused State | +|--------|------------|---------------|--------------| +| **OK** | Run script | Stop | End | +| **Left** | Config menu | - | - | +| **Right** | Toggle USB/BLE | Pause | Resume | +| **Back** | Exit | - | - | + +## DuckyScript 3.0 Examples + +### Loop with Counter + +``` +DELAY 2000 +VAR $i = 1 +WHILE ($i <= 5) + STRING Iteration: + VAR $i = ($i + 1) + ENTER + DELAY 300 +END_WHILE +STRING Done! +ENTER +``` + +### Conditional Logic + +``` +VAR $mode = 1 + +IF ($mode == 1) + STRING USB Mode +ELSE + STRING BLE Mode +END_IF +ENTER +``` + +### Random Delays + +``` +REM Human-like typing with random delays +LOOP 10 + STRING Hello World + ENTER + VAR $wait = $_RANDOM + VAR $wait = ($wait % 500) + VAR $wait = ($wait + 100) + DELAY $wait +END_LOOP +``` + +### Nested Loops + +``` +VAR $row = 0 +WHILE ($row < 3) + VAR $col = 0 + WHILE ($col < 3) + STRING * + VAR $col = ($col + 1) + END_WHILE + ENTER + VAR $row = ($row + 1) +END_WHILE +``` + +## Script Configuration Commands + +Scripts can override HID settings: + +``` +REM USB Configuration +ID 1234:5678 +MFR_NAME CustomManufacturer +PROD_NAME CustomProduct + +REM BLE Configuration +BLE_NAME MyKeyboard +BLE_MAC 11:22:33:44:55:66 +BLE_PAIRING PIN_CONFIRM +``` + +## Limits + +| Resource | Limit | +|----------|-------| +| Nested loops | 8 | +| Nested IF blocks | 8 | +| Variables | 32 | +| Variable name | 16 chars | +| String value | 128 chars | + +## Credits + +- DuckyScript 3.0 spec by [Hak5](https://docs.hak5.org/hak5-usb-rubber-ducky/) +- Flipper Zero Unleashed by [DarkFlippers](https://github.com/DarkFlippers/unleashed-firmware) +- Author: [@dutchpatriot](https://github.com/dutchpatriot) +- AI-assisted development with Claude Code + +## License + +GPL-3.0 (same as Unleashed firmware) + +## Disclaimer + +For authorized security testing and educational purposes only. Always obtain proper authorization before testing on systems you do not own. diff --git a/non_catalog_apps/bad_duck3/application.fam b/non_catalog_apps/bad_duck3/application.fam new file mode 100644 index 00000000..cdc6096a --- /dev/null +++ b/non_catalog_apps/bad_duck3/application.fam @@ -0,0 +1,15 @@ +App( + appid="bad_duck3", + name="Bad Duck3", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_duck3_app", + stack_size=4 * 1024, + order=71, + fap_libs=["assets", "ble_profile"], + fap_icon="icon.png", + fap_category="USB", + fap_author="@dutchpatriot", + fap_weburl="https://github.com/dutchpatriot/unleashed-firmware", + fap_version="1.1", + fap_description="DuckyScript 3.0 interpreter with USB/BLE HID support. Loops, variables, conditionals.", +) diff --git a/non_catalog_apps/bad_duck3/bad_duck3_app.c b/non_catalog_apps/bad_duck3/bad_duck3_app.c new file mode 100644 index 00000000..9fd333eb --- /dev/null +++ b/non_catalog_apps/bad_duck3/bad_duck3_app.c @@ -0,0 +1,322 @@ +#include "bad_duck3_app_i.h" +#include +#include +#include +#include +#include + +#define BAD_DUCK3_SETTINGS_PATH BAD_DUCK3_APP_BASE_FOLDER "/.badduck3.settings" +#define BAD_DUCK3_SETTINGS_FILE_TYPE "Flipper BadDuck3 Settings File" +#define BAD_DUCK3_SETTINGS_VERSION 1 +#define BAD_DUCK3_SETTINGS_DEFAULT_LAYOUT BAD_DUCK3_APP_PATH_LAYOUT_FOLDER "/en-US.kl" + +static bool bad_duck3_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadDuck3App* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_duck3_app_back_event_callback(void* context) { + furi_assert(context); + BadDuck3App* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_duck3_app_tick_event_callback(void* context) { + furi_assert(context); + BadDuck3App* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_duck3_load_settings(BadDuck3App* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + bool loaded = false; + + Duck3HidConfig* hid_cfg = &app->user_hid_cfg; + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_uint = 0; + + if(flipper_format_file_open_existing(fff, BAD_DUCK3_SETTINGS_PATH)) { + do { + if(!flipper_format_read_header(fff, temp_str, &temp_uint)) break; + if((strcmp(furi_string_get_cstr(temp_str), BAD_DUCK3_SETTINGS_FILE_TYPE) != 0) || + (temp_uint != BAD_DUCK3_SETTINGS_VERSION)) + break; + + if(flipper_format_read_string(fff, "layout", temp_str)) { + furi_string_set(app->keyboard_layout, temp_str); + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat( + storage, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); + if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) { + furi_string_set(app->keyboard_layout, BAD_DUCK3_SETTINGS_DEFAULT_LAYOUT); + } + } else { + furi_string_set(app->keyboard_layout, BAD_DUCK3_SETTINGS_DEFAULT_LAYOUT); + flipper_format_rewind(fff); + } + + if(!flipper_format_read_uint32(fff, "interface", &temp_uint, 1) || + temp_uint >= Duck3HidInterfaceMAX) { + temp_uint = Duck3HidInterfaceUsb; + flipper_format_rewind(fff); + } + app->interface = temp_uint; + + if(!flipper_format_read_bool(fff, "ble_bonding", &hid_cfg->ble.bonding, 1)) { + hid_cfg->ble.bonding = true; + flipper_format_rewind(fff); + } + + if(!flipper_format_read_uint32(fff, "ble_pairing", &temp_uint, 1) || + temp_uint >= GapPairingCount) { + temp_uint = GapPairingPinCodeVerifyYesNo; + flipper_format_rewind(fff); + } + hid_cfg->ble.pairing = temp_uint; + + if(flipper_format_read_string(fff, "ble_name", temp_str)) { + strlcpy( + hid_cfg->ble.name, furi_string_get_cstr(temp_str), sizeof(hid_cfg->ble.name)); + } else { + hid_cfg->ble.name[0] = '\0'; + flipper_format_rewind(fff); + } + + if(!flipper_format_read_hex( + fff, "ble_mac", hid_cfg->ble.mac, sizeof(hid_cfg->ble.mac))) { + memset(hid_cfg->ble.mac, 0, sizeof(hid_cfg->ble.mac)); + flipper_format_rewind(fff); + } + + if(flipper_format_read_string(fff, "usb_manuf", temp_str)) { + strlcpy( + hid_cfg->usb.manuf, + furi_string_get_cstr(temp_str), + sizeof(hid_cfg->usb.manuf)); + } else { + hid_cfg->usb.manuf[0] = '\0'; + flipper_format_rewind(fff); + } + + if(flipper_format_read_string(fff, "usb_product", temp_str)) { + strlcpy( + hid_cfg->usb.product, + furi_string_get_cstr(temp_str), + sizeof(hid_cfg->usb.product)); + } else { + hid_cfg->usb.product[0] = '\0'; + flipper_format_rewind(fff); + } + + if(!flipper_format_read_uint32(fff, "usb_vid", (uint32_t*)&hid_cfg->usb.vid, 1)) { + hid_cfg->usb.vid = 0; + flipper_format_rewind(fff); + } + + if(!flipper_format_read_uint32(fff, "usb_pid", (uint32_t*)&hid_cfg->usb.pid, 1)) { + hid_cfg->usb.pid = 0; + flipper_format_rewind(fff); + } + + loaded = true; + } while(0); + } + + furi_string_free(temp_str); + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); + + if(!loaded) { + furi_string_set(app->keyboard_layout, BAD_DUCK3_SETTINGS_DEFAULT_LAYOUT); + app->interface = Duck3HidInterfaceUsb; + hid_cfg->ble.name[0] = '\0'; + memset(hid_cfg->ble.mac, 0, sizeof(hid_cfg->ble.mac)); + hid_cfg->ble.bonding = true; + hid_cfg->ble.pairing = GapPairingPinCodeVerifyYesNo; + hid_cfg->usb.vid = 0; + hid_cfg->usb.pid = 0; + hid_cfg->usb.manuf[0] = '\0'; + hid_cfg->usb.product[0] = '\0'; + } +} + +static void bad_duck3_save_settings(BadDuck3App* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + Duck3HidConfig* hid_cfg = &app->user_hid_cfg; + uint32_t temp_uint = 0; + + if(flipper_format_file_open_always(fff, BAD_DUCK3_SETTINGS_PATH)) { + do { + if(!flipper_format_write_header_cstr( + fff, BAD_DUCK3_SETTINGS_FILE_TYPE, BAD_DUCK3_SETTINGS_VERSION)) + break; + if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; + temp_uint = app->interface; + if(!flipper_format_write_uint32(fff, "interface", &temp_uint, 1)) break; + if(!flipper_format_write_bool(fff, "ble_bonding", &hid_cfg->ble.bonding, 1)) break; + temp_uint = hid_cfg->ble.pairing; + if(!flipper_format_write_uint32(fff, "ble_pairing", &temp_uint, 1)) break; + if(!flipper_format_write_string_cstr(fff, "ble_name", hid_cfg->ble.name)) break; + if(!flipper_format_write_hex( + fff, "ble_mac", (uint8_t*)&hid_cfg->ble.mac, sizeof(hid_cfg->ble.mac))) + break; + if(!flipper_format_write_string_cstr(fff, "usb_manuf", hid_cfg->usb.manuf)) break; + if(!flipper_format_write_string_cstr(fff, "usb_product", hid_cfg->usb.product)) break; + temp_uint = hid_cfg->usb.vid; + if(!flipper_format_write_uint32(fff, "usb_vid", &temp_uint, 1)) break; + temp_uint = hid_cfg->usb.pid; + if(!flipper_format_write_uint32(fff, "usb_pid", &temp_uint, 1)) break; + } while(0); + } + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); +} + +void bad_duck3_set_interface(BadDuck3App* app, Duck3HidInterface interface) { + app->interface = interface; + bad_duck3_view_set_interface(app->bad_duck3_view, interface); +} + +BadDuck3App* bad_duck3_app_alloc(char* arg) { + BadDuck3App* app = malloc(sizeof(BadDuck3App)); + + app->duck3_script = NULL; + + app->file_path = furi_string_alloc(); + app->keyboard_layout = furi_string_alloc(); + if(arg && strlen(arg)) { + furi_string_set(app->file_path, arg); + } + + bad_duck3_load_settings(app); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->dialogs = furi_record_open(RECORD_DIALOGS); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&bad_duck3_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_duck3_app_tick_event_callback, 250); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_duck3_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_duck3_app_back_event_callback); + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadDuck3AppViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, BadDuck3AppViewPopup, popup_get_view(app->popup)); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + BadDuck3AppViewConfig, + variable_item_list_get_view(app->var_item_list)); + + app->bad_duck3_view = bad_duck3_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadDuck3AppViewWork, bad_duck3_view_get_view(app->bad_duck3_view)); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadDuck3AppViewTextInput, text_input_get_view(app->text_input)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadDuck3AppViewByteInput, byte_input_get_view(app->byte_input)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + if(furi_hal_usb_is_locked()) { + app->error = BadDuck3AppErrorCloseRpc; + app->usb_if_prev = NULL; + scene_manager_next_scene(app->scene_manager, BadDuck3SceneError); + } else { + app->usb_if_prev = furi_hal_usb_get_config(); + furi_check(furi_hal_usb_set_config(NULL, NULL)); + + if(!furi_string_empty(app->file_path)) { + scene_manager_set_scene_state(app->scene_manager, BadDuck3SceneWork, true); + scene_manager_next_scene(app->scene_manager, BadDuck3SceneWork); + } else { + furi_string_set(app->file_path, BAD_DUCK3_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadDuck3SceneFileSelect); + } + } + + return app; +} + +void bad_duck3_app_free(BadDuck3App* app) { + furi_assert(app); + + if(app->duck3_script) { + duck3_script_close(app->duck3_script); + app->duck3_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadDuck3AppViewWork); + bad_duck3_view_free(app->bad_duck3_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadDuck3AppViewWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, BadDuck3AppViewPopup); + popup_free(app->popup); + + // Config menu + view_dispatcher_remove_view(app->view_dispatcher, BadDuck3AppViewConfig); + variable_item_list_free(app->var_item_list); + + // Text Input + view_dispatcher_remove_view(app->view_dispatcher, BadDuck3AppViewTextInput); + text_input_free(app->text_input); + + // Byte Input + view_dispatcher_remove_view(app->view_dispatcher, BadDuck3AppViewByteInput); + byte_input_free(app->byte_input); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + + bad_duck3_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + if(app->usb_if_prev) { + furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL)); + } + + free(app); +} + +int32_t bad_duck3_app(void* p) { + BadDuck3App* bad_duck3_app = bad_duck3_app_alloc((char*)p); + + view_dispatcher_run(bad_duck3_app->view_dispatcher); + + bad_duck3_app_free(bad_duck3_app); + return 0; +} diff --git a/non_catalog_apps/bad_duck3/bad_duck3_app.h b/non_catalog_apps/bad_duck3/bad_duck3_app.h new file mode 100644 index 00000000..62a430fc --- /dev/null +++ b/non_catalog_apps/bad_duck3/bad_duck3_app.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BadDuck3App BadDuck3App; + +#ifdef __cplusplus +} +#endif diff --git a/non_catalog_apps/bad_duck3/bad_duck3_app_i.h b/non_catalog_apps/bad_duck3/bad_duck3_app_i.h new file mode 100644 index 00000000..bf929dff --- /dev/null +++ b/non_catalog_apps/bad_duck3/bad_duck3_app_i.h @@ -0,0 +1,70 @@ +#pragma once + +#include "bad_duck3_app.h" +#include "scenes/bad_duck3_scene.h" +#include "helpers/duck3_script.h" +#include "helpers/duck3_hid.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/bad_duck3_view.h" +#include + +#define BAD_DUCK3_APP_BASE_FOLDER EXT_PATH("badusb") +#define BAD_DUCK3_APP_PATH_LAYOUT_FOLDER BAD_DUCK3_APP_BASE_FOLDER "/assets/layouts" +#define BAD_DUCK3_APP_SCRIPT_EXTENSION ".txt" +#define BAD_DUCK3_APP_LAYOUT_EXTENSION ".kl" + +typedef enum { + BadDuck3AppErrorNoFiles, + BadDuck3AppErrorCloseRpc, +} BadDuck3AppError; + +struct BadDuck3App { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + Popup* popup; + VariableItemList* var_item_list; + TextInput* text_input; + ByteInput* byte_input; + + char ble_name_buf[FURI_HAL_BT_ADV_NAME_LENGTH]; + uint8_t ble_mac_buf[GAP_MAC_ADDR_SIZE]; + char usb_name_buf[HID_MANUF_PRODUCT_NAME_LEN]; + uint16_t usb_vidpid_buf[2]; + + BadDuck3AppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadDuck3View* bad_duck3_view; + Duck3Script* duck3_script; + + Duck3HidInterface interface; + Duck3HidConfig user_hid_cfg; + Duck3HidConfig script_hid_cfg; + FuriHalUsbInterface* usb_if_prev; +}; + +typedef enum { + BadDuck3AppViewWidget, + BadDuck3AppViewPopup, + BadDuck3AppViewWork, + BadDuck3AppViewConfig, + BadDuck3AppViewByteInput, + BadDuck3AppViewTextInput, +} BadDuck3AppView; + +void bad_duck3_set_interface(BadDuck3App* app, Duck3HidInterface interface); diff --git a/non_catalog_apps/bad_duck3/helpers/duck3_hid.c b/non_catalog_apps/bad_duck3/helpers/duck3_hid.c new file mode 100644 index 00000000..25e72108 --- /dev/null +++ b/non_catalog_apps/bad_duck3/helpers/duck3_hid.c @@ -0,0 +1,342 @@ +#include "duck3_hid.h" +#include +#include +#include +#include + +#define TAG "Duck3 HID" + +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" + +// BLE profile template for extended HID +typedef struct { + char name[FURI_HAL_BT_ADV_NAME_LENGTH]; + uint8_t mac[GAP_MAC_ADDR_SIZE]; + bool bonding; + GapPairing pairing; +} BleProfileHidExtParams; + +// ============================================================================ +// USB HID Implementation +// ============================================================================ + +static void hid_usb_adjust_config(Duck3HidConfig* hid_cfg) { + if(hid_cfg->usb.vid == 0) hid_cfg->usb.vid = HID_VID_DEFAULT; + if(hid_cfg->usb.pid == 0) hid_cfg->usb.pid = HID_PID_DEFAULT; +} + +static void* hid_usb_init(Duck3HidConfig* hid_cfg) { + hid_usb_adjust_config(hid_cfg); + FuriHalUsbHidConfig usb_cfg = { + .vid = hid_cfg->usb.vid, + .pid = hid_cfg->usb.pid, + }; + strlcpy(usb_cfg.manuf, hid_cfg->usb.manuf, sizeof(usb_cfg.manuf)); + strlcpy(usb_cfg.product, hid_cfg->usb.product, sizeof(usb_cfg.product)); + furi_check(furi_hal_usb_set_config(&usb_hid, &usb_cfg)); + return NULL; +} + +static void hid_usb_deinit(void* inst) { + UNUSED(inst); + furi_check(furi_hal_usb_set_config(NULL, NULL)); +} + +static void hid_usb_set_state_callback(void* inst, Duck3HidStateCallback cb, void* context) { + UNUSED(inst); + furi_hal_hid_set_state_callback(cb, context); +} + +static bool hid_usb_is_connected(void* inst) { + UNUSED(inst); + return furi_hal_hid_is_connected(); +} + +static bool hid_usb_kb_press(void* inst, uint16_t button) { + UNUSED(inst); + return furi_hal_hid_kb_press(button); +} + +static bool hid_usb_kb_release(void* inst, uint16_t button) { + UNUSED(inst); + return furi_hal_hid_kb_release(button); +} + +static bool hid_usb_mouse_press(void* inst, uint8_t button) { + UNUSED(inst); + return furi_hal_hid_mouse_press(button); +} + +static bool hid_usb_mouse_release(void* inst, uint8_t button) { + UNUSED(inst); + return furi_hal_hid_mouse_release(button); +} + +static bool hid_usb_mouse_scroll(void* inst, int8_t delta) { + UNUSED(inst); + return furi_hal_hid_mouse_scroll(delta); +} + +static bool hid_usb_mouse_move(void* inst, int8_t dx, int8_t dy) { + UNUSED(inst); + return furi_hal_hid_mouse_move(dx, dy); +} + +static bool hid_usb_consumer_press(void* inst, uint16_t button) { + UNUSED(inst); + return furi_hal_hid_consumer_key_press(button); +} + +static bool hid_usb_consumer_release(void* inst, uint16_t button) { + UNUSED(inst); + return furi_hal_hid_consumer_key_release(button); +} + +static bool hid_usb_release_all(void* inst) { + UNUSED(inst); + bool state = furi_hal_hid_kb_release_all(); + state &= furi_hal_hid_consumer_key_release_all(); + state &= furi_hal_hid_mouse_release(0); + return state; +} + +static uint8_t hid_usb_get_led_state(void* inst) { + UNUSED(inst); + return furi_hal_hid_get_led_state(); +} + +static const Duck3HidApi hid_api_usb = { + .adjust_config = hid_usb_adjust_config, + .init = hid_usb_init, + .deinit = hid_usb_deinit, + .set_state_callback = hid_usb_set_state_callback, + .is_connected = hid_usb_is_connected, + .kb_press = hid_usb_kb_press, + .kb_release = hid_usb_kb_release, + .mouse_press = hid_usb_mouse_press, + .mouse_release = hid_usb_mouse_release, + .mouse_scroll = hid_usb_mouse_scroll, + .mouse_move = hid_usb_mouse_move, + .consumer_press = hid_usb_consumer_press, + .consumer_release = hid_usb_consumer_release, + .release_all = hid_usb_release_all, + .get_led_state = hid_usb_get_led_state, +}; + +// ============================================================================ +// BLE HID Implementation +// ============================================================================ + +typedef struct { + Bt* bt; + FuriHalBleProfileBase* profile; + Duck3HidStateCallback state_callback; + void* callback_context; + bool is_connected; +} BleHidInstance; + +static void hid_ble_connection_status_callback(BtStatus status, void* context) { + furi_assert(context); + BleHidInstance* ble_hid = context; + ble_hid->is_connected = (status == BtStatusConnected); + if(ble_hid->state_callback) { + ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); + } +} + +static void hid_ble_adjust_config(Duck3HidConfig* hid_cfg) { + const uint8_t* normal_mac = furi_hal_version_get_ble_mac(); + uint8_t empty_mac[GAP_MAC_ADDR_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t default_mac[GAP_MAC_ADDR_SIZE] = {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72}; + + if(memcmp(hid_cfg->ble.mac, empty_mac, sizeof(hid_cfg->ble.mac)) == 0 || + memcmp(hid_cfg->ble.mac, normal_mac, sizeof(hid_cfg->ble.mac)) == 0 || + memcmp(hid_cfg->ble.mac, default_mac, sizeof(hid_cfg->ble.mac)) == 0) { + memcpy(hid_cfg->ble.mac, normal_mac, sizeof(hid_cfg->ble.mac)); + hid_cfg->ble.mac[2]++; + uint16_t duck3_mac_xor = 0x0003; + hid_cfg->ble.mac[0] ^= duck3_mac_xor; + hid_cfg->ble.mac[1] ^= duck3_mac_xor >> 8; + } + + if(hid_cfg->ble.name[0] == '\0') { + const char* duck3_device_name_prefix = "Duck3"; + snprintf( + hid_cfg->ble.name, + sizeof(hid_cfg->ble.name), + "%s %s", + duck3_device_name_prefix, + furi_hal_version_get_name_ptr()); + } + + if(hid_cfg->ble.pairing >= GapPairingCount) { + hid_cfg->ble.pairing = GapPairingPinCodeVerifyYesNo; + } +} + +static void* hid_ble_init(Duck3HidConfig* hid_cfg) { + BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); + ble_hid->bt = furi_record_open(RECORD_BT); + ble_hid->bt->suppress_pin_screen = true; + bt_disconnect(ble_hid->bt); + + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + hid_ble_adjust_config(hid_cfg); + + BleProfileHidParams ble_params = { + .device_name_prefix = hid_cfg->ble.name, + .mac_xor = 0x0003, + }; + + ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, &ble_params); + furi_check(ble_hid->profile); + + furi_hal_bt_start_advertising(); + + bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); + + return ble_hid; +} + +static void hid_ble_deinit(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + + bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); + bt_disconnect(ble_hid->bt); + + furi_delay_ms(200); + bt_keys_storage_set_default_path(ble_hid->bt); + + furi_check(bt_profile_restore_default(ble_hid->bt)); + ble_hid->bt->suppress_pin_screen = false; + furi_record_close(RECORD_BT); + free(ble_hid); +} + +static void hid_ble_set_state_callback(void* inst, Duck3HidStateCallback cb, void* context) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + ble_hid->state_callback = cb; + ble_hid->callback_context = context; +} + +static bool hid_ble_is_connected(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_hid->is_connected; +} + +static bool hid_ble_kb_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_press(ble_hid->profile, button); +} + +static bool hid_ble_kb_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_release(ble_hid->profile, button); +} + +static bool hid_ble_mouse_press(void* inst, uint8_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_mouse_press(ble_hid->profile, button); +} + +static bool hid_ble_mouse_release(void* inst, uint8_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_mouse_release(ble_hid->profile, button); +} + +static bool hid_ble_mouse_scroll(void* inst, int8_t delta) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_mouse_scroll(ble_hid->profile, delta); +} + +static bool hid_ble_mouse_move(void* inst, int8_t dx, int8_t dy) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_mouse_move(ble_hid->profile, dx, dy); +} + +static bool hid_ble_consumer_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_press(ble_hid->profile, button); +} + +static bool hid_ble_consumer_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_release(ble_hid->profile, button); +} + +static bool hid_ble_release_all(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + bool state = ble_profile_hid_kb_release_all(ble_hid->profile); + state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); + state &= ble_profile_hid_mouse_release_all(ble_hid->profile); + return state; +} + +static uint8_t hid_ble_get_led_state(void* inst) { + UNUSED(inst); + FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); + return 0; +} + +static const Duck3HidApi hid_api_ble = { + .adjust_config = hid_ble_adjust_config, + .init = hid_ble_init, + .deinit = hid_ble_deinit, + .set_state_callback = hid_ble_set_state_callback, + .is_connected = hid_ble_is_connected, + .kb_press = hid_ble_kb_press, + .kb_release = hid_ble_kb_release, + .mouse_press = hid_ble_mouse_press, + .mouse_release = hid_ble_mouse_release, + .mouse_scroll = hid_ble_mouse_scroll, + .mouse_move = hid_ble_mouse_move, + .consumer_press = hid_ble_consumer_press, + .consumer_release = hid_ble_consumer_release, + .release_all = hid_ble_release_all, + .get_led_state = hid_ble_get_led_state, +}; + +// ============================================================================ +// Public API +// ============================================================================ + +const Duck3HidApi* duck3_hid_get_interface(Duck3HidInterface interface) { + if(interface == Duck3HidInterfaceUsb) { + return &hid_api_usb; + } else { + return &hid_api_ble; + } +} + +void duck3_hid_ble_remove_pairing(void) { + Bt* bt = furi_record_open(RECORD_BT); + bt_disconnect(bt); + + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + bt_forget_bonded_devices(bt); + + furi_delay_ms(200); + bt_keys_storage_set_default_path(bt); + + furi_check(bt_profile_restore_default(bt)); + furi_record_close(RECORD_BT); +} diff --git a/non_catalog_apps/bad_duck3/helpers/duck3_hid.h b/non_catalog_apps/bad_duck3/helpers/duck3_hid.h new file mode 100644 index 00000000..7b645e74 --- /dev/null +++ b/non_catalog_apps/bad_duck3/helpers/duck3_hid.h @@ -0,0 +1,64 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +typedef enum { + Duck3HidInterfaceUsb, + Duck3HidInterfaceBle, + Duck3HidInterfaceMAX, +} Duck3HidInterface; + +typedef struct { + char name[FURI_HAL_BT_ADV_NAME_LENGTH]; + uint8_t mac[GAP_MAC_ADDR_SIZE]; + bool bonding; + GapPairing pairing; +} Duck3BleConfig; + +typedef struct { + char manuf[HID_MANUF_PRODUCT_NAME_LEN]; + char product[HID_MANUF_PRODUCT_NAME_LEN]; + uint16_t vid; + uint16_t pid; +} Duck3UsbConfig; + +typedef struct { + Duck3BleConfig ble; + Duck3UsbConfig usb; +} Duck3HidConfig; + +typedef void (*Duck3HidStateCallback)(bool state, void* context); + +typedef struct { + void (*adjust_config)(Duck3HidConfig* hid_cfg); + void* (*init)(Duck3HidConfig* hid_cfg); + void (*deinit)(void* inst); + void (*set_state_callback)(void* inst, Duck3HidStateCallback cb, void* context); + bool (*is_connected)(void* inst); + + bool (*kb_press)(void* inst, uint16_t button); + bool (*kb_release)(void* inst, uint16_t button); + bool (*mouse_press)(void* inst, uint8_t button); + bool (*mouse_release)(void* inst, uint8_t button); + bool (*mouse_scroll)(void* inst, int8_t delta); + bool (*mouse_move)(void* inst, int8_t dx, int8_t dy); + bool (*consumer_press)(void* inst, uint16_t button); + bool (*consumer_release)(void* inst, uint16_t button); + bool (*release_all)(void* inst); + uint8_t (*get_led_state)(void* inst); +} Duck3HidApi; + +const Duck3HidApi* duck3_hid_get_interface(Duck3HidInterface interface); + +void duck3_hid_ble_remove_pairing(void); + +#ifdef __cplusplus +} +#endif diff --git a/non_catalog_apps/bad_duck3/helpers/duck3_script.c b/non_catalog_apps/bad_duck3/helpers/duck3_script.c new file mode 100644 index 00000000..e4c7b954 --- /dev/null +++ b/non_catalog_apps/bad_duck3/helpers/duck3_script.c @@ -0,0 +1,1024 @@ +/** + * @file duck3_script.c + * @brief DuckyScript 3.0 interpreter with BLE/USB HID support + * + * Full implementation with LOOP, WHILE, IF/ELSE, VAR support + * and threaded execution like Bad USB. + */ + +#include "duck3_script.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "Duck3Script" + +#define MAX_LINES 512 +#define MAX_LINE_LEN 256 +#define MAX_LOOP_DEPTH 8 +#define MAX_VARIABLES 16 +#define MAX_VAR_NAME 16 +#define MAX_IF_DEPTH 8 + +#define SCRIPT_STATE_ERROR -1 +#define SCRIPT_STATE_END -2 +#define SCRIPT_STATE_WAIT_BTN -3 + +typedef struct { + uint32_t start_line; + int32_t remaining; + bool is_while; + char condition[64]; +} LoopContext; + +typedef struct { + char name[MAX_VAR_NAME]; + int32_t value; + bool is_set; +} Variable; + +typedef struct { + bool condition_met; + bool in_else; +} IfContext; + +struct Duck3Script { + FuriThread* thread; + Duck3State state; + + // HID interface + Duck3HidInterface* interface; + Duck3HidConfig* hid_cfg; + const Duck3HidApi* hid; + void* hid_inst; + + // Lines stored in memory + char** lines; + uint32_t line_count; + uint32_t line_cur; + + // Keyboard layout + uint16_t layout[128]; + + // Delays + uint32_t defdelay; + uint32_t stringdelay; + + // Repeat + uint32_t repeat_cnt; + uint32_t repeat_line; + + // Loop stack + LoopContext loop_stack[MAX_LOOP_DEPTH]; + uint8_t loop_depth; + + // Variables + Variable variables[MAX_VARIABLES]; + uint8_t var_count; + + // IF stack + IfContext if_stack[MAX_IF_DEPTH]; + uint8_t if_depth; + + // Skip flags + bool skip_until_endif; + bool skip_until_endloop; + uint8_t skip_depth; + + // Worker state + bool worker_running; + bool paused; + uint32_t start_time; +}; + +// Forward declarations +static int32_t duck3_worker(void* context); +static int32_t duck3_parse_line(Duck3Script* script, const char* line); + +// ============================================================================ +// Variable Management +// ============================================================================ + +static Variable* find_variable(Duck3Script* script, const char* name) { + for(uint8_t i = 0; i < script->var_count; i++) { + if(strcmp(script->variables[i].name, name) == 0) { + return &script->variables[i]; + } + } + return NULL; +} + +static int32_t get_var(Duck3Script* script, const char* name) { + if(strcmp(name, "$_RANDOM") == 0 || strcmp(name, "RANDOM") == 0) { + return (int32_t)(furi_hal_random_get() % 65536); + } + if(strcmp(name, "$_LINE") == 0) { + return (int32_t)script->line_cur; + } + + Variable* var = find_variable(script, name); + if(var && var->is_set) { + return var->value; + } + return 0; +} + +static bool set_var(Duck3Script* script, const char* name, int32_t value) { + Variable* var = find_variable(script, name); + if(var) { + var->value = value; + var->is_set = true; + return true; + } + + if(script->var_count >= MAX_VARIABLES) return false; + + var = &script->variables[script->var_count++]; + strncpy(var->name, name, MAX_VAR_NAME - 1); + var->name[MAX_VAR_NAME - 1] = '\0'; + var->value = value; + var->is_set = true; + return true; +} + +// ============================================================================ +// Expression Evaluator +// ============================================================================ + +static int32_t parse_value(Duck3Script* script, const char** ptr) { + while(**ptr == ' ') (*ptr)++; + + if(**ptr == '$') { + (*ptr)++; + char var_name[MAX_VAR_NAME] = "$"; + int i = 1; + while(isalnum((unsigned char)**ptr) || **ptr == '_') { + if(i < MAX_VAR_NAME - 1) var_name[i++] = **ptr; + (*ptr)++; + } + var_name[i] = '\0'; + return get_var(script, var_name); + } + + bool negative = false; + if(**ptr == '-') { + negative = true; + (*ptr)++; + } + + int32_t value = 0; + while(isdigit((unsigned char)**ptr)) { + value = value * 10 + (**ptr - '0'); + (*ptr)++; + } + + return negative ? -value : value; +} + +static bool eval_expr(Duck3Script* script, const char* expr, int32_t* result) { + const char* ptr = expr; + while(*ptr == ' ' || *ptr == '(') ptr++; + + int32_t left = parse_value(script, &ptr); + while(*ptr == ' ') ptr++; + + if(*ptr == '\0' || *ptr == ')') { + *result = left; + return true; + } + + char op1 = *ptr++; + char op2 = (*ptr == '=' || *ptr == '&' || *ptr == '|') ? *ptr++ : '\0'; + + while(*ptr == ' ') ptr++; + int32_t right = parse_value(script, &ptr); + + if(op1 == '+') *result = left + right; + else if(op1 == '-') *result = left - right; + else if(op1 == '*') *result = left * right; + else if(op1 == '/' && right != 0) *result = left / right; + else if(op1 == '%' && right != 0) *result = left % right; + else if(op1 == '<' && op2 == '=') *result = left <= right ? 1 : 0; + else if(op1 == '>' && op2 == '=') *result = left >= right ? 1 : 0; + else if(op1 == '=' && op2 == '=') *result = left == right ? 1 : 0; + else if(op1 == '!' && op2 == '=') *result = left != right ? 1 : 0; + else if(op1 == '<') *result = left < right ? 1 : 0; + else if(op1 == '>') *result = left > right ? 1 : 0; + else if(op1 == '&' && op2 == '&') *result = (left && right) ? 1 : 0; + else if(op1 == '|' && op2 == '|') *result = (left || right) ? 1 : 0; + else return false; + + return true; +} + +// ============================================================================ +// HID Wrappers +// ============================================================================ + +static void hid_press(Duck3Script* script, uint16_t keycode) { + script->hid->kb_press(script->hid_inst, keycode); +} + +static void hid_release(Duck3Script* script, uint16_t keycode) { + script->hid->kb_release(script->hid_inst, keycode); +} + +static void hid_release_all(Duck3Script* script) { + script->hid->release_all(script->hid_inst); +} + +static bool type_string(Duck3Script* script, const char* str) { + for(size_t i = 0; str[i] != '\0'; i++) { + uint8_t c = (uint8_t)str[i]; + if(c == '\n') { + hid_press(script, HID_KEYBOARD_RETURN); + hid_release(script, HID_KEYBOARD_RETURN); + } else if(c < 128) { + uint16_t keycode = script->layout[c]; + if(keycode != 0) { + hid_press(script, keycode); + hid_release(script, keycode); + } + } + if(script->stringdelay > 0) { + furi_delay_ms(script->stringdelay); + } + } + return true; +} + +// ============================================================================ +// Command Handlers +// ============================================================================ + +static int32_t cmd_delay(Duck3Script* script, const char* arg) { + UNUSED(script); + return atoi(arg); +} + +static int32_t cmd_string(Duck3Script* script, const char* arg, bool newline) { + type_string(script, arg); + if(newline) { + hid_press(script, HID_KEYBOARD_RETURN); + hid_release(script, HID_KEYBOARD_RETURN); + } + return 0; +} + +static int32_t cmd_loop(Duck3Script* script, const char* arg) { + if(script->loop_depth >= MAX_LOOP_DEPTH) { + snprintf(script->state.error, sizeof(script->state.error), "Loop depth exceeded"); + return SCRIPT_STATE_ERROR; + } + + int32_t count; + if(arg[0] == '$') { + count = get_var(script, arg); + } else { + count = atoi(arg); + } + + if(count <= 0) { + script->skip_until_endloop = true; + script->skip_depth = 1; + return 0; + } + + LoopContext* ctx = &script->loop_stack[script->loop_depth]; + ctx->start_line = script->line_cur + 1; + ctx->remaining = count; + ctx->is_while = false; + script->loop_depth++; + + return 0; +} + +static int32_t cmd_end_loop(Duck3Script* script) { + if(script->skip_until_endloop) { + script->skip_depth--; + if(script->skip_depth == 0) { + script->skip_until_endloop = false; + } + return 0; + } + + if(script->loop_depth == 0) { + snprintf(script->state.error, sizeof(script->state.error), "END_LOOP without LOOP"); + return SCRIPT_STATE_ERROR; + } + + LoopContext* ctx = &script->loop_stack[script->loop_depth - 1]; + + if(!ctx->is_while) { + ctx->remaining--; + if(ctx->remaining > 0) { + script->line_cur = ctx->start_line - 1; + } else { + script->loop_depth--; + } + } + + return 0; +} + +static int32_t cmd_while(Duck3Script* script, const char* arg) { + if(script->loop_depth >= MAX_LOOP_DEPTH) { + return SCRIPT_STATE_ERROR; + } + + const char* cond = strchr(arg, '('); + if(!cond) cond = arg; + + int32_t result; + if(!eval_expr(script, cond, &result)) { + return SCRIPT_STATE_ERROR; + } + + if(result == 0) { + script->skip_until_endloop = true; + script->skip_depth = 1; + return 0; + } + + LoopContext* ctx = &script->loop_stack[script->loop_depth]; + ctx->start_line = script->line_cur + 1; + ctx->remaining = -1; + ctx->is_while = true; + strncpy(ctx->condition, cond, sizeof(ctx->condition) - 1); + script->loop_depth++; + + return 0; +} + +static int32_t cmd_end_while(Duck3Script* script) { + if(script->skip_until_endloop) { + script->skip_depth--; + if(script->skip_depth == 0) { + script->skip_until_endloop = false; + } + return 0; + } + + if(script->loop_depth == 0) return SCRIPT_STATE_ERROR; + + LoopContext* ctx = &script->loop_stack[script->loop_depth - 1]; + + if(ctx->is_while) { + int32_t result; + if(eval_expr(script, ctx->condition, &result) && result != 0) { + script->line_cur = ctx->start_line - 1; + } else { + script->loop_depth--; + } + } + + return 0; +} + +static int32_t cmd_var(Duck3Script* script, const char* arg) { + const char* ptr = arg; + while(*ptr == ' ') ptr++; + + if(*ptr != '$') return SCRIPT_STATE_ERROR; + + char var_name[MAX_VAR_NAME]; + int i = 0; + while(*ptr && *ptr != ' ' && *ptr != '=' && i < MAX_VAR_NAME - 1) { + var_name[i++] = *ptr++; + } + var_name[i] = '\0'; + + while(*ptr == ' ') ptr++; + if(*ptr != '=') return SCRIPT_STATE_ERROR; + ptr++; + while(*ptr == ' ') ptr++; + + int32_t value; + if(!eval_expr(script, ptr, &value)) { + value = atoi(ptr); + } + + set_var(script, var_name, value); + return 0; +} + +static int32_t cmd_if(Duck3Script* script, const char* arg) { + if(script->if_depth >= MAX_IF_DEPTH) return SCRIPT_STATE_ERROR; + + const char* cond = strchr(arg, '('); + if(!cond) cond = arg; + + int32_t result; + bool condition = eval_expr(script, cond, &result) && result != 0; + + IfContext* ctx = &script->if_stack[script->if_depth]; + ctx->condition_met = condition; + ctx->in_else = false; + script->if_depth++; + + if(!condition) { + script->skip_until_endif = true; + script->skip_depth = 1; + } + + return 0; +} + +static int32_t cmd_else(Duck3Script* script) { + if(script->if_depth == 0) return SCRIPT_STATE_ERROR; + + IfContext* ctx = &script->if_stack[script->if_depth - 1]; + ctx->in_else = true; + + if(script->skip_until_endif && script->skip_depth == 1) { + script->skip_until_endif = false; + script->skip_depth = 0; + } else if(!script->skip_until_endif && ctx->condition_met) { + script->skip_until_endif = true; + script->skip_depth = 1; + } + + return 0; +} + +static int32_t cmd_end_if(Duck3Script* script) { + if(script->skip_until_endif) { + script->skip_depth--; + if(script->skip_depth == 0) { + script->skip_until_endif = false; + } + } + + if(script->if_depth > 0) { + script->if_depth--; + } + + return 0; +} + +static int32_t cmd_break(Duck3Script* script) { + if(script->loop_depth == 0) return SCRIPT_STATE_ERROR; + + script->skip_until_endloop = true; + script->skip_depth = 1; + script->loop_stack[script->loop_depth - 1].remaining = 0; + + return 0; +} + +static int32_t cmd_continue(Duck3Script* script) { + if(script->loop_depth == 0) return SCRIPT_STATE_ERROR; + + LoopContext* ctx = &script->loop_stack[script->loop_depth - 1]; + + if(ctx->is_while) { + script->line_cur = ctx->start_line - 1; + } else { + ctx->remaining--; + if(ctx->remaining > 0) { + script->line_cur = ctx->start_line - 1; + } else { + script->skip_until_endloop = true; + script->skip_depth = 1; + } + } + + return 0; +} + +// ============================================================================ +// Key codes +// ============================================================================ + +static uint16_t get_keycode(const char* name) { + if(strcmp(name, "ENTER") == 0 || strcmp(name, "RETURN") == 0) return HID_KEYBOARD_RETURN; + if(strcmp(name, "ESCAPE") == 0 || strcmp(name, "ESC") == 0) return HID_KEYBOARD_ESCAPE; + if(strcmp(name, "BACKSPACE") == 0) return HID_KEYBOARD_DELETE; + if(strcmp(name, "TAB") == 0) return HID_KEYBOARD_TAB; + if(strcmp(name, "SPACE") == 0) return HID_KEYBOARD_SPACEBAR; + if(strcmp(name, "CAPSLOCK") == 0) return HID_KEYBOARD_CAPS_LOCK; + if(strcmp(name, "DELETE") == 0) return HID_KEYBOARD_DELETE_FORWARD; + if(strcmp(name, "INSERT") == 0) return HID_KEYBOARD_INSERT; + if(strcmp(name, "HOME") == 0) return HID_KEYBOARD_HOME; + if(strcmp(name, "END") == 0) return HID_KEYBOARD_END; + if(strcmp(name, "PAGEUP") == 0) return HID_KEYBOARD_PAGE_UP; + if(strcmp(name, "PAGEDOWN") == 0) return HID_KEYBOARD_PAGE_DOWN; + if(strcmp(name, "UP") == 0 || strcmp(name, "UPARROW") == 0) return HID_KEYBOARD_UP_ARROW; + if(strcmp(name, "DOWN") == 0 || strcmp(name, "DOWNARROW") == 0) return HID_KEYBOARD_DOWN_ARROW; + if(strcmp(name, "LEFT") == 0 || strcmp(name, "LEFTARROW") == 0) return HID_KEYBOARD_LEFT_ARROW; + if(strcmp(name, "RIGHT") == 0 || strcmp(name, "RIGHTARROW") == 0) return HID_KEYBOARD_RIGHT_ARROW; + if(strcmp(name, "PRINTSCREEN") == 0) return HID_KEYBOARD_PRINT_SCREEN; + if(strcmp(name, "PAUSE") == 0) return HID_KEYBOARD_PAUSE; + if(strcmp(name, "MENU") == 0 || strcmp(name, "APP") == 0) return HID_KEYBOARD_APPLICATION; + if(strcmp(name, "F1") == 0) return HID_KEYBOARD_F1; + if(strcmp(name, "F2") == 0) return HID_KEYBOARD_F2; + if(strcmp(name, "F3") == 0) return HID_KEYBOARD_F3; + if(strcmp(name, "F4") == 0) return HID_KEYBOARD_F4; + if(strcmp(name, "F5") == 0) return HID_KEYBOARD_F5; + if(strcmp(name, "F6") == 0) return HID_KEYBOARD_F6; + if(strcmp(name, "F7") == 0) return HID_KEYBOARD_F7; + if(strcmp(name, "F8") == 0) return HID_KEYBOARD_F8; + if(strcmp(name, "F9") == 0) return HID_KEYBOARD_F9; + if(strcmp(name, "F10") == 0) return HID_KEYBOARD_F10; + if(strcmp(name, "F11") == 0) return HID_KEYBOARD_F11; + if(strcmp(name, "F12") == 0) return HID_KEYBOARD_F12; + return 0; +} + +static uint16_t get_modifier(const char* name) { + if(strcmp(name, "CTRL") == 0 || strcmp(name, "CONTROL") == 0) return KEY_MOD_LEFT_CTRL; + if(strcmp(name, "SHIFT") == 0) return KEY_MOD_LEFT_SHIFT; + if(strcmp(name, "ALT") == 0) return KEY_MOD_LEFT_ALT; + if(strcmp(name, "GUI") == 0 || strcmp(name, "WINDOWS") == 0 || strcmp(name, "COMMAND") == 0) return KEY_MOD_LEFT_GUI; + return 0; +} + +static bool duck3_execute_keys(Duck3Script* script, const char* line) { + uint16_t modifiers = 0; + const char* ptr = line; + + while(*ptr) { + while(*ptr == ' ' || *ptr == '-') ptr++; + if(!*ptr) break; + + char word[32]; + int i = 0; + while(*ptr && *ptr != ' ' && *ptr != '-' && i < 31) { + word[i++] = *ptr++; + } + word[i] = '\0'; + + uint16_t mod = get_modifier(word); + if(mod) { + modifiers |= mod; + } else { + uint16_t key = get_keycode(word); + if(key == 0 && strlen(word) == 1) { + uint8_t c = (uint8_t)word[0]; + if(c < 128) { + key = script->layout[c]; + } + } + + if(key != 0 || modifiers != 0) { + hid_press(script, modifiers | key); + hid_release(script, modifiers | key); + } + return true; + } + } + + if(modifiers != 0) { + hid_press(script, modifiers); + hid_release(script, modifiers); + } + + return true; +} + +// ============================================================================ +// Line Parser +// ============================================================================ + +static int32_t duck3_parse_line(Duck3Script* script, const char* line) { + if(!line || !*line) return 0; + + // Skip comments + if(strncmp(line, "REM", 3) == 0) return 0; + if(strncmp(line, "ID", 2) == 0) return 0; + if(strncmp(line, "BT_ID", 5) == 0) return 0; + if(strncmp(line, "BLE_ID", 6) == 0) return 0; + + // Skip if in skip mode + if(script->skip_until_endif || script->skip_until_endloop) { + if(strncmp(line, "END_IF", 6) == 0) return cmd_end_if(script); + if(strncmp(line, "ELSE", 4) == 0) return cmd_else(script); + if(strncmp(line, "END_LOOP", 8) == 0) return cmd_end_loop(script); + if(strncmp(line, "END_WHILE", 9) == 0) return cmd_end_while(script); + + if(strncmp(line, "IF", 2) == 0 && script->skip_until_endif) script->skip_depth++; + if(strncmp(line, "LOOP", 4) == 0 && script->skip_until_endloop) script->skip_depth++; + if(strncmp(line, "WHILE", 5) == 0 && script->skip_until_endloop) script->skip_depth++; + + return 0; + } + + size_t cmd_len = 0; + while(line[cmd_len] && line[cmd_len] != ' ') cmd_len++; + const char* arg = line[cmd_len] ? &line[cmd_len + 1] : ""; + + // Commands + if(strncmp(line, "DELAY", 5) == 0 && cmd_len == 5) return cmd_delay(script, arg); + if(strncmp(line, "STRING ", 7) == 0) return cmd_string(script, arg, false); + if(strncmp(line, "STRINGLN ", 9) == 0) return cmd_string(script, arg, true); + if(strncmp(line, "DEFAULT_DELAY", 13) == 0 || strncmp(line, "DEFAULTDELAY", 12) == 0) { + script->defdelay = atoi(arg); + return 0; + } + if(strncmp(line, "STRING_DELAY", 12) == 0 || strncmp(line, "STRINGDELAY", 11) == 0) { + script->stringdelay = atoi(arg); + return 0; + } + if(strncmp(line, "LOOP", 4) == 0 && cmd_len == 4) return cmd_loop(script, arg); + if(strncmp(line, "END_LOOP", 8) == 0) return cmd_end_loop(script); + if(strncmp(line, "WHILE", 5) == 0 && cmd_len == 5) return cmd_while(script, arg); + if(strncmp(line, "END_WHILE", 9) == 0) return cmd_end_while(script); + if(strncmp(line, "VAR", 3) == 0 && cmd_len == 3) return cmd_var(script, arg); + if(strncmp(line, "IF", 2) == 0 && cmd_len == 2) return cmd_if(script, arg); + if(strncmp(line, "ELSE", 4) == 0 && cmd_len == 4) return cmd_else(script); + if(strncmp(line, "END_IF", 6) == 0) return cmd_end_if(script); + if(strncmp(line, "BREAK", 5) == 0) return cmd_break(script); + if(strncmp(line, "CONTINUE", 8) == 0) return cmd_continue(script); + if(strncmp(line, "REPEAT", 6) == 0) { + script->repeat_cnt = atoi(arg); + script->repeat_line = script->line_cur; + return 0; + } + if(strncmp(line, "WAIT_FOR_BUTTON_PRESS", 21) == 0) { + return SCRIPT_STATE_WAIT_BTN; + } + + // Mouse commands + if(strncmp(line, "MOUSE_MOVE", 10) == 0) { + int dx = 0, dy = 0; + sscanf(arg, "%d %d", &dx, &dy); + script->hid->mouse_move(script->hid_inst, (int8_t)dx, (int8_t)dy); + return 0; + } + if(strncmp(line, "MOUSE_CLICK", 11) == 0) { + script->hid->mouse_press(script->hid_inst, 1); + furi_delay_ms(50); + script->hid->mouse_release(script->hid_inst, 1); + return 0; + } + if(strncmp(line, "MOUSE_SCROLL", 12) == 0) { + int delta = atoi(arg); + script->hid->mouse_scroll(script->hid_inst, (int8_t)delta); + return 0; + } + + // Default: treat as key combo + duck3_execute_keys(script, line); + return 0; +} + +// ============================================================================ +// Script Loading +// ============================================================================ + +static bool load_script_file(Duck3Script* script, const char* path) { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + bool success = false; + + if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + uint32_t line_count = 0; + char buf[MAX_LINE_LEN]; + size_t line_len = 0; + + // Count lines + while(!storage_file_eof(file)) { + uint8_t c; + if(storage_file_read(file, &c, 1) == 1) { + if(c == '\n') { + if(line_len > 0) line_count++; + line_len = 0; + } else if(c != '\r') { + line_len++; + } + } + } + if(line_len > 0) line_count++; + + if(line_count > MAX_LINES) { + FURI_LOG_E(TAG, "Too many lines: %lu", line_count); + goto cleanup; + } + + // Allocate + script->lines = malloc(sizeof(char*) * line_count); + if(!script->lines) goto cleanup; + memset(script->lines, 0, sizeof(char*) * line_count); + + // Read lines + storage_file_seek(file, 0, true); + script->line_count = 0; + line_len = 0; + + while(!storage_file_eof(file) && script->line_count < line_count) { + uint8_t c; + if(storage_file_read(file, &c, 1) == 1) { + if(c == '\n') { + if(line_len > 0) { + buf[line_len] = '\0'; + while(line_len > 0 && (buf[line_len-1] == ' ' || buf[line_len-1] == '\t')) { + buf[--line_len] = '\0'; + } + char* start = buf; + while(*start == ' ' || *start == '\t') start++; + if(strlen(start) > 0) { + script->lines[script->line_count] = strdup(start); + script->line_count++; + } + } + line_len = 0; + } else if(c != '\r' && line_len < MAX_LINE_LEN - 1) { + buf[line_len++] = c; + } + } + } + if(line_len > 0) { + buf[line_len] = '\0'; + char* start = buf; + while(*start == ' ' || *start == '\t') start++; + if(strlen(start) > 0 && script->line_count < line_count) { + script->lines[script->line_count] = strdup(start); + script->line_count++; + } + } + + script->state.line_nb = script->line_count; + success = true; + FURI_LOG_I(TAG, "Loaded %lu lines", script->line_count); + } + +cleanup: + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return success; +} + +// ============================================================================ +// Keyboard Layout +// ============================================================================ + +void duck3_script_set_keyboard_layout(Duck3Script* script, FuriString* layout_path) { + // Reset to default + memcpy(script->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(script->layout))); + + if(furi_string_empty(layout_path)) return; + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + if(storage_file_open( + file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + uint16_t layout_data[128]; + if(storage_file_read(file, layout_data, sizeof(layout_data)) == sizeof(layout_data)) { + memcpy(script->layout, layout_data, sizeof(script->layout)); + } + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +// ============================================================================ +// Worker Thread +// ============================================================================ + +static void duck3_hid_state_callback(bool state, void* context) { + Duck3Script* script = context; + if(state) { + if(script->state.state == Duck3StateNotConnected) { + script->state.state = Duck3StateIdle; + } else if(script->state.state == Duck3StateWillRun) { + script->state.state = Duck3StateRunning; + furi_thread_flags_set(furi_thread_get_id(script->thread), 1); + } + } else { + if(script->state.state == Duck3StateRunning || + script->state.state == Duck3StateDelay) { + script->state.state = Duck3StateNotConnected; + } + } +} + +static int32_t duck3_worker(void* context) { + Duck3Script* script = context; + FURI_LOG_I(TAG, "Worker started"); + + // Wait for start signal + while(script->worker_running) { + uint32_t flags = furi_thread_flags_wait(1, FuriFlagWaitAny, 100); + if(flags == 1) break; + if(script->state.state == Duck3StateRunning) break; + } + + if(!script->worker_running) return 0; + + // Delay for host recognition + furi_delay_ms(500); + + script->state.state = Duck3StateRunning; + script->start_time = furi_get_tick(); + + while(script->worker_running && script->state.state == Duck3StateRunning) { + // Handle pause + while(script->paused && script->worker_running) { + script->state.state = Duck3StatePaused; + furi_delay_ms(100); + } + if(!script->worker_running) break; + if(script->state.state == Duck3StatePaused) { + script->state.state = Duck3StateRunning; + } + + // Handle repeat + if(script->repeat_cnt > 0 && script->repeat_line < script->line_count) { + script->repeat_cnt--; + const char* line = script->lines[script->repeat_line]; + int32_t result = duck3_parse_line(script, line); + if(result > 0) { + script->state.state = Duck3StateDelay; + script->state.delay_remain = result; + while(result > 0 && script->worker_running && !script->paused) { + uint32_t chunk = (result > 100) ? 100 : result; + furi_delay_ms(chunk); + result -= chunk; + script->state.delay_remain = result; + } + script->state.state = Duck3StateRunning; + } + continue; + } + + // Check if done + if(script->line_cur >= script->line_count) { + script->state.state = Duck3StateDone; + hid_release_all(script); + break; + } + + // Execute line + const char* line = script->lines[script->line_cur]; + script->state.line_cur = script->line_cur + 1; + script->state.elapsed = furi_get_tick() - script->start_time; + + int32_t result = duck3_parse_line(script, line); + script->line_cur++; + + if(result == SCRIPT_STATE_ERROR) { + script->state.state = Duck3StateScriptError; + script->state.error_line = script->state.line_cur; + break; + } else if(result == SCRIPT_STATE_WAIT_BTN) { + script->state.state = Duck3StateWaitForBtn; + while(script->state.state == Duck3StateWaitForBtn && script->worker_running) { + furi_delay_ms(100); + } + if(script->state.state == Duck3StateWaitForBtn) { + script->state.state = Duck3StateRunning; + } + } else if(result > 0) { + result += script->defdelay; + script->state.state = Duck3StateDelay; + script->state.delay_remain = result; + while(result > 0 && script->worker_running && !script->paused) { + uint32_t chunk = (result > 100) ? 100 : result; + furi_delay_ms(chunk); + result -= chunk; + script->state.delay_remain = result; + } + if(script->state.state == Duck3StateDelay) { + script->state.state = Duck3StateRunning; + } + } else if(script->defdelay > 0) { + furi_delay_ms(script->defdelay); + } + } + + FURI_LOG_I(TAG, "Worker finished"); + return 0; +} + +// ============================================================================ +// Public API +// ============================================================================ + +Duck3Script* duck3_script_open( + FuriString* file_path, + Duck3HidInterface* interface, + Duck3HidConfig* hid_cfg, + bool load_id_config) { + UNUSED(load_id_config); + + Duck3Script* script = malloc(sizeof(Duck3Script)); + memset(script, 0, sizeof(Duck3Script)); + + script->interface = interface; + script->hid_cfg = hid_cfg; + + // Load default layout + memcpy(script->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(script->layout))); + + // Load script + if(!load_script_file(script, furi_string_get_cstr(file_path))) { + script->state.state = Duck3StateFileError; + return script; + } + + // Initialize HID + script->hid = duck3_hid_get_interface(*interface); + script->hid->adjust_config(hid_cfg); + script->hid_inst = script->hid->init(hid_cfg); + script->hid->set_state_callback(script->hid_inst, duck3_hid_state_callback, script); + + // Check connection + if(script->hid->is_connected(script->hid_inst)) { + script->state.state = Duck3StateIdle; + } else { + script->state.state = Duck3StateNotConnected; + } + + // Start worker thread + script->worker_running = true; + script->thread = furi_thread_alloc_ex("Duck3Worker", 4096, duck3_worker, script); + furi_thread_start(script->thread); + + return script; +} + +void duck3_script_close(Duck3Script* script) { + furi_assert(script); + + // Stop worker + script->worker_running = false; + script->state.state = Duck3StateDone; + furi_thread_flags_set(furi_thread_get_id(script->thread), 1); + furi_thread_join(script->thread); + furi_thread_free(script->thread); + + // Deinit HID + if(script->hid_inst) { + script->hid->set_state_callback(script->hid_inst, NULL, NULL); + script->hid->deinit(script->hid_inst); + } + + // Free lines + if(script->lines) { + for(uint32_t i = 0; i < script->line_count; i++) { + if(script->lines[i]) free(script->lines[i]); + } + free(script->lines); + } + + free(script); +} + +void duck3_script_start_stop(Duck3Script* script) { + furi_assert(script); + + if(script->state.state == Duck3StateNotConnected) { + script->state.state = Duck3StateWillRun; + } else if(script->state.state == Duck3StateIdle || script->state.state == Duck3StateDone) { + // Reset state + script->line_cur = 0; + script->loop_depth = 0; + script->if_depth = 0; + script->var_count = 0; + script->skip_until_endif = false; + script->skip_until_endloop = false; + script->skip_depth = 0; + script->repeat_cnt = 0; + script->defdelay = 0; + script->stringdelay = 0; + script->paused = false; + script->state.error[0] = '\0'; + + script->state.state = Duck3StateRunning; + furi_thread_flags_set(furi_thread_get_id(script->thread), 1); + } else if(script->state.state == Duck3StateRunning || + script->state.state == Duck3StateDelay || + script->state.state == Duck3StatePaused) { + script->worker_running = false; + script->state.state = Duck3StateIdle; + hid_release_all(script); + } else if(script->state.state == Duck3StateWillRun) { + script->state.state = Duck3StateNotConnected; + } else if(script->state.state == Duck3StateWaitForBtn) { + script->state.state = Duck3StateRunning; + } +} + +void duck3_script_pause_resume(Duck3Script* script) { + furi_assert(script); + + if(script->state.state == Duck3StateRunning || script->state.state == Duck3StateDelay) { + script->paused = true; + } else if(script->state.state == Duck3StatePaused) { + script->paused = false; + } +} + +Duck3State* duck3_script_get_state(Duck3Script* script) { + furi_assert(script); + return &script->state; +} diff --git a/non_catalog_apps/bad_duck3/helpers/duck3_script.h b/non_catalog_apps/bad_duck3/helpers/duck3_script.h new file mode 100644 index 00000000..12dd6b27 --- /dev/null +++ b/non_catalog_apps/bad_duck3/helpers/duck3_script.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include "duck3_hid.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Duck3Script Duck3Script; + +typedef enum { + Duck3StateInit, + Duck3StateNotConnected, + Duck3StateIdle, + Duck3StateWillRun, + Duck3StateRunning, + Duck3StateDelay, + Duck3StatePaused, + Duck3StateDone, + Duck3StateError, + Duck3StateFileError, + Duck3StateScriptError, + Duck3StateWaitForBtn, +} Duck3WorkerState; + +typedef struct { + Duck3WorkerState state; + uint32_t line_cur; + uint32_t line_nb; + uint32_t delay_remain; + uint32_t elapsed; + uint32_t error_line; + char error[64]; +} Duck3State; + +// Script lifecycle +Duck3Script* duck3_script_open( + FuriString* file_path, + Duck3HidInterface* interface, + Duck3HidConfig* hid_cfg, + bool load_id_config); + +void duck3_script_close(Duck3Script* script); + +// Script control +void duck3_script_start_stop(Duck3Script* script); +void duck3_script_pause_resume(Duck3Script* script); + +// Keyboard layout +void duck3_script_set_keyboard_layout(Duck3Script* script, FuriString* layout_path); + +// State +Duck3State* duck3_script_get_state(Duck3Script* script); + +#ifdef __cplusplus +} +#endif diff --git a/non_catalog_apps/bad_duck3/icon.png b/non_catalog_apps/bad_duck3/icon.png new file mode 100644 index 00000000..2b5a3bf9 Binary files /dev/null and b/non_catalog_apps/bad_duck3/icon.png differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_gnome.txt b/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_gnome.txt new file mode 100644 index 00000000..ec2ec108 --- /dev/null +++ b/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_gnome.txt @@ -0,0 +1,51 @@ +ID 1234:abcd Generic:USB Keyboard +REM Declare ourselves as a generic usb keyboard + +REM This will install qFlipper on Linux/Gnome, using the latest AppImage package + +REM Open a terminal +ALT F2 +DELAY 1000 +STRINGLN gnome-terminal --maximize +DELAY 1000 + +REM Ensure we have a folder to run executables from +STRINGLN mkdir -p $HOME/.local/bin + +REM Download the latest AppImage +STRINGLN curl -fsSL "https://update.flipperzero.one/qFlipper/release/linux-amd64/AppImage" -o "$HOME/.local/bin/qFlipper" +DELAY 1000 + +REM Make it executable +STRINGLN chmod +x $HOME/.local/bin/qFlipper + +REM Extract the appimage in /tmp to install icon and .desktop file +STRINGLN cd /tmp +STRINGLN $HOME/.local/bin/qFlipper --appimage-extract > /dev/null +STRINGLN sed "s@Exec=qFlipper@Exec=$HOME/.local/bin/qFlipper@" squashfs-root/usr/share/applications/qFlipper.desktop > $HOME/.local/share/applications/qFlipper.desktop +STRINGLN mkdir -p $HOME/.local/share/icons/hicolor/512x512/apps +STRINGLN cp squashfs-root/usr/share/icons/hicolor/512x512/apps/qFlipper.png $HOME/.local/share/icons/hicolor/512x512/apps/qFlipper.png +STRINGLN rm -rf squashfs-root +STRINGLN cd + +REM Depending on the Linux distribution and display manager +REM there might be several ways to update desktop entries +REM try all +STRINGLN xdg-desktop-menu forceupdate || true +STRINGLN update-desktop-database ~/.local/share/applications || true + +STRINGLN echo " +ENTER +REPEAT 60 +STRINGLN ========================================================================================== +STRINGLN qFlipper has been installed to $HOME/.local/bin/ +STRINGLN It should appear in your Applications menu. +STRINGLN If it does not, you might want to log out and log in again. +ENTER +STRINGLN If you prefer to run qFlipper from your terminal, either use the absolute path +STRINGLN or make sure $HOME/.local/bin/ is included in your PATH environment variable. +ENTER +STRINGLN Additional configurations might be required by your Linux distribution such as +STRINGLN group membership, udev rules or else. +STRINGLN ========================================================================================== +STRINGLN " diff --git a/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_macOS.txt b/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_macOS.txt new file mode 100644 index 00000000..252954ec --- /dev/null +++ b/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_macOS.txt @@ -0,0 +1,16 @@ +ID 05ac:021e Apple:Keyboard +REM Keep these 3 lines IF (and only if) it's the first time you are performing a badKB attack against a specific macOS target. +REM In fact, it helps Flipper Zero bypass the macOS keyboard setup assistant. Otherwise the attack will not start. +REM Author: 47LeCoste +REM Version 1.0 (Flipper Ducky) +REM Target: macOS +DELAY 3000 +F4 +DELAY 2500 +STRING Terminal +DELAY 2500 +ENTER +DELAY 1500 +STRING (cd /tmp && curl -L -o qFlipper.dmg https://update.flipperzero.one/qFlipper/release/macos-amd64/dmg && hdiutil attach qFlipper.dmg && app_volume=$(ls /Volumes | grep -i "qFlipper") && (test -e /Applications/qFlipper.app && rm -rf /Applications/qFlipper.app ); cp -R "/Volumes/$app_volume/qFlipper.app" /Applications/ && hdiutil detach "/Volumes/$app_volume" && rm qFlipper.dmg && open /Applications/qFlipper.app) +DELAY 1000 +ENTER diff --git a/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_windows.txt b/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_windows.txt new file mode 100644 index 00000000..94282321 --- /dev/null +++ b/non_catalog_apps/bad_duck3/resources/badusb/Install_qFlipper_windows.txt @@ -0,0 +1,42 @@ +REM Written by @dexv +DELAY 2000 +GUI r +DELAY 500 +STRING powershell +ENTER +DELAY 1000 +STRING $url = "https://update.flipperzero.one/qFlipper/release/windows-amd64/portable" +ENTER +STRING $output = "$env:USERPROFILE\Documents\qFlipper.zip" +ENTER +STRING $destination = "$env:USERPROFILE\Documents\qFlipper" +ENTER +STRING $shortcutPath = "$env:USERPROFILE\Desktop\qFlipper.lnk" +ENTER +STRING $scriptPath = "$env:USERPROFILE\Documents\qFlipperInstall.ps1" +ENTER +STRING $driverPath = "$destination\STM32 Driver" +ENTER +STRING $installBat = "$driverPath\install.bat" +ENTER +STRING (New-Object System.Net.WebClient).DownloadFile($url, $output) +ENTER +STRING Expand-Archive -Path $output -DestinationPath $destination -Force +ENTER +STRING Set-Location -Path $destination +ENTER +STRING Start-Process -FilePath ".\qFlipper.exe" +ENTER +STRING Start-Process -Wait -FilePath "cmd.exe" -ArgumentList "/c $installBat" +ENTER +STRING $shell = New-Object -ComObject WScript.Shell +ENTER +STRING $shortcut = $shell.CreateShortcut($shortcutPath) +ENTER +STRING $shortcut.TargetPath = "$destination\qFlipper.exe" +ENTER +STRING $shortcut.Save() +ENTER +DELAY 500 +STRING "powershell -ExecutionPolicy Bypass -File $scriptPath" +ENTER diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/ba-BA.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/ba-BA.kl new file mode 100644 index 00000000..379f6c64 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/ba-BA.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/colemak.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/colemak.kl new file mode 100644 index 00000000..47b9a9cf Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/colemak.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/cz_CS.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/cz_CS.kl new file mode 100644 index 00000000..d42e9a4a Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/cz_CS.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/da-DA.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/da-DA.kl new file mode 100644 index 00000000..b3fcc6a9 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/da-DA.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-CH.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-CH.kl new file mode 100644 index 00000000..1704bc9d Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-CH.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-DE-mac.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-DE-mac.kl new file mode 100755 index 00000000..471b7143 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-DE-mac.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-DE.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-DE.kl new file mode 100644 index 00000000..67b05c04 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/de-DE.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/dvorak.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/dvorak.kl new file mode 100644 index 00000000..06b51924 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/dvorak.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/en-UK.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/en-UK.kl new file mode 100644 index 00000000..0f18b39c Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/en-UK.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/en-US.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/en-US.kl new file mode 100644 index 00000000..8089d825 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/en-US.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/es-ES.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/es-ES.kl new file mode 100644 index 00000000..15e9d799 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/es-ES.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/es-LA.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/es-LA.kl new file mode 100644 index 00000000..2a6c78ad Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/es-LA.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fi-FI.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fi-FI.kl new file mode 100644 index 00000000..5c55bb9e Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fi-FI.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-BE.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-BE.kl new file mode 100644 index 00000000..ea9e553e Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-BE.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-CA.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-CA.kl new file mode 100644 index 00000000..9de8a473 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-CA.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-CH.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-CH.kl new file mode 100644 index 00000000..1704bc9d Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-CH.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-FR-mac.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-FR-mac.kl new file mode 100644 index 00000000..2887aae0 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-FR-mac.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-FR.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-FR.kl new file mode 100644 index 00000000..f9193297 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/fr-FR.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/hr-HR.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/hr-HR.kl new file mode 100644 index 00000000..379f6c64 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/hr-HR.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/hu-HU.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/hu-HU.kl new file mode 100644 index 00000000..90a68e32 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/hu-HU.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/it-IT-mac.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/it-IT-mac.kl new file mode 100644 index 00000000..6c10e426 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/it-IT-mac.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/it-IT.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/it-IT.kl new file mode 100644 index 00000000..059e4288 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/it-IT.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/nb-NO.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/nb-NO.kl new file mode 100644 index 00000000..381945bf Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/nb-NO.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/nl-NL.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/nl-NL.kl new file mode 100644 index 00000000..597d1db8 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/nl-NL.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/pt-BR.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/pt-BR.kl new file mode 100644 index 00000000..d36421cf Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/pt-BR.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/pt-PT.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/pt-PT.kl new file mode 100644 index 00000000..02450419 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/pt-PT.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/si-SI.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/si-SI.kl new file mode 100644 index 00000000..379f6c64 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/si-SI.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/sk-SK.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/sk-SK.kl new file mode 100755 index 00000000..0c67458a Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/sk-SK.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/sv-SE.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/sv-SE.kl new file mode 100644 index 00000000..5c55bb9e Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/sv-SE.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/tr-TR.kl b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/tr-TR.kl new file mode 100644 index 00000000..6377b770 Binary files /dev/null and b/non_catalog_apps/bad_duck3/resources/badusb/assets/layouts/tr-TR.kl differ diff --git a/non_catalog_apps/bad_duck3/resources/badusb/demo_chromeos.txt b/non_catalog_apps/bad_duck3/resources/badusb/demo_chromeos.txt new file mode 100644 index 00000000..7f42574c --- /dev/null +++ b/non_catalog_apps/bad_duck3/resources/badusb/demo_chromeos.txt @@ -0,0 +1,85 @@ +REM This is BadUSB demo script for Chrome and ChromeOS by kowalski7cc + +REM Exit from Overview +ESC +REM Open a new tab +CTRL t +REM wait for some slower chromebooks +DELAY 1000 +REM Make sure we have omnibox focus +CTRL l +DELAY 200 +REM Open an empty editable page +DEFAULT_DELAY 50 +STRING data:text/html, Flipper Zero BadUSB Demo