ThinkPadをプログラム可能なUSBデバイスに変えた過程
(xairy.io)- ThinkPad X1 Carbon 6th Genの隠れたxDCIコントローラを有効化すると、ノートPC自体がキーボードやストレージ機器のような任意のUSBデバイスをエミュレートできるようになった
- 手がかりは
/sys/class/usb_role/intel_xhci_usb_sw-role-switchだったが、xHCIのロール切り替えだけでは不十分で、別個のDWC3ベースのxDCI UDCがPCIeデバイスとして見える必要があった - BIOSイメージには
xDCI Support設定があったが、標準のSetup UIでは隠されており、有効化後はlspciにxDCI8086:9d30とdwc3-pciドライバが現れた - 成功した方法はAdvanced BIOSページのロック解除とNVRAMパッチであり、PCHレジスタの再構成はPMCの一部変更にとどまり、PSFロックのため失敗した
- xDCI有効化後、
g_mass_storage、Raw Gadget、syzkallerの再現プログラム、Facedancerバックエンドが外部ハードウェアなしで動作したが、VBUSが切断されたUSBケーブルとdeviceロールへの切り替えが必要だった
ThinkPadで見つけたUSBデバイスモードの手がかり
- Raw GadgetはLinuxカーネルのUSB Gadgetサブシステムにユーザー空間APIを提供するカーネルモジュールで、不正なUSBディスクリプタを提供してUSBホストのファジングやエクスプロイトに利用できる
- 一般的なPCには通常USB Device Controller、つまりUDCがなく、Raspberry Piのようなシングルボードコンピュータに主に内蔵されている
- これまでの作業にはRaspberry PiやUSB 3380ベースのEC3380-ABのような外部ハードウェアが必要だった
- ThinkPad X1 Carbon 6th Genで
/sys/class/usb_roleの下にintel_xhci_usb_sw-role-switchが見つかった/sys/class/usb_roleは、ハードウェアUSBコンポーネントをhostモードとdeviceモードの間で切り替えるインターフェースである- roleファイルに
deviceを書いても、当初はdmesgメッセージも/sys/class/udc/の項目も現れなかった
- xHCIはx86システムでUSB hostとして動作させるHCDで、xDCIはIntelがUSB device側インターフェースに使う名称である
カーネルコードとメーリングリストで確認した構造
- Linuxカーネルでは
intel_xhci_usb_swを2か所で確認できるdrivers/usb/roles/intel-xhci-usb-role-switch.cは、deviceまたはhostが書き込まれたときにxHCIレジスタを操作するUSB Role Switchドライバを実装しているdrivers/usb/host/xhci-ext-caps.cは、xHCIドライバがXHCI_INTEL_USB_ROLE_SWquirkを設定した場合にintel_xhci_usb_sw仮想プラットフォームデバイスを作成する
- ThinkPadのxHCI PCIe device IDは
0x9d2fで、カーネルのPCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCIと一致していた - Linuxカーネルのメーリングリストの議論から、xHCIとDWC3 UDCは別個のデバイスであり、その間にmuxがあることが確認できた
- role switchはこのmuxを切り替える役割を持つ
- DWC3 UDC自体がPCIデバイスとして見えないなら、device modeで使うUDCがない状態である
- DWC3はSynopsysのDesignWare Core SuperSpeed USB 3.0 Controller IPで、Intelを含む複数ベンダーがシステムで利用している
- ThinkPadで
lspciを確認した時点では、当初はxHCI8086:9d2fしか見えず、dwc3-pci.cに基づくSunrise Point-LPのxDCI device IDである0x9d30は見えていなかった
ACPIとBIOSに残っていたxDCIの痕跡
- ACPI DSDTには
Device (XDCI)と_STAメソッドが存在した/sys/bus/acpi/devices/device:33/pathは\\_SB_.PCI0.XDCIだった/sys/bus/acpi/devices/device:33/statusは15で、これはデバイスがenabled and functioning状態であることを意味する- しかし
lspciにはxDCIがなかったため、ACPIの観点でのみ正常に見える状態だった
- 標準のBIOS Setup画面にはxDCIやOTGに関する設定は見当たらなかった
- Lenovo BIOS Bootable CD更新イメージをダウンロードし、BIOSバイナリ
/mnt/bios/FLASH/N23ET86W/$0AN2300.FL1を抽出してUEFIToolで確認した SetupモジュールのPE32 image sectionで文字列検索すると、次の項目が見つかったxDCI SupportEnable/Disable xDCI (USB OTG Device).
- BIOS内にはxDCI Support設定があるが、標準UIでは隠されていた
Advanced BIOSページでxDCIを有効化
- x1c6-hackintoshプロジェクトの文書に従って、隠されたAdvanced BIOSページを開く方法を試した
- この方法では、BIOSが保存されたSPIチップを再フラッシュする必要があった
- SPIクリップを使う代わりにSPIチップをソケットに交換し、何度もチップを抜き差しして実験した
- FTDI FT2232H Mini Moduleと
flashromを使ってSPIチップの内容をダンプし、再書き込みした
- BIOSパッチは2段階で構成された
UEFIPatchでDate/TimeBIOSページのGUID参照をAdvancedページ側に変更する- 別のバイトパッチでTPMを
MFG Modeに強制し、Boot GuardがBIOS改変を検知しないようにする
- BIOS 1.37では最初のパッチだけを適用するとノートPCがビープ音を出して起動せず、2つ目のパッチまで適用した後にAdvancedページが現れた
Intel Advanced Menu→PCI-IO Configuration→USB Configuration配下のxDCI SupportをEnabledに変更すると、起動後にxDCIが見えるようになったlspciに00:14.1 USB controller [8086:9d30]が現れた- カーネルは
dwc3-pciドライバを自動ロードした /sys/class/udc/にdwc3.1.autoが現れた
xDCIポートとケーブル条件
- xDCIに接続された外部USBポートを見つけるため、roleを
deviceにした状態でUSBフラッシュドライブを各ポートに挿して確認した- 特定のポートではOSがフラッシュドライブを検出せず、roleを
hostに戻すと再び動作した - そのポートがxDCI接続ポートだと判断された
- 特定のポートではOSがフラッシュドライブを検出せず、roleを
- ThinkPadのxDCIポートはUSB Type-Aだったため、hostコンピュータに接続するにはType-A to Type-Aのmale-to-maleケーブルが必要だった
- role switchingではポートの電源
VBUSが切れなかったため、2台の機器のVBUSが直接つながらないようにVBUSが切断されたケーブルを使用した- 当初はUSB 2.0ケーブル2本を切って
VBUSを切断したmale-to-maleケーブルを自作した - 後にはPortaPow USB Power Blockerとmale-to-male USB 2.0ケーブルを使った
- 当初はUSB 2.0ケーブル2本を切って
g_zerogadget driverをロードすると、別のノートPCでテストUSBデバイスが正常にエミュレートされた- 2024年6月11日の更新時点では、DataPro USB 3.0 Super-Speed A/A Debugging CableはUSB 2.0の
D-とD+も切断しており、SuperSpeed接続確立に必要なHigh-Speed接続に失敗したVBUSを手動で切断したLogiLink CU0038ケーブルはSuperSpeedデバイスのエミュレーションで動作した
PCHレジスタでのソフトウェア有効化の試み
- BIOSがxDCIをどう有効化しているかを把握するため、Kaby Lake BIOSソースコードの
Pch/Library/Private/PeiPchInitLib/PchXdci.cを確認した - BIOSのxDCI無効化経路は、xDCIをPMCとPSFで無効化する構造だった
-
PMC変更
- PMCの
NST_PG_FDIS_1レジスタoffset0x628のbit #24XDCI Function Disableをクリアすれば、xDCIの電源遮断を解除できると考えた - Linuxカーネルの
pmc_core_deviceとpmc->regbaseマッピングを再利用するカーネルモジュールを書いた - 起動直後は
ST_PG_FDIS1のST_FDIS_LKbit #31が設定されていて、NST_PG_FDIS_1の値を変更できなかった - ノートPCをsuspendしてから復帰するとlock bitが設定されておらず、
XDCI Function Disablebitのクリアに成功した
- PMCの
-
PSF変更
- BIOSは
PID_PSF2Port ID0xBBのPSF2 PCRでUSB Dual Role (OTG) Function Disablebitを設定し、xDCIを無効化していた - PCRアドレスは
PCH_PCR_BASE_ADDRESS 0xFD000000とPort ID、offsetの組み合わせで計算される - カーネルモジュールでPSF2 PCRを
ioremapしても、読み取り値は0xffffffffとなり変更できなかった - P2SBをunhideしても結果は同じだった
- BIOSの
RemoveSidebandAccessがEPMASK5でPID_PSF2をIOSF-SBから切り離し、MASKLOCKでロックしているため、OSからPSFを再設定できなかった - PCHアクセスではPMCの一部設定には成功したが、PSFの再構成に失敗したため、xDCI有効化手段にはならなかった
- BIOSは
NVRAMパッチでBoot Guard回避なしに有効化
- BIOS設定値はSPIチップのNVRAM領域に保存されるため、隠しAdvancedページを開かずに
xDCI Support値だけを変更する方法を試した - IFRExtractor-RSで
SetupモジュールのUEFI IFR情報を抽出した xDCI Support設定はPchSetupUEFI変数内に保存されていた- VarStore GUID:
4570B7F1-ADE8-4943-8DC3-406472842384 - VarStoreId:
0x5 - VarOffset:
0x40 - 値の範囲:
0x0または0x1
- VarStore GUID:
- Linuxの
efivarfsで/sys/firmware/efi/efivars/PchSetup-4570b7f1-ade8-4943-8dc3-406472842384を確認したefivarfsファイルの先頭4バイトはヘッダなので、実際のxDCI Support値はファイルoffset0x44にある- xDCI無効状態では値は
00で、BIOS Advancedページで有効化した後は01に変わった
- ユーザー空間やEFI Shellでその変数を直接書き込もうとすると
EFI_WRITE_PROTECTEDエラーが発生したefivarfsのimmutable属性を外してもRead-only file systemエラーが発生した- chipsecでEFI Shellから変更しても同じエラーになった
- SPIプログラマで読み出した元のBIOSイメージから
PchSetup付近のバイト列を探し、xDCI Supportbitだけを01にパッチして再フラッシュするとxDCIが有効化された - この方法でもSPIチップの再フラッシュは必要だが、AdvancedページのパッチやBoot Guard回避は不要で、TPMも壊れなかった
- BIOS 1.37でNVRAMをパッチした後に1.61へ更新してもBIOS設定値は保持され、パッチを再適用する必要はなかった
xDCIでUSBデバイスをエミュレートする
- すべての例は、xDCIがBIOSで有効化され、
intel_xhci_usb_sw-role-switchがdeviceに切り替えられ、VBUSが切断されたケーブルでxDCIポートとUSB hostが接続されていることを前提とする -
Legacy gadget drivers
- Linux USB Gadgetサブシステムのlegacy gadget driversは、モジュールをロードすると特定のUSB classデバイスをエミュレートする
g_mass_storageはファイルシステムイメージをUSB mass storageデバイスとして公開する- FATイメージ
disk.imgを作成し、file.txtにHi from xDCIを入れてからsudo modprobe g_mass_storage file=./disk.img stall=0でロードした - USB host側ではドライブが自動マウントされ、ファイル内容を確認できた
-
Raw Gadget
- Raw GadgetをxDCIとともに実行し、USBキーボードの例を試した
- 当初は
SET_CONFIGURATION処理中に停止した - 原因は、
dwc3と一部のUDCドライバが、長さ0のcontrol requestをgadget driverの確認なしに即座にacknowledgeできると仮定していたためだった - Raw Gadgetにworkaround patchを適用し、GadgetFSも修正した後、キーボードのエミュレーションに成功した
- 関連する挙動は文書パッチのcommitで整理されている
syzkallerとFacedancerの活用
- syzkallerはRaw Gadgetを使ってLinuxカーネルのUSBスタックを外部からファジングできる
- 標準のsyzkaller USBファジングは、カーネル内部でvirtual USB DeviceとHost controllerを接続するDummy HCD/UDCを使用する
- xDCIを使うため、syzkallerのUDC指定コードを
dummy_udcではなくdwc3-gadget、dwc3.1.autoに変更した - syzkallerが見つけた
WARNING in smsusb_start_streaming/usb_submit_urbバグのreproducerをsyz-execprog -enable=usbで実行した- Ubuntu
5.15.0-91-genericカーネルをhostにしたノートPCでusb_submit_urb警告が再現した - このバグは
smsusbドライバがUSB endpoint typeの検証を省いていたために発生したWARNINGで、有害な挙動ではなかった
- Ubuntu
- FacedancerはPythonベースのUSBデバイスエミュレーションフレームワークである
- Raw GadgetベースのFacedancer backendはまだprototypeで、out-of-treeなRaw Gadgetパッチに依存している
BACKEND=rawgadget、RG_UDC_DRIVER=dwc3-gadget、RG_UDC_DEVICE=dwc3.1.autoを設定してrubber-ducky.pyの例を実行すると正常に動作した
- Raw GadgetベースのUSB ProxyもxDCIとともに動作し、xDCIを使えばノートPCをUSB snifferやUSB Man-in-the-Middleツールとして使える
他の機器とソフトウェア方式の可能性
- ThinkPad X1 Carbon 6th Gen以外のPCでもxDCIを有効化できる可能性がある
- 最も単純な場合は、BIOS設定でxDCIを有効にするだけで動く可能性がある
- ACPIとrole switchingのサポートがあり、xDCIポートが外部ポートに配線されている必要がある
- 他のThinkPadでもxDCIのBIOS設定が隠され保護されている可能性があり、SPIチップの再フラッシュが必要かもしれない
- NVRAM方式はBoot Guard回避なしで適用可能である
- 一部の新しいシステムのSPIチップはRPMCで再フラッシュが保護されていることがあり、そのようなチップは
W25R128FWのように型番にRが含まれることがある
- 更新によれば、Shiny QuagsireはXPS 13とSurface Go 1で
grub-mod-setup_varによりPchSetup変数を変更してxDCIを有効化したことがある- それらの機器では
PchSetup変数が保護されておらず、純粋なソフトウェア方式が可能だった
- それらの機器では
- 別のソフトウェア的アプローチとしては、BIOS脆弱性を利用してxDCI関連のNVRAM変数を変更したり、起動中にDMAでBIOSを攻撃して任意コードを実行したりする方法が考えられる
- タイトルの“secret”は、意図的な隠蔽というより、文書化されていない機能を指す表現に近い
1件のコメント
Hacker News のコメント
ノートPCを別のコンピュータ用の汎用キーボード兼モニターとして使う機能が、なぜ一般的でないのかずっと不思議に思っている
ヘッドレス機のメンテナンスのためにキーボードとモニターを新たに買うのは変だ。すでにノートPCがその役割を果たせるから
近く登場する Minisforum V3 タブレットにも映像入力があり、外部モニターのように使えるが、キーボードとマウスまでは共有されなさそう
こういう機能がもっと一般的になると本当にうれしい
マウスとキーボードを外部ポートに切り替えて別のマシンへケーブル接続でき、側面にはHDMIポートが2つあって、一方は出力、もう一方は入力
内部スイッチで、デッキ自体のマシンか外部入力のどちらをモニターソースにするか選べる
基本的には、ヘッドレス機の HDMI信号をキャプチャしてUSB経由でノートPCにエミュレートされたWebカメラとして見せるチップで、電子部品がケーブル内に収まるほど小さい
キーボードについては、標準的なワイヤレスキーボードを使い、まれにマウスが必要になる場合に備えてトラックパッド内蔵型を買う以外に、良い解決策は見つけられていない
聞いたところでは、USB 4 にはこれを可能にするモードがあるらしい
https://web.archive.org/web/20180610141436/http://anselm.hoffmeister.be/computer/hidclient/index.html.en
https://github.com/benizi/hidclient
数年前に Travis Goodspeed がやっていたことで、思い出す事例がある
スマートTVが一般的なUSBメモリ上のファイルからファームウェア更新を受け取り、TVはデジタル署名を確認するためにファイルを最初から最後まで読み、その後アップデートのためにもう一度読んでいた
USBストレージ装置のふりをするデバイスなら、1回目の読み取りではメーカーのファームウェアファイルを送り、2回目には非公式ファームウェアを送れる
https://en.m.wikipedia.org/wiki/Time-of-check_to_time-of-use
本当に興味深い。似たように実現可能な選択肢を調べてきた
使う口実をずっと探している強力な NASサーバー があり、家中のあちこちに40Gと10Gの接続も引いてある
PS5とXBoxは追加ストレージとしてUSBハードディスクを使っている
NASのストレージをPS5とXBoxに公開できるか調べたところ、可能だった。NAS共有をiSCSIまたはNFSでマウントし、g_mass_storage モジュールでUSBストレージをエミュレートして、そのストレージをUSBホストに公開すればよい
現在の大きな障害は、時間と費用に加えて、このようなシステムが提供できる帯域幅だ。アップグレード幅はそれほど大きくない
Raspberry PiはUSB-OTG対応で有名だがUSB 2.0速度にとどまり、調べた同クラスの他のSBCもほとんど同じだった。例外はRockPi4で、RockPiにはギガビットEthernetポートがあり、Ethernetを使い切ればPS5とXboxの両方に安定したHDD速度を提供できる
カスタムPCIeネットワークカードやExpressCardを挿して 1GbEを超えられる解決策 を作れれば、USB 3.0インターフェースをきちんと飽和させられるので、かなり面白そうだ
サーバーと通信するクライアントは必要だが、かなりうまく動くようだ
机の上に「薄いUSBクライアント」を置いてUSBデバイスをそこに接続し、実験室内の任意の物理ホストやVMホストへ接続または切り替える構成を考えている
NAS → SBC → PS5だと推測しているが、それともNASをPS5にUSBケーブルで直接接続するのだろうか?
PS5でのコンテンツ再生がどうなるのかも気になる。内蔵NVMeは特定の速度を満たす必要があると理解しているが、外付けドライブには適用されないのだろうか?
実際、NanoPi R6Sは1ギガビットポートに加えて 2.5G Ethernetポートを2つ 搭載している
とても詳しい記事でありがたい。品質が高い
これが可能だったという事実のおかげで、改造したUSBケーブルだけで、ネットワークなしの Synergy / Mouse Without Borders のような構成ができるかもしれないという希望が湧いた
複数のコンピュータを同時に操作しなければならない人にとっては、とても良い方向性だ
それでも、提案された方式は興味深く聞こえる
最近、似たようなことを調べていて、USB-C搭載マシン2台をUSB-Cケーブル1本でつないで10Gbps通信をしたかった。
残念ながら、ほとんどのRyzenボードにはまだThunderboltがなく、Thunderboltならこれをネイティブで簡単にサポートできる。
「デュアルロール」のUSBコントローラも、USB 3.2/4.0以前はかなり珍しく、この記事で扱われているように、可能であってもサポートされていない場合が多かった。
ThunderboltなしでUSB-Cケーブルを使ってマシン2台をネットワーク化するのがここまで難しいのは、まったくおかしく感じる。
サスペンド後にロックビットが解除される部分もよかった。こういう問題は本当によくある。たとえば、SATAデバイスをセキュア消去を妨げる状態から抜け出させるときにも非常によく使われる方法だ。
深さが印象的なレベル。
PCをUSBデバイスとして使えると、面白い可能性がたくさん開ける。
必要なxDCIオプションがこのデバイスのハードウェアにはあるのに公開されておらず、アクセスするにはファームウェアのハックが必要という点は少し残念。
ハードウェア的には可能なのに、メーカーが単に無効化して制御機能をロックしているというわけだ。
Lenovoが、ThinkPad 1台で別のThinkPadをデバッグする経路としてこれを使っているのか気になる。
https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/secure-coding/intel-debug-technology.html
更新:おそらくそうらしい。
https://www.youtube.com/watch?v=ExUvQa_jB7c
Intel FirmwareをDCIとUSB 3.0でデバッグする動画だ。
「しかしRaspberry Piを扱うのは面倒だ:配線、ボードの起動、シェルへの接続など」という部分は、おそらくUSB OTGを設定すればよかったのではないかと思う。
接続先のデバイスから電源を受け取り、シェルを有効にしてからスクリプトで処理できたはず。
そうすればSSHでマウント設定やコマンド実行を行い、おそらくデバイスのマウントまで可能だったと思う。
それでも、自分のデバイスを理解し、カーネルを読んでみようとする試みは推奨したい。これからももっと上手くやりたいことであり、自分たちの機材は自分たちで直そう、という感じだ。
実際にやってみた。
初期インターネットのハッカー文化を強く思い起こさせて、郷愁を誘うほどだ。本当に素晴らしい記事。
ESRが「ハッカーになること」や、Hacker News的な意味でのハッカーについて語ったときに指していたのは、まさにこういうタイプの人だった。
好奇心、粘り強さ、技術力が組み合わさった姿は極めて珍しく、大きな印象を残す。
Celeron N3350/Pentium N4200/Atom E3900ベースのシステムを持っているが、BIOSにxDCIスイッチが公開されている。
有効にすると他にもいくつかオプションが現れ、Linuxで起動するとPCIデバイス
USB controller: Intel Corporation Device 5aaa (rev 0b)が見える。しかしUDCデバイスノードは現れない。元記事のヒントをもとに、もう少し掘ってみる必要がありそうだ。