はじめに

LibVMIはXenならびにKVMを対象としたVMIライブラリである.ここでは,その基盤と応用についてまとめる.

libxc

そもそもXenにおけるVMIの研究ではかねてより,各ドメインの制御機能ないしハイパーコールを提供するlibxcというライブラリが用いられてきた.libxcという名称はthe Xen Control libraryの略称であり,Xenによって公式に提供されている(tools/libxc/).なお,libxcとして提供されている関数はxc_というprefixを備えている.
このうち,VMIと密接に関連があるのはxenctrl.hxenguest.hである.なかでもxenctrl.hはlibxenctrlとも呼ばれ,PFNの参照やハイパーコールの追加などの機能が提供されている.
LibVMIのソースコードにgrepを掛けると,libxcに由来する構造体と関数が多く用いられていることが分かる.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
% grep -rhoe "xc_[a-zA-z]*_[a-zA-z]*" . | grep -v "]" | sort | uniq
xc_domain_debug_control
xc_domain_decrease_reservation_exact
xc_domain_getinfo
xc_domain_getinfolist
xc_domain_hvm_getcontext
xc_domain_hvm_getcontext_partial
xc_domain_hvm_setcontext
xc_domain_maximum_gpfn
xc_domain_pause
xc_domain_populate_physmap_exact
xc_domain_set_access_required
xc_domain_unpause
xc_domaininfo_t
xc_dominfo_t
xc_evtchn_bind_interdomain
xc_evtchn_close
xc_evtchn_fd
xc_evtchn_notify
xc_evtchn_open
xc_evtchn_pending
xc_evtchn_t
xc_evtchn_unbind
xc_evtchn_unmask
xc_get_hvm_param
xc_hvm_get_mem_access
xc_hvm_inject_trap
xc_hvm_set_mem_access
xc_interface_close
xc_interface_open
xc_map_foreign_batch
xc_map_foreign_range
xc_mem_access_disable
xc_mem_access_enable
xc_mem_access_resume
xc_mem_event_disable
xc_mem_event_enable
xc_memory_op
xc_set_hvm_param
xc_set_mem_access
xc_vcpu_getcontext
xc_vcpu_setcontext

とりわけVMIにおいて重要なのはxc_map_foreign_range()という関数である.

1
2
3
void *xc_map_foreign_ranges(xc_interface *xch, uint32_t dom,
size_t size, int prot, size_t chunksize,
privcmd_mmap_entry_t entries[], int nentries);

Xenの各ドメインにはドメインや仮想CPUの構造体(xen/include/xen/sched.h)のほか,マシンメモリ(物理メモリ)と擬似物理メモリが割り当てられる.ドメインにおけるページングは,このマシンフレーム番号(MFN)と疑似物理フレーム番号(PFN)とを対応付けるP2Mテーブルによって実現されている.つまり,ドメインの仮想アドレスは擬似物理メモリフレーム番号に対応し,その疑似物理フレーム番号はさらにマシンフレーム番号に対応する.なお,Intel EPTが有効な環境においてはP2MはEPTによって代替される(ept-p2m.c).
xc_map_foreign_range()はこうした仕組みに基づき,ページテーブルを辿ってDom0のメモリ空間にDomUのマシンフレームを割り当てる.
VMIに求められるデータ構造の解釈はXenにおいて,この関数があることから成り立つものである.また,これによるフォールトインジェクションに関してもかつて研究が行われていたようだ.

libxenstore

Xenではxenstore-lsやxenstore-readなどのコマンドからxenstoredというデーモンを経由して各ドメインの情報を読み出すことができる.この仕組みをXenStoreといい,関連してlibxenstoreというライブラリが提供されている.(tools/xenstore/).libxsと呼ばれることもある.
XenStoreは名の通りKVSとしてドメインのUUIDやドメイン名,メモリのコミット状況などを格納している.
LibVMIにおいても,xenstordへのアクセスのため用いられている(xen_private.h).

libxenlight

libxenlightはlibxcとlibxenstoreのラッパとして公式に開発されているライブラリである(tools/libxl/).
LibVMIでは用いられていないものの,Xen Projectの本流ではこちらが推進されているように見受けられる.

XenAccess

XenAccessはlibxcのラッパであり,LibVMIの前身にあたる.
目的としてセマンティックギャップの解決が掲げられており,任意のPIDに対応するEPROCESS構造体を取得することができる.
EPROCESS構造体はWindowsの各プロセスに割り当てられる構造体であり,Windowsのプロセスリストはこの構造体のActiveProcessLinksメンバからなる双方向リンクリストによって成り立っている.タスクマネージャが参照するのはこの構造体である.
XenAccessはこの構造体を手掛かりにカーネルのベースアドレスを取得する.
まず,メモリ空間をPEヘッダの先頭すなわちMZという文字列で検索する.そして,発見したアドレスについてシステムプロセス固有のPsInitialSystemProcessのオフセットを加算し,EPROCESS構造体の先頭メンバである_EPROCESS.Pcb.headerにあたるシグネチャと比較する.一致した場合,発見したアドレスがベースアドレスということになる(windows_memory.c, windows_core.c, windows_process.c).
これによって,プロセスリストと任意のプロセスの情報を取得することができるようになった.

LibVMI

LibVMIはXenAccessの後継であり,かつてvmitoolsとも呼ばれていた.
先述したようなカーネル空間の解析機能をLibVMIはフォレンジックツールに委譲した.フォレンジックツールを導入するメリットとして,より詳細な構造体の解析を低い実装コストで実現できるという点が挙げられる.
さて,LibVMIはVolatilityRekallという二種類のフォレンジックツールに対応している.RekallはGoogleによるVolatilityの拡張であり,かつてはLibVMIのサポート外だったものの,commit a4f065a31b986562dc39c9a10d9dff080792f3f4にて導入された.
では,両者の差異はどこにあるのか.

Kernel Debugger Block

XenAccessが行っていたようなEPROCESS構造体のスキャンについて考えてみよう.
VolatilityとRekallはいずれも,まずリンクリストの先頭であるPsActiveProcessHeadを特定しようとする.
x86において,このPsActiveProcessHeadは,KPCR(Kernel Processor Control Region)構造体のKdVersionBlockメンバが示すKDDEBUGGER_DATA32構造体から取得することができる.
このKPCR構造体だが,x86ではFSレジスタ,x86_64ではGSレジスタが先頭アドレスを保持しており,Windows XPでは0xFFDFF000というマジックナンバーを用いて検索することができる.
では,KdVersionBlockはどうか.なんと,Windows 7の64-bit版からこのメンバは空になってしまっている.ゆえに,x86_64ではKDDEBUGGER_DATA64構造体をKdVersionBlockに頼らず検索する必要が生ずる.
ここで,VolatilityはKDDEBUGGER_DATA64構造体のヘッダに存在するKDBGシグネチャをスキャンするのに対し(kdbgscan.py),Rekallはカーネルのバージョンに対応したプロファイル情報を用いて構造体にアクセスする.

DRAKVUF

DRAKVUFはTamas K Lengyelによって開発されたサンドボックスであり,論文のオーサーにはXenAccessとLibVMIの開発者であるBryan D. Payneも名を連ねている.
LibVMIにRekallのサポートを追加したのがほかでもないTamas K Lengyelその人である.
Volatilityは前述の通りメモリからKDBG構造体を検索する.だが,ゲストOSのメモリはマルウェアによって改竄されてしまうおそれがあることに加え,Windows 8の64-bit版ではKDBG構造体が難読化されているという問題点がある.
一方で,Rekallのプロファイルはゲストから不可視であり,改竄の影響を受けないため,この問題を解決することができる.
DRAKVUFはマルウェア解析を目的としてLibVMIを拡張しており,当然プログラムの実行をトラップする機能を備えている.
特筆すべきは,Intel EPTを用いたメモリアクセスの監視である(vmi.c).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Now we can set the EPT permissions
if (!container->guard) {
container->guard = g_malloc0(sizeof(vmi_event_t));
SETUP_MEM_EVENT(container->guard, container->pa, VMI_MEMEVENT_PAGE,
VMI_MEMACCESS_RW, trap_guard);
if (VMI_FAILURE == vmi_register_event(vmi, container->guard)) {
printf("*** FAILED TO REGISTER MEMORY GUARD @ PAGE %lu ***\n",
pa >> 12);
free(container->guard);
free(container);
continue;
}
container->guard->data = g_hash_table_new(g_int64_hash,
g_int64_equal);
printf("\t\tNew memory event trap set on page %lu\n", pa >> 12);
} else {
printf("\t\tMemory event trap already set on page %lu\n", pa >> 12);
}
struct memevent *test = g_hash_table_lookup(container->guard->data,
&container->pa);
if (!test) {
g_hash_table_insert(container->guard->data, &container->pa,
container);
} else if (test->sID == SYMBOLWRAP) {
printf("Address is already guarded\n");
} else {
printf("Address is trapped by another feature! ERROR/TODO!\n");
}

このアイデア自体はKVMをベースとして開発されたCXPInspectorを下敷きにしている.
かつては任意のアドレスについてNX bitを設定し,ページフォルトハンドラをブレークポイントとする手法が一般的であった.これがカーネル空間から可視であるのに対し,EPTは不可視であるためよりカーネルルートキットの解析に適している.

おわりに

LibVMIの登場と洗練によってもはや,セマンティックギャップは過去の問題になりつつある.XenにおけるVMIにおいて,これを用いない手はないだろう.

参考文献