
Nintendo Switch向けFusee Geleeエクスプロイト(CVE-2018-6242)の実装、およびカスタムペイロード。
Nintendo Switch向けのFusee Geleeエクスプロイト(CVE-2018-6242)の実装です。2018年3月にKate Temkin / ReSwitchedによって公開された脆弱性に基づいています。
USBリカバリモード(RCM)にあるTegra X1デバイス上で任意のペイロード(例:hekate)を起動し、ブートROMの署名検証を完全にバイパスします。
RCM(リカバリモード)は、Tegra X1のブートROMに組み込まれたUSBベースのリカバリプロトコルです。NVIDIAは、診断や修理のために小さなプログラム(「アプレット」)をデバイスにロードできるように設計しました。たとえば、Switchがストレージ上に有効なブートローダーを見つけられない場合などです。
Nintendo Switchでは、起動時に右Joyconレールのピン1と10を短絡することでRCMに入ります。通常の動作では、RCMを使用できるのはNVIDIAのみです。すべてのコマンドはNVIDIAのRSA秘密鍵で署名されている必要があり、ブートROMは何かを実行する前に署名を検証します。
このエクスプロイトは、IRAM(オンチップRAM)を共有する2つの独立したプロトコルレイヤーにまたがって動作します。
USBレイヤー(標準、EP0): すべてのUSBデバイスには、GET_STATUS、GET_DESCRIPTOR、SET_ADDRESSなどの標準リクエストを処理するコントロールエンドポイント(EP0)があります。ブートROMは、USB 2.0仕様で要求されるこれらを実装しています。EP0は暗黙的であり、デバイスのエンドポイントディスクリプタには現れません。
RCMレイヤー(NVIDIAプロプライエタリ、EP1): NVIDIAは、RCMコマンドとペイロードを転送するためのバルクエンドポイント(EP1)を定義しています。EP1はデバイスディスクリプタに2つの方向として現れます:
0x01 = OUT(ホストがデバイスにデータを送信)0x81 = IN(デバイスがホストにデータを送信)EP1を介して送信されるデータは次のように構造化されています:
[680-byte RCM command header] [payload bytes]
680バイトのヘッダーはrcm_msg_t構造体です。これはNVIDIAプロプライエタリな構造体で、RSAモジュラス/署名、ECID、オペコード、その他のフィールドを含みます。このサイズはTegra X1ブートROMのリバースエンジニアリングによって決定されました(q3kのIDAデータベースを参照)。NVIDIAのオープンソースtegrarcmは最大644バイト(Tegra124)までしか文書化しておらず、T210バリアントは36バイト大きくなっています。
ブートROMのEP0コントロールリクエストハンドラには、ENDPOINTレシピエント向けのGET_STATUS実装にバグがあります。Temkinのホワイトペーパーより:
// BUG: should be size_to_tx = sizeof(status), i.e. 2 bytes
size_to_tx = length_read; // attacker-controlled via wLength, up to 65535
data_to_tx = &status; // a uint16_t on the stack
memcpy(dma_buffer, data_to_tx, size_to_tx);
そのmemcpyは&status(0x40010000のすぐ下にあるスタック変数)から読み取り、DMAバッファ(0x40009000)に書き込みます。過大な長さの場合:
&statusを超えて、スタックの残りを通り、0x40010000+にある攻撃者制御のペイロード領域(EP1バルク書き込みで配置)まで読み取ります。ソースデータには「スタックスプレー」(0x40010000の繰り返し)が含まれており、これがスタックのリターンアドレスに書き込まれます。ハンドラが戻ると、実行は0x40010000にジャンプします。そこにはintermezzoと呼ばれる小さなリロケータスタブを配置しています。
これはすべて、ブートROMが署名を検証する前に、RCM受信ループ(handle_control_requests内)中に発生します。RCMプロトコルは配信メカニズムであり、USBコントロールハンドラがトリガーです。
0x40005000 +------------------+
| DMA buffer LOW | USB controller writes odd packets here
0x40009000 +------------------+
| DMA buffer HIGH | USB controller writes even packets here
+------------------+
| execution stack | grows downward toward DMA buffers
0x40010000 +------------------+ <-- stack ends here / payload starts here
| intermezzo | small relocator stub (124 bytes)
0x40010E40 +------------------+
| user payload pt1 | first ~16KB of the user payload
0x40014E40 +------------------+
| stack spray | 0x40010000 repeated (8640 bytes)
0x40017000 +------------------+
| user payload pt2 | remainder of user payload
+------------------+
ユーザーペイロードはスタックスプレーの周りで分割されています。スプレーがオーバーフローによってスタックのリターンアドレスにコピーされるように配置する必要があるためです。Intermezzoは2つの半分を0x40010000の連続したブロックに再構成し、そこへジャンプします。
0x0955:0x7321)でデバイスを見つける0x40010000+にコピーし、intermezzo、ユーザーペイロード、スタックスプレーを配置する0x40009000)をターゲットにしていることを確認するwLength=0x7000のGET_STATUSコントロールリクエストを送信する — これにより脆弱なmemcpyがトリガーされ、スタックスプレーがリターンアドレスを上書きし、ハンドラはintermezzoに戻るpip install pyusb
python launcher.py
USB経由で接続されたRCMモードのSwitchが必要です。macOSではbrew install libusbが必要な場合があります。
ペイロードバイナリをbinaries/payload.binに配置してください。同梱のintermezzo.binがペイロードのリロケーションを処理するため、置き換える必要はありません。
launcher.py — エクスプロイトスクリプトbinaries/intermezzo.bin — リロケータスタブ(124バイト)、分割されたペイロードを再構成するbinaries/payload.bin — 実行するユーザーペイロード(例:hekate)