
Pi Zero 2W を BadUSB として設定する
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 でガジェットをクリーンに破棄する systemd 統合。configfs の
状態が残らず、再起動なしで再スタートできます。/dev/hidg0 の udev 管理によるパーミッション — 全ユーザー書き込み
可能なデバイスノードはありません。+----------------+ USB cable +-------------+
| Raspberry Pi | ===================>>> | Host PC |
| Zero 2 W | (Pi emulates HID + | (target) |
| (this device) | optional drive) | |
+----------------+ +-------------+
|
| /home/pi/pi-badusb/
|
+-- badusb.service ----> monitor_and_run.py
|
| on `state == configured`:
v
run_payload.py
|
v
/dev/hidg0 (USB HID gadget)
badusb.service が gadget_setup.sh を実行します。
configfs / libcomposite を使用して、HID キーボード機能と(オプションで)
/var/badusb/storage.img のフラットイメージファイルをバックエンドとする
マスストレージ LUN を備えた USB ガジェットを構成します。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 ハードウェアリビジョンでテスト済み。BCM2835 dwc_otg ドライバーを搭載した旧型の Pi Zero W も原理的には動作しますが、この README は dwc2 を搭載した 2 W を前提としています。 |
| micro-USB - USB-A ケーブル | または、Pi をホストポートに直接挿せる「USB スティック」型フォームファクタのアダプター。 |
| オプション: 別の充電器 | ターゲットに挿す前にリスナーを起動させたい場合に、Pi をホスト以外の電源から給電するために使用します(そうでない場合はホストポートが電源を供給します)。 |
Pi Zero 2 W には 2 つの micro-USB ポートがあります:
PWR IN — 電源専用で、dwc2 OTG ブロックに USB データラインを公開しません。USB — OTG データポート。ここをターゲットに接続します。リポジトリを 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 のデフォルトではこの行は
Pi Zero 2 W には適用されない [cm5] フィルター内に置かれるため、
インストーラーはセンチネルマーク付きのオーバーライドを追記し、再実行時に
重複しないようにします。cmdline.txt に modules-load=dwc2 が含まれるようにします。cmdline.txt に g_ether が残っている場合は警告します
(libcomposite から UDC を奪い、ガジェットモードを壊します)。/etc/systemd/system/badusb.service にインストールします。/dev/hidg0 が plugdev グループによってグループ書き込み可能になるよう、
udev ルールを にインストールします。# Start / stop / restart
sudo systemctl start badusb
sudo systemctl stop badusb
sudo systemctl restart badusb # safe to do while plugged in
# Watch live
journalctl -u badusb -f
# Disable autostart on boot
sudo systemctl disable badusb
# Tune timings (creates an override drop-in)
sudo systemctl edit badusb
# (paste an [Service] block with Environment="BADUSB_REARM_COOLDOWN_S=8" etc)
sudo systemctl restart badusb
このサービスは sys-kernel-config.mount と UDC の存在に依存するため、
ガジェットが実際に準備できるまでペイロードを発火できません。
ペイロードの編集に再起動は不要です — payload.txt はプラグインのたびに
毎回新しく読み込まれます。
payload.txt はインストールディレクトリにあります。完全なコマンド
リファレンスは payload_commands.md にあります。
REM Open Run dialog and type a greeting via Notepad
LAYOUT US
GUI r
DELAY 1500
STRING notepad
ENTER
DELAY 2500
STRINGLN Hello from the Pi Zero 2 W
VAR $USER="alice"
VAR $COUNT=0
WHILE $COUNT < 3
STRINGLN Hello $USER (iteration $COUNT)
VAR $COUNT = $COUNT + 1
END_WHILE
IF $USER == "alice"
STRINGLN matched
ELSE
STRINGLN missed
END_IF
VAR 内の数式は ast ウォーカーによる安全な評価器を通過します —
名前、呼び出し、属性アクセスは不可で、数値リテラルと
+ - * / // % ** のみが許可されます。
HOLD SHIFT
STRINGLN this line is in capitals
RELEASE SHIFT
REM Hold Ctrl+Shift (0x01 + 0x02) and tap A
INJECT_MOD 0x03
STRING a
REM Release all modifiers
INJECT_MOD 0x00
RANDOM_LETTER 12 # 12 random mixed-case letters
RANDOM_NUMBER 6 # 6 random digits
RANDOM_SPECIAL 4 # 4 random ASCII symbols
LAYOUT UK # switch to UK ISO mappings for subsequent STRING/STRINGLN
STRING @ " # ~ £ \ | # types correctly on a UK-locale host
keymaps/(us.py と uk.py と同じ場所)に別のファイルを置くと、
LAYOUT <name> ディレクティブが importlib 経由でそれを読み込みます。
gadget_setup.sh の先頭)これらは、gadget_setup.sh を手動で実行するときに環境変数として
設定することで呼び出しごとに上書きできるほか、ユニットの Environment=
ディレクティブでグローバルに上書きできます。
monitor_and_run.py)上書きするには:
sudo systemctl edit badusb
# In the editor:
# [Service]
# Environment="BADUSB_REARM_COOLDOWN_S=8"
# Environment="BADUSB_MIN_INTER_FIRE_S=20"
sudo systemctl restart badusb
/dev/hidg0 ではなく UDC の state を使用する理由以前の実装では、/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 を挿したままにした場合、再バインドによって ホストはガジェットを再列挙します。暴走発火ループを防ぐため、2 つの保護機能が 作動します:
BADUSB_MIN_INTER_FIRE_S — 前回の発火からこのウィンドウ内に configured
遷移が発生した場合、発火を抑制して再度アンバインドします。ガジェットは
バックグラウンドで静かにサイクルします。BADUSB_MAX_FIRES_PER_MINUTE — スライディングウィンドウ方式のハードキャップ。
キャップを超えると、リスナーは BADUSB_RATELIMIT_PAUSE_S の間一時停止し、
警告をログに記録します。rm -rf を決して使用しない理由configfs のカーネル管理属性ファイル(bcdUSB、idVendor、webusb/*、
os_desc/*、…)は rm(2) では削除できません — カーネルは EPERM を返します。
これらは親ディレクトリが rmdir されたときにのみ解放されます。
gadget_setup.sh と teardown_gadget.sh はどちらも、標準的な libcomposite
の順序で configfs ツリーを走査します — rmdir のみで、属性ファイルへの rm
は決して行わず、残りはカーネルが自動的にクリーンアップします。
file.write ではなく os.write を使用する理由open(path, "w").write("") による空文字列の書き込みは、ゼロバイトの
write(2) を呼び出しません — CPython の TextIOWrapper がそれを省略する
ためです。configfs のアンバインド(カーネルは改行除去後のゼロ長書き込みから
それを解釈します)では、os.write(fd, b"\n") を直接使用することで、
システムコールが常に少なくとも 1 バイトで発行されるようにしています。
Ducky パーサーには、MockHIDEngine(実際の HID ライターのインメモリ
代替品)に対して実行される 34 テストの pytest スイートがあり、Pi も
USB ハードウェアも必要ありません。
cd /home/pi/pi-badusb
python3 -m pytest tests/
テスト範囲は以下のとおりです:
safe_eval_math。evaluate_condition。=、+=、-=、*=、/= を使用した VAR。IF / ELSE / END_IF。< と <= の WHILE / END_WHILE の反復回数。cat /sys/class/udc/*/state — ホストが列挙するときに configured に
達している必要があります。not attached のままなら、ホストと通信できて
いません: 別のケーブル(充電専用のものもあります)または別のホスト
ポートを試してください。lsmod | grep dwc2 — ロードされている必要があります。dwc_otg しか
ない場合は、dtoverlay=dwc2,dr_mode=otg が適用されていません。
install.sh を再実行して再起動してください。journalctl -u badusb -f — ホストの列挙から約 2 秒以内に
Host attached. Running payload. が表示されるはずです。これは実際にあったバグで、現在は修正されています。発生した場合、
os.write 導入前のビルドに戻っている可能性があります。monitor_and_run.py
が現在の main ブランチと一致していることを確認してください
(os.write(fd, payload) を検索)。
Operation not permitted」修正前の gadget_setup.sh は configfs に対して rm -rf を使用していました。
現在のバージョンは teardown_gadget() を使用します — これらのエラーが
表示された場合、古いコピーを使用しています。main から再デプロイしてください。
/dev/hidg0 で Permission denied」udev ルールを適用するにはホットプラグが必要です。または
sudo udevadm trigger を実行し、pi ユーザーが plugdev グループを
認識できるよう再ログインしてください。
g_ether 警告」cmdline.txt から g_ether を削除してください。libcomposite がバインド
する前に UDC を奪ってしまいます。
マスストレージのサイズは BACKING_SIZE_MB で設定され、バックエンドイメージ
が作成される初回実行時にのみ有効になります。サイズを変更するには:
sudo systemctl stop badusb
sudo rm /var/badusb/storage.img
sudo BACKING_SIZE_MB=256 /home/pi/pi-badusb/gadget_setup.sh
sudo systemctl start badusb
それが BADUSB_MIN_INTER_FIRE_S と BADUSB_MAX_FIRES_PER_MINUTE の目的です。
systemctl edit badusb でより高い値に設定してください。デフォルトでは、
挿したままの Pi は最初の 1 分間は約 15 秒ごとに再発火し、その後 60 秒間
一時停止してから再開します。
.
├── README.md This file
├── LICENSE MIT
├── install.sh Idempotent installer (firmware config, systemd, udev, plugdev)
├── gadget_setup.sh Composes the USB gadget via configfs/libcomposite
├── teardown_gadget.sh Canonical configfs teardown (wired as ExecStop)
├── reload_gadget.sh Manual UDC unbind/rebind helper
├── autorun.sh Legacy manual-launch wrapper (systemd is preferred)
├── monitor_and_run.py Listener: waits for host attach, runs payload, forces re-arm
├── run_payload.py Ducky-Script-style interpreter
├── payload.txt Your payload — edit freely; re-read on each plug-in
├── payload_commands.md Full command reference
├── etc/
│ ├── badusb.service systemd unit
│ └── 99-badusb-hidg.rules udev rule for /dev/hidg0 ownership
├── keymaps/
│ ├── __init__.py Dynamic layout loader
│ ├── us.py US ANSI (default)
│ └── uk.py UK ISO
└── tests/
├── __init__.py
├── conftest.py pytest path setup
└── test_parser.py 34 parser tests against a MockHIDEngine
libcomposite の
ドキュメント、dwc2 カーネルドライバー。MIT — LICENSE を参照。
| 要件 | 理由 |
|---|
| 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 に触れるため。 |
/etc/udev/rules.d/99-badusb-hidg.rulespi ユーザーを plugdev グループに追加します。/var/badusb/ を作成します。| 変数 | デフォルト | 説明 |
|---|
ENABLE_MASS_STORAGE | 1 | 0 にすると HID のみのガジェットになります。 |
BACKING_FILE | /var/badusb/storage.img | ホストに公開されるフラットイメージ。 |
BACKING_SIZE_MB | 64 | 存在しない場合、初回実行時に作成されます。 |
BACKING_LABEL | BADUSB | FAT ボリュームラベル。 |
MASS_STORAGE_RO | 1 | デフォルトで読み取り専用。 |
| 環境変数 | デフォルト | 説明 |
|---|
BADUSB_POST_PAYLOAD_FLUSH_S | 0.5 | アンバインド前にペイロード後スリープし、HID 書き込みをフラッシュします。 |
BADUSB_REARM_COOLDOWN_S | 5 | アンバインドと再バインドの間、ガジェットがホストから見えない状態を維持する時間。 |
BADUSB_MIN_INTER_FIRE_S | 10 | 2 回のペイロード発火の間隔の最小秒数。これを下回ると発火は抑制され、ガジェットは再アンバインドされます。 |
BADUSB_MAX_FIRES_PER_MINUTE | 6 | ハードキャップ。これを超えると BADUSB_RATELIMIT_PAUSE_S の間一時停止します。 |
BADUSB_RATELIMIT_PAUSE_S | 60 | レート制限トリガー後の一時停止時間。 |
RANDOM_*RANDOM_<unknown>INJECT_MOD 修飾子バイトの永続性。STRINGLN abc の各文字を大文字化する HOLD SHIFT。@ と " に対して異なるレポートを生成する LAYOUT US と LAYOUT UK の
比較。不明なレイアウトは以前のものにフォールバックします。STRING_BLOCK、最小インデントを尊重する STRINGLN_BLOCK。