在《处理器虚拟化技术》3.10.1.2中,描述了vm-exit产生的原因。其中指出了会无条件导致vmexit事件产生的指令,如下图所示

可见,在虚拟机中,执行所有除vmfunc之外的指令时,都会无条件导致vmexit事件的发生。除此之外, cpuid
、 getsec
、 invd
、 xsetbv
指令也会无条件导致vmexit事件的发生。那么我们需要进行处理的无条件退出事件如下






在3.4.3 32位字段ID中,可以找到控制区中对应的vmexit信息字段。

可以找到退出原因、导致退出的指令的长度、信息。
在《处理器虚拟化技术》3.10.1.1中,列出了exit reason字段的组成。如下图

可见,0~15位表明了vm退出的原因,其他位还有其他的指示作用。因此这里应该将其他位取出,只通过0~15位进行vm退出原因的判断。
综上所述,可以搭建出一个vmexit事件处理函数的框架如下
由于我们并不准备对VT嵌套进行实现,因此这里我们需要对guest中进行的vmx事件返回错误。在我们之前的文章中提到过在开启VT的时候,如果出现错误,则CF和ZF不全为0。只有vmx指令被成功执行的时候CF和ZF才会全部被置为0。因此这里为了让虚拟机其意识到无法继续进入VT环境,需要把CF和ZF置为1。对于执行错误时对rflags寄存器的影响在《处理器虚拟化技术》2.6.2中有说明,如下

rflag寄存器中位置如图(rflag寄存器是eflag寄存器的简单扩充,高32位没有使用,全为0)

可见CF和ZF分别为第0位和第6位。只要将这两个位置为1并返回即可。代码如下
cpuid也是一定会导致vm-exit事件发生的指令。如果不需要对cpuid的一些特定行为进行特定的处理,直接在处理函数中进行一次cpuid然后将得到的值返回给guest即可。这里注意处理函数相当于是在host环境下,在这里进行cpuid并不会重复导致vm-exit事件的发生。
为了检验我们是否真的拦截到了cpuid指令,可以通过对特殊值的判断来进行,如下
如果rax的值为0x8888的话,就将rax rbx rcx rdx分别置为特殊值。结果如下

可见我们已经成功hook了cpuid指令。
getsec
这个指令一般不会被调用,在开启 SGX
的时候可能会调用。不过我们用不到,因此这里可以暂时不进行处理。
invd
直接简单在host环境下进行一次 invd
指令并返回即可。
当需要在虚拟机内部和虚拟机外部进行通信的时候,我们可以用任意一个会产生vmexit的事件来进行。只要在寄存器中存放约定好的参数即可。这里我们利用这个特性来实现关闭VT的功能。
我们规定这样一个规则,使用vmcall指令导致退出时,如果发现当前rax为'abcd',那么就退出VT环境。这里要注意vmcall这个函数vs并没有提供接口,因此需要自己进行汇编代码的编写。如下
则需要退出虚拟机时进行如下调用即可
当发现退出事件是 vmcall
,且rax='abcd'时,直接调用 __vmx_off()
指令关闭VT。要注意的是,在关闭了VT之后,还是要跳回到vmcall指令的下一条指令继续执行。因此这里我们需要通过汇编直接修改rsp和rip跳回去。我们封装一个 AsmJmpRet
函数,传入需要返回的rip和返回后的rsp,进行rsp的设置和rip的跳转。
在上一篇文章中,我们提到了processor-based vm-execution control字段。对这个字段的设置可以让执行一些特定指令或者读写某些特定寄存器的时候产生vm-exit事件。上一篇文章中我们给这个字段填为全0,并未对其进行相应设置。现在我们对其进行一定的设置。如图,可以看到该寄存器的第28位为1的话,则表示将会启动MSR bitmap。

MSR bitmap的具体含义如下

即当Use MSR bitmap位为1时,可以为 MSR_BITMAP
字段提供一个MSR bitmap区域的物理地址。根据一定规则对其进行填充后,读写相应的寄存器时即会产生有条件的vm-exit事件。至于这块内存区域应该如何填充才能对特定MSR进行拦截,其规则在《处理器虚拟化技术》3.5.15小节中有具体描述,规则如下


对MSR bitmap的设置较为简单,对想要拦截的位置1即可,代码如下,不做过多阐述。
论坛中小宝来了前辈发过一篇分析如何让VT支持win10的精华帖https://bbs.pediy.com/thread-212786.htm,其中提到了为了让VT支持win10,需要对rdtscp指令进行处理。如果不处理,则会因为产生#UD异常而导致系统崩溃。
为了防止因为指令不存在而出现#UD异常,我们需要将所有如果不处理的话可能导致#UD异常的指令进行处理。由于《处理器虚拟化技术》这本书成书时间比较早,书上对Secondary Processor-Based VM-Execution Controls字段的描述并不是很全,因此这里我们看intel白皮书中的相关内容。在intel白皮书24.6中,有Secondary Processor-Based VM-Execution Controls字段的描述表格。可以看到里面有如下几个不处理会导致#UD异常的指令


这里首先要对Secondary Processor-Based VM-Execution Controls字段进行相应设置,使其能够拦截以上的指令
对于rdtscp指令,其作用如下
于是我搜索了下RDTSCP这个指令的用途,它是RDTSC的升级版,在一些比较新的处理器中用于获得CPU时间计数器。
按照intel白皮书上的调用方法写出对应处理流程即可,其具体做了什么并不用太关心。
小宝在进行处理的时候将 rdtsc
指令也进行了处理。在intel白皮书25.1.3中写到,如果处理rdtsc和rdtscp的位同时置为1才会产生vm-exit事件

25.3中说明了

如果不设置 rdtsc exiting
和 use tsc offsetting
的话,那么 rdtscp
会正常执行,因此这里其实可以只设置 SECONDARY_EXEC_ENABLE_RDTSCP
,其他不设置。这样的话 rdtscp
指令的执行并不会导致vm-exit事件的发生。这里我尝试了只将 SECONDARY_EXEC_ENABLE_RDTSCP
控制位置位,其他位不动,发现完全不会进入到vm-exit事件中。因此如果图省事的话其实可以只将 SECONDARY_EXEC_ENABLE_RDTSCP
置位,其他位不进行操作即可。
在《处理器虚拟化技术》3.10.4.2中描述了invept、invpcid、invvpid指令vm-exit事件时寄存器中保存的信息以及应该如何对其进行处理。

在由这三条指令产生vm-exit事件时,vm-exit qualification字段中会记录指令操作数中的偏移量,也就是图中的disp值。而vm-exit instruction information字段中会记录其他的信息。具体保存的信息内容如下


可见其中存储了 register operand
segment
base
index
scale
信息。只需要将这些寄存器填入获取地址,然后调用 _invpcid
指令即可。注意字段中还有 index invalid
base invalid
字段用来指示对应的字段是否有效以及 address size
用来指示地址的大小。对这些标志也要进行相应的判断。代码如下
跟上面的类似,如果 invlpg exiting
没有被设置,那么也不会导致vm-exit事件的发生。因此这里其实也可以只设置 SECONDARY_EXEC_ENABLE_INVPCID
,不设置 invlpg exiting
。这样可以让其正常执行,不用在vm-exit处理函数中对其进行复杂的处理。

这个指令好像没看到有人处理,4baK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6I4M7e0p5H3y4o6f1#2y4e0p5H3y4K6m8Q4x3V1k6h3N6q4c8G2e0h3g2Q4c8e0S2Q4b7V1k6Q4z5e0W2Q4c8e0c8Q4b7U0S2Q4b7f1q4Q4c8e0c8Q4b7V1u0Q4z5e0y4Q4c8e0g2Q4b7V1q4Q4z5e0y4Q4c8e0c8Q4b7U0S2Q4b7f1c8Q4c8e0c8Q4b7U0W2Q4z5f1k6Q4c8e0g2Q4z5e0m8Q4z5p5y4Q4c8e0k6Q4b7e0m8Q4b7U0N6Q4c8e0k6Q4b7U0u0Q4b7e0q4Q4c8e0k6Q4z5f1y4Q4z5o6W2Q4c8e0g2Q4b7e0c8Q4z5o6c8Q4c8e0N6Q4z5e0m8Q4z5o6k6Q4c8e0S2Q4b7V1k6Q4z5e0W2Q4c8e0c8Q4b7U0S2Q4b7f1q4Q4c8e0c8Q4b7V1q4Q4z5p5u0Q4c8e0c8Q4b7V1u0Q4b7U0k6Q4c8e0y4Q4z5o6m8Q4z5o6u0Q4c8e0c8Q4b7U0S2Q4z5p5c8Q4c8e0S2Q4b7V1k6Q4z5o6N6%4K9h3&6V1L8%4N6K6i4@1f1#2i4K6R3$3i4K6R3#2i4@1f1$3i4@1p5H3i4@1t1^5i4@1f1@1i4@1t1^5i4@1q4p5i4@1f1%4i4@1p5I4i4@1q4q4i4@1f1#2i4@1q4q4i4K6W2q4i4@1f1$3i4K6W2o6i4K6R3&6i4@1f1^5i4@1u0r3i4K6V1&6i4@1f1@1i4@1t1^5i4@1q4m8i4@1f1$3i4K6S2o6i4K6R3%4i4@1f1@1i4@1u0n7i4@1p5@1i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1^5i4@1t1H3i4K6R3K6i4@1f1%4i4K6V1@1i4@1p5^5i4@1f1K6i4K6R3H3i4K6R3J5i4@1f1^5i4@1u0r3i4K6V1&6i4@1f1@1i4@1t1^5i4@1q4m8i4@1f1$3i4K6S2o6i4K6R3%4i4@1f1@1i4@1u0n7i4@1p5@1i4@1f1@1i4@1u0m8i4@1p5%4i4@1f1%4i4K6V1@1i4K6W2r3N6X3#2Q4x3X3c8W2P5r3W2@1i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1&6i4K6R3H3i4@1u0n7i4@1f1^5i4@1u0q4i4K6V1I4i4@1f1@1i4@1t1&6i4K6W2r3i4@1f1@1i4@1t1^5i4K6S2q4i4@1f1#2i4K6R3&6i4K6S2p5i4@1f1&6i4K6W2p5i4@1p5J5i4@1f1@1i4@1t1^5i4@1p5@1i4@1f1@1i4@1t1^5i4@1q4m8i4@1f1$3i4K6S2o6i4K6R3%4i4@1f1@1i4@1u0n7i4@1p5@1i4@1f1%4i4@1t1I4i4@1u0n7i4@1f1@1i4@1u0o6i4@1u0o6

因此这里不进行处理,只设置 SECONDARY_EXEC_XSAVES
位,不设置 xss-exiting bitmap
,让其不产生vm-exit事件,进行正常的处理。
本篇文章对应的代码晚些会传到github仓库431K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6K6L8h3q4D9L8s2A6Z5L8$3&6Y4i4K6u0r3L8i4W2$3N6q4!0q4y4q4!0n7z5q4!0m8c8l9`.`.
EXTERN_C VOID VmxExitHandler(PGuestContext context)
{
ULONG64 reason
=
0
;
ULONG64 instLen
=
0
;
ULONG64 instinfo
=
0
;
ULONG64 mrip
=
0
;
ULONG64 mrsp
=
0
;
__vmx_vmread(VM_EXIT_REASON, &reason);
__vmx_vmread(VM_EXIT_INSTRUCTION_LEN, &instLen);
/
/
获取指令长度
__vmx_vmread(VMX_INSTRUCTION_INFO, &instinfo);
/
/
指令详细信息
__vmx_vmread(GUEST_RIP, &mrip);
/
/
获取客户机触发VT事件的地址
__vmx_vmread(GUEST_RSP, &mrsp);
/
/
获取事件码
reason
=
reason &
0xFFFF
;
switch (reason)
{
case EXIT_REASON_CPUID:
case EXIT_REASON_GETSEC:
case EXIT_REASON_TRIPLE_FAULT:
case EXIT_REASON_INVD:
case EXIT_REASON_VMCALL :
case EXIT_REASON_VMCLEAR :
case EXIT_REASON_VMLAUNCH :
case EXIT_REASON_VMPTRLD :
case EXIT_REASON_VMPTRST :
case EXIT_REASON_VMREAD :
case EXIT_REASON_VMRESUME :
case EXIT_REASON_VMWRITE :
case EXIT_REASON_VMXOFF :
case EXIT_REASON_VMXON :
case EXIT_REASON_MSR_READ:
case EXIT_REASON_MSR_WRITE:
case EXIT_REASON_XSETBV:
}
__vmx_vmwrite(GUEST_RIP, mrip
+
instLen);
__vmx_vmwrite(GUEST_RSP, mrsp);
}
EXTERN_C VOID VmxExitHandler(PGuestContext context)
{
ULONG64 reason
=
0
;
ULONG64 instLen
=
0
;
ULONG64 instinfo
=
0
;
ULONG64 mrip
=
0
;
ULONG64 mrsp
=
0
;
__vmx_vmread(VM_EXIT_REASON, &reason);
__vmx_vmread(VM_EXIT_INSTRUCTION_LEN, &instLen);
/
/
获取指令长度
__vmx_vmread(VMX_INSTRUCTION_INFO, &instinfo);
/
/
指令详细信息
__vmx_vmread(GUEST_RIP, &mrip);
/
/
获取客户机触发VT事件的地址
__vmx_vmread(GUEST_RSP, &mrsp);
/
/
获取事件码
reason
=
reason &
0xFFFF
;
switch (reason)
{
case EXIT_REASON_CPUID:
case EXIT_REASON_GETSEC:
case EXIT_REASON_TRIPLE_FAULT:
case EXIT_REASON_INVD:
case EXIT_REASON_VMCALL :
case EXIT_REASON_VMCLEAR :
case EXIT_REASON_VMLAUNCH :
case EXIT_REASON_VMPTRLD :
case EXIT_REASON_VMPTRST :
case EXIT_REASON_VMREAD :
case EXIT_REASON_VMRESUME :
case EXIT_REASON_VMWRITE :
case EXIT_REASON_VMXOFF :
case EXIT_REASON_VMXON :
case EXIT_REASON_MSR_READ:
case EXIT_REASON_MSR_WRITE:
case EXIT_REASON_XSETBV:
}
__vmx_vmwrite(GUEST_RIP, mrip
+
instLen);
__vmx_vmwrite(GUEST_RSP, mrsp);
}
case EXIT_REASON_VMCALL :
case EXIT_REASON_VMCLEAR :
case EXIT_REASON_VMLAUNCH :
case EXIT_REASON_VMPTRLD :
case EXIT_REASON_VMPTRST :
case EXIT_REASON_VMREAD :
case EXIT_REASON_VMRESUME :
case EXIT_REASON_VMWRITE :
case EXIT_REASON_VMXOFF :
case EXIT_REASON_VMXON :
{
ULONG64 rfl
=
0
;
__vmx_vmread(GUEST_RFLAGS, &rfl);
rfl |
=
0x41
;
__vmx_vmwrite(GUEST_RFLAGS, &rfl);
}
break
;
case EXIT_REASON_VMCALL :
case EXIT_REASON_VMCLEAR :
case EXIT_REASON_VMLAUNCH :
case EXIT_REASON_VMPTRLD :
case EXIT_REASON_VMPTRST :
case EXIT_REASON_VMREAD :
case EXIT_REASON_VMRESUME :
case EXIT_REASON_VMWRITE :
case EXIT_REASON_VMXOFF :
case EXIT_REASON_VMXON :
{
ULONG64 rfl
=
0
;
__vmx_vmread(GUEST_RFLAGS, &rfl);
rfl |
=
0x41
;
__vmx_vmwrite(GUEST_RFLAGS, &rfl);
}
break
;
VOID VmxHandlerCpuid(PGuestContext context)
{
if
(context
-
>mRax
=
=
0x8888
)
{
context
-
>mRax
=
0x11111111
;
context
-
>mRbx
=
0x22222222
;
context
-
>mRcx
=
0x33333333
;
context
-
>mRdx
=
0x44444444
;
}
else
{
int
cpuids[
4
]
=
{
0
};
__cpuidex(cpuids,context
-
>mRax, context
-
>mRcx);
context
-
>mRax
=
cpuids[
0
];
context
-
>mRbx
=
cpuids[
1
];
context
-
>mRcx
=
cpuids[
2
];
context
-
>mRdx
=
cpuids[
3
];
}
}
VOID VmxHandlerCpuid(PGuestContext context)
{
if
(context
-
>mRax
=
=
0x8888
)
{
context
-
>mRax
=
0x11111111
;
context
-
>mRbx
=
0x22222222
;
context
-
>mRcx
=
0x33333333
;
context
-
>mRdx
=
0x44444444
;
}
else
{
int
cpuids[
4
]
=
{
0
};
__cpuidex(cpuids,context
-
>mRax, context
-
>mRcx);
context
-
>mRax
=
cpuids[
0
];
context
-
>mRbx
=
cpuids[
1
];
context
-
>mRcx
=
cpuids[
2
];
context
-
>mRdx
=
cpuids[
3
];
}
}
AsmInvd proc
invd;
ret
AsmInvd endp;
AsmInvd proc
invd;
ret
AsmInvd endp;
case EXIT_REASON_INVD:
{
AsmInvd();
}
break
;
case EXIT_REASON_INVD:
{
AsmInvd();
}
break
;
case EXIT_REASON_XSETBV:
{
ULONG64 value
=
MAKE_REG(context
-
>mRax, context
-
>mRdx);
_xsetbv(context
-
>mRcx, value);
}
case EXIT_REASON_XSETBV:
{
ULONG64 value
=
MAKE_REG(context
-
>mRax, context
-
>mRdx);
_xsetbv(context
-
>mRcx, value);
}
AsmVmCall proc
mov rax,rcx
vmcall
ret;
AsmVmCall endp;
AsmVmCall proc
mov rax,rcx
vmcall
ret;
AsmVmCall endp;
AsmVmCall(
'abcd'
);
case EXIT_REASON_VMCALL:
{
if
(context
-
>mRax
=
=
'abcd'
)
{
__vmx_off();
AsmJmpRet(mrip
+
instLen, mrsp);
return
;
}
else
{
ULONG64 rfl
=
0
;
__vmx_vmread(GUEST_RFLAGS, &rfl);
rfl |
=
0x41
;
__vmx_vmwrite(GUEST_RFLAGS, &rfl);
}
}
break
;
case EXIT_REASON_VMCALL:
{
if
(context
-
>mRax
=
=
'abcd'
)
{
__vmx_off();
AsmJmpRet(mrip
+
instLen, mrsp);
return
;
}
else
{
ULONG64 rfl
=
0
;
__vmx_vmread(GUEST_RFLAGS, &rfl);
rfl |
=
0x41
;
__vmx_vmwrite(GUEST_RFLAGS, &rfl);
}
}
break
;
AsmJmpRet proc
mov rsp,rdx;
jmp rcx;
ret;
AsmJmpRet endp;
AsmJmpRet proc
mov rsp,rdx;
jmp rcx;
ret;
AsmJmpRet endp;
BOOLEAN VmxSetReadMsrBitMap(PUCHAR msrBitMap, ULONG64 msrAddrIndex, BOOLEAN isEnable)
{
if
(msrAddrIndex >
=
0xC0000000
)
{
msrBitMap
+
=
1024
;
msrAddrIndex
-
=
0xC0000000
;
}
ULONG64 moveByte
=
0
;
ULONG64 setBit
=
0
;
if
(msrAddrIndex !
=
0
)
{
moveByte
=
msrAddrIndex
/
8
;
setBit
=
msrAddrIndex
%
8
;
msrBitMap
+
=
moveByte;
}
if
(isEnable)
{
*
msrBitMap |
=
1
<< setBit;
}
else
{
*
msrBitMap &
=
~(
1
<< setBit);
}
return
TRUE;
}
BOOLEAN VmxSetWriteMsrBitMap(PUCHAR msrBitMap, ULONG64 msrAddrIndex, BOOLEAN isEnable)
{
msrBitMap
+
=
0x800
;
return
VmxSetReadMsrBitMap(msrBitMap, msrAddrIndex, isEnable);
}
BOOLEAN VmxSetReadMsrBitMap(PUCHAR msrBitMap, ULONG64 msrAddrIndex, BOOLEAN isEnable)
{
if
(msrAddrIndex >
=
0xC0000000
)
{
msrBitMap
+
=
1024
;
msrAddrIndex
-
=
0xC0000000
;
}
ULONG64 moveByte
=
0
;
ULONG64 setBit
=
0
;
if
(msrAddrIndex !
=
0
)
{
moveByte
=
msrAddrIndex
/
8
;
[培训]科锐逆向工程师培训第53期2025年7月8日开班!
最后于 2022-3-22 14:52
被smallzhong_编辑
,原因: