ANZEN 研究:私たちはいかにしてUSB脆弱性を利用してKeystoneを突破したか

キーストーン
• USB SDK がホスト制御可能な長さを適切に検証していなかったため、固定バッファで境界外読み書きが発生した
• 攻撃者は USB class コールバックテーブルをハイジャックすることで、境界外書き込みを MCU 上での任意コード実行へと変換できる
• 誤った MPU 設定により、SRAM 内の shellcode が直接実行可能になっていた
• Passcode、OTP、およびセキュアチップ内の暗号化素材を取得後、AES Key を再構築して BIP39 シードフレーズを抽出可能
• サードパーティ製 SDK もハードウェアウォレットのコアセキュリティ境界の一部であり、完全な監査対象に含める必要がある
セキュリティアップデート(2026年7月): 本稿で説明する問題は、2026年3月にOneKey AnzenがKeystoneと実施した共同セキュリティ監査において発見されたもので、MCUベンダーのUSB SDKを統合したKeystone 3 Proファームウェアに影響します。Keystoneは2026年4月1日にファームウェアv2.4.0をリリースし、本問題を完全に修正しました。共同発表の時点において、双方はこの脆弱性がKeystoneユーザーへの攻撃に利用された証拠を確認していません。悪用を成功させるには、攻撃者がデバイスを物理的に所持し、正しいPINを把握し、デバイスのロックを解除し、USB接続に対するユーザーの承認を得た上で、攻撃者が制御するコンピュータにデバイスを接続する必要があります。v2.4.0未満のファームウェアを実行しているユーザーは直ちに最新版へアップグレードしてください。QR署名を使用することでこの特定のUSB攻撃経路を回避できますが、それでもアップグレードの完了が推奨されます。詳細はKeystoneとOneKey Anzenの共同セキュリティアップデートをご参照ください。
8月初旬、ラスベガスで Black Hat USA 2026 が開催されましたが、ビザの関係により私たちの発表は on-demand 形式へと変更されました。このセッションは、Keystone ウォレットに対する監査から端を発しており、チップベンダーの USB SDK における深刻な問題を発見し、最終的にシードフレーズを抽出するに至ったものです。
実のところ、USB プロトコルスタックに問題が生じるのは今回が初めてではありません。過去10数年の間、数多くのデバイスにおける深刻な攻撃が USB プロトコルスタックに関連してきました。2014年の USB ドライバの脆弱性とリモートリダイレクトから、Fusée Gelée、checkm8、Kamakiri、そして2026年に公開された usbliter8 に至るまで続いています。
頻発する USB プロトコル脆弱性
2014:USB 脆弱性には本当に物理的アクセスが必要なのか?
2014年、NCC Group は Black Hat Asia で『USB Attacks Need Physical Access Right? Not Any More...』を発表しました。
当時、人々は USB の脆弱性を「ローカルな物理攻撃」と分類しがちでした。攻撃者は標的のマシンに不正なデバイスを物理的に接続し、不正な descriptor や class-specific data を通じてドライバの脆弱性をトリガーする必要があると考えられていたのです。
しかしこのセッションでは、研究者らが RemoteFX USB Redirection を利用し、低レイヤーの USB デバイスをリモートの Windows Server へとリダイレクトしました。不正な USB デバイスは物理的には攻撃者自身のクライアントに接続されていますが、実際に不正な USB データをパースするドライバはリモートサーバー上で動作していたのです。
2018,Fusée Gelée:要求長をオブジェクト長と誤認
2018年に公開された Fusée Gelée は、USB BootROM 脆弱性の中でも最も古典的な事例の1つです。脆弱性は NVIDIA Tegra の Recovery Mode、すなわち RCM に存在しました。デバイスは極めて初期の BootROM 環境で USB スタックを起動し、ホストからのリカバリイメージのアップロードを待機します。脆弱性の発生箇所は、一見通常に見える GET_STATUS コントロールリクエストの中にありました。
USB のセマンティクスに従えば、status オブジェクトはわずか 2 バイトです。ホストは wLength によって自身が最大でどれだけのデータを受信可能かを示し、デバイスは min(ホスト要求長, 実際の利用可能データ長) を返すべきです。
しかし endpoint recipient を処理する際、BootROM は誤って次のようなロジックを実行していました:
status = get_usb_endpoint_status(index);
/* 誤り:ホストから与えられた要求長を使用 */
size_to_tx = setup_packet.length;
memcpy(dma_buffer, &status, size_to_tx);
つまり、2 バイトのオブジェクトの実際の長さは使用されず、攻撃者が制御する wLength がそのまま memcpy の長さとなり、理論上は最大 65,535 バイトに達します。
それだけでなく、RCM は署名検証を行う前に、攻撃者が比較的大きな payload をメモリ内に配置することをすでに許可していました。その payload 領域が、偶然にも BootROM のアクティブなスタック近傍に位置していたのです。DMA buffer を選択しメモリレイアウトを構成することで、境界外コピーによって最終的に攻撃者が制御可能なデータで実行スタックを上書きすることができました。
当時の BootROM 環境には一般的な stack canary、ASLR、有効なメモリ実行保護が存在しませんでした。最終的な結果として、いかなるセキュリティロックや権限降格が行われる前に、攻撃者は BootROM コンテキスト内での任意コード実行を獲得してしまったのです。
これは、本記事で後述するハードウェアウォレットの脆弱性と非常に直接的な類似性を持っています。
2019,checkm8:攻撃者が制御するのはデータだけでなく「未完了」状態も
2019年に公開された checkm8 は、Apple SecureROM 脆弱性の中で最も広範囲に影響を与えた事例の1つです。複数世代の iOS デバイスをカバーし、攻撃者が DFU モードにおいて BootROM コンテキストでのコード実行を取得し、SecureROM のダンプ、ファームウェア keybag の復号、あるいはデバイスを JTAG を許可する demotion 状態へ切り替えることを可能にしました。脆弱性がチップ製造時に焼き込まれた BootROM 内に存在するため、すでに出荷されたデバイスはこの脆弱性を iOS アップデートによって解消することはできません。
Fusée Gelée とは異なり、checkm8 は単純な長さチェックのエラーではありません。その核心は、Apple DFU USB ステートマシン内で発生する use-after-free にあります。ある USB データフェーズが正常に終了せず、データフェーズの状態を保持するグローバル変数がクリアされなかったにもかかわらず、下層の DFU buffer は USB スタックの終了時に解放されてしまうというものです。
通常、BootROM は DATA フェーズを伴う DFU_DNLOAD リクエストを受信すると、受信 buffer と wLength を保存します。データの受信が完了すると、完了コールバックがこれらの状態をクリアし、DFU を終了する際に対応する I/O buffer を解放します。
DFU_DNLOAD(setup)
{
ep0_buffer = dfu_io_buffer;
ep0_remaining = setup.wLength;
}
DATA_complete()
{
ep0_buffer = NULL;
ep0_remaining = 0;
}
DFU_exit()
{
free(dfu_io_buffer);
}
checkm8 が悪用したのは、これらの一連のステップの間にある隙間です。攻撃者はまず DATA フェーズを伴うリクエストを発行し、BootROM に buffer ポインタと残り長さを記録させた後、意図的にデータを最後まで送信しません。転送のキャンセルやタイムアウトを発生させることで DATA 完了コールバックを実行させず、続けて DFU 状態の遷移または USB reset をトリガーして USB スタックを終了させ、元の buffer を解放させます。
この時点でメモリはすでに解放されているにもかかわらず、受信状態の中には古いポインタが残されたままになります:
free(dfu_io_buffer);
ep0_buffer = old_dfu_io_buffer; // ダングリングポインタ
続いて heap の整理(ヒープスプレー等)が必要になります。公開されたエクスプロイトでは、複数の USB request オブジェクトをリークさせて後続の割り当て位置を変更し、新しい DFU buffer を別の場所へ配置させると同時に、古いアドレスを usb_device_io_request などのオブジェクトで再占有させます。
その後再び USB データを送信すると、BootROM は依然として残留している ep0_buffer を通じて書き込みを行いますが、実際に上書きされるのは新しいオブジェクトとなります。エクスプロイトはその中の callback や next などのフィールドを変更することで、コールバックアドレスをあらかじめ配置しておいた payload へ向けます。次の USB リクエストが完了した際、SecureROM は攻撃者のコードへとジャンプします。
2020,Kamakiri:USB リクエストはいかにして間接ジャンプとなったか
Kamakiri は、一連の MediaTek SoC の BootROM ダウンロードモードを対象としたものです。未認可の Download Agent を制限する本来の認証パスをバイパスし、BootROM コンテキスト内で未署名の第1ステージ payload を実行することを可能にします。この実行能力を得た後、後続の payload はフラッシュメモリの操作、メモリのダンプ、ブート状態の変更、あるいは正常に起動しなくなったデバイスの復旧などを継続して行うことができます。
Kamakiri のエクスプロイトチェーンは非常に短いものでした。MediaTek BootROM 自体が、攻撃者が必要とする最初の能力(ダウンロードプロトコルを介して既知の SRAM 領域へデータをアップロードする機能)をすでに提供していたためです。攻撃者は第1ステージの payload をそこへ配置し、特殊な USB control request を通じて脆弱性をトリガーします。
脆弱性はまさに USB control request のディスパッチ処理の中に存在していました。独自のインバース解析では、関連ロジックは以下のような形式として要約されています:
handler = handler_array[value * 13];
handler();
肝心な問題は、USB リクエストからの選択値が十分な範囲チェックなしに関数ポインタテーブルのインデックスに関与していた点です。攻撃者は適切な制御値を見つけ出し、テーブルエントリの読み取り結果が事前にアップロードした SRAM payload アドレスに一致するようにするだけで、USB リクエストを payload への間接呼び出しへと変換できました。SoC ごとに BootROM のレイアウトは完全に同一ではないため、具体的なインデックス値や payload アドレスには適応や列挙が必要な場合があります。
公開された PoC は、stage 1 をアップロードした後に次のようなコントロール転送を送信していました:
ctrl_transfer(
0xA1, # class request, device-to-host, interface recipient
0,
0,
10, # wIndex
0
)
2026,usbliter8:USB コントローラが DMA 書き込みポインタを buffer の前方へ移動
2026年6月に公開された usbliter8 は、ソフトウェアでパッチ不可能な BootROM エクスプロイトを Apple A12、A13、および S4、S5 SoC へと再び拡張しました。
エクスプロイトが成功すると、攻撃者は SecureROM の EL1 コンテキストでコードを実行し、DFU の挙動を変更したり、一時的にチップの production mode を demote したり、署名検証を回避して未署名の生の iBoot を起動したりできます。これはアプリケーションプロセッサの起動における信頼の連鎖が突破されたことを意味し、以後の iOS アップデートでも修正することはできません。
usbliter8 はそれまでの事例とは異なり、主に Synopsys DWC2 USB コントローラ自体が SETUP transaction を処理する際の DMA ポインタセマンティクスを悪用しています。
USB プロトコルに従えば、SETUP transaction のデータ部分は 8 バイト固定です。DWC2 コントローラは3つの SETUP パケットを連続受信し、DMA を通じてそれらをメモリへ順次書き込むことを許可しています。通常の3つの SETUP パケットは合計で 24 バイトを占有します。
4つ目の SETUP transaction が到着した際、コントローラは DOEPDMA 書き込みポインタから一律に 24 バイトを減算し、アドレスをこの一連の SETUP buffer の起点へと巻き戻します。通常の 8 バイトの SETUP パケットであれば、このロジックは整合します。
しかしコントローラは、8 バイト未満の異常な SETUP データも同時に受け付け、実際に書き込まれた長さに応じて DMA ポインタを進めます。内部の書き込み粒度は 4 バイトです。攻撃者が 4 バイトのショートパケットを繰り返し送信した場合、最初の3パケットではアドレスが合計 12 バイトしか進まないにもかかわらず、4パケット目が到着した際にコントローラは依然として一律 24 バイト巻き戻します。その結果、次の一連の SETUP データは元の buffer には書き込まれず、buffer 起点の 12 バイト手前に書き込まれることになります。このプロセスを繰り返すことで、DMA の書き込み位置は予測可能なステップ幅で低位アドレスへと移動し、最終的に安定した buffer underflow が形成されます。
通常、USB コントローラの境界外 DMA が任意のシステムメモリを破壊できるとは限りません。現代の SoC は通常、IOMMU を介して USB コントローラがアクセス可能なアドレス範囲を制限しています。しかし A12 および A13 の SecureROM 環境では、USB DART が bypass 状態になっており、コントローラの DMA には実効的なアドレス分離が欠如していました。これにより underflow は狭い特定の SETUP buffer に留まらず、他の SRAM オブジェクトを継続して上書きできるようになりました。
A12 では、USB DMA buffer は USB task のスタックと隣接しており、エクスプロイトは保存された link register を上書きし、タスクスイッチ時にプログラムカウンタの制御を獲得できます。A13 では Pointer Authentication が導入されたため、戻りアドレスの直接上書きは困難ですが、公開されたエクスプロイトはより複雑なメモリ破壊チェーンを採用し、最終的に BSS 内の USB interrupt handler ポインタを書き換えて、次の USB 割り込みから攻撃者コードへ入るようにしました。どちらのパスも最終的に SecureROM EL1 での実行を獲得します。
USB 境界外読み書きから任意実行へ
今回の Black Hat USA で共有した脆弱性の根本原因も、実際には USB SDK にありました。
CDC における line-coding リクエストは非常に小さな固定構造体しか必要としませんが、統合された USB SDK はそのために固定 8 バイトの CmdBuff を用意し、その後ホストから提供された req->wLength を直接下層の送信または受信関数へと渡していました。そしてコードには長さチェックが存在せず、特定のリクエストに基づいてプロトコル規定の固定長と一致することを要求してもいませんでした。その結果、境界外の読み取りおよび書き込みが発生したのです。
USB SDK 境界外読み書き脆弱性のコード
当然ながら境界外の読み書きを獲得したのは第一歩に過ぎず、単にメモリ破壊のプリミティブを手に入れた状態です。私たちの最終目標は任意実行能力を獲得することでした。
脆弱性が USB CDC パス内で発生したため、私はまず現在のリクエストのディスパッチフローに沿って USB プロトコルスタックを調査しました。g_usbDev を解析している際、その中の dev.class_cb に着目しました。これは単なるデータポインタではなく、USB class driver のランタイムコールバックテーブルです。USB core はリクエストを処理する際、このテーブルを介して Setup、DataIn、DataOut などの関数を呼び出します。class_cb を書き換えることさえできれば、後続の通常の USB リクエストが私たちが制御する関数ポインタを経由することになるため、ハイジャックのターゲットとして極めて適していました。
エクスプロイト全体は2回の USB リクエストに分割できます。
1回目のリクエストでは SET_LINE_CODING を使用して境界外書き込みをトリガーします。この書き込みにおいて、私は同時に3つの事柄を完了させる必要があります:shellcode を CmdBuff に配置すること、近傍の SRAM 内に偽造したコールバックテーブルを構築すること、そして最後に class_cb をこの偽のテーブルのアドレスへと書き換えることです。
その後、通常の GET_LINE_CODING リクエストを再度送信します。USB core は通常どおりこのリクエストを処理し、class_cb->Setup(...) を通じて class driver の Setup コールバックを呼び出します。異なる点は、この時点で class_cb はもはや元のコールバックテーブルを指しておらず、私たちが偽造したテーブルを指していることです。偽の Setup はさらに CmdBuff 内の shellcode を指しています。これにより、2回目の USB リクエストによって PC は私たちが SRAM に書き込んだコードへと直接遷移します。
shellcode は SRAM 内に位置するため、本来はデータ領域が実行不可能である問題(NX/DEP)を考慮する必要がありました。しかしファームウェアが MPU を設定する際に誤った size フォーマットを使用していたため、最終的に得られた保護範囲は 0 となり、BSS の実行権限を制限するはずだった MPU region は実際には機能していませんでした。そのため、CmdBuff 内に配置されたこのコードは直接実行が可能でした。
USB コールバックテーブルを乗っ取り shellcode を実行
理論的に実現可能であることを確認後、実際の運用に着手しました。まず実際の境界外読み書きの範囲をテストしました。64 バイトのコントロール転送を構築したところ、境界外読み取りにより約 24 KB の SRAM を継続的にリークできましたが、境界外書き込みを 4,352 バイトの位置まで行うと安定してクラッシュすることが判明しました。
その後の調査で、この位置では境界外データが CmdBuff + 0x1130 にある使用中のポインタを上書きしてしまい、EP0 が後続データを受信し続ける能力を即座に失うことが分かりました。
境界外書き込みによりデバイスがクラッシュする位置
この問題の解決は比較的シンプルでした。すでに境界外読み取りの能力を持っているため、実際の状態を直接境界外読み取りし、ダンプした状態データを shellcode にロードするだけで解決しました。これにより、完全な任意コード実行ロジックが完成しました。
これで class_cb を上書きできるようになりましたが、このハイジャックによって USB チャネル自体を破壊することは避けたいと考えました。ロングライトによりまず shellcode と偽造コールバックテーブルを SRAM に配置し、一時的に class_cb が偽のテーブルを指すようにします。続けて GET_LINE_CODING を送信すると、USB core は fake Setup を経由して shellcode へとジャンプします。タスク完了後、shellcode は結果を CmdBuff の後方に書き込み、元の class_cb を復元した上で、元の Setup を呼び出して今回の EP0 転送を正常に終了させます。これにより、毎回のハイジャックは一時的な detour(回り道)に過ぎず、USB は引き続き利用可能であり、次のタスクを継続して注入することが可能になります。
ファームウェアは当初 MPU を介して BSS を実行不可に設定しようとしていましたが、設定コードが誤った size フォーマットを使用していたため、最終的に計算された保護範囲はゼロになっていました。
MCU 任意実行からシードフレーズ抽出へ
MCU の任意実行を獲得したからといって、直ちにシードフレーズを抽出できるわけではありません。ハードウェアウォレットの具体的な構造は以下のとおりです。大まかに言えば、シードフレーズの暗号化情報は DS28S60 SE 内に保存されており、この情報を復号するための AES KEY には OTP、ATECC608、DS28S60 の2つの SE がそれぞれ関与しています。
ハードウェアウォレットのセキュリティアーキテクチャ
この AES KEY の生成プロセスは以下のようになります。
AES Key 生成フロー
そして SE とのやり取りや AES 生成に関与するクレデンシャルは、主にウォレットの passcode(ロック解除パスワード)と OTP から生成され、フローは以下のようになります:
Passcode と OTP 認証情報生成フロー
Passcode は password hash を取得した後にブルートフォース攻撃によって割り出すことが可能であるため、最終的には OTP の salt さえ取得できれば AES key の生成を完了できます。OTP の抽出に関しては、shellcode が通常のファームウェアと同じ特権レベルで実行されているため、USB 脆弱性を利用して MPU レジスタへ書き込みを行い MPU を無効化することで、OTP を読み取ることができます。
最終的なエクスプロイト手順は以下のとおりです。AES KEY を取得した後は、復号を行って BIP39 entropy を取得し、シードフレーズを再構築できます。
USB 脆弱性からシードフレーズ抽出に至る完全な攻撃チェーン
まとめ
今回の研究プロセスでは、脆弱性分析および exploit フローにおいて AI を大規模に活用しました。AI の登場はセキュリティ研究者のエクスプロイト効率を大幅に向上させました。AI はこの種の脆弱性研究にかかるコストも変えつつあります。かつては脆弱性の発見から安定したエクスプロイトに至るまでに膨大な人的時間を費やす必要がありましたが、現在ではコードの理解、構造のマッチング、スクリプトの生成、失敗原因のトラブルシューティングが格段に高速化されています。
そして、USB SDK であれその他のサードパーティ製コンポーネントであれ、製品のセキュリティ審査において見落とされがちなポイントです。しかし、往々にしてこのような弱点こそが、最終的に製品全体のセキュリティを陥落させる原因となるのです。






