エクスプロイト&脆弱性
「WannaCry」を拡散させた脆弱性攻撃「EternalBlue」の仕組みを解説
世界規模でさまざまな業種の法人に大きな影響を及ぼしたこの脆弱性攻撃ついて、その仕組みのより深い理解に役立つ技術的な情報を提供するため、本稿では EternalBlue の脆弱性攻撃の詳細を解説します。世界規模でさまざまな業種の法人に大きな影響を及ぼしたこの脆弱性攻撃ついて、その仕組みのより深い理解に役立つ技術的な情報を提供するため、本稿では EternalBlue の脆弱性攻撃の詳細を解説します。
脆弱性攻撃ツール「EternalBlue」は、2017 年 5 月中旬に世界的に拡散した暗号化型ランサムウェア「WannaCry」の事例で大きく注目されました。「WannaCry」の後には、ファイルを利用しないランサムウェア「UIWIX」、ワーム「EternalRocks」、仮想通貨発掘ボット「Adylkuzz」など、EternalBlue を利用した「模倣犯」が連続して登場しました。
この脆弱性を利用すると、攻撃者は、標的の PC 上で任意のコードを実行できます。この脆弱性は Microsoft のセキュリティ情報「MS17-010」で発表された更新プログラムによって 3 月の時点で既に修正されており、危険度と複雑度はハッキング集団「Shadow Brokers」によって公開されたその他の脆弱性と同様、中~高程度と考えられていました。
世界規模でさまざまな業種の法人に大きな影響を及ぼしたこの脆弱性攻撃ついて、その仕組みのより深い理解に役立つ技術的な情報を提供するため、本稿ではEternalBlue の脆弱性攻撃の詳細を解説します。
■脆弱性の解析
EternalBlue は、「Windows SMB1.0(SMBv1)」サーバが特定のリクエストを処理する際のセキュリティ上の欠陥です。より具体的には、WindowsSMBv1 のコード内のカーネル関数「srv!SrvOs2FeaListToNt」による「File ExtendedAttribute(拡張ファイル属性、FEA)」処理時に「Large Non-PagedPool(ラージ非ページプール)領域」のバッファオーバーフローを発生させる脆弱性です。「非ページプール」とは、OS の中枢機能である「カーネル」が使用するメモリのうちページアウト(メモリ領域を一定のページサイズに区切ってファイルに書き出すこと)できない領域を指します。このうち、ページサイズを越えるものを特にラージ非ページプールと呼びます。ここで利用される関数「srv!SrvOs2FeaListToNt」は、FEA を「NTFEA(Windows NT FEA)」に変換する際、関数「srv!SrvOs2FeaListSizeToNt」を呼び出して FEA リストのサイズを計算します。実はここに不具合があり、下記のようにオーバーフローが発生します。
- 関数「srv!SrvOs2FeaListSizeToNt」がFEA リストのサイズを計算し、受け取った FEA リストのサイズを更新する。
- この時、間違ったデータ型(WORD型、2 バイトの符号なし整数を扱う C 言語のデータ型)への型変換により、FEA のサイズが元の値より大きくなる。
- 不正確なリストサイズにより、FEA リストを NTFEA リストに変換する繰り返し処理時、非ページプール上でオーバーフローが発生する。
■オーバーフローの解析
本稿では、特にビルド番号「6.1.7601.17514_x86」のWindows 7 OS 上で SMB トラフィックを処理するドライバ「SRV.sys」のオーバーフローを解析しました。脆弱なコードは、関数「srv!SrvSmbOpen2」の中で実行されます。スタックトレースの結果は以下の通りです。
番号 | ChildEBP | RetAddr | 関数 |
00 | 94527bb4 | 82171149 | srv!SrvSmbOpen2 →SrvOs2FeaListSizeToNt() |
01 | 94527bc8 | 821721b8 | srv!ExecuteTransaction+0x101 |
02 | 94527c00 | 8213b496 | srv!SrvSmbTransactionSecondary+0x2c5 |
03 | 94527c28 | 8214a922 | srv!SrvProcessSmb+0x187 |
04 | 94527c50 | 82c5df5e | srv!WorkerThread+0x15c |
05 | 94527c90 | 82b05219 | nt!PspSystemThreadStartup+0x9e |
06 | 00000000 | 00000000 | nt!KiThreadStartup+0x19 |
オーバーフローを解析するために、以下の位置にブレークポイントを設置しました。
bp srv!SrvSmbOpen2+0x79 ".printf \feasize: %p indatasize: %p fealist addr:%p\\n\",edx,ecx,eax;g;"
プログラムがブレークポイントに到達した時点における各変数の値は以下の通りです。値は十六進数で、括弧内に十進数を表示しています。
- feasize(FEAのサイズ):00010000 (65536)
- indatasize(送信データの合計バイト数):000103d0 (66512)
- fealist addr(FEAリストのアドレス):89e980d8
この時点ではまだ、「NT Trans Request(データ送信などに利用される SMB のコマンドの1つ)」の「Total DataCount(送信データの合計バイト数、66512)」は、送信する FEA リストのサイズ(65536)よりも大きくなっています。
ここで注目すべき点は、図 2 のように送信データへのポインタが FEALIST 構造体に変換される点です。
送信データのバッファを変換した後、「FEALIST -> cbList」の値となる FEA サイズは 00010000(65536)になります。次に SMB ドライバが FEA リストを NTFEA リストに変換する際に必要なメモリのバッファ領域が確保されます。この確保のため、関数「srv!SrvOs2FeaListSizeToNt」を呼んで NTFEA リストのサイズが計算されます。
戻り値を確認するために、以下のようにブレークポイントを設置しました。
bp srv!SrvOs2FeaListToNt+0x10 ".printf \"feasize before: %p\\n\",poi(edi);r$t0 = @edi;g;"bp srv!SrvOs2FeaListToNt+0x15 ".printf \"NTFEA size: %p feasize after:%p\\n\",eax,poi(@$t0);g;"
プログラムがブレークポイントに到達した時点における各変数の値は以下の通りです。値は十六進数で、括弧内に十進数を表示しています。
- feasize (変換前のFEAのサイズ): 00010000 (65536)
- feasize (変換後のFEAのサイズ): 0001ff5d (130909)
- NTFEA size(NTFEAのサイズ): 00010fe8 (69608)
結果、FEALIST -> cbList の値となる FEA サイズが 0x10000 (65536)から 0x1ff5d(130909)に更新されていることを確認しました。では、どこで不正確な計算が行われたのでしょうか。図 3 が不具合を発生させるコードです。
図 3 の 40 ~ 48 行目は計算結果が不正確になる例です。元の FEA リストのサイズが更新されたため、NTFEA リストに値をコピーする繰り返し処理が、戻り値「v6(値は00010fe8)」で表される NTFEA のサイズを越えてしまいます。この関数が 28 行目あるいは 21 行目で値を返す場合は、FEA リストが更新されないことにも注意してください。なお、EternalBlue 利用による上記の条件以外で変数「v1」が更新されるケースは、FEA リストの最後に余分のバッファ領域があり、かつそれがもう1つの FEA を格納するには不十分な場合です。
■カーネルメモリの解析
ラージ非ページカーネルプールでのバッファオーバーフロー発生時、カーネルメモリでは次の事象が確認されます。関数「SrvOs2FeaListSizeToNt」の戻り値から、NTFEA リストを格納するために必要なサイズは 00010fe8 (69608) となります。このため、SRV.sys では、ページサイズより大きなカーネルプールを確保する必要があります。FEA リストが NTFEA リストに変換される際に起きている事象を正確に追跡するため、下記のようにブレークポイントを設置しました。
bp srv!SrvOs2FeaListToNt+0x99 ".printf \"NEXT: FEA: %p NTFEA:%p\\n\",esi,eax;g;"bp srv!SrvOs2FeaToNt+04d ".printf \"MOV2: dst: %p src: %p size:%p\\n\",ebx,eax,poi(esp+8);g;"
bp srv!SrvOs2FeaListToNt+0xd5
つまり、関数「SrvOs2FeaListSizeToNt」が呼び出されてプール領域が確保されると、FEA リストの各要素を変換する繰り返し処理の間、この関数「SrvOs2FeaToNt」が実行されます。この関数「SrvOs2FeaToNt」の中にはメモリブロックを移動する関数「_memmove」が2 回使われており、バッファのコピー操作の全てはこの関数を利用して実行されます。図 4 は、上述したブレークポイントを用いた FEA リスト変換時のスタックトレースです。なお、この処理には非常に時間がかかる点にもご留意する必要があります。
スタックトレースが終了し、プログラムが「srv!SrvOs2FeaListToNt+0xd5」に設置したブレークポイントに到達すると、バッファオーバーフローを解析するための全ての情報が出揃います。データ部の始まりには空の FEA 構造体が並んでおり、605 回のゼロサイズのコピー処理が実行されます。606 回目にコピーされる FEA のサイズは F383(62339)で、結果的にコピー操作は「85915ff0」の箇所で終了します。
606 回目のコピー操作が終わると、バッファの終端「85905008 + 10FE8 =85915FF0」に到達しますが、ここでもう一度繰り返し処理が発生します。今回の場合、サイズは A8(168)で次のメモリ領域である「SRVNET.sys」のプールが上書きされてしまいます。
607 回目にコピーされる FEA は壊れているため、サーバは STATUS_INVALID_PARAMETER (0xC000000D)を返します。この時、NT Transaction の最後の FEA がサーバに送信されます。
■EternalBlueの脆弱性攻撃
オーバーフローは、非ページプールメモリで発生します。より厳密には、ラージ非ページプールメモリで発生し、このラージ非ページプールにはプールヘッダが存在しないため、この非ページプールを使い切った後、別のドライバが使用しているメモリ領域が確保されます。
したがって今回の脆弱性攻撃は、オーバーフローしたバッファの次のメモリ領域を操作することで成立します。つまり、EternalBlue が SRVNET ドライバのバッファを操作するという手口です。ただしこの際、両方のバッファがメモリ内でアライメントしている必要があります。非ページプールのアライメントを取るため、EternalBlueは、カーネルプールに下記のようにデータを配置します。
- 複数の SRVNET バッファを作成(プール領域を確保)
- SRV バッファをコピーするための未使用領域を作成するためにいくつかのバッファを開放する
- SRVNET バッファをオーバーフローさせるために SRV バッファを送信する
■脆弱性攻撃の仕組み
バッファオーバーフローを起こす脆弱なコードは、カーネルメモリの非ページのメモリ領域で動作します。これはまたラージ非ページ領域でも動作します。これらの非ページプールは、ページ領域の先頭にプールヘッダを持たないため、これらを利用するEternalBlueの脆弱性攻撃の場合、特別な技術が行使されます。この場合、図 6 のようにオーバーフロー領域に確保されるヘッダ部を書き換える必要があります。
今回の脆弱性攻撃の考え方は、端的には、複数の SRVNET バッファの作成(「Kernel Grooming(カーネルグルーミング)」)がメモリ上に生じた状態に近いと言えます。なお、この説明では、解析結果の悪用を防ぐため、その他の詳細を意図的に省略しています。
■EternalBlue の脆弱性攻撃経路
EternalBlue の脆弱性攻撃では、図 7 のような手順でPCやネットワークの脆弱性が利用されます。
この脆弱性攻撃ではまず、最後のパケットを除く SRV バッファが送信されます。これは、トランザクションの最後のデータがサーバに届いた際にラージ非ページプールバッファが作成されるようにするためです。SMBサーバは、すべてのトランザクション情報を読み込むまで入力バッファのデータを蓄積します。なお、読み込まれるすべてのトランザクション情報は、最初の TRANS パケットで指定されます。この読み込み完了後、SMB サーバがデータ処理を開始します。今回の場合、このデータは、「CommonInternet File System(CFIS)」を介して読み込まれ、「SrvOpen2」に渡されます。
この時点で、サーバがすべての送信データを受け取り、SMB ECHO パケットに送信されたことが確認されます。攻撃対象のネットワークの回線速度が遅い場合、この ECHO コマンドが重要になります。
今回の解析の場合、初期データ送信の時点では、メモリにはまだ「脆弱なバッファ」は作成されていません。次に SRVNET バッファの直前に脆弱な SRV バッファを確保するため、カーネルグルーミングが実行されます。このカーネルグーミングは以下のような手順を取ります。
- FreeHole_A:SMBv1パケットを送信してカーネルホール A の作成を開始
- SMBv2_1n:SMBv2のパケット群を送信
- FreeHole_B:新たにカーネルホールを作成するために別のバッファを送信(別のホールを作成するために、前のホールを解放する前に送信)
- FreeHole_A_CLOSE:バッファを解放するために接続を切断し、その後、未使用領域を作成するために Aを終了
- SMBv2_2n: SMBv2のパケット群を送信
- FreeHole_B_CLOSE:バッファを開放するために接続を終了
- FINAL_Vulnerable_Buffer:脆弱なバッファの最後のパケットを送信
こうして SRVNET バッファと SRVNET の一部が上書きされる直前、メモリ上に脆弱なバッファが作成されます。そして最後の FEA は壊れているため、FEA リストから NTFEA リストへの変換はエラーを返します。今回の場合、サーバは STATUS_INVALID_PARAMETER(0xC000000D)を返します。
■被害に遭わないためには
EternalBlue が世界中の個人や法人に深刻な被害を与えた多くのマルウェアの侵入口となったことを考えると、パッチを適用し PC とネットワークを最新の状態に保つことがいかに重要であるかがわかります。サポートが終了した OS も含め、EternalBlue に対するWindows の修正パッチは既にリリースされています。
PC とネットワークに対する通常のパッチ管理の他、侵入検知および予防システムの導入、使われなくなったプロトコルとポート(例:445)の無効化、ネットワークトラフィックの監視、エンドポイントの保護、被害軽減のための情報のカテゴリ分けやネットワークのセグメンテーション のような対策の実施が推奨されます。未知の脆弱性に対しては、仮想パッチも有効です。
■トレンドマイクロの対策
サーバ向け総合セキュリティ製品「Trend Micro Deep Security™」をご利用のお客様は、このような脆弱性を利用する脅威から保護されています。また、弊社のネットワーク挙動監視ソリューション「Deep Discovery™ファミリー」のサンドボックスやファイル解析エンジンにより、他のエンジンやパターンの更新がなくても、この脅威をその挙動で検出することができます。
EternalBlue に対するトレンドマイクロの対策の詳細は下記テクニカルサポートページをご参照ください。
- https://success.trendmicro.com/solution/1117192(英語)
- https://success.trendmicro.com/solution/1117391(英語)
- 「MS-17-010: EternalBlue’s Large Non-Paged Pool Overflow in SRV Driver」
by William Gamazo Sanchez (Vulnerability Research)
参考記事:
翻訳:澤山 高士(Core Technology Marketing, TrendLabs)
- 「MS-17-010: EternalBlue’s Large Non-Paged Pool Overflow in SRV Driver」