セキュリティ・コンサルタント
村上 純一

 前回に引き続いてルートキットで利用される技術について紹介したい。今回紹介するのは,「Shadow Walker」という技術だ。前回ルートキットの技術を二つに分類したが,これはそのうちの実行パスの改ざんに当たる技術といえる。

 Shadow Walkerは,Sherri Sparks氏とJamie Butler氏によって2005年に米国で開催されたBlackHatブリーフィングスで発表されたもので,メモリーへのアクセスをフィルタし,メモリー上のデータを隠ぺいする技術である。

 これを利用することで例えばシグネチャ・ベースのメモリー・スキャン(※メモリー上からマルウエアのシグニチャにマッチするデータを検索する手法)からカーネル空間にロードされたルートキットのコードを隠ぺいすることができる。

 Shadow Walkerは,i386以降のx86プロセッサが備える仮想記憶の仕組みを巧妙に利用することで実現する。そこで,仮想記憶の仕組みを解説しながらShadow Walkerの原理を説明したい。

メモリーの扱いを容易にする仮想アドレス

 まずShadow Walkerを理解するための前提として物理アドレスと仮想アドレスを知る必要がある。物理アドレスは,コンピュータに搭載されている物理メモリーにおけるメモリー・セルの位置を特定するものであり,その上限値はCPUのアドレス・バスの幅に制限される。仮想アドレス(リニア・アドレス)は,アプリケーションがメモリーの物理的な位置を気にせず,メモリーを利用できるようにするために考えられた仮想的なアドレス空間である。アドレス空間の大きさは,CPUのアーキテクチャに依存し,32ビットCPUでは4Gバイト(2の32乗)のアドレス範囲を取り扱うことができる。

 i386以降のx86プロセッサにはセグメンテーションとページングの2種類のメモリー管理機構が備わっている。セグメンテーションは,仮想アドレス空間をセグメントと呼ばれる区画に分割して管理する方式である。セグメントごとにアクセスに必要な特権レベルや読み書き・実行などの属性を設定することができる。WindowsをはじめLinuxなどの一般的なOSの多くは,セグメンテーションによるメモリー管理の煩雑さを回避するために,セグメントとしてコード,データともに全領域を指定しており,実質的にはセグメンテーションを活用していない(図1)。

図1●Windows XPの仮想アドレス メモリー空間を4Kバイト(=4096バイト)に分割したページとして管理する。プロセスから見えるメモリー空間は仮想アドレスとして扱われ,メモリー物理的な位置(物理アドレス)と全く関連性がない。仮想アドレスと,物理アドレスをマッピングするページ・テーブルによって,これらを関連付けている。
図1●Windows XPの仮想アドレス メモリー空間を4Kバイト(=4096バイト)に分割したページとして管理する。プロセスから見えるメモリー空間は仮想アドレスとして扱われ,メモリー物理的な位置(物理アドレス)と全く関連性がない。仮想アドレスと,物理アドレスをマッピングするページ・テーブルによって,これらを関連付けている。

ページング機構で物理メモリーを超えたデータを扱う

 ページングは仮想アドレス空間をページと呼ばれる単位に分割し,ページと物理アドレスをページ・テーブルと呼ばれるアドレス変換テーブルを使って動的に対応付ける方式である。

 ページングの最大の利点は,仮想アドレスと物理アドレスを分離できる点にある。その時点でOSやプロセスの実行に必要となるページのみを物理アドレスと対応付けておけば良いため,物理アドレスが4Gバイト未満の場合でも,仮想アドレスは4Gバイトのアドレス範囲を取り扱うことができる。もし,プロセスのアクセスしたページがアクセス時点で物理メモリーに対応していない場合は,処理をいったん止め,該当のページにデータを取り込んでから,処理を再開する。

 具体的には,ページに物理メモリーが対応していない場合,ページ・フォルト例外が発生してOSのページ・フォルト例外ハンドラに処理が移る。例外ハンドラは,使用頻度の低いページに対応している物理メモリのデータをハードディスクに待避し(図2(1)スワップ・アウト),必要なデータを物理メモリに読み込んだ後(図2(3)スワップ・イン),ページ・テーブルを適切に更新して処理をプロセスに戻す(図2(4))。こうした仕組みによりプロセスは,何ごともなかったかのようにページにアクセスできる。

図2●ページングの仕組み 仮想アドレスの対となる物理アドレスがない場合,物理メモリー上にあるあまり使われていないデータを一時ハード・ディスクに退避する一方,必要なデータを読み出し物理メモリーに書き込む。さらにページ・テーブルを修正し,仮想アドレスにその物理アドレスを割り当てる。この処理は例外ハンドラが実施する。
図2●ページングの仕組み 仮想アドレスの対となる物理アドレスがない場合,物理メモリー上にあるあまり使われていないデータを一時ハード・ディスクに退避する一方,必要なデータを読み出し物理メモリーに書き込む。さらにページ・テーブルを修正し,仮想アドレスにその物理アドレスを割り当てる。この処理は例外ハンドラが実施する。

リアルと仮想を結びつけるページテーブル

 図3にページ・テーブルに格納されるページ・テーブル・エントリ(PTE)のデータ構造を示す。PTEには,ページに対応する物理アドレスの値に加えて,ページのアクセス制御や属性にかかわる関連情報が格納されている。

図3●ページテーブルエントリ(PTE)の構造
図3●ページテーブルエントリ(PTE)の構造
[画像のクリックで拡大表示]

 一般的なx86プロセッサでは1ページのサイズは4K(4096)バイトであるため,ページの総数は,100万ページ(=4Gバイト÷4Kバイト)となる。単純に考えるとPTEはページの数だけ必要なためページ・テーブルだけで数Mバイト(PTEのサイズ×100万)の物理メモリを消費することになる。そのため,Windowsでは2段のページ・テーブルを利用することで物理メモリーの利用効率を向上させている(※PAE(Physical Address Extension)が有効の場合は,3段のページ・テーブルが利用される)。

 具体的には,仮想アドレスの上位10ビットを1段目のページ・テーブル(ページ・ディレクトリ・テーブル)のインデックス,11ビット目から20ビット目までの10ビットを2段目のページ・テーブルのインデックス,残りの12ビットをページ内オフセットとして利用している(図4)。2段のページ・テーブルを利用した際のアドレス変換のプロセスは以下のようになる。

図4●仮想アドレスから物理アドレスへの変換プロセス
図4●仮想アドレスから物理アドレスへの変換プロセス

(1)CPUのCR3レジスタに格納されているページ・ディレクトリ・テーブルの物理アドレスを取り出す。

(2)以下のアドレスに格納されているPTEをページ・ディレクトリ・テーブルから取り出す。
(1)の値 + 仮想アドレスの上位10ビット x PTEのサイズ

(3)(2)で取り出したPTE中のページ番号(ページの先頭アドレス)を取り出す。

(4)以下のアドレスに格納されているPTEをページ・ディレクトリから取り出す。
(3)の値 + 仮想アドレスの11~20ビット x PTEのサイズ

(5)(4)で取り出したPTE中のページ番号に仮想アドレスの残りの12ビットの値を加算した

 (5)の結果が,仮想アドレスに対応する物理アドレスとなる。

 ページ・ディレクトリ・テーブル,ページ・テーブルともに最大1024(2の10乗)個のPTEを持つため依然としてPTEの総数は100万である。しかし,前述の仕組みにより仮想記憶を使ってページ・テーブル自体を管理し,必要なページ・テーブルのみを物理アドレスと対応付ければ良いため大幅にメモリーの利用効率を向上できるというわけだ。

メモリー読み出しを高速化する「TLB」

 前述のようにページ・ディレクトリ・テーブル,ページ・テーブルともに仮想記憶によってページとして管理されているため,メモリー・アクセスのたびに目的以外の二つのページにアクセスする必要があり,パフォーマンスの低下は避けられない。そのため,i386以降のx86プロセッサでは,TLB(translation lookaside buffer)と呼ばれるPTE専用のキャッシュ・メモリーを搭載している。TLBを利用した場合,メモリー・アクセスの際に読み込まれたPTEはTLBにキャッシュされるため,前述の手順でのアドレス変換が行なわれるのは一度目のアクセス時だけである。以降のアクセス時にはまずTLBがチェックされ対応するPTEが発見された場合は,直接目的のページにアクセスできる。


村上純一(むらかみ・じゅんいち)
国内の大手セキュリティ・コンサルティング会社に勤務。セキュリティ・コンサルタント業務やソフトウエアのぜい弱性を発見する業務に携わる。