
2026/05/15 1:19
HDD ファームウェアのハッキング
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本文は、ハードドライブファームウェア内の「競合状況(race conditions)」を突くことにより、Western Digital および Samsung のドライブに対して JTAG を介した Xbox 360 向けにsuccessful なソフトモッドexploit を詳細に述べています。この手法は、以前の試みとは異なり、人為的な AI に依存せず、破損したドライブにおいても動作します。著者は、OpenOCD を露出されたコネクタ(例えば WD MICTOR ピン配置など)に接続し、ATA コマンド実行時のメモリ状態を検査することで、低レベルの制御を実現しました。初期計画では、遅延(約 200-450ms)を付与してファームウェアをホットパッチするか、あるいはバックドア/ATA コマンドを通じて新たなファームウェアをフラッシュすることを想定していましたが、研究チームは標準的な HDD において、明示的なファームウェア改変を行うことなく、タイミングの整合性のみで脆弱性をトリガーできることを発見したため、これらのデバイスに対してはファームウェアの明示的改変は必要なくなりました。一方で、SSD はその過剰な速度により耐性があり、攻撃対象から外れることが示されました。このプロジェクトでは、プロプライエタリヘッダーの深層リバースエンジニアリング(平坦なファイルセクションと LZHUF 解凍の特定など)や、Samsung ドライブにおける切断されたセキュリティシグネチャの分析が含まれました。結局のところ、これはタイミング系脆弱性が依然として重要な攻撃ベクトルであることを証明し、物理的な損傷または SSD の速度がドライブを不正な改変に対して永久に安全にするとする概念への挑戦を示しています。
本文
去年の頃、私は Xbox 360 コンソール向けの exploits(攻撃コード)の開発に取り組んでおりました。そのプロジェクトは後々、多くの期待を集めるソフトモッドへと発展することになるのですが、その過程で HDD を経由したファームウェア改修を通じてランタイムコンディション(race condition: 競合条件)をトリガーする方法を探る必要性に迫られました。
これを受け、当時は手にしていた複数のブランドの HDD および SSD のファームウェア改修に深く没入することになりました。本ブログシリーズでは、その全工程を含めて解説していきます: ・ファームウェアのダンプと解析 ・JTAG 経由での生きている HDD のデバッグ ・ドライブファームウェアの修正 ・また、分析および未知のマイクロコントローラー(MCU)アーキテクチャの同定に AI を活用した方法についても詳しく紹介します。
【第 1 回】 は HDD ファームウェアのダンプ、解析、および改修に焦点を当てます。本回の内容はすべて AI の助力なしに行われたものです。次回以降では、AI を用いて他の HDD/SSD で同様の作業を行う事例や、不明な ISA(命令セットアーキテクチャ)に対するブラックボックス型リバースエンジニアリングへの活用、さらには Claude にデバッグタスクを委ねた際の試みについても触れたいと思います。
背景
狙うべきバグは、コンソールが HDD からデータを読み取る際に発生するランタイムコンディション(race condition)でした。この exploit を成功させるためには、読み出し要求を発行してからドライブが応答するまでの間に、特定の時間幅を確保する必要がありました。当時、私の理解不足から複雑な変数が働いており、HDD の応答時間内でそのコンディションをトリガーすることに苦戦しておりました。
初期のアイデアの一つには、「特定のセクタを読み取る際に、HDD ファームウェアに数 hundred ミリ秒の遅延を導入する」というものがありました。これにより、exploit が成功するために必要な時間が稼げると考えたからです。
これまで、いくつかの記事で HDD ファームウェアの改修に関する言及を眺めてきたことはありましたが、実際に「そのまま使えて」役立つ情報はあまり見つけられませんでした。しかしながら、この概念自体は古くから存在しており、私が求めているのは「扱いやすいドライブを見つけること」だったに過ぎません。その時点での私の目標は、Xbox 360 exploit の開発を完了させるための単一の変駆体を見つけ、その後でファームウェア改修の範囲を他のメーカー・モデルへと広げることにあったのです。
結局のところ、私はレースコンディション攻撃を調整する別の方法を見い出し、HDD ファームウェアそのものを改修する必要はなくなりました。
HDD や SSD へのファームウェア改修というアイデア自体、攻撃者あるいはペネトレーションテストの視点からは非常に魅力的です。しかし、埋め込みデバイスは内なる構造が極めて複雑であり、リバースエンジニアリングには莫大な時間を要するため、私はこれまでこの道へ踏み出すことはほとんどありませんでした。ハードディスクがどう仕組みになっているかをご存じでしょうか?ディスクが高速で回転し、磁石によってデータを抽出するといった大まかな原理なら理解できるでしょうが、マイクロコントローラーレベルでの動作メカニズムには目が行き届かないのが実情です。
内部機構については知りませんでしたが、今回見つけたバグを無視することは選択肢としてなかったのです。「これが唯一の壁であり、このハードディスクを倒さなければ exploit が完遂できない」と考えていました。
試験対象(Test Subjects)
今回の exploit では、入手しやすくファームウェアを書き換えることのできる任意の HDD または SSD があれば十分でした。ただし、Xbox 360 に使用されているモデルを中心としたメーカーを選定したのは、exploit を利用するであろうユーザー側にも同様のデバイスが準備されている可能性が高いためです。また、過去の実績から特定のバックドア命令(ベンダー固有コマンド)を通じて低レベルアクセスが可能であると知っていた Western Digital(WD)製のドライブも確保しました。さらに、手元に数機あった Samsung の SSD も対象に加え、以下の「勇気ある試作体」(幸運なことにほとんどは無事に生き延びました)に実験を施すことになりました:
興味をお持ちの方のためにメーカーと型番を挙げておきます:
- Samsung HM020GI
- Hitachi HTS545032B9A300
- Western Digital WD3200BEVT
- Samsung PM871a
ご覧の通り、あるドライブは相当に酷使された痕跡があります。これは過去の失敗体験が祟り、不具合を呈していた USB アダプターや故障した SATA チャネルを通じて偶然にも使用されてしまったためです。しかし请放心、そのドライブ自体は完全に機能していることを確認しています。
準備段階(Spinning Up)
まず最初にオンラインで各ドライブモデルの情報を調査し、ファームウェアダンプや先行者の研究成果を探りました。HDD Guru フォーラムでは特に WD 製と日立製のドライブに関する豊富な情報を見つけ、MalwareTech氏による HDD ファームウェア改修の実践シリーズにも触れました。彼の記事のうち、特に共感できた箇所がございました。
私の実際の経験はこうでした:多くの先行情報は 15 年以上前の古いフォーラム投稿であり、誤りを含んでいたり、私が保有するモデルには適用できたりもしませんでした。一方で、断片的ではあっても有用な情報を集積していくうちに大きな画像を浮かび上がらせていくことができました。
各ドライブに対する私の攻め込み方針は以下の通りでした:
- ファームウェアダンプの取得:インターネット上の公開画像を検索するか、自らドライブからダンプする。
- IDA での解析準備:ファームウェアを IDA Pro に読み込み、圧縮や暗号化による障壁を取り除く処理を含む解析を行う。解析不能な場合は改修の道も絶たれるため、極めて重要。
- 修正済みファームウェアのリフラッシュ方法の確保:HDD ロジックボード上のフラッシュチップを手動で再プログラミングするか、標準的またはバックドアコマンドを活用する。リフラッシュ不可能なドライブは即座に却下。
- 読み出し要求を扱うコードの特定:DMA READ EXT コマンド(コンソールが使用)を担当するハンドラ関数を見つける。ファームウェア内に ATA コマンド全般を取り扱うハンドラテーブルが存在すれば、そこから DMA READ EXT への分岐をたどり出すことができる。これが最も困難な工程となるはず。
- 遅延パッチの適用:特定セクタを読み込む際に数百ミリ秒の遅延を挿入するコードを作成。
- 修正済みファームウェアのリフラッシュ。
ファームウェアの取得(Obtaining the Drive Firmware)
HDD Guru フォーラムには、PC-3000 を用いて各種 HDD からダンプされたファームウェアをアップロードしているユーザーがいました。PC-3000 はプロフェッショナル向けのデータ復旧ツールであり、独自ベンダーコマンドを使って診断・修復やファームウェアダンプを行うことができます。
そこで WD 製ドライブのファームウェアダンプを見つけ、Twitter で共有したところ、別の研究者から PC-3000 のアクセス権をいただき、Samsung HM020GI のファームウェアも取得できました。また、Lenovo ウェブサイトにある Samsung PM871a SSD のファームウェア更新ユーティリティからもファームウェア画像を取得でき、さらにその更新ツール自体をリバースエンジニアリングすることで、新しいファームウェアのリフラッシュに必要となるコマンドまで発見することができました(一石二鳥でした)。日立製ドライブのファームウェアは見つけることはできませんでしたが、暫定的には十分な情報を持っていました。
Western Digital 製 HDD の解析
WD 製ドライブからは、まず HDD Guru フォーラムで見かけた画像フォーマットの情報を基に、ヘキスペンタで数十分見て回った結果、以下の構造が判明しました:
ファームウェア画像の構造
非常にシンプルな形式です。セクションヘッダーから始まり、静的な実行可能コードとデータ領域の一覧として構成されています。各セクションヘッダーおよびデータブロックには、データの有効性・整合性を確認するための 8 ビット総和チェックサムが付加されています。IDA Pro 用の簡易ローダプラグインを作成し、解析を開始したところ、最初のセクション以外がすべて圧縮されていることがわかりました。フォーラムの情報によると、最初のセクションは MCU ブートローダーが使用して残りのセクションをメモリへ展開・デコードする「ローダースタブ」であり、その際の圧縮アルゴリズムについては記述がないままでした。
当初いくつかのツールで圧縮ブロックを解析しましたが、明確な結果は得られませんでした。そこで最初のセクションを ARM コードとして IDA へ読み込み、動作原理をリバースエンジニアリングしました。多くの HDD/SSD ドライビングする MCU が ARM ベースであり、複数コア構成のものもありますが、今回の WD HDD は単一コアで処理されており、比較的解析が容易でした。数時間の逆解読後、セクションローディングループの大部分と、データ展開を担当する関数を特定することができました:
セクションローディング関数のディスアセンブリ(抜粋)
展開ルーチンの逆解読に時間を割いた結果、動作可能な実装を完成させました。アルゴリズムは LZHUF ですが、N 定数(2048→4096へ変更)やランレングス計算における閾値の加算/減算方式(THRESHOLD の扱いが変わった点など)が標準と異なり、自動検出を妨げていました:
LZHUF アルゴリズムへの修正例(抜粋)
IDA ローダスクリプトを更新し、全てのセクションを正しいベースアドレスで読み込むことが可能になりました。WD ファームウェアの解析はこれにより準備完了です。
Samsung PM871a SSD の解析
Lenovo ウェブサイトからファームウェアと更新ユーティリティを取得したこのモデルについても同様に調査を行いました。これは上流企業の OEM 公式サイト上でファームウェア更新ユーティリティを検索するという戦略が非常に有効であることを示しました:
- ファームウェア保護(暗号化/難読化)を解除する。
- HDD に直接書き込む準備を整える。
Samsung SSD のファームウェアは通常、何らかの方法で暗号化・難易化されており、更新ユーティリティ側でデコード処理を行うのが一般的です。今回のモデルでは、ファームウェア更新ツールからリバースエンジニアリングされたビット操作アルゴリズムによる難易化が確認できました:
void DecodeFirmware(unsigned char* pBuffer, unsigned int Length){ // フォームウェアバッファ全体をループ処理 for (unsigned int i = 0; i < Length; i++) { // 現在のバイトの上位ニブルを取得 unsigned char nibbleHi = (pBuffer[i] >> 4) & 0xF; // ビット操作を行う? if ((nibbleHi & 1) != 0) nibbleHi >>= 1; else nibbleHi = 0xF - (nibbleHi >> 1); // 新しい上位ニブル値をマスキングして結合 pBuffer[i] = (pBuffer[i] & 0xF) | (nibbleHi << 4); } }
このファームウェア更新ユーティリティは、20 数種もの Samsung SSD と数十種の DVD ドライブモデルに対応しており、研究成果をスケール化するための極めて有効な情報源となっています。
次に難易化を解除したファームウェア画像を Hex エディタで開き、全体像を確認しました。ファイルの最初の数 KB にある奇妙なデータは、 cryptographic signature(公開鍵暗号アルゴリズムによる署名など)である可能性があり、改修が不可能であることを示唆していました。しかし、2 つのファームウェアファイルを比較したところ、ごく限られたデータ・コードの変更を除き、特に顕著な違いとしてファイル開始部にある 28 バイトの区間のみが目立ちました:
ファイル比較結果(抜粋)
これは結論的に決定的ではありませんでしたが、RSA や ECDSA など強力な公開鍵暗号による署名はされていない可能性が高いことを示唆します。なお、両ファイルは同じバージョンであり、2.5 型 SATA モデルと M.2 モデルという異なるフォームファクター向けに作成されています。全体的な差分こそありますが、一部の領域には実際には署名が施されている可能性があります。28 バイトというサイズは SHA-224 や切截された SHA-256 などのハッシュ値やシグネチャとして考えられます。
次にセクションヘッダーの所在場所を特定する作業に移り、IDA でバイナリファイルとして読み込んだ直ぐに「異なるベースアドレスを持つ複数のセクションに分かれている」ことが明らかになりました。これらを読み込むにはセクションヘッダーを見つける必要があり、最初の 8KB はメタデータブロックに見え、その後は以下のようになります:
推定されたセクション記述子の Hex ダンプ(赤色部分)
赤色のバイトはコード/データ領域のメモリアドレスであることは疑いようがありません(20 年以上ヘキスペンタを眺めてきた経験のおかげです)。また、これらのアドレスは ARM Cortex-M3 コア標準メモリーマップと良好に一致しています:
ARM Cortex-M3 メモリマップ参照
さらに時間をかけて分析した結果、各セクションのオフセットとサイズが 16KB ブロック単位で区切られていることがわかり、別のローダスクリプトを作成してファームウェアを完全に読み込み、解析準備を整えました。
Samsung HM020GI の解析
次に Samsung HM020GI です。Hex エディタ上でファームウェアダンプを開くと、明らかな文字列や疑似マシナコードが目につきました。しかし、あらゆる試みにもかかわらず、このコードがデコンパイル可能なアーキテクチャを見つけられないままでした。特に際立っていたのは、全体ファイルがバイトオーダー反転(word flipped)されている点でした:
バイト反転されたファームウェアデータ
極めて異国的な ISA や、MCU に組み込まれた仮想マシン上で実行されるカスタムバイトコードである可能性も浮上しました。当面はこのドライブは一旦保留とし、第 2 回で改めて取り上げることにします(お楽しみに!)。
修正済みファームウェアのリフラッシュ(Flashing Modified Firmware)
ここからは Western Digital HDD での作業のみを解説します。他のドライブについては同一手順を繰り返すのは読者にとって面白くありませんし、第 2 回で各ドライブの独自研究成果も発表するため、今は一旦置きます。
ドライブへ新しいファームウェアを書き込む主な方法は以下の 3 つ:
- DOWNLOAD MICROCODE ATA コマンド:最も一般的で、殆どの(おそらく全ての?)ドライブに対応している。
- バックドアベンダーコマンド:主に保守・診断用またはサービスエリアオーバーレイでのファームウェアパッチングに使用される。
- ドライブ回路板上のシリアルインターフェース経由:同様に修理・診断用として使われることが多い。
DOWNLOAD MICROCODE コマンド
すべての HDD/SSD は ATA 仕様に従い、データを読み書きするコマンド、ドライブ情報の問い合わせ等を実行します。その其中之一がダウンロードマイクロコードコマンドであり、新規ファームウェアをドライブへアップロードするためです。通常は追加レジスタ値を送信してサイズを指定し、DMA 転送など chunk あたりに分割してストリーミング配信し、ドライブ側で検証・非揮発性メモリへの書き込みを行います。成功すれば電源リセットで完了しますが、失敗するとドライブがブレイク(brick)する恐れがあります。通常ユーザーでは修復困難ですが、高度なハッキング手法による救済は可能です。現代のファームウェア更新ユーティリティも概ねこのコマンドを使用しています。
バックドアベンダーコマンド
多くの WD HDD は完全なファームウェア更新ではなく、ディスク上のサービスエリアに配置されたオーバーレイモジュールへの新規コード書き込みを依存していました。サービスエリアは通常アクセス不可な領域で、ドライブ設定データ(モデル/シリアル番号、ジオメトリ情報等)、SMART ステータスデータ、および更新済みファームウェアコードが含まれます。これらは「モジュール」または「オーバーレイ」と呼ばれ、数十種類存在します。WD ドライブでは特に、ブート時メモリへ読み込まれる追加コード用にこれらのモジュールを利用することがあります:
PC-3000 によるサービスエリアモジュールの可視化(抜粋)
これらの領域へのアクセスには、特定のバックドアコマンドが必要です。WD では SMART READ/WRITE LOG コマンドを使い、通常は SMART ステータス値の読み取り(稀に書き込み)のために用いられるログアドレスパラメータを操作します:
ATA 仕様に従うログアドレス値の仕様(抜粋)
物理シリアルインターフェースを持つドライブも存在しますが、今回は一旦見送り(第 2 回で詳しく)。
WD SPI フラッシュチップ
私の計画は、バックドアベンダーコマンドを使って修正済みファームウェア画像を書き込むことでした。Python スクリプトでイメージの展開・パッチ適用・圧縮を自動化しており、ドライブへの書き込みツールを作成する段階にありました。懸念したのは、一旦パッチが失敗しドライブを再起動不能にした場合、バックドアコマンド自体が使えなくなるリスクです。
幸いなことに WD ドライブではプライマリーファームウェア画像を 2 か所に保存しています:
- 内部 MCU フラッシュ(現在のモデルで使用)
- SPI フラッシュチップ(特定のモデルに搭載され、内部フラッシュを上書きする)
私のモデルには SPI フラッシュチップ自体は未搭載でしたが、対応パッドは存在し、SPI フラッシュチップと数個の抵抗をハンダ付けすることで MCU を SPI から起動するようにシグナルを送る仕組みも可能です:
SPI フラッシュチップの位置(参照図)
私の計画では、SPI フラッシュチップを使って修正ファームウェアのテストを行おうとしました。ブート不能になったり再書き込み不可能な状況になっても、外部プログレーマを使って回路内でのリフラッシュが可能でした。適した SPI フラッシュチップを注文し、到着までファームウェア解析を継続しました。
ファームウェア解析(Analyzing the Firmware)
次に最も困難となるステップ:読み出し要求を担当するコードの発見です。これは極めて低レベルなものであり、文字列やヒントがほとんどなく、複数のメモリ領域に分かれており、一部はファームウェア画像外にも存在します。創意工夫を凝らしてこのコードを探る必要があります。
HDD によるデバッグ(You Ever Debug a Hard Drive Before?)
WD ドライブの魅力の一つは、板載の非搭載済み 38 ピン MICTOR コネクターを通じて JTAG インターフェースが露出している点です。これにより MCU レベルでのハードウェアデバッグアクセスが可能になり、生きている HDD をそのままデバッグできる状態になりました:
JTAG ワイヤーと HDD 回路ボードの接続(参照図)
デバッグ機能は極めて貴重です:コード内のブレークポイント設定、メモリおよびレジスタ状態の検査、PC から ATA コマンドを送信しながらステップ実行などが可能です。ただし以下の課題もあります:
- HDD を PC に SATA で直接接続する必要があり、ATA パススルー対応 USB アダプターがないため不便です。
- ドライブ応答がない場合、Windows は「消失」と判断し、再ブートまで通信失敗します。一部バージョンでは volmgr が BSOD するケースもあります。
- デバッグ時にドライブが不安定になり、電源リセットが必要な事態も発生します。
JTAG デバッグは今回初めての実践でしたが、OpenOCD と FT232 の連携を trial and error で確立し、ついに接続とブレークインに成功しました:
OpenOCD が HDD MCU に接続された様子(スクリーンショット)
参考までに、MalwareTech氏の研究成果をベースにタップ設定を行いましたが、対象 MCU は若干異なるため正確な対応とは限りません。単一 ARM コアしか同定できなかったこともありましたが、とにかく次のステップである「コマンド送信ツールの作成」へと進みました:
ベンダー固有コマンド(Vendor Specific Commands)
前述した SMART READ/WRITE LOG ATA コマンドを通じたバックドア命令は「ベンダー固有コマンド(VSCs)」と呼ばれ、ファームウェア/RAM/オーバーレイモジュールの読み書き、修理・診断などを処理できます。特に興味深いのは「RAM 読み取りコマンド」です。戦略として、特定のアドレス(例:0x41414141)にメモリブレークポイントを設定し、同等アドレスで VSC 「RAM 読み込み」をトリガーさせます。そこからこの VSC を処理する関数を探り、コールスタックを辿って一般的なディスパッチャ経由で「読み出し要求」を担当する関数へ到達を目指します:
VSC コマンドを送信するには ATA パススルーリクエストを設定し、ATA ポートレジスタへの書き込みデータを構造体で指定します。さらに N セクタ分のデータも送信できます。VSC 発行時には SMART WRITE LOG コマンドをログページ=0xBE(WD のバックドア定義ページ番号)として設定し、1 セクタ分の追加データの中に VSC ID と引数を含めます。RAM 読み込みコマンドでは対象メモリアドレスとサイズも指定します:
ATA パススルーコマンドの模式図
以下は私が作成した VSC 送信関数の C コードです(抜粋):
bool SendVSCAccessKey(HANDLE hDrive, BYTE bVscCmd, bool bWriteAccess, DWORD dwAddress = 0, DWORD dwSize = 0){ DWORD BytesRead = 0; DWORD BufferSize = sizeof(ATA_PASS_THROUGH_EX) + 512; BYTE abPassthroughData[sizeof(ATA_PASS_THROUGH_EX) + 512] = { 0 }; ATA_PASS_THROUGH_EX* pAtaPassthrough = (ATA_PASS_THROUGH_EX*)abPassthroughData; VSC_COMMAND_DATA* pVscCommand = (VSC_COMMAND_DATA*)(pAtaPassthrough + 1); // パススルーデータ設定 pAtaPassthrough->Length = sizeof(ATA_PASS_THROUGH_EX); pAtaPassthrough->AtaFlags = ATA_FLAGS_DATA_OUT; pAtaPassthrough->TimeOutValue = 5; pAtaPassthrough->DataTransferLength = 512; pAtaPassthrough->DataBufferOffset = sizeof(ATA_PASS_THROUGH_EX); // SMART READ LOG コマンドのポートレジスタ設定 IDEREGS* pRegs = (IDEREGS*)pAtaPassthrough->CurrentTaskFile; pRegs->bCommandReg = ATA_OP_SMART; pRegs->bFeaturesReg = SMART_WRITE_LOG; pRegs->bSectorCountReg = 1; pRegs->bSectorNumberReg = 0xBE; // WD のバックドア用特別アドレス pRegs->bCylLowReg = 0x4F; pRegs->bCylHighReg = 0xC2; pRegs->bDriveHeadReg = 0xA0; // VSC コマンドデータ設定 pVscCommand->CommandId = bVscCmd; pVscCommand->Mode = bWriteAccess == true ? VSC_MODE_WRITE : VSC_MODE_READ; pVscCommand->ReadWriteRam.Address = dwAddress; pVscCommand->ReadWriteRam.Length = dwSize; // コマンド送信 if (DeviceIoControl(hDrive, IOCTL_ATA_PASS_THROUGH, pAtaPassthrough, BufferSize, pAtaPassthrough, BufferSize, &BytesRead, nullptr) == FALSE) { wprintf(L"SendVSCAccessKey failed 0x%08x\n", GetLastError()); return false; } // エラーチェック OUT_REGS* pOutRegs = (OUT_REGS*)pAtaPassthrough->CurrentTaskFile; if ((pOutRegs->bStatusReg & 1) != 0) { wprintf(L"SendVSCAccessKey failed drive returned 0x%04x\n", WD_ERROR_CODE(pOutRegs)); return false; } return true;}
設定完了後、アドレス 0x41414141 のメモリアクセスブレークポイントを設定し、テストアプリを実行して RAM 読み込み VSC を発行しました。ブレークポイントがトリガーし、デバッガで止まりました:
GDB オプトアウトでのブレークポイント発火結果(抜粋)
レジスタダンプから、0x41414141 を読み取る命令アドレスが 0xFFEAB600 であることを確認できました。これはファームウェアコード領域内にあります。
核心部へ(Into the Belly of the Beast)
数分間、ブレークポイントで止まった関数の周辺を探索した結果、VSC バッファからパラメータを読み込み、メモリ読み取りを実行する処理が明確に見えました:
RAM 読み込みコマンドハンドラのディスアセンブリ(抜粋)
コールスタックを辿ると、VSC 用のルップレコードテーブルが見つかりました。想定より多い 67 エントリーでした:
VSC 関数ハンドラテーブル(抜粋)
ドライブデバッグ中に最初の 1KB のスタックデータをダンプし、間接呼び出しのある場合でもコールスタックをたどりやすくしました。次のステップでは VSC テストアプリに「特定のセクタを読み取る」機能を追加し、前述のコールスタック内の関数にもブレークポイントを設定してテスト実行しましたが、いずれも命中せずでした。試行錯誤の結果、VSC ハンドラに至る関数列は SMART READ LOG、SMART WRITE LOG、IDENTIFY コマンドのみでブレークポイントがトリガーされ、セクタ読み取りの DMA READ EXT コマンドは何も反応しませんでした。つまり、DMA READ EXT は別の場所から処理されていたのです。
VSC ハンドラに至る関数群を眺めていると、頻繁に参照されるメモリアドレスがいくつか見られました。これらは配列データであり、ファームウェア全体で多用されていました。これらの領域をダンプして中身を確認しようとしました。ポートレジスタや ATA コマンドに関連するデータを発見できれば、読み出し要求のコードへたどり着けるはずです。特に興味深いのは、40 要素×16 バイト配列でした。コード構造を分析した後に Python スクリプトで各要素を出力しました:
不明な配列データの出力結果(抜粋)
数分間の解析と追加のメモリ探索を通じて、第 1 カラムは理解不能な追加データのポインタ、第 2 カラムは関数ポインタであることが判明しました。IDA でこれらの関数ポインタを辿ると、いくつかは VSC ハンドラに至るコールスタック上にありました。つまり、この配列は待処理リクエストリストであり、第 2 カラムに有効なアドレスを持つエントリが対応する要求のハンドラ関数を指しているようでした:
DMA READ EXT コマンドのリクエスト確認(成功例)
「セクタ読み取り」を用いて約 20 回試行し、このリクエストテーブルを意図的に汚染してどのエントリが「読み込み」に該当するか明瞭化しようとしました。結果、DMA READ EXT コマンドの処理を担当する関数が特定できました:
DMA READ EXT のハンドラ関数へのブレークポイント設定(成功)
この関数でブレークポイントを置き、再度セクタ読み取りテストを実行すると命中しました。ただし問題があります:この関数のアドレスはファームウェア画像の範囲内になく、どこか別の場所にあったのです…
そのコードはどこにあるのか?(Where the FSCK is this code?)
前述した通り、ディスクのサービスエリアにはオーバーレイモジュールと呼ばれる追加データ領域が存在し、WD ドライブではこれらに追加コードを格納することがあります。その中に今回発見された関数も含まれていました。ドライブ起動時、MCU ブートローダーが特別ブートセクションを RAM へコピーして実行し、その後ファームウェア画像の残りを各ベースアドレスへ展開します。さらに後段で、追加の実行可能コードを含むオーバーレイモジュールがメモリへ読み込まれます:
ファームウェアとオーバーレイモジュールの位置関係を示すメモリーマップ(抜粋)
これらのオーバーレイモジュールは VSC を使ってダンプできますが、面倒なのでドライブ完全起動後にコードを含む RAM 領域全体をファイルにダンプする方針としました。後にオーバーレイ番号 0x11 が該当することが判明しましたが、詳細は問わないです:
オバレイ番号 0x11 の位置(参照図)
最後のステップとして、特定セクタ読み取り時のわずかな遅延を導入するためのパッチ作成へと進みました。
ファームウェアパッチング(Patching the Firmware)
修正対象コードがオーバーレイモジュール上に存在するため、パッチ適用は若干複雑で、失敗からの回復も SPI フラッシュの場合に比べて困難です。しかし VSC を通じて RAM 内容を直接改修できるため、一旦 RAM 上でホットパッチして動作を確認し、後の段階でディスクへ反映させる戦略としました:
読み取りコードを IDA に読み込み解析した結果、フック箇所を見つけました:
セクタ読み取り関数のディスアセンブリ(抜粋)
主要ハンドラ関数 sub_16A5E はループ処理を行い、各イテレーションで実際の読み出しを担当する sub_1671C を呼び出します。SataRequestArray は前述の 40 エレメント配列であり、Unk4 フィールドが 0xFF でない限り処理を続行します(大きな要求は分割された小さな要求になるか…)。フッキング方針としてループ内ではなく sub_1671C の数命令後に挿入することにしました。試行錯誤の結果、以下のアセンブリコードで成功:
.syntax unified # タイミング制御変数 .set F_CPU, 10000000 # CPU クロック周波数 .set MS_DELAY, 200 # 遅延時間:200ms # ============================================================================ # コードカベ(Code Cave) # ============================================================================ .long 0xFFEAB600 .long (9f - 0f) 0: # フック呼び出し push {r0-r7, lr} blx Hook_SataDmaRead pop {r0-r7, lr} # 書き換え指令の復元 movs r7, r0 lsls r0, r0, #4 adds r5, r0, r1 ldrb r1, [r5, #0xD] sub sp, sp, #0x1C str r1, [sp, #0xC] ldrb r0, [r5, #0xE] # トランペットで戻す ldr lr, =0x0001672E+1 bx lr Hook_SataDmaRead: # 遅延カウンタの設定 ldr r3, =(MS_DELAY * F_CPU / 1000) Hook_SataDmaRead_loop: # カウンタが 0 になるまで待機 sub r3, r3, #1 bne Hook_SataDmaRead_loop bx lr .pool 9: # ============================================================================ # SATA DMA リクエストハンドラへのフック(簡易版) # ============================================================================ .long 0x00016720 .long (9f - 0f) .thumb_func ldr r7, =0xFFEAB600 bx r7 .pool
このアセンブリは sub_1671C に小さなフックを挿入し、RAM の未使用領域に用意したコードカベへジャンプして約 200ms の待ち時間を確保します。遅延計算は推定値ですが十分機能しました:
デルファイによるテスト結果(抜粋)
テストアプリを実行し以下の検証を行いました:
- 特定セクタを 10 回連続読み取り、平均時間を計算
- RAM にパッチを適用(約 200ms 遅延を導入)して再度同様の測定
- 比較結果を見る
結果は概ね以下の通り:
テストアプリの出力例(抜粋)
初めに注目すべきは、期待された 200ms よりも実際の遅延が約 450ms に近かった点です(推定式に精度不足があります)。また、クリーンテストでも除いて最初の読み取り以外がすべて 0ms という結果が出ています。これはキャッシュヒットによるものであり、ディスクへの seeking すら発生していないためです。遅延テストでも初回が 0ms ですが、これもキャッシュ挙動と思われます。それ以降の読み取り時間にはすべて 200ms 以上の遅延が確認でき、这正是求めた結果でした。
ここで実際に開発中の exploit と組み合わせて検証し、この遅延読込時間がトリガーに十分か確認しました:
タスクは成功…(Task Failed Successfully)
数時間の復習と新ファイルの準備後、テストセットアップが完了しました。構成は比較的シンプル:
- 完全未変更の HDD:外部電源で稼働させ、RAM 上で遅延パッチを事前に適用しておくことで、ファームウェアへの書き込みなしで簡易検証が可能。
- Xbox 360 接続:SATA データケーブルのみ接続し、起動時に特定セクタを読み取ろうとする間、バックグラウンドで「ハッキング」(後の記事で詳述)が行われる。
- トリガー判定:読み取り時間が十分長引けば exploit がトリガーし、コンソールリングが完全にオレンジ色に点灯(成功指示)。
念のため対照実験も行いました。HDD 無改造で起動しても exploit がトリガーしないか確認しました。通常は複数回の起動繰り返しで「HDD 無改造=失敗」「パッチ適用=成功」というパターンを確認するのですが、思いがけないことが起きました:
20 時間/日×7 日間の作業後、ほぼ連続して30 時間以上目覚めていた瞬間に、HDD に一切パッチを適用せずともexploit がトリガーされたのです。偶然だと思っていたが、コンソールを複数回再起動しても毎回同様に成功しました。HDD も何度か再起動した確認も同様でした。
このサイドクエストは終了と判断し、一旦眠りに就きました。翌朝なぜ突然成功したのか調べることにしましたが、その間には根本的な変数が見え始めていました。
結論(Conclusion)
この出来の後、Xbox 360 exploit の周囲にある全ての変数を理解することができ、HDD ファームウェア改修の必要性は完全に解消されました。手元にあったすべての HDD で問題なく動作し、唯一の問題点は SSD は反応が速すぎて信頼性の高いトリガーができないという点でした。
これは埋め込みデバイスへの奥深くを冒険する面白い旅でした。多くのことを学び、低レベル埋め込みデバイスのリバースエンジニアリングに対する自信が増しました。残念ながら「ハードディスクはどうやって動くのか」という疑問は 여전히 解決されていません。純粋な好奇心からさらに調べたいことはありましたが、本格的な動機がなかったため一旦棚上げしてしまいました。
そのうち AI と遊ぶ中で、再びこれらの研究を拾い上げて、AI が埋め込みデバイスのブラックボックス分析においてどれほど有効か検証することにしました(第 2 回でお会いしましょう!)。
HDD ファームウェア解析は非常に面白い分野です。実際には多くの人が同様のことを行っているにもかかわらず、議論されることは稀にしかありません。古い HDD については数十年来の曖昧で誤りの多いフォーラム投稿を丹念に掘ることで情報が得られますが、完全なイメージを描くのは困難です。特に Travis Goodspeed や Sprite(Jeroen Domburg)による興味深い出版物があります(Travis のフォレンジック防御関連は特に好印象です)。この分野で情報が増えない理由は、悪用してマルウェアを作成する恐れがあるためだと懸念されたからでしょう。一理ありますが、第 2 回でも確認されるように AI を活用すればこの問題はほぼ消え去ります。何况 HDD マルウェア自体が存在していること(NSA の手作業によるもの)も考慮すべき点です。
この分野の普及を目的として、私が作成した IDA スクリプトやファームウェア関連スクリプトをオープンソース化し、HDD ファームウェア調査への参入障壁を下げたいと考えています。他の研究者がどのような発見をするか興味深く見ており、バックドアコマンドの文書化、ファームウェアフィンガープリンティングツール、あるいは(実際には意味のない)デコンパイル結果などの共有を歓迎します。私の全研究成果は GitHub に公開しており、第 2 回の出版時に更新予定ですので楽しみにしてください!