一个基于 Raspberry Pi Zero 2 W 构建的可编程 BadUSB / HID 攻击平台。 Pi 枚举为 USB 复合设备(键盘加一个可选的只读大容量存储卷),并在插入的主机上执行 Ducky-Script 风格的载荷。专为授权的红队演练、渗透测试和 CTF 设计。
⚠️ 法律声明。 仅在您拥有或已获得明确书面许可测试的系统上使用。未经授权的访问在大多数司法管辖区是非法的,声称不知道也无济于事。
configured 状态实现——不会在启动时错误触发载荷。LAYOUT 分别为每个载荷选择。IF / ELSE、WHILE、HOLD / RELEASE、INJECT_MOD、RANDOM_* 生成器、STRING_BLOCK / STRINGLN_BLOCK、DEFINE。ExecStop 干净地拆除小工具。不会遗留 configfs 状态,无需重启即可重新启动。/dev/hidg0——没有全局可写的设备节点。+----------------+ USB 数据线 +-------------+
| Raspberry Pi | ===================>>> | 主机 PC |
| Zero 2 W | (Pi 模拟 HID + 可选驱动) | (目标) |
| (本设备) | | |
+----------------+ +-------------+
|
| /home/pi/pi-badusb/
|
+-- badusb.service ----> monitor_and_run.py
|
| 当 `state == configured` 时:
v
run_payload.py
|
v
/dev/hidg0 (USB HID 小工具)
badusb.service systemd 单元运行 gadget_setup.sh,它使用 configfs/libcomposite 组合一个 USB 小工具,包含 HID 键盘功能和(可选)由 /var/badusb/storage.img 平面镜像文件支持的大容量存储 LUN。monitor_and_run.py。它轮询 /sys/class/udc/<udc>/state 并等待 configured——这是 USB 规范状态,表示主机已成功枚举小工具。(我们不使用 /dev/hidg0 的存在作为信号:因为小工具在启动时绑定到 UDC 时该节点就已存在,无论是否插入任何设备。)run_payload.py,解析 payload.txt 并将 HID 报告写入 /dev/hidg0。configured 状态转换。| 组件 | 说明 |
|---|---|
| Raspberry Pi Zero 2 W | 已在 2026-05 硬件版本上测试。较旧的 Pi Zero W 使用 BCM2835 dwc_otg 驱动程序理论上也可行,但本文档假设使用带 dwc2 的 2 W。 |
| micro-USB 转 USB-A 数据线 | 或“U 盘”形式适配器,使 Pi 可直接插入主机端口。 |
| 可选:独立充电器 | 如果希望从非主机源为 Pi 供电,以便监听器在插入目标之前启动(否则主机端口提供电源)。 |
Pi Zero 2 W 有两个 micro-USB 端口:
PWR IN — 仅供电,不向 dwc2 OTG 模块暴露 USB 数据线。USB — OTG 数据端口;此处插入目标。| 要求 | 原因 |
|---|---|
| Raspberry Pi OS(推荐 Debian Bookworm 或 Trixie,64 位 Lite) | 安装脚本在 Bookworm+ 上写入 /boot/firmware/...,在旧镜像上回退到 /boot/...。 |
| Python 3 | Pi OS 自带。 |
mkfs.vfat | 用于首次运行时格式化大容量存储后备镜像。如果禁用大容量存储则无需。 |
| 安装需要 root 权限 | 涉及 systemd、udev 和 /boot/firmware/config.txt。 |
克隆或复制仓库到 Pi,然后运行安装程序:
git clone http://your-gitea/admin/Pi-Zero-2W-Bad-USB.git /home/pi/pi-badusb
cd /home/pi/pi-badusb
sudo ./install.sh
sudo reboot
重启后,启用并启动服务:
sudo systemctl enable --now badusb.service
journalctl -u badusb -f
install.sh 是幂等的——每当更改项目文件时重新运行即可。它会:
/boot/firmware(Bookworm+)与 /boot(旧版)。config.txt 的 [all] 块下启用 dtoverlay=dwc2,dr_mode=otg。Raspberry Pi Imager 默认将此行放在 [cm5] 过滤器中,该过滤器不适用于 Pi Zero 2 W;安装器会附加一个带标记的覆盖项,以便重新运行时不会重复。cmdline.txt 中包含 modules-load=dwc2。cmdline.txt 中仍然存在 g_ether 则发出警告(它会从 libcomposite 中抢占 UDC 并破坏小工具模式)。/etc/systemd/system/badusb.service。/etc/udev/rules.d/99-badusb-hidg.rules,使 /dev/hidg0 对 plugdev 组可写。pi 用户添加到 plugdev。/var/badusb/ 用于大容量存储后备镜像。# 启动 / 停止 / 重启
sudo systemctl start badusb
sudo systemctl stop badusb
sudo systemctl restart badusb # 插入时安全操作
# 实时查看日志
journalctl -u badusb -f
# 禁用开机自启
sudo systemctl disable badusb
# 调整时间参数(创建覆盖配置)
sudo systemctl edit badusb
# (粘贴一个包含 Environment="BADUSB_REARM_COOLDOWN_S=8" 等的 [Service] 块)
sudo systemctl restart badusb
该服务依赖于 sys-kernel-config.mount 和 UDC 的存在,因此在小工具实际准备就绪之前无法触发载荷。
编辑载荷无需重启——payload.txt 每次插入时都会重新读取。
payload.txt 位于安装目录中。完整命令参考位于 payload_commands.md。
REM 打开运行对话框并通过记事本输入问候语
LAYOUT US
GUI r
DELAY 1500
STRING notepad
ENTER
DELAY 2500
STRINGLN 来自 Raspberry Pi Zero 2 W 的问候
VAR $USER="alice"
VAR $COUNT=0
WHILE $COUNT < 3
STRINGLN 你好 $USER (第 $COUNT 次迭代)
VAR $COUNT = $COUNT + 1
END_WHILE
IF $USER == "alice"
STRINGLN 匹配
ELSE
STRINGLN 未匹配
END_IF
VAR 中的数学表达式通过 ast 遍历安全求值器处理——不允许名称、函数调用、属性访问,仅允许数字字面量和 + - * / // % **。
HOLD SHIFT
STRINGLN 这一行都是大写
RELEASE SHIFT
REM 按住 Ctrl+Shift (0x01 + 0x02) 并敲击 A
INJECT_MOD 0x03
STRING a
REM 释放所有修饰键
INJECT_MOD 0x00
RANDOM_LETTER 12 # 12 个随机大小写字母
RANDOM_NUMBER 6 # 6 个随机数字
RANDOM_SPECIAL 4 # 4 个随机 ASCII 符号
LAYOUT UK # 切换为 UK ISO 映射,用于后续的 STRING/STRINGLN
STRING @ " # ~ £ \ | # 在 UK 语言环境的主机上正确输入
将另一个文件放入 keymaps/ 目录(与 us.py 和 uk.py 一起),LAYOUT <name> 指令将通过 importlib 加载它。
gadget_setup.sh 顶部)| 变量 | 默认值 | 含义 |
|---|---|---|
ENABLE_MASS_STORAGE | 1 | 设为 0 仅使用 HID 小工具。 |
BACKING_FILE | /var/badusb/storage.img | 暴露给主机的平面镜像文件。 |
BACKING_SIZE_MB | 64 | 首次运行时创建,如果文件不存在。 |
BACKING_LABEL | BADUSB | FAT 卷标。 |
MASS_STORAGE_RO | 1 | 默认为只读。 |
这些值可以通过在手动运行 gadget_setup.sh 时设置环境变量来覆盖,或者通过单元的 Environment= 指令全局设置。
monitor_and_run.py)| 环境变量 | 默认值 | 含义 |
|---|---|---|
BADUSB_POST_PAYLOAD_FLUSH_S | 0.5 | 载荷执行后、解除绑定前休眠的时间,以便 HID 写入完成。 |
BADUSB_REARM_COOLDOWN_S | 5 | 小工具在解除绑定和重新绑定之间对主机保持“不活跃”的时间。 |
BADUSB_MIN_INTER_FIRE_S | 10 | 两次触发之间的最小秒数;低于此值时,触发被抑制,小工具重新解除绑定。 |
BADUSB_MAX_FIRES_PER_MINUTE | 6 | 硬上限;超过时,暂停 BADUSB_RATELIMIT_PAUSE_S 秒。 |
BADUSB_RATELIMIT_PAUSE_S | 60 | 达到速率限制后的暂停持续时间。 |
覆盖示例:
sudo systemctl edit badusb
# 在编辑器中:
# [Service]
# Environment="BADUSB_REARM_COOLDOWN_S=8"
# Environment="BADUSB_MIN_INTER_FIRE_S=20"
sudo systemctl restart badusb
state 而不是 /dev/hidg0之前的实现通过测试 /dev/hidg0 的存在性和可写性作为“主机已连接”的信号。该设备节点在小工具在启动时绑定到 UDC 的那一刻就创建了——远在任何主机实际枚举它之前。因此,无论 Pi 是否插入,载荷都会在上电后立即触发。
可靠的信号是 /sys/class/udc/<udc>/state,它报告 USB 规范设备状态。只有 configured 意味着主机已发出 SetConfiguration(1)——设备现在可以发送 HID 报告。
在 Pi Zero 2 W 上检测物理断开在软件层面是不可能的:该板没有将 VBUS 检测连接到 SoC 的 dwc2 OTG 模块。物理拔出后:
/sys/class/udc/<udc>/state 保持 configured。current_speed 保持 high-speed。GOTGCTL 寄存器保持 0x000d0000(BSesVld 位设置)。因此,监听器不等待永远不会到来的信号,而是主动引起断开:每次载荷后,它将 "" 写入小工具的 UDC configfs 文件(内核解释为解除绑定),休眠 BADUSB_REARM_COOLDOWN_S 秒,然后将 UDC 名称写回以重新绑定。下次主机插入会产生一个干净的 configured 状态转换,监听器可以检测到。
如果操作员在载荷后将 Pi 保持插入状态,重新绑定会导致主机重新枚举小工具。为防止失控的触发循环,两个安全机制生效:
BADUSB_MIN_INTER_FIRE_S——如果在上次触发后的这个时间窗口内发生 configured 转换,则抑制该触发并再次解除绑定。小工具在后台静默循环。BADUSB_MAX_FIRES_PER_MINUTE——滑动窗口硬上限。超过上限时,监听器暂停 BADUSB_RATELIMIT_PAUSE_S 秒并记录警告。rm -rf 处理 configfsconfigfs 的内核管理属性文件(bcdUSB、idVendor、webusb/*、os_desc/*……)不能通过 rm(2) 删除——内核返回 EPERM。它们仅在其父目录被 rmdir 时释放。gadget_setup.sh 和 teardown_gadget.sh 都按规范的 libcomposite 顺序遍历 configfs 树——仅使用 rmdir,从不使用 rm 处理属性文件——内核会自动清理其余内容。
os.write 而不是 file.write