• 検索結果がありません。

第 4 章 Alkanet 19

4.3 システムコールトレースの実装

表 4.1 Alkanet がログに記録するシステムコール [18]

挙動 フックするシステムコールの例

ファイル NtCreateFile, NtOpenFile, NtWriteFile, NtReadFile,    NtSetInformationFile, NtDeleteFile

レジストリ NtCreateKey, NtOpenKey, NtQueryKey,        NtSetInformationKey, NtDeleteKey, NtSetValueKey,    NtQueryValueKey, NtDeleteValueKey

仮想メモリ NtWriteVirtualMemory, NtReadVirtualMemory,      NtAllocateVirtualMemory, NtProtectVirtualMemory

ファイルマッピング NtCreateSection, NtOpenSection, NtMapViewOfSection ネットワーク NtDeviceIoControlFile, NtCreateFile, NtReadFile,    

NtWriteFile

プロセス NtCreateProcess, NtCreateProcessEx, NtTerminateProcess スレッド NtCreateThread, NtSuspendThread, NtResumeThread,  

NtTerminateThread, NtGetContextThread,        NtSetContextThread

ドライバ NtLoadDriver, NtUnloadDriver

時間 NtQueryPerformanceCounter

スリープ NtDelayExecution

システムコールの引数と戻り値

Cid は,プロセスIDとスレッドIDの組であり,ログ取得時におけるシステムコー ル発行元の区別に用いられる.また,その実行ファイルの名前も取得する.さらに,

システムコールが示す挙動を調査するために,システムコールの引数と戻り値も取得 する.

4.3. システムコールトレースの実装 23

引数と戻り値の取得

4.3.1 システムコールのフック

Alkanetのシステムコールフックの流れを図4.2に示す.x86版Windows XP Service

Pack 3 におけるシステムコールは,通常 sysenter 命令によってカーネルモードへ遷

移し,sysexit 命令でユーザモードへ遷移する.Alkanetは,sysenter命令のジャンプ 先である KiFastCallEntry と,sysexit命令を実行するKiSystemCallExit2に,ハード ウェアブレイクポイントを設定することでフックを行う.これらのアドレスの取得に は,公開されているシンボル情報を用いる [38].

具体的なAlkanetによるシステムコールフックの流れを以下に示す.本論文では,

sysenter 命令実行時のフック(図中の③〜⑤),sysexit 命令実行時のフック(図中の⑦

〜⑨) をそれぞれ sysenterフック,sysexit フックと呼称する.

① ユーザプログラムがシステムコールのスタブを呼出す.

② スタブは,システムコール番号の設定などを行った後,KiFastSystemCall を経 由して sysenter命令を実行する.

③ KiFastCallEntryに設定されたブレイクポイントによりデバッグ例外が発生し,

Alkanet に制御が移る.

④ Alkanetが発行元プロセスやスレッド,システムコールの情報など必要な情報を

取得する.

⑤ Windows に制御が戻り,KiFastCallEntry から実行が再開される.

⑥ システムコールサービスルーチンが実行される.

⑦ KiSystemCallExit2に設定されたブレイクポイントにより再びAlkanetに制御が 移る.

⑧ Alkanetは,④で取得した情報に加え,システムコールの結果を取得する.

⑨ Windows に制御が戻り,KiSystemCallExit2 から実行が再開される.

⑩ KiSystemCallExit2が sysexit命令を実行する.

⑪ KiFastSystemCallRetとスタブを経由しつつ,ユーザプログラムに制御が戻る.

図 4.2 システムコールフックの流れ

なお,ハードウェアブレイクポイントの設定に使用するデバッグレジスタは,デバッ グレジスタに対するmov命令をフックすることで,ゲスト OS から隠蔽する.ハード ウェアブレイクポイントの使用の有無を確認するアンチデバッグ機能を持つマルウェ アについては4.5節で述べる.

4.3.2 システムコールの特定

Windows では,システムコール発行時にシステムコール番号が EAX レジスタに格

納される.したがって, sysenter フックでは,EAX レジスタの値からシステムコー ルを特定する.一方,sysexit フック時は,レジスタなどにシステムコール番号が格納 されていないため,同様の方法が使えない.そこで,Alkanetでは,システムコールが

ntdll.dll に実装されているスタブを用いて発行されることを利用してシステムコール

4.3. システムコールトレースの実装 25 を特定する.スタブは,発行するシステムコールと同名のシンボルになっている.そ のため,スタックに格納された戻りアドレスから実行されたスタブを求めることで,

発行されたシステムコールを特定できる.

Windows では,システムコール発行時にESP レジスタの値を EDXレジスタに格

納する.一方, sysexit 命令は, ECX レジスタの値を ESP レジスタにロードする.

したがって, Alkanet は,システムコールを発行したプロセスのスタックの位置を,

sysenter フック時は EDX レジスタ,sysexit フック時は ECX レジスタからそれぞれ 取得できる.その後,スタックの先頭に格納された戻りアドレスを調べ,発行された システムコールを特定する.

4.3.3 プロセスとスレッドの特定

Alkanet は,システムコール発行元の Cid と実行ファイル名を収集するために,

Windows のPCR(Processor Control Region)と呼ばれるデータ構造を用いる.PCR

は,Windows が個々のプロセッサの状態を保持するためのデータ構造であり,図4.3

に示すように,そのプロセッサで現在動作しているスレッドの情報を示すスレッドオ ブジェクトへのポインタを保持している.スレッドオブジェクトは,Cid と自身が属 するプロセスオブジェクトへのポインタを保持しており,実行ファイル名はそのプロ セスオブジェクトから取得できる.

PCRは,Windows のカーネルモードにおいて各プロセッサの FS レジスタが指す

セグメントの先頭に存在する.しかし,Alkanet がシステムコールをフックした時点 は,カーネルモードに移行した直後およびユーザモードに戻る直前であるため,この 時点での FSレジスタの値はユーザモード時の値である.文献 [39]によれば Windows 2000 のカーネルモードにおける FSレジスタの値は 0x30であり,Windows XP につ いても調査した結果,同様に0x30であることを確認している.したがって,セレクタ 値 0x30 を用いて Windows の持つ GDT (Global Descriptor Table) を参照すること で,PCRのアドレスを取得できる.

具体的な実行中のスレッド情報の取得手順を以下に示す.

(1) Intel VT のVM管理用データ構造VMCS (Virtual-Machine Control Structure) より,仮想CPUに設定されているGDTR の値を取得する.

(2) GDT を参照し,0x30 に対応するセグメントディスクリプタを取得する.

(3) セグメントディスクリプタより,PCRが存在するアドレスを取得する.

図 4.3 動作中のスレッド情報取得の流れ

(4) PCRに含まれるポインタCurrentThreadより,実行中のスレッドオブジェクト

のアドレスを取得する.

(5) スレッドオブジェクトが所属するプロセスオブジェクトも取得し,これらのデー タ構造からCidや実行ファイル名(図中のImageFileName)を取得する.

4.3.4 引数と戻り値の取得

マルウェアの挙動を理解するためには,システムコールの種類に加えて,マルウェ アがアクセスしたファイルやレジストリなどの操作対象の情報も必要である.これら の情報は,システムコールの引数から取得できる.また,Alkanet が監視対象とする システムコールの戻り値は,状態を示す NTSTATUSと呼ばれる値であり,この値か らシステムコールの成否および失敗した場合はその理由がわかる.

Windows のAPI では,stdcall呼出規約[40] が用いられる.stdcallでは,引数はス タックに,戻り値は EAX に格納される.ある関数Aからシステムコールが呼び出さ れたときのスタックは,図4.4のような状態にある.4.3.2項で述べたように,スタック の位置は,sysenterフック時は EDX レジスタ,sysexit フック時は ECXレジスタか らそれぞれ特定できる.引数は,スタブへの戻りアドレス,スタブを呼び出した関数 Aへの戻りアドレスに続いて格納されている.したがって,sysenterフックと sysexit フックそれぞれで必要な引数をスタックから取得し,さらに,sysexitフックではEAX から戻り値も取得する.