尧图精选

Momentum Firmware 的 BadUSB JS 模块全解析:用 JavaScript 实现 USB 键盘注入

🕒 发布时间:2026/9/16 12:58:53 📁 来源:尧图网络
Momentum Firmware 的 BadUSB JS 模块全解析用 JavaScript 实现 USB 键盘注入【免费下载链接】Momentum-Firmware Feature-rich, stable and customizable Flipper Firmware项目地址: https://gitcode.com/GitHub_Trending/mo/Momentum-Firmware导读BadUSB 模块是 Momentum Firmware 内置 JavaScript 运行环境中用于 USB HIDHuman Interface Device人机接口设备键盘仿真的核心 API。它允许在脚本中模拟按键按下、组合键、字符串输入甚至通过 AltNumpad 方式注入任意字符可应用于自动化输入、演示脚本、测试工装等场景。读完本文你将掌握badusb模块的全部 9 个方法、按键命名体系、键盘布局文件加载机制以及一份可直接运行的完整示例脚本。模块引入与整体架构BadUSB 模块以 CommonJS 风格引入这与 JS 运行环境中的其他内置模块如notification、flipper、event_loop、gui保持一致let badusb require(badusb);模块的真实实现在 applications/system/js_app/modules/js_badusb.c 中。从源码结构看它由以下几部分构成模块描述符js_badusb_desc以badusb为名注册通过js_badusb_create创建实例、js_badusb_destroy销毁实例实例结构体JsBadusbInst保存当前 USB HID 配置FuriHalUsbHidConfig、键盘布局映射表layout[128]、切换前的 USB 接口指针usb_if_prev以及按住键计数key_hold_cnt按键名到键码的静态映射表key_codes[]定义了全部可识别的修饰键、特殊键、F1-F24 与数字小键盘键名。模块被实现为一个 Flipper 应用插件FlipperAppPluginDescriptor这意味着它是可被动态加载的独立扩展点而非硬编码进内核。setup()启动 USB HIDsetup()必须在调用其他所有方法之前执行一次用于启动 USB HID 接口。它有两点关键副作用自动调用furi_hal_usb_unlock()解锁 USB profile因此qFlipper 等通过 USB 连接的调试工具会被中断保存切换前的 USB 配置供quit()时恢复。参数说明setup()接受一个可选的配置对象参数类型说明vidnumberUSB Vendor ID厂商 ID与pid必须成对出现pidnumberUSB Product ID产品 ID与vid必须成对出现mfrNamestring制造商名称最多 32 个 ASCII 字符可选prodNamestring产品名称最多 32 个 ASCII 字符可选layoutPathstring键盘布局文件路径可选三种典型调用方式// 1. 使用默认参数启动 USB HID badusb.setup(); // 2. 自定义 vid:pid AAAA:BBBB不定义厂商与产品字符串 badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); // 3. 自定义 vid:pid、制造商名称与产品名称 badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: Flipper Devices, prodName: Flipper Zero });源码级解析参数到底如何被处理从setup_parse_paramsjs_badusb.c可以看到参数校验的细节vid/pid必须同时是数字类型否则直接返回参数错误MJS_BAD_ARGS_ERROR。两者会被写入FuriHalUsbHidConfig结构体mfrName/prodName通过strlcpy拷贝进manuf/product字段而这两个字段的容量由 furi_hal_usb_hid.h 中的HID_MANUF_PRODUCT_NAME_LEN值为 32决定超长字符串会被截断layoutPath通过storage_file_open以只读方式打开 SD 卡上的布局文件读取128 个uint16_t键码共 256 字节到badusb-layout数组。可以推断布局文件中第 N 个键码即对应 ASCII 码 N 的 HID 键码。若读取失败文件不存在或大小不符则整体参数解析失败若未指定layoutPath则直接拷贝内置的hid_asciimap作为默认布局见 furi_hal_usb_hid.h。注官方 BadUSB 应用的布局文件位于 SD 卡/ext/badusb/assets/layouts/目录典型文件如en-US.kl英文美式布局其加载与配置逻辑可参考 applications/main/bad_usb 中的布局场景实现。若setup()已调用过且尚未quit()再次调用会抛出HID is already started错误调用时未传参数或参数解析失败同样会报错。isConnected()检测 USB 连接状态isConnected()返回当前主机是否已通过 USB 连接其底层调用furi_hal_hid_is_connected()。该函数常用于在注入前判断目标主机是否就绪避免空操作if (badusb.isConnected()) { // 已连接执行注入 } else { // 未连接提示错误 }注意若尚未调用setup()就调用本方法会抛出HID is not started内部错误。press()单击一个键press()用于按下并立即释放一个键可接受多个参数以组合修饰键也支持直接传入HID 键码数字。badusb.press(a); // 按下并释放 a badusb.press(A); // SHIFT a大写字母自动附加 SHIFT 修饰 badusb.press(CTRL, a); // CTRL a badusb.press(CTRL, SHIFT, ESC); // CTRL SHIFT ESC 组合 badusb.press(98); // 按 HID 码十进制98小键盘 0 / Insert badusb.press(0x47); // 按 HID 码十六进制0x47Scroll Lock键码编码原理从源码parse_keycode与get_keycode_by_namejs_badusb.c可以还原键码的内部表示每个键码是 16 位uint16_t值低 8 位是 HID 标准键码高 8 位是修饰键位掩码。例如KEY_MOD_LEFT_CTRL (1 8)、KEY_MOD_LEFT_SHIFT (1 9)见 furi_hal_usb_hid.h单字符参数长度为 1 的字符串会通过当前布局表ASCII_TO_KEY换算成键码——大写字符的键码中已经内嵌了 SHIFT 修饰位这正是press(A)等价于 SHIFTa 的原因多字符参数会与key_codes[]中的键名做精确匹配区分大小写、全大写命名数字参数被当作 HID 键码使用取值不能超过 0xFF且与已解析出的主键低 8 位非 0冲突时会报错同一参数列表中只能有一个主键非修饰键修饰键可任意叠加。press()最终通过furi_hal_hid_kb_press(keycode)furi_hal_hid_kb_release(keycode)完成一次完整敲击。hold() 与 release()按住与释放hold()用于按下并保持一个键适合模拟长按或组合操作release()则释放之前按住的键。badusb.hold(a); // 按住 a badusb.hold(CTRL, v); // 按住 CTRL v badusb.release(); // 释放全部按键 badusb.release(a); // 释放 a最多同时按住 5 个键文档明确最多可同时按住 5 个键不含修饰键。这一限制来自 USB HID 协议与硬件缓冲furi_hal_usb_hid.h中定义HID_KB_MAX_KEYS为 6而其中 1 个位置留给修饰键故主键上限为 5。源码中的js_badusb_hold通过key_hold_cnt计数器实现校验当按住的主键数超过HID_KB_MAX_KEYS - 1时会抛出Too many keys are hold错误并调用furi_hal_hid_kb_release_all()释放所有按键避免卡键。release()的细节无参数调用时释放全部按键并将计数器清零带参数时只释放指定按键同时递减计数器对仅含修饰键的参数如release(CTRL)不会改变主键计数。print() 与 println()字符串注入print()逐字符打印字符串println()在打印结束后追加一次 ENTER 敲击。两者都支持可选的按键间隔延迟参数单位毫秒。badusb.print(Hello, world!); // 打印 Hello, world! badusb.print(Hello, world!, 100); // 每个字符之间间隔 100ms badusb.println(Hello, world!); // 打印 Hello, world! 并回车参数校验与延迟上限从badusb_printjs_badusb.c的实现可以看到第一个参数必须是非空字符串延迟参数必须是数字且上限为 60000ms60 秒超出会报参数错误普通模式下每个字符通过当前布局表换算键码后执行 press/release打印过程中若设置了延迟会调用js_delay_with_flags实现可被中断的异步延时脚本被请求退出时可及时返回println在循环结束后额外执行一次 ENTERHID_KEYBOARD_RETURN的按下与释放。altPrint() 与 altPrintln()AltNumpad 注入altPrint()使用AltNumpad数字小键盘输入 ASCII 码方式打印字符串altPrintln()则在末尾追加回车。该方式仅适用于 Windows 系统因为 Alt数字小键盘输入是 Windows 特有的字符输入手段通常用于输入当前键盘布局无法直接表达的字符。badusb.altPrint(Hello, world!); // 打印 Hello, world! badusb.altPrint(Hello, world!, 100); // 每个字符间隔 100ms badusb.altPrintln(Hello, world!); // 打印 Hello, world! 并回车源码级原理ducky 算法源码中的两个辅助函数揭示了实现细节js_badusb.cducky_numlock_on()先检查 LED 状态位HID_KB_LED_NUM若 NumLock 未开启则先按一下HID_KEYBOARD_LOCK_NUM_LOCK开启数字键盘锁——因为 AltNumpad 必须依赖小键盘数字键ducky_altchar()按住左 ALTKEY_MOD_LEFT_ALT然后对字符 ASCII 码的每一位数字依次构造NUMx键名如NUM1、NUM0并通过get_keycode_by_name查出小键盘键码逐个敲击最后松开 ALT。因此altPrint每打印一个字符实际上执行的是ALT 数字小键盘逐位输入该字符 ASCII 十进制码的完整序列。quit()释放 USB 并切换配置quit()用于释放 USB HID 接口属于可选调用。它的价值在于释放后可以与usbdiskU 盘模式等其他 USB profile 互相切换否则 USB 接口会一直被键盘模式占用。badusb.quit(); usbdisk.start(...); // 示例切换为 USB 存储模式从js_badusb_quit_freejs_badusb.c的实现看quit()会依次执行furi_hal_hid_kb_release_all()释放所有仍被按住的按键furi_hal_usb_set_config(usb_if_prev, NULL)恢复setup()之前保存的 USB 配置释放动态分配的FuriHalUsbHidConfig内存。若未启动 HID 就调用quit()会抛出HID is not started错误。按键名称完整参考表press()/hold()/release()可接受的命名按键分为三类对应源码key_codes[]表js_badusb.c。修饰键Modifier keys名称说明CTRL左 CtrlSHIFT左 ShiftALT左 AltGUI左 GUIWindows 的 Win / macOS 的 Command特殊键Special keys名称说明名称说明DOWN下方向键LEFT左方向键RIGHT右方向键UP上方向键ENTER回车DELETE向前删除DeleteBACKSPACE退格ENDEndHOMEHomeESCEscapeINSERTInsertPAGEUPPage UpPAGEDOWNPage DownCAPSLOCKCaps LockNUMLOCKNum LockSCROLLLOCKScroll LockPRINTSCREENPrint ScreenPAUSEPause/BreakSPACE空格TABTabMENU应用菜单键Context menu功能键与数字小键盘名称说明FxF1 ~ F24共 24 个功能键NUMxNUM0 ~ NUM9数字小键盘 0-9源码中为 F1-F24 与 NUM0-NUM9 逐个定义了HID_KEYBOARD_F1...HID_KEYBOARD_F24与HID_KEYPAD_0...HID_KEYPAD_9键码命名匹配是大小写敏感的例如必须写badusb.press(F1)而非f1。实战示例完整的 BadUSB 脚本仓库提供了一个完整的可运行示例 applications/system/js_app/examples/apps/Scripts/Examples/badusb_demo.js它综合运用了本文介绍的大部分 API并示范了与gui/dialog、event_loop、notification、flipper模块的协作方式let badusb require(badusb); let notify require(notification); let flipper require(flipper); let eventLoop require(event_loop); let gui require(gui); let dialog require(gui/dialog); badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: Flipper, prodName: Zero, layoutPath: /ext/badusb/assets/layouts/en-US.kl }); // ... 通过 dialog 等待用户确认后 if (badusb.isConnected()) { notify.blink(green, short); badusb.println(Hello, world!); // 打印并回车 badusb.press(CTRL, a); // 全选 badusb.press(CTRL, c); // 复制 badusb.press(DOWN); // 方向键下移 delay(1000); badusb.press(CTRL, v); // 粘贴 delay(1000); badusb.press(CTRL, v); // 再次粘贴 badusb.println(1234, 200); // 打印数字字符间隔 200ms badusb.println(Flipper Model: flipper.getModel()); badusb.println(Flipper Name: flipper.getName()); badusb.println(Battery level: flipper.getBatteryCharge().toString() %); // AltNumpad 方式仅 Windows 生效 badusb.altPrintln(This was printed with AltNumpad method!); notify.success(); } else { notify.error(); // 未连接则提示错误 } badusb.quit(); // 释放 USB允许切换 profile eventLoop.stop();这个示例演示了一个典型的注入-反馈闭环先检测连接状态注入文本与快捷键组合利用flipper模块动态读取设备信息并注入到目标主机最后通过quit()释放 USB 以便切换其他 profile。flipper、event_loop、gui等模块的更多细节可参阅 documentation/js 目录下的对应文档如 js_flipper.md、js_gui.md。使用注意事项先 setup 再使用除require外所有方法都要求先成功调用setup()否则抛出HID is not started错误qFlipper 会掉线setup()自动解锁 USB profile一旦切换为 HID 键盘模式qFlipper 等 USB 调试工具的连接将被中断布局文件layoutPath指向 SD 卡上的键盘布局文件如/ext/badusb/assets/layouts/en-US.kl从源码可以推断其内容为 128 个uint16_tHID 键码按 ASCII 码顺序排列加载失败会导致setup()参数解析失败未指定时使用内置默认映射按住上限同时按住的主键最多 5 个超出会触发全部释放AltNumpad 仅限 WindowsaltPrint/altPrintln依赖 Windows 的 Alt小键盘数字输入机制在 macOS/Linux 上不适用延迟上限print系列方法的字符间隔延迟不得超过 60000ms键名大小写敏感修饰键与特殊键必须使用全大写名称如CTRL、ESC、F1、NUM3。如需了解 BadUSB 脚本语言Ducky Script在完整应用中的用法可进一步阅读 applications/main/bad_usb 下的实现其中 ducky_script.c 也复用了同一张hid_asciimap键码映射表两者在按键语义上保持一致。【免费下载链接】Momentum-Firmware Feature-rich, stable and customizable Flipper Firmware项目地址: https://gitcode.com/GitHub_Trending/mo/Momentum-Firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →