Backdoor !!
vmware-tools と 統合サービスに見るハイパーバイザの呼び出し方
2010/4/12
VTCライトニングトーク発表内容
しろやまたかゆき<shiro.t@gmail.com>
Hypercall とは?
l ゲストOS からハイパーバイザを明示的に呼び出す方法
l Binary Translation や VMEXIT/VMRESUME に比べコストが低い
l 呼び出し方法が低コスト
l ハイパーバイザに都合のいいデータのやり取りが可能
l 準仮想化された OS で使用されている
l Xen 対応の Linux カーネルなど
Xen での Hypercall の実装
l 単純に int 命令でソフトウェア割り込みをかけているだけ
l Ring0 の Xen hyper-visor が割り込みをキャッチし、指定のルーチンを
呼び出している
http://www.ylug.jp/download/hypercall_ylug_20061027.pdf
Hypercall は準仮想化だけのもの?
l いえ、類似/同等の機能は完全仮想化のハイパーバイザにもあります!
l 性能上の理由
l Binary Translation や VMEXIT/VMRESUME に比べコストが低い
l 仮想化ならではの機能の実現
l VM間 / VM-Hypervisor 間の通信や制御 (VMCI)
例: VMCI
int a, s, len;
char buf[255];
struct sockaddr_vm addr = { 0 };
s = socket(PF_VSOCK_VMCI, SOCK_STREAM, 0);
addr.svm_family = AF_VSOCK_VMCI;
addr.svm_cid = VMADDR_CID_ANY;
addr.svm_rid = 2000;
bind(s, (struct sockaddr *) &addr, sizeofaddr);
listen(s, 5);
a = accept(s, (struct sockaddr *) &addr, &len);
len = recv(a, buf, sizeof buf, 0);
int a, s, len;
char *buf = “Hello, world!”;
struct sockaddr_vm addr = { 0 };
s = socket(PF_VSOCK_VMCI, SOCK_STREAM, 0);
addr.svm_family = AF_VSOCK_VMCI;
addr.svm_cid = GUEST2;
addr.svm_rid = 2000;
connect(s, (struct sockaddr *) &addr, sizeof addr);
len = send(s, buf, len, 0);
基本はソケットプログラミング
通常のPF_INETの代わりに
PF_VSOCK_VMCIに変更。
読み書き(send/recv)は変わらず
Hyper-­visor
Application
Guest  OS
仮想マシン
Application
Guest  OS
仮想マシン
Server
Client
ESX / Hyper-V の Hypercall
l では、ESX / Hyper-V の Hypercall って どうやってるの?
ソースコードにきいてみよう!
VMware の場合: open-vm-tools
Open Virtual Machine Tools
l vmware-tools のソースコードは公開されている (2007.9.4~)
l Linux, FreeBSD, Solaris 用
l vmsync など、標準の VMware Tools にないモジュールも存在
http://open-­vm-­tools.sourceforge.net/
Microsoft の場合
l Hyper-V の統合サービス(Linux向け)のソースコードを公開!(2009.7.21)
l これを読めば Hyper-V の hypercall も解明できる?
で、どうやってダウンロードすればいいの?
l MS のサイトを漁りまわったけどソースコードを発見できず!!
l 公開を報じる国内記事は多々あれど、ソースそのもののリンクはどこにもない
l 駄目ぢゃん、っと思ってたら...
で、どうやってダウンロードすればいいの?
l Linux-Kernel ML に Greg-KH 氏が投稿していた
l パッチを 54 分割して...
l 何とか苦労して取り出しました
l 最近のカーネルソースには既に含まれている模様
ソースツリー: open-vm-tools の場合
l open-vm-tools
l 綺麗な階層構造、Makfile 完備
l さまざまなOSでのビルドを前提としている
l コード数、約24万行
- 「find –name ‘*.[ch]’ -print0 | xargs -0 wc –l 」の実行結果より
ソースツリー: open-vm-tools の場合
l open-vm-tools の階層構造
+-­-­ autom4te.cache/
+-­-­ checkvm/
+-­-­ config/
+-­-­ docs/
+-­-­ hgfsclient/
+-­-­ hgfsmounter/
+-­-­ lib/
+-­-­ libguestlib/
+-­-­ libvmtools/
+-­-­ m4/
+-­-­ modules/
|      +-­-­ freebsd/
|      +-­-­ linux/
|      +-­-­ solaris/
+-­-­ rpctool/
+-­-­ scripts/
+-­-­ services/
|      +-­-­ plugins/
||      +-­-­ vmtoolsd/
+-­-­ tests/
+-­-­ toolbox/
+-­-­ vmblock-­fuse/
+-­-­ vmware-­user/
+-­-­ vmware-­user-­suid-­wrapper/
+-­-­ xferlogs/  
|      +-­-­ linux/
|      |      +-­-­ pvscsi/
|      |      +-­-­ shared/
|      |      +-­-­ vmblock/
|      |      +-­-­ vmci/
|      |      +-­-­ vmhgfs/
|      |      +-­-­ vmmemctl/
|      |      +-­-­ vmsync/
|      |      +-­-­ vmxnet/
|      |      +-­-­ vsock/
独自のツリー構造
多くのOSに対応
共通化モジュール、ユーザランドツール、
ドライバなど綺麗に分離されている
ソースツリー: Hyper-V の場合
l linux integration component
l linux-kernel に対するパッチで、Linux にべったりの構造
- カーネルパッチだけ?ユーザランドに何もない??
l driver/staging/hv 以下に集中配置
l 約2万行
ソースツリー: Hyper-V の場合
ファイル一覧
BlkVsc.c HvVpApi.h Vmbus.c
Channel.c Kconfig VmbusApi.h
Channel.h List.h VmbusChannelInterface.h
ChannelInterface.c Makefile VmbusPacketFormat.h
ChannelInterface.h Makefile.rej VmbusPrivate.h
ChannelMessages.h NetVsc.c blkvsc_drv.c
ChannelMgmt.c NetVsc.h hv.diff
ChannelMgmt.h NetVscApi.h logging.h
Connection.c RingBuffer.c netvsc_drv.c
Hv.c RingBuffer.h nvspprotocol.h
Hv.h RndisFilter.c osd.c
HvHalApi.h RndisFilter.h osd.h
HvHcApi.h Sources.c rndis.h
HvPtApi.h StorVsc.c storvsc_drv.c
HvStatus.h StorVscApi.h vmbus.h
HvSynicApi.h TODO vmbus_drv.c
HvTypes.h VersionInfo.h vstorage.h
Linux への侵食方法:VMware の場合
あくまで「物理デバイスに対するドライバ」の立場
l VMware-tools の有無に関わらず、仮想デバイスが存在する前提
l PCIバス上にデバイスがつながっているように見える
l PCIバスのデバイス検出手順に従い、ドライバがロードされていく
l VendorID: 0x15AD で、指定
の DeviceID をもつドライバが
選定される
l 選定されたドライバのprobe 関数を
実行して、本当に対応するデバイスか
を確認させる
l 失敗が帰ったら、次の候補となる
ドライバを探す
/* Our own PCI IDs
* VMware SVGA II (Unified VGA)
* VMware SVGA (PCI Accelerator)
* VMware vmxnet (Idealized NIC)
* VMware vmxscsi (Abortive idealized SCSI controller)
* VMware chipset (Subsystem ID for our motherboards)
* VMware e1000 (Subsystem ID)
* VMware vmxnet3 (Uniform Pass Through NIC)
*/
#define PCI_VENDOR_ID_VMWARE 0x15AD
#define PCI_DEVICE_ID_VMWARE_SVGA2 0x0405
#define PCI_DEVICE_ID_VMWARE_SVGA 0x0710
#define PCI_DEVICE_ID_VMWARE_NET 0x0720
#define PCI_DEVICE_ID_VMWARE_SCSI 0x0730
#define PCI_DEVICE_ID_VMWARE_VMCI 0x0740
#define PCI_DEVICE_ID_VMWARE_CHIPSET 0x1976
#define PCI_DEVICE_ID_VMWARE_82545EM 0x0750 /* single port */
#define PCI_DEVICE_ID_VMWARE_82546EB 0x0760 /* dual port */
#define PCI_DEVICE_ID_VMWARE_EHCI 0x0770
#define PCI_DEVICE_ID_VMWARE_UHCI 0x0774
#define PCI_DEVICE_ID_VMWARE_XHCI 0x0778
#define PCI_DEVICE_ID_VMWARE_1394 0x0780
#define PCI_DEVICE_ID_VMWARE_BRIDGE 0x0790
#define PCI_DEVICE_ID_VMWARE_ROOTPORT 0x07A0
#define PCI_DEVICE_ID_VMWARE_VMXNET3 0x07B0
#define PCI_DEVICE_ID_VMWARE_VMXWIFI 0x07B8
#define PCI_DEVICE_ID_VMWARE_PVSCSI 0x07C0
#define PCI_DEVICE_ID_VMWARE_82574 0x07D0
lib/include/vm_device_version.h  より
Linux への侵食方法:VMware の場合
余談: vmxnet の Morphing
l ESX 2.x では、vlance と vmxnet は区別されてました
l vlance vendorID: 0x1022(AMD) deviceID: 0x2000
l vmxnet vendorID: 0x15AD(VMware) deviceID: 0x0720
l ESX 3.x 以降で、vlance -> vmxnet の入れ替えしました?
Linux への侵食方法:VMware の場合
余談: vmxnet の Morphing
l ESX 3.x 以降の vlance は、vmxnet を「兼用」している
l たまたま、AMD-PCNET が 32byte しか IO空間を使ってなかった点を利用
l 64byte まで取れるIO空間の後ろ32byte に vmxnet の IO空間を追加
/*
* Since this is a vlance adapter we can only use it if
* its I/0 space is big enough for the adapter to be
* capable of morphing. This is the first requirement
* for this adapter to potentially be morphable. The
* layout of a morphable LANCE adapter is
*
* I/O space:
*
* |------------------|
* | LANCE IO PORTS |
* |------------------|
* | MORPH PORT |
* |------------------|
* | VMXNET IO PORTS |
* |------------------|
*
* VLance has 8 ports of size 4 bytes, the morph port is 4 bytes, and
* Vmxnet has 10 ports of size 4 bytes.
*
* We shift up the ioaddr with the size of the LANCE I/O space since
* we want to access the vmxnet ports. We also shift the ioaddr up by
* the MORPH_PORT_SIZE so other port access can be independent of
* whether we are Vmxnet or a morphed VLance. This means that when
* we want to access the MORPH port we need to subtract the size
* from ioaddr to get to it.
*/
Linux への侵食方法:VMware の場合
余談: vmxnet の Morphing
l vmxnet ドライバの probe 関数(vmxnet_probe_device)の挙動
l PCI の Vendor ID を取得
- 0x15AD (VMware) なら、そのまま vmxnet デバイスと判断
• 旧 vmxnet との互換性の確保
- 0x1022 (AMD) の場合
• IO空間のサイズを取得し、64byte とサイズを比較
• それより小さかったら? 「うちの子じゃない」 で probe に失敗を返す
• 64byte 以上なら、「うちの子だから」と認定
• morph port に outw で値を書き込み、vmxnet として振舞わせる
Linux への侵食方法:VMware の場合
static int
vmxnet_morph_device(unsigned int morphAddr) // IN
{
uint16 magic;
/* Read morph port to verify that we can morph the adapter. */
magic = inw(morphAddr);
if (magic != LANCE_CHIP && magic != VMXNET_CHIP) {
printk(KERN_ERR "Invalid magic, read: 0x%08X¥n", magic);
return -1;
}
/* Morph adapter. */
outw(VMXNET_CHIP, morphAddr);
/* Verify that we morphed correctly. */
magic = inw(morphAddr);
if (magic != VMXNET_CHIP) {
printk(KERN_ERR "Couldn't morph adapter. Invalid magic, read: 0x%08X¥n",
magic);
goto morph_back;
}
return 0;
morph_back:
/* Morph back to LANCE hw. */
outw(LANCE_CHIP, morphAddr);
return -1;
}
VMM
Linux への侵食方法:VMware の場合
l あくまで正規のデバイスドライバとしてロードされる
l 比較的、物理デバイスに近いエミュレーションがなされている
l Backdoor を使ってデバイスエミュレーションをある程度「バイパス」する
Linux  kernel
vmxnet_drv VGA LSILogic
NIC VGA SCSI
VendorID:  xxxx
DeviceID  :  xxxx
VendorID:  xxxx
DeviceID  :  xxxx
VendorID:  xxxx
DeviceID  :  xxxx
PCI  バス Backdoor
vmsync
vmUser
vmmemctl
VMkernel
vmtools   提供ドライバ
ネイティブドライバ
Backdoor : IO空間の in/out 命令
l VMware の Backdoor は、指定のIO空間への in /out 命令
l in / out : x86 のもつ IO 空間へのデータの読み書きの命令
l NICなどの一般的なデバイスは IO空間の一部を通じてデータをやり取りする
- グラフィックカードなどのより大きな空間を必要とするものは、メモリ空間
にマップされる
- PCI は IO空間 / メモリマップのどちらの方法もサポートする
l VMware ではこの in / out 命令を転用している
l 詳細: http://chitchat.at.infoseek.co.jp/vmware/backdoorj.html
Backdoor : IO空間の in/out 命令
static  void
BalloonTimerHandler(void  *clientData)  //  IN
{
Balloon  *b  =  (Balloon  *)  clientData;;
uint32  target  =  0;;  //  Silence  compiler  warning.
int  status;;
/*  update  stats  */
STATS_INC(b-­>stats.timer);;
/*  reset,  if  specified  */
if  (b-­>resetFlag)  {
BalloonReset(b);;
}
/*  contact  monitor  via  backdoor  */
status  =  BalloonMonitorGetTarget(b,  &target);;
/*  decrement  slowPageAllocationCycles  counter  */
if  (b-­>slowPageAllocationCycles  >  0)  {
b-­>slowPageAllocationCycles-­-­;;
}
if  (status  ==  BALLOON_SUCCESS)  {
/*  update  target,  adjust  size  */
b-­>nPagesTarget  =  target;;
(void)  BalloonAdjustSize(b,  target);;
}
}
Backdoor : IO空間の in/out 命令
static  void
BalloonTimerHandler(void  *clientData)  //  IN
{
Balloon  *b  =  (Balloon  *)  clientData;;
uint32  target  =  0;;  //  Silence  compiler  warning.
int  status;;
/*  update  stats  */
STATS_INC(b-­>stats.timer);;
/*  reset,  if  specified  */
if  (b-­>resetFlag)  {
BalloonReset(b);;
}
/*  contact  monitor  via  backdoor  */
status  =  BalloonMonitorGetTarget(b,  &target);;
/*  decrement  slowPageAllocationCycles  counter  */
if  (b-­>slowPageAllocationCycles  >  0)  {
b-­>slowPageAllocationCycles-­-­;;
}
if  (status  ==  BALLOON_SUCCESS)  {
/*  update  target,  adjust  size  */
b-­>nPagesTarget  =  target;;
(void)  BalloonAdjustSize(b,  target);;
}
}
static  int
BalloonMonitorGetTarget(Balloon  *b,          //  IN
uint32  *target)  //  OUT
{
Backdoor_proto  bp;;
unsigned  long  limit;;
uint32  limit32;;
uint32  status;;
limit  =  OS_ReservedPageGetLimit();;
/*  Ensure  limit  fits  in  32-­bits  */
limit32  =  (uint32)limit;;
if  (limit32  !=  limit)  {
return  BALLOON_FAILURE;;
}
/*  prepare  backdoor  args  */
bp.in.cx.halfs.low  =  BALLOON_BDOOR_CMD_TARGET;;
bp.in.size  =  limit;;
/*  invoke  backdoor  */
Backdoor_Balloon(&bp);;
/*  parse  return  values  */
status    =  bp.out.ax.word;;
*target  =  bp.out.bx.word;;
Backdoor : IO空間の in/out 命令
static  void
BalloonTimerHandler(void  *clientData)  //  IN
{
Balloon  *b  =  (Balloon  *)  clientData;;
uint32  target  =  0;;  //  Silence  compiler  warning.
int  status;;
/*  update  stats  */
STATS_INC(b-­>stats.timer);;
/*  reset,  if  specified  */
if  (b-­>resetFlag)  {
BalloonReset(b);;
}
/*  contact  monitor  via  backdoor  */
status  =  BalloonMonitorGetTarget(b,  &target);;
/*  decrement  slowPageAllocationCycles  counter  */
if  (b-­>slowPageAllocationCycles  >  0)  {
b-­>slowPageAllocationCycles-­-­;;
}
if  (status  ==  BALLOON_SUCCESS)  {
/*  update  target,  adjust  size  */
b-­>nPagesTarget  =  target;;
(void)  BalloonAdjustSize(b,  target);;
}
}
static  int
BalloonMonitorGetTarget(Balloon  *b,          //  IN
uint32  *target)  //  OUT
{
Backdoor_proto  bp;;
unsigned  long  limit;;
uint32  limit32;;
uint32  status;;
limit  =  OS_ReservedPageGetLimit();;
/*  Ensure  limit  fits  in  32-­bits  */
limit32  =  (uint32)limit;;
if  (limit32  !=  limit)  {
return  BALLOON_FAILURE;;
}
/*  prepare  backdoor  args  */
bp.in.cx.halfs.low  =  BALLOON_BDOOR_CMD_TARGET;;
bp.in.size  =  limit;;
/*  invoke  backdoor  */
Backdoor_Balloon(&bp);;
/*  parse  return  values  */
status    =  bp.out.ax.word;;
*target  =  bp.out.bx.word;;
static  INLINE
void  Backdoor_Balloon(Backdoor_proto  *myBp)  {
myBp-­>in.ax.word  =  BALLOON_BDOOR_MAGIC;;
myBp-­>in.dx.halfs.low  =  BALLOON_BDOOR_PORT;;
Backdoor_InOut(myBp);;
}
Backdoor : IO空間の in/out 命令
Backdoor_InOut(Backdoor_proto *myBp) // IN/OUT
{
uint32 dummy;
__asm__ __volatile__(
#ifdef __PIC__
"pushl %%ebx" "¥n¥t"
#endif
"pushl %%eax" "¥n¥t"
"movl 20(%%eax), %%edi" "¥n¥t"
"movl 16(%%eax), %%esi" "¥n¥t"
"movl 12(%%eax), %%edx" "¥n¥t"
"movl 8(%%eax), %%ecx" "¥n¥t"
"movl 4(%%eax), %%ebx" "¥n¥t"
"movl (%%eax), %%eax" "¥n¥t"
"inl %%dx, %%eax" "¥n¥t"
"xchgl %%eax, (%%esp)" "¥n¥t"
"movl %%edi, 20(%%eax)" "¥n¥t"
"movl %%esi, 16(%%eax)" "¥n¥t"
"movl %%edx, 12(%%eax)" "¥n¥t"
"movl %%ecx, 8(%%eax)" "¥n¥t"
"movl %%ebx, 4(%%eax)" "¥n¥t"
"popl (%%eax)" "¥n¥t"
#ifdef __PIC__
"popl %%ebx" "¥n¥t"
#endif
: "=a" (dummy)
: "0" (myBp)
/*
* vmware can modify the whole VM state without the compiler knowing
* it. So far it does not modify EFLAGS. --hpreg
*/
:
#ifndef __PIC__
"ebx",
#endif
"ecx", "edx", "esi", "edi", "memory"
);
}
この inl 命令の実行した瞬間に
VMkernel 側に処理が渡り、該当する
機能が実行され、結果が vCPU の
EAX 、ECX 、 EDX 、 ESI、EDI レジスタ
に書き込まれる
Hyper-V のドライバイメージ
l Hyper-V の場合は、デバイスではなく「バス」を追加している
l PCI バスの下に、なんかバスがある...
l なんかバスが「外」に伸びてるっぽい...
VMM
Linux  kernel
レガシNIC VGA IDE
VendorID:  xxxx
DeviceID  :  xxxx
VendorID:  xxxx
DeviceID  :  xxxx
VendorID:  xxxx
DeviceID  :  xxxx
PCI  バス
ネイティブドライバ
21140 VGA IDE
VMBUS
Backdoor
vmbus_drv
netvsc_drv storage_drv
NIC SCSI
Hyper-­V
ParentVM
Hyper-V のドライバイメージ
l Hyper-V の場合は、デバイスではなく「バス」を追加している
l PCI バスの下に、なんかバスがある...
l なんかバスが「外」に伸びてるっぽい...
Linux の侵食方法 : Hyper-V の場合
l とにかく vmbus_drv デバイスドライバをロードする
l VMBUS という何らかの「バス」があると Linux に誤認させる
l VMBUS も、PCI と同じようにバス上のノードの検出を行う
- VendorID と Device ID ではなく、ClassID という GUID で識別
- ノードごと、netvsc_drv, storagevsc_drv, blkvsc_drv がロードされる
• VSC : Virtualization Service Client の略
l 各 VSC はデバイスドライバだが、その下にはエミュレーションされたデバイス
はまったく存在しない!!
Linux の侵食方法 : Hyper-V の場合
http://enterprise.watch.impress.co.jp/cda/parts/image_for_link/41502-­13748-­4-­1.html
本当にこの通りでした...
Linux の侵食方法 : Hyper-V の場合
もう少し詳しい 階層構造
l 大きく4層に分かれる
l *_drv : Linux のデバイスドライバ(カーネルモジュール)としての体裁を整える
l Vsc, Vmbus : 各ドライバの実態となる実装
l Channel, Connection : VMBUS の通信機能の実装
l osd : メモリ確保など
OS機能の抽象化
l Hv : Hyper-V との
実際のインターフェイス
Linux  kernel
osd
VMM
Vmbus
vmbus_drvstoragevsc_drvnetvsc_drv blcvsc_drv
Hv
Channel
Connection
Channel  Mgmt
StorVscNetVsc BlcVsc
Linux  Kernel  とのインターフェイス
ドライバー中心部分
VMBUS  通信機能
Backdoor : そのメモリに触ると...
Hyper-V のバックドアは特定のメモリアドレス
l Hv はロードされると以下の振る舞いをする
l CPUID 命令を実行し、Hyper-V の上かを確認する
- CPUID(1) の ECXの31bit 目を確認
l 続けて CPUID 命令を実行し、ハイパーバイザのベンダー(!)や
バージョンを取得する
l メモリを1ページ確保する
- これは通常の valloc が使用され、実行時により異なるページが
割り当てられる
l wmsr でそのメモリの物理アドレスを vCPU に書き込む(!)
- MSR: Model Specific Register
• PentiumPro 以降のCPUにある、製品独自の設定を保存したり、
プロファイリング用のカウンタとなるレジスタ
• 詳細: http://mcn.oops.jp/wiki/index.php?CPU%2FCPUID%2FMSR
l 以降、Hypercall はこのメモリへのアクセスすると、Hypercall が発生する
Backdoor : Hv初期化部分、Hyper-V 上かを確認
Name:
HvQueryHypervisorPresence()
Description:
Query the cpuid for presense of windows hypervisor
--*/
static int
HvQueryHypervisorPresence (
void
)
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
unsigned int op;
eax = 0;
ebx = 0;
ecx = 0;
edx = 0;
op = HvCpuIdFunctionVersionAndFeatures;
do_cpuid(op, &eax, &ebx, &ecx, &edx);
return (ecx & HV_PRESENT_BIT);
}
Backdoor : Hv初期化部分、Hyper-V 上かを確認
Name:
HvQueryHypervisorPresence()
Description:
Query the cpuid for presense of windows hypervisor
--*/
static int
HvQueryHypervisorPresence (
void
)
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
unsigned int op;
eax = 0;
ebx = 0;
ecx = 0;
edx = 0;
op = HvCpuIdFunctionVersionAndFeatures;
do_cpuid(op, &eax, &ebx, &ecx, &edx);
return (ecx & HV_PRESENT_BIT);
}
typedef enum _HV_CPUID_FUNCTION
{
HvCpuIdFunctionVersionAndFeatures = 0x00000001,
HvCpuIdFunctionHvVendorAndMaxFunction = 0x40000000,
HvCpuIdFunctionHvInterface = 0x40000001,
//
// The remaining functions depend on the value of HvCpuIdFunctionInterface
//
HvCpuIdFunctionMsHvVersion = 0x40000002,
HvCpuIdFunctionMsHvFeatures = 0x40000003,
HvCpuIdFunctionMsHvEnlightenmentInformation = 0x40000004,
HvCpuIdFunctionMsHvImplementationLimits = 0x40000005
} HV_CPUID_FUNCTION, *PHV_CPUID_FUNCTION;
Backdoor : Hv初期化部分、Hyper-V 上かを確認
Name:
HvQueryHypervisorPresence()
Description:
Query the cpuid for presense of windows hypervisor
--*/
static int
HvQueryHypervisorPresence (
void
)
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
unsigned int op;
eax = 0;
ebx = 0;
ecx = 0;
edx = 0;
op = HvCpuIdFunctionVersionAndFeatures;
do_cpuid(op, &eax, &ebx, &ecx, &edx);
return (ecx & HV_PRESENT_BIT);
}
static inline void do_cpuid(unsigned int op, unsigned int *eax, unsigned int *ebx, unsigned int *ecx,
unsigned int *edx)
{
__asm__ __volatile__("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (op),
"c" (ecx));
}
Backdoor : Hv初期化部分、Hyper-V 上かを確認
Name:
HvQueryHypervisorPresence()
Description:
Query the cpuid for presense of windows hypervisor
--*/
static int
HvQueryHypervisorPresence (
void
)
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
unsigned int op;
eax = 0;
ebx = 0;
ecx = 0;
edx = 0;
op = HvCpuIdFunctionVersionAndFeatures;
do_cpuid(op, &eax, &ebx, &ecx, &edx);
return (ecx & HV_PRESENT_BIT);
}
//
// #defines
//
#define HV_PRESENT_BIT 0x80000000
Backdoor : Hv初期化部分、Hyper-V 上かを確認
Hyper-V 上の仮想マシンでの
CPUID 。確かに ECX の31bit目が
立っている
ESX上の仮想マシン
31bit 目は立っていない
0x8C082201
0x80000000
0x80000000
OR
0x00080201
0x80000000
0x00000000
OR
Backdoor : Hv初期化部分、アクセス用のアドレスを設定
if  (gHvContext.GuestId  ==  HV_LINUX_GUEST_ID)
{
//  Allocate  the  hypercall  page  memory
//virtAddr  =  PageAlloc(1);;
virtAddr  =  VirtualAllocExec(PAGE_SIZE);;
if  (!virtAddr)
{
DPRINT_ERR(VMBUS,  "unable  to  allocate  hypercall  page!!");;
goto  Cleanup;;
}
hypercallMsr.Enable  =  1;;
//hypercallMsr.GuestPhysicalAddress  =  Logical2PhysicalAddr(virtAddr)  >>  PAGE_SHIFT;;
hypercallMsr.GuestPhysicalAddress  =  Virtual2Physical(virtAddr)  >>  PAGE_SHIFT;;
WriteMsr(HV_X64_MSR_HYPERCALL,  hypercallMsr.AsUINT64);;
//  Confirm  that  hypercall  page  did  get  setup.
hypercallMsr.AsUINT64  =  0;;
hypercallMsr.AsUINT64  =  ReadMsr(HV_X64_MSR_HYPERCALL);;
if  (!hypercallMsr.Enable)
{
DPRINT_ERR(VMBUS,  "unable  to  set  hypercall  page!!");;
goto  Cleanup;;
}
gHvContext.HypercallPage  =  virtAddr;;
osd.c にて定義。単に __valloc を
呼び出しているのみ。
単なるメモリ確保のため、どのアドレスが
変えるかは実行依存の模様
仮想アドレスを物理アドレスに書き換えた後、
CPU の MSR レジスタにアドレスを書き込む
Backdoor : Hypercall の実際
static u64
HvDoHypercall (
u64 Control,
void* Input,
void* Output
)
{
#ifdef CONFIG_X86_64
u64 hvStatus=0;
u64 inputAddress = (Input)? GetPhysicalAddress(Input) : 0;
u64 outputAddress = (Output)? GetPhysicalAddress(Output) : 0;
volatile void* hypercallPage = gHvContext.HypercallPage;
DPRINT_DBG(VMBUS, "Hypercall <control %llx input phys %llx virt %p output phys %llx virt %p hypercall %p>",
Control,
inputAddress,
Input,
outputAddress,
Output,
hypercallPage);
__asm__ __volatile__ ("mov %0, %%r8" : : "r" (outputAddress): "r8");
__asm__ __volatile__ ("call *%3" : "=a"(hvStatus): "c" (Control), "d" (inputAddress), "m" (hypercallPage));
DPRINT_DBG(VMBUS, "Hypercall <return %llx>", hvStatus);
return hvStatus;
RCXに命令コードを、RDX に入力データのアドレスを
突っ込んだ後、hypercallPage に call でジャンプし、
Hypercall を行っている (コールゲート?)
Backdoor : そのメモリに触ると...
l 割り込み、どうするのん?
l VMware の場合、各デバイスは PCI 上のデバイスのため、PCIバスを通じて
個々のデバイスが (ゲスト)OSへ 割り込みをかけることができた
- = VMM 側からゲストOSにデータを投げることができた
l Hyper-V の場合は?
l Linux Intergrated Module では、 IRQ 5 を VMBUS の割り込みとして使用
余談: なぜ、Hyper-V の起動ディスクは IDE only なのか
l VMBUS がないと統合モジュールが動かないため
l OS ロード前は、VMBUS などというバスを認識できない
l そもそも 仮想マシンの上の BIOS レベルで VMBUS ドライバを実装する必要
l OS ロード後なら、追加的に VMBUS をロードできる
まとめ
l 同じハイパーバイザ型の 仮想化ソフトウェアでも、Hypercall の
方法は各社各様
l 意外ときっちり実装している ESX の VMM
l vmxnet ドライバも割と普通に IO 処理を行っている
l 物理とは大きく異なる Hyper-V の VMM
l エミュレートされたデバイスとは別に、本当に架空のデバイスを生成
l VMBUS という架空のバスを通じて架空のデバイスを接続
l 原理的に、morphing はできない。
- エミュレートデバイスと架空のデバイスは永久に混在する見込み

Backdoor!! vmware-tools と 統合サービスに見るハイパーバイザの呼び出し方

  • 1.
    Backdoor !! vmware-tools と統合サービスに見るハイパーバイザの呼び出し方 2010/4/12 VTCライトニングトーク発表内容 しろやまたかゆき<shiro.t@gmail.com>
  • 2.
    Hypercall とは? l ゲストOSからハイパーバイザを明示的に呼び出す方法 l Binary Translation や VMEXIT/VMRESUME に比べコストが低い l 呼び出し方法が低コスト l ハイパーバイザに都合のいいデータのやり取りが可能 l 準仮想化された OS で使用されている l Xen 対応の Linux カーネルなど
  • 3.
    Xen での Hypercallの実装 l 単純に int 命令でソフトウェア割り込みをかけているだけ l Ring0 の Xen hyper-visor が割り込みをキャッチし、指定のルーチンを 呼び出している http://www.ylug.jp/download/hypercall_ylug_20061027.pdf
  • 4.
    Hypercall は準仮想化だけのもの? l いえ、類似/同等の機能は完全仮想化のハイパーバイザにもあります! l性能上の理由 l Binary Translation や VMEXIT/VMRESUME に比べコストが低い l 仮想化ならではの機能の実現 l VM間 / VM-Hypervisor 間の通信や制御 (VMCI)
  • 5.
    例: VMCI int a,s, len; char buf[255]; struct sockaddr_vm addr = { 0 }; s = socket(PF_VSOCK_VMCI, SOCK_STREAM, 0); addr.svm_family = AF_VSOCK_VMCI; addr.svm_cid = VMADDR_CID_ANY; addr.svm_rid = 2000; bind(s, (struct sockaddr *) &addr, sizeofaddr); listen(s, 5); a = accept(s, (struct sockaddr *) &addr, &len); len = recv(a, buf, sizeof buf, 0); int a, s, len; char *buf = “Hello, world!”; struct sockaddr_vm addr = { 0 }; s = socket(PF_VSOCK_VMCI, SOCK_STREAM, 0); addr.svm_family = AF_VSOCK_VMCI; addr.svm_cid = GUEST2; addr.svm_rid = 2000; connect(s, (struct sockaddr *) &addr, sizeof addr); len = send(s, buf, len, 0); 基本はソケットプログラミング 通常のPF_INETの代わりに PF_VSOCK_VMCIに変更。 読み書き(send/recv)は変わらず Hyper-­visor Application Guest  OS 仮想マシン Application Guest  OS 仮想マシン Server Client
  • 6.
    ESX / Hyper-Vの Hypercall l では、ESX / Hyper-V の Hypercall って どうやってるの? ソースコードにきいてみよう!
  • 7.
    VMware の場合: open-vm-tools OpenVirtual Machine Tools l vmware-tools のソースコードは公開されている (2007.9.4~) l Linux, FreeBSD, Solaris 用 l vmsync など、標準の VMware Tools にないモジュールも存在 http://open-­vm-­tools.sourceforge.net/
  • 8.
    Microsoft の場合 l Hyper-Vの統合サービス(Linux向け)のソースコードを公開!(2009.7.21) l これを読めば Hyper-V の hypercall も解明できる?
  • 9.
    で、どうやってダウンロードすればいいの? l MS のサイトを漁りまわったけどソースコードを発見できず!! l公開を報じる国内記事は多々あれど、ソースそのもののリンクはどこにもない l 駄目ぢゃん、っと思ってたら...
  • 10.
    で、どうやってダウンロードすればいいの? l Linux-Kernel MLに Greg-KH 氏が投稿していた l パッチを 54 分割して... l 何とか苦労して取り出しました l 最近のカーネルソースには既に含まれている模様
  • 11.
    ソースツリー: open-vm-tools の場合 lopen-vm-tools l 綺麗な階層構造、Makfile 完備 l さまざまなOSでのビルドを前提としている l コード数、約24万行 - 「find –name ‘*.[ch]’ -print0 | xargs -0 wc –l 」の実行結果より
  • 12.
    ソースツリー: open-vm-tools の場合 lopen-vm-tools の階層構造 +-­-­ autom4te.cache/ +-­-­ checkvm/ +-­-­ config/ +-­-­ docs/ +-­-­ hgfsclient/ +-­-­ hgfsmounter/ +-­-­ lib/ +-­-­ libguestlib/ +-­-­ libvmtools/ +-­-­ m4/ +-­-­ modules/ |      +-­-­ freebsd/ |      +-­-­ linux/ |      +-­-­ solaris/ +-­-­ rpctool/ +-­-­ scripts/ +-­-­ services/ |      +-­-­ plugins/ ||      +-­-­ vmtoolsd/ +-­-­ tests/ +-­-­ toolbox/ +-­-­ vmblock-­fuse/ +-­-­ vmware-­user/ +-­-­ vmware-­user-­suid-­wrapper/ +-­-­ xferlogs/   |      +-­-­ linux/ |      |      +-­-­ pvscsi/ |      |      +-­-­ shared/ |      |      +-­-­ vmblock/ |      |      +-­-­ vmci/ |      |      +-­-­ vmhgfs/ |      |      +-­-­ vmmemctl/ |      |      +-­-­ vmsync/ |      |      +-­-­ vmxnet/ |      |      +-­-­ vsock/ 独自のツリー構造 多くのOSに対応 共通化モジュール、ユーザランドツール、 ドライバなど綺麗に分離されている
  • 13.
    ソースツリー: Hyper-V の場合 llinux integration component l linux-kernel に対するパッチで、Linux にべったりの構造 - カーネルパッチだけ?ユーザランドに何もない?? l driver/staging/hv 以下に集中配置 l 約2万行
  • 14.
    ソースツリー: Hyper-V の場合 ファイル一覧 BlkVsc.cHvVpApi.h Vmbus.c Channel.c Kconfig VmbusApi.h Channel.h List.h VmbusChannelInterface.h ChannelInterface.c Makefile VmbusPacketFormat.h ChannelInterface.h Makefile.rej VmbusPrivate.h ChannelMessages.h NetVsc.c blkvsc_drv.c ChannelMgmt.c NetVsc.h hv.diff ChannelMgmt.h NetVscApi.h logging.h Connection.c RingBuffer.c netvsc_drv.c Hv.c RingBuffer.h nvspprotocol.h Hv.h RndisFilter.c osd.c HvHalApi.h RndisFilter.h osd.h HvHcApi.h Sources.c rndis.h HvPtApi.h StorVsc.c storvsc_drv.c HvStatus.h StorVscApi.h vmbus.h HvSynicApi.h TODO vmbus_drv.c HvTypes.h VersionInfo.h vstorage.h
  • 15.
    Linux への侵食方法:VMware の場合 あくまで「物理デバイスに対するドライバ」の立場 lVMware-tools の有無に関わらず、仮想デバイスが存在する前提 l PCIバス上にデバイスがつながっているように見える l PCIバスのデバイス検出手順に従い、ドライバがロードされていく l VendorID: 0x15AD で、指定 の DeviceID をもつドライバが 選定される l 選定されたドライバのprobe 関数を 実行して、本当に対応するデバイスか を確認させる l 失敗が帰ったら、次の候補となる ドライバを探す /* Our own PCI IDs * VMware SVGA II (Unified VGA) * VMware SVGA (PCI Accelerator) * VMware vmxnet (Idealized NIC) * VMware vmxscsi (Abortive idealized SCSI controller) * VMware chipset (Subsystem ID for our motherboards) * VMware e1000 (Subsystem ID) * VMware vmxnet3 (Uniform Pass Through NIC) */ #define PCI_VENDOR_ID_VMWARE 0x15AD #define PCI_DEVICE_ID_VMWARE_SVGA2 0x0405 #define PCI_DEVICE_ID_VMWARE_SVGA 0x0710 #define PCI_DEVICE_ID_VMWARE_NET 0x0720 #define PCI_DEVICE_ID_VMWARE_SCSI 0x0730 #define PCI_DEVICE_ID_VMWARE_VMCI 0x0740 #define PCI_DEVICE_ID_VMWARE_CHIPSET 0x1976 #define PCI_DEVICE_ID_VMWARE_82545EM 0x0750 /* single port */ #define PCI_DEVICE_ID_VMWARE_82546EB 0x0760 /* dual port */ #define PCI_DEVICE_ID_VMWARE_EHCI 0x0770 #define PCI_DEVICE_ID_VMWARE_UHCI 0x0774 #define PCI_DEVICE_ID_VMWARE_XHCI 0x0778 #define PCI_DEVICE_ID_VMWARE_1394 0x0780 #define PCI_DEVICE_ID_VMWARE_BRIDGE 0x0790 #define PCI_DEVICE_ID_VMWARE_ROOTPORT 0x07A0 #define PCI_DEVICE_ID_VMWARE_VMXNET3 0x07B0 #define PCI_DEVICE_ID_VMWARE_VMXWIFI 0x07B8 #define PCI_DEVICE_ID_VMWARE_PVSCSI 0x07C0 #define PCI_DEVICE_ID_VMWARE_82574 0x07D0 lib/include/vm_device_version.h  より
  • 16.
    Linux への侵食方法:VMware の場合 余談:vmxnet の Morphing l ESX 2.x では、vlance と vmxnet は区別されてました l vlance vendorID: 0x1022(AMD) deviceID: 0x2000 l vmxnet vendorID: 0x15AD(VMware) deviceID: 0x0720 l ESX 3.x 以降で、vlance -> vmxnet の入れ替えしました?
  • 17.
    Linux への侵食方法:VMware の場合 余談:vmxnet の Morphing l ESX 3.x 以降の vlance は、vmxnet を「兼用」している l たまたま、AMD-PCNET が 32byte しか IO空間を使ってなかった点を利用 l 64byte まで取れるIO空間の後ろ32byte に vmxnet の IO空間を追加 /* * Since this is a vlance adapter we can only use it if * its I/0 space is big enough for the adapter to be * capable of morphing. This is the first requirement * for this adapter to potentially be morphable. The * layout of a morphable LANCE adapter is * * I/O space: * * |------------------| * | LANCE IO PORTS | * |------------------| * | MORPH PORT | * |------------------| * | VMXNET IO PORTS | * |------------------| * * VLance has 8 ports of size 4 bytes, the morph port is 4 bytes, and * Vmxnet has 10 ports of size 4 bytes. * * We shift up the ioaddr with the size of the LANCE I/O space since * we want to access the vmxnet ports. We also shift the ioaddr up by * the MORPH_PORT_SIZE so other port access can be independent of * whether we are Vmxnet or a morphed VLance. This means that when * we want to access the MORPH port we need to subtract the size * from ioaddr to get to it. */
  • 18.
    Linux への侵食方法:VMware の場合 余談:vmxnet の Morphing l vmxnet ドライバの probe 関数(vmxnet_probe_device)の挙動 l PCI の Vendor ID を取得 - 0x15AD (VMware) なら、そのまま vmxnet デバイスと判断 • 旧 vmxnet との互換性の確保 - 0x1022 (AMD) の場合 • IO空間のサイズを取得し、64byte とサイズを比較 • それより小さかったら? 「うちの子じゃない」 で probe に失敗を返す • 64byte 以上なら、「うちの子だから」と認定 • morph port に outw で値を書き込み、vmxnet として振舞わせる
  • 19.
    Linux への侵食方法:VMware の場合 staticint vmxnet_morph_device(unsigned int morphAddr) // IN { uint16 magic; /* Read morph port to verify that we can morph the adapter. */ magic = inw(morphAddr); if (magic != LANCE_CHIP && magic != VMXNET_CHIP) { printk(KERN_ERR "Invalid magic, read: 0x%08X¥n", magic); return -1; } /* Morph adapter. */ outw(VMXNET_CHIP, morphAddr); /* Verify that we morphed correctly. */ magic = inw(morphAddr); if (magic != VMXNET_CHIP) { printk(KERN_ERR "Couldn't morph adapter. Invalid magic, read: 0x%08X¥n", magic); goto morph_back; } return 0; morph_back: /* Morph back to LANCE hw. */ outw(LANCE_CHIP, morphAddr); return -1; }
  • 20.
    VMM Linux への侵食方法:VMware の場合 lあくまで正規のデバイスドライバとしてロードされる l 比較的、物理デバイスに近いエミュレーションがなされている l Backdoor を使ってデバイスエミュレーションをある程度「バイパス」する Linux  kernel vmxnet_drv VGA LSILogic NIC VGA SCSI VendorID:  xxxx DeviceID  :  xxxx VendorID:  xxxx DeviceID  :  xxxx VendorID:  xxxx DeviceID  :  xxxx PCI  バス Backdoor vmsync vmUser vmmemctl VMkernel vmtools   提供ドライバ ネイティブドライバ
  • 21.
    Backdoor : IO空間のin/out 命令 l VMware の Backdoor は、指定のIO空間への in /out 命令 l in / out : x86 のもつ IO 空間へのデータの読み書きの命令 l NICなどの一般的なデバイスは IO空間の一部を通じてデータをやり取りする - グラフィックカードなどのより大きな空間を必要とするものは、メモリ空間 にマップされる - PCI は IO空間 / メモリマップのどちらの方法もサポートする l VMware ではこの in / out 命令を転用している l 詳細: http://chitchat.at.infoseek.co.jp/vmware/backdoorj.html
  • 22.
    Backdoor : IO空間のin/out 命令 static  void BalloonTimerHandler(void  *clientData)  //  IN { Balloon  *b  =  (Balloon  *)  clientData;; uint32  target  =  0;;  //  Silence  compiler  warning. int  status;; /*  update  stats  */ STATS_INC(b-­>stats.timer);; /*  reset,  if  specified  */ if  (b-­>resetFlag)  { BalloonReset(b);; } /*  contact  monitor  via  backdoor  */ status  =  BalloonMonitorGetTarget(b,  &target);; /*  decrement  slowPageAllocationCycles  counter  */ if  (b-­>slowPageAllocationCycles  >  0)  { b-­>slowPageAllocationCycles-­-­;; } if  (status  ==  BALLOON_SUCCESS)  { /*  update  target,  adjust  size  */ b-­>nPagesTarget  =  target;; (void)  BalloonAdjustSize(b,  target);; } }
  • 23.
    Backdoor : IO空間のin/out 命令 static  void BalloonTimerHandler(void  *clientData)  //  IN { Balloon  *b  =  (Balloon  *)  clientData;; uint32  target  =  0;;  //  Silence  compiler  warning. int  status;; /*  update  stats  */ STATS_INC(b-­>stats.timer);; /*  reset,  if  specified  */ if  (b-­>resetFlag)  { BalloonReset(b);; } /*  contact  monitor  via  backdoor  */ status  =  BalloonMonitorGetTarget(b,  &target);; /*  decrement  slowPageAllocationCycles  counter  */ if  (b-­>slowPageAllocationCycles  >  0)  { b-­>slowPageAllocationCycles-­-­;; } if  (status  ==  BALLOON_SUCCESS)  { /*  update  target,  adjust  size  */ b-­>nPagesTarget  =  target;; (void)  BalloonAdjustSize(b,  target);; } } static  int BalloonMonitorGetTarget(Balloon  *b,          //  IN uint32  *target)  //  OUT { Backdoor_proto  bp;; unsigned  long  limit;; uint32  limit32;; uint32  status;; limit  =  OS_ReservedPageGetLimit();; /*  Ensure  limit  fits  in  32-­bits  */ limit32  =  (uint32)limit;; if  (limit32  !=  limit)  { return  BALLOON_FAILURE;; } /*  prepare  backdoor  args  */ bp.in.cx.halfs.low  =  BALLOON_BDOOR_CMD_TARGET;; bp.in.size  =  limit;; /*  invoke  backdoor  */ Backdoor_Balloon(&bp);; /*  parse  return  values  */ status    =  bp.out.ax.word;; *target  =  bp.out.bx.word;;
  • 24.
    Backdoor : IO空間のin/out 命令 static  void BalloonTimerHandler(void  *clientData)  //  IN { Balloon  *b  =  (Balloon  *)  clientData;; uint32  target  =  0;;  //  Silence  compiler  warning. int  status;; /*  update  stats  */ STATS_INC(b-­>stats.timer);; /*  reset,  if  specified  */ if  (b-­>resetFlag)  { BalloonReset(b);; } /*  contact  monitor  via  backdoor  */ status  =  BalloonMonitorGetTarget(b,  &target);; /*  decrement  slowPageAllocationCycles  counter  */ if  (b-­>slowPageAllocationCycles  >  0)  { b-­>slowPageAllocationCycles-­-­;; } if  (status  ==  BALLOON_SUCCESS)  { /*  update  target,  adjust  size  */ b-­>nPagesTarget  =  target;; (void)  BalloonAdjustSize(b,  target);; } } static  int BalloonMonitorGetTarget(Balloon  *b,          //  IN uint32  *target)  //  OUT { Backdoor_proto  bp;; unsigned  long  limit;; uint32  limit32;; uint32  status;; limit  =  OS_ReservedPageGetLimit();; /*  Ensure  limit  fits  in  32-­bits  */ limit32  =  (uint32)limit;; if  (limit32  !=  limit)  { return  BALLOON_FAILURE;; } /*  prepare  backdoor  args  */ bp.in.cx.halfs.low  =  BALLOON_BDOOR_CMD_TARGET;; bp.in.size  =  limit;; /*  invoke  backdoor  */ Backdoor_Balloon(&bp);; /*  parse  return  values  */ status    =  bp.out.ax.word;; *target  =  bp.out.bx.word;; static  INLINE void  Backdoor_Balloon(Backdoor_proto  *myBp)  { myBp-­>in.ax.word  =  BALLOON_BDOOR_MAGIC;; myBp-­>in.dx.halfs.low  =  BALLOON_BDOOR_PORT;; Backdoor_InOut(myBp);; }
  • 25.
    Backdoor : IO空間のin/out 命令 Backdoor_InOut(Backdoor_proto *myBp) // IN/OUT { uint32 dummy; __asm__ __volatile__( #ifdef __PIC__ "pushl %%ebx" "¥n¥t" #endif "pushl %%eax" "¥n¥t" "movl 20(%%eax), %%edi" "¥n¥t" "movl 16(%%eax), %%esi" "¥n¥t" "movl 12(%%eax), %%edx" "¥n¥t" "movl 8(%%eax), %%ecx" "¥n¥t" "movl 4(%%eax), %%ebx" "¥n¥t" "movl (%%eax), %%eax" "¥n¥t" "inl %%dx, %%eax" "¥n¥t" "xchgl %%eax, (%%esp)" "¥n¥t" "movl %%edi, 20(%%eax)" "¥n¥t" "movl %%esi, 16(%%eax)" "¥n¥t" "movl %%edx, 12(%%eax)" "¥n¥t" "movl %%ecx, 8(%%eax)" "¥n¥t" "movl %%ebx, 4(%%eax)" "¥n¥t" "popl (%%eax)" "¥n¥t" #ifdef __PIC__ "popl %%ebx" "¥n¥t" #endif : "=a" (dummy) : "0" (myBp) /* * vmware can modify the whole VM state without the compiler knowing * it. So far it does not modify EFLAGS. --hpreg */ : #ifndef __PIC__ "ebx", #endif "ecx", "edx", "esi", "edi", "memory" ); } この inl 命令の実行した瞬間に VMkernel 側に処理が渡り、該当する 機能が実行され、結果が vCPU の EAX 、ECX 、 EDX 、 ESI、EDI レジスタ に書き込まれる
  • 26.
    Hyper-V のドライバイメージ l Hyper-Vの場合は、デバイスではなく「バス」を追加している l PCI バスの下に、なんかバスがある... l なんかバスが「外」に伸びてるっぽい... VMM Linux  kernel レガシNIC VGA IDE VendorID:  xxxx DeviceID  :  xxxx VendorID:  xxxx DeviceID  :  xxxx VendorID:  xxxx DeviceID  :  xxxx PCI  バス ネイティブドライバ 21140 VGA IDE VMBUS Backdoor vmbus_drv netvsc_drv storage_drv NIC SCSI Hyper-­V ParentVM
  • 27.
    Hyper-V のドライバイメージ l Hyper-Vの場合は、デバイスではなく「バス」を追加している l PCI バスの下に、なんかバスがある... l なんかバスが「外」に伸びてるっぽい...
  • 28.
    Linux の侵食方法 :Hyper-V の場合 l とにかく vmbus_drv デバイスドライバをロードする l VMBUS という何らかの「バス」があると Linux に誤認させる l VMBUS も、PCI と同じようにバス上のノードの検出を行う - VendorID と Device ID ではなく、ClassID という GUID で識別 - ノードごと、netvsc_drv, storagevsc_drv, blkvsc_drv がロードされる • VSC : Virtualization Service Client の略 l 各 VSC はデバイスドライバだが、その下にはエミュレーションされたデバイス はまったく存在しない!!
  • 29.
    Linux の侵食方法 :Hyper-V の場合 http://enterprise.watch.impress.co.jp/cda/parts/image_for_link/41502-­13748-­4-­1.html 本当にこの通りでした...
  • 30.
    Linux の侵食方法 :Hyper-V の場合 もう少し詳しい 階層構造 l 大きく4層に分かれる l *_drv : Linux のデバイスドライバ(カーネルモジュール)としての体裁を整える l Vsc, Vmbus : 各ドライバの実態となる実装 l Channel, Connection : VMBUS の通信機能の実装 l osd : メモリ確保など OS機能の抽象化 l Hv : Hyper-V との 実際のインターフェイス Linux  kernel osd VMM Vmbus vmbus_drvstoragevsc_drvnetvsc_drv blcvsc_drv Hv Channel Connection Channel  Mgmt StorVscNetVsc BlcVsc Linux  Kernel  とのインターフェイス ドライバー中心部分 VMBUS  通信機能
  • 31.
    Backdoor : そのメモリに触ると... Hyper-Vのバックドアは特定のメモリアドレス l Hv はロードされると以下の振る舞いをする l CPUID 命令を実行し、Hyper-V の上かを確認する - CPUID(1) の ECXの31bit 目を確認 l 続けて CPUID 命令を実行し、ハイパーバイザのベンダー(!)や バージョンを取得する l メモリを1ページ確保する - これは通常の valloc が使用され、実行時により異なるページが 割り当てられる l wmsr でそのメモリの物理アドレスを vCPU に書き込む(!) - MSR: Model Specific Register • PentiumPro 以降のCPUにある、製品独自の設定を保存したり、 プロファイリング用のカウンタとなるレジスタ • 詳細: http://mcn.oops.jp/wiki/index.php?CPU%2FCPUID%2FMSR l 以降、Hypercall はこのメモリへのアクセスすると、Hypercall が発生する
  • 32.
    Backdoor : Hv初期化部分、Hyper-V上かを確認 Name: HvQueryHypervisorPresence() Description: Query the cpuid for presense of windows hypervisor --*/ static int HvQueryHypervisorPresence ( void ) { unsigned int eax; unsigned int ebx; unsigned int ecx; unsigned int edx; unsigned int op; eax = 0; ebx = 0; ecx = 0; edx = 0; op = HvCpuIdFunctionVersionAndFeatures; do_cpuid(op, &eax, &ebx, &ecx, &edx); return (ecx & HV_PRESENT_BIT); }
  • 33.
    Backdoor : Hv初期化部分、Hyper-V上かを確認 Name: HvQueryHypervisorPresence() Description: Query the cpuid for presense of windows hypervisor --*/ static int HvQueryHypervisorPresence ( void ) { unsigned int eax; unsigned int ebx; unsigned int ecx; unsigned int edx; unsigned int op; eax = 0; ebx = 0; ecx = 0; edx = 0; op = HvCpuIdFunctionVersionAndFeatures; do_cpuid(op, &eax, &ebx, &ecx, &edx); return (ecx & HV_PRESENT_BIT); } typedef enum _HV_CPUID_FUNCTION { HvCpuIdFunctionVersionAndFeatures = 0x00000001, HvCpuIdFunctionHvVendorAndMaxFunction = 0x40000000, HvCpuIdFunctionHvInterface = 0x40000001, // // The remaining functions depend on the value of HvCpuIdFunctionInterface // HvCpuIdFunctionMsHvVersion = 0x40000002, HvCpuIdFunctionMsHvFeatures = 0x40000003, HvCpuIdFunctionMsHvEnlightenmentInformation = 0x40000004, HvCpuIdFunctionMsHvImplementationLimits = 0x40000005 } HV_CPUID_FUNCTION, *PHV_CPUID_FUNCTION;
  • 34.
    Backdoor : Hv初期化部分、Hyper-V上かを確認 Name: HvQueryHypervisorPresence() Description: Query the cpuid for presense of windows hypervisor --*/ static int HvQueryHypervisorPresence ( void ) { unsigned int eax; unsigned int ebx; unsigned int ecx; unsigned int edx; unsigned int op; eax = 0; ebx = 0; ecx = 0; edx = 0; op = HvCpuIdFunctionVersionAndFeatures; do_cpuid(op, &eax, &ebx, &ecx, &edx); return (ecx & HV_PRESENT_BIT); } static inline void do_cpuid(unsigned int op, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx) { __asm__ __volatile__("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (op), "c" (ecx)); }
  • 35.
    Backdoor : Hv初期化部分、Hyper-V上かを確認 Name: HvQueryHypervisorPresence() Description: Query the cpuid for presense of windows hypervisor --*/ static int HvQueryHypervisorPresence ( void ) { unsigned int eax; unsigned int ebx; unsigned int ecx; unsigned int edx; unsigned int op; eax = 0; ebx = 0; ecx = 0; edx = 0; op = HvCpuIdFunctionVersionAndFeatures; do_cpuid(op, &eax, &ebx, &ecx, &edx); return (ecx & HV_PRESENT_BIT); } // // #defines // #define HV_PRESENT_BIT 0x80000000
  • 36.
    Backdoor : Hv初期化部分、Hyper-V上かを確認 Hyper-V 上の仮想マシンでの CPUID 。確かに ECX の31bit目が 立っている ESX上の仮想マシン 31bit 目は立っていない 0x8C082201 0x80000000 0x80000000 OR 0x00080201 0x80000000 0x00000000 OR
  • 37.
    Backdoor : Hv初期化部分、アクセス用のアドレスを設定 if (gHvContext.GuestId  ==  HV_LINUX_GUEST_ID) { //  Allocate  the  hypercall  page  memory //virtAddr  =  PageAlloc(1);; virtAddr  =  VirtualAllocExec(PAGE_SIZE);; if  (!virtAddr) { DPRINT_ERR(VMBUS,  "unable  to  allocate  hypercall  page!!");; goto  Cleanup;; } hypercallMsr.Enable  =  1;; //hypercallMsr.GuestPhysicalAddress  =  Logical2PhysicalAddr(virtAddr)  >>  PAGE_SHIFT;; hypercallMsr.GuestPhysicalAddress  =  Virtual2Physical(virtAddr)  >>  PAGE_SHIFT;; WriteMsr(HV_X64_MSR_HYPERCALL,  hypercallMsr.AsUINT64);; //  Confirm  that  hypercall  page  did  get  setup. hypercallMsr.AsUINT64  =  0;; hypercallMsr.AsUINT64  =  ReadMsr(HV_X64_MSR_HYPERCALL);; if  (!hypercallMsr.Enable) { DPRINT_ERR(VMBUS,  "unable  to  set  hypercall  page!!");; goto  Cleanup;; } gHvContext.HypercallPage  =  virtAddr;; osd.c にて定義。単に __valloc を 呼び出しているのみ。 単なるメモリ確保のため、どのアドレスが 変えるかは実行依存の模様 仮想アドレスを物理アドレスに書き換えた後、 CPU の MSR レジスタにアドレスを書き込む
  • 38.
    Backdoor : Hypercallの実際 static u64 HvDoHypercall ( u64 Control, void* Input, void* Output ) { #ifdef CONFIG_X86_64 u64 hvStatus=0; u64 inputAddress = (Input)? GetPhysicalAddress(Input) : 0; u64 outputAddress = (Output)? GetPhysicalAddress(Output) : 0; volatile void* hypercallPage = gHvContext.HypercallPage; DPRINT_DBG(VMBUS, "Hypercall <control %llx input phys %llx virt %p output phys %llx virt %p hypercall %p>", Control, inputAddress, Input, outputAddress, Output, hypercallPage); __asm__ __volatile__ ("mov %0, %%r8" : : "r" (outputAddress): "r8"); __asm__ __volatile__ ("call *%3" : "=a"(hvStatus): "c" (Control), "d" (inputAddress), "m" (hypercallPage)); DPRINT_DBG(VMBUS, "Hypercall <return %llx>", hvStatus); return hvStatus; RCXに命令コードを、RDX に入力データのアドレスを 突っ込んだ後、hypercallPage に call でジャンプし、 Hypercall を行っている (コールゲート?)
  • 39.
    Backdoor : そのメモリに触ると... l割り込み、どうするのん? l VMware の場合、各デバイスは PCI 上のデバイスのため、PCIバスを通じて 個々のデバイスが (ゲスト)OSへ 割り込みをかけることができた - = VMM 側からゲストOSにデータを投げることができた l Hyper-V の場合は? l Linux Intergrated Module では、 IRQ 5 を VMBUS の割り込みとして使用
  • 40.
    余談: なぜ、Hyper-V の起動ディスクはIDE only なのか l VMBUS がないと統合モジュールが動かないため l OS ロード前は、VMBUS などというバスを認識できない l そもそも 仮想マシンの上の BIOS レベルで VMBUS ドライバを実装する必要 l OS ロード後なら、追加的に VMBUS をロードできる
  • 41.
    まとめ l 同じハイパーバイザ型の 仮想化ソフトウェアでも、Hypercallの 方法は各社各様 l 意外ときっちり実装している ESX の VMM l vmxnet ドライバも割と普通に IO 処理を行っている l 物理とは大きく異なる Hyper-V の VMM l エミュレートされたデバイスとは別に、本当に架空のデバイスを生成 l VMBUS という架空のバスを通じて架空のデバイスを接続 l 原理的に、morphing はできない。 - エミュレートデバイスと架空のデバイスは永久に混在する見込み