AI要約
マイコン(RP2040-One)を用いて小型ハッキングデバイス「BadUSB」を自作する新シリーズ。第1回は物理スイッチを配線し、CircuitPythonでの「3つのモード切り替え」の制御構造を実装・解説します。
はじめに
新シリーズ「BadUSBを作ってハッキングする」へようこそ。
BadUSBは小型のハッキングデバイスのことで、カフェでターゲットがノートPCをテーブルに置いたままトイレに行ったわずかな間に秘密情報を抜き取ったり、ファイルにロックをかけてランサムしたりするやつです。もうね、挿したら一瞬です。

このデバイスはPCからは「キーボード」と認識され、キー操作を許します。そして高速でキー操作が行われてあんなこといいな♪できたらいいな♪されてしまいます。
今年度、中高生向けのワークショップでBadUSBを作って技術の二面性を学ぶことになりました。私たちで事前検証をしていますので、技術面の備忘録を残しておきます。
マイコン
マイコンには「RP2040-One」を使用します。

RaspberryPi picoでもいいのですが、せっかくなのでそのままPCに挿せるタイプにしました。
物理スイッチを付ける
3つのモードの切り替えを行うため、3接点のスライドスイッチを付けます。あいにく手元に2接点のしかなかったので2個付けました。

ピン配列はこちらを参照。

ファームウェア
CircuitPython公式ページからPico用のver10系の「.uf2」ファイルを入手。

BOOTボタンを押しながらPCに接続し、ファームウェアを書き込みます。
また、デッドロックしたときのためにRaspberryPi公式が配布しているフラッシュメモリ消去用ファイル「nuke_universal.uf2」もダウンロードしておくといいでしょう。使い方は省略。
モード切替の話
さて、いきなりですが全編を通してもっとも頭を使うところです。
モードはいくつ必要か?
ハッキングデバイスはPCに挿した瞬間に動作を開始するわけですから、開発中はそうならないように「開発者モード」にしておく必要があります。
ここで、PCとマイコンの接続では「ストレージモード」と「COMモード(USB-CDC)」が明確に区別されています。まずこれらの違いを理解する必要があります。
ストレージモードではUSBメモリのようにフォルダが立ち上がり、ファイルをドラッグ&ドロップで書き込むことができます。ただし、ブラウザのコーディングツール「ViperIDE」でコードを編集することができません(COM経由での書き込み権限がない)。※まあ、コードファイルをドラッグ&ドロップすればいいのですが、ローカルにコーディング環境を用意するのが面倒なので
一方、COMモードではフォルダが立ち上がらず、ViperIDEに書き込み権限を移します。ViperIDEはコード編集に便利なので、ぜひともこれは利用したい。
ということで、開発中は「ファイルのアップロード」と「コード編集」を両方とも行うので、これらを切り替える必要がある。よって、「開発者モード」は「ストレージモード」と「コード編集モード」に分かれる必要があります。
結局、
- ①ストレージモード(ハッキングの自動実行をしない)
- ②コード編集モード(ハッキングの自動実行をしない)
- ③コード自動実行モード(ハッキングの自動実行をする)
の3モードを切り替える必要があり、かつ、PCに挿す前にあらかじめ切り替えておかなければならないため、外部の物理スイッチが必須ということになります。
モードをいつ切り替えるか?
さっき「外部の物理スイッチであらかじめ切り替える」と言いましたが、実際には、PCに挿してからGPIOの状態を読み取ってソフトウェア的に切り替えます。
ここでマイコンの実行の手続きを知っておく必要があります。
CircuitPythonのFWは、通電時に「boot.py」を実行し、その後、メイン処理として
- code.txt
- code.py
- main.txt
- main.py
をこの優先順位で探して実行します。一旦、「main.py」を使うことにしましょう。
ここでまず最初に思いつくのは、boot.pyの中で3つのモードを切り替えてやろうということです。
ところが、これはうまくいきません。モード①と②ではmain.py(ハッキング)の自動実行をさせないように「usb_hid.disable()」で阻止するのですが(このコマンドでしか阻止できない)、これによってHID化が解除されるためモード②が成立しなくなります。つまり、boot.pyだけではモード②が作れない。
次のアイデアとして、main.pyの中で3つのモードを切り替えることを思いつきます。
しかし、これもうまくいきません。main.pyが実行されてからモード①に切り替えようと「storage.disable_usb_drive()」を叩いても、main.pyにはこの実行権限がありません。つまり、main.pyだけではモード①が作れない。
まとめると、「ストレージにするかCOMにするか」はboot.pyの中でしか切り替えできず、「ハッキングコードを自動実行するか」はmain.pyの中でしか選択できないのです。
最終的に以下の構造になりました。

boot.pyとmain.py
一段階目、「boot.py」で「①ストレージモード」か「コードモード」を切り替えます。
import storage
import usb_hid
import board
import digitalio
# GP1をGND(0V)にする
GP1 = digitalio.DigitalInOut(board.GP1)
GP1.direction = digitalio.Direction.OUTPUT
GP1.value = False
# GP2をHIGH(5V)にしておく
GP2 = digitalio.DigitalInOut(board.GP2)
GP2.direction = digitalio.Direction.INPUT
GP2.pull = digitalio.Pull.UP
# スイッチ1がOFF → GP2がHIGH(True) → ストレージモード
# スイッチ1がON → GP2がLOW(False) → コードモード
if GP2.value == True:
# ストレージモード
# storage.disable_usb_drive() # デフォルトでPCにフォルダが見える
usb_hid.disable() # キーボード化を無効化してコード編集/自動実行を防ぐ
else:
# コードモード
storage.disable_usb_drive() # ストレージを隠す、マイコンに書き込み権限を渡す
# usb_hid.disable() # 後ほどキーボード化してコード自動実行する二段階目、「main.py」で「②コード編集モード」と「③コード自動実行モード」を切り替えます。ブラウザのコーディング環境「ViperIDE」を使用します。
import sys
import board
import digitalio
# GP7をGND(0V)にする
GP7 = digitalio.DigitalInOut(board.GP7)
GP7.direction = digitalio.Direction.OUTPUT
GP7.value = False
# GP8をHIGH(5V)にしておく
GP8 = digitalio.DigitalInOut(board.GP8)
GP8.direction = digitalio.Direction.INPUT
GP8.pull = digitalio.Pull.UP
# スイッチ2がOFF → GP8がHIGH(True) → コード編集モード
# スイッチ2がON → GP8がLOW(False) → コード自動実行モード
if GP8.value == True:
# コード編集モード
print('コード編集モードです。処理を終わります。')
sys.exit()
else:
# コード自動実行モード
# 「hacking.py」を実行する
print('コード自動実行モードです。「hacking.py」を実行します...')
import hacking物理スイッチを切り替えるとうまく動作しました。これでようやくハッキングコードの中身に入れますね。
おわりに
ハードウェアの準備ができました。次回は、デスクトップにあるファイルを盗み取ってみます。



