询问者
Hyper-v虚拟机CPU执行BUG

常规讨论
-
现代的虚拟机,都使用了VT技术,以便加快虚拟机的执行效率
Hyper-v虚拟机,在处理CPUID指令上,有点小BUG,导致同一代码片段,在真机和虚拟机上表现行为不同
下面我们来重现一下这个BUG,并完整分析一下造成的原因
测试环境:
Intel Core i7 2600K
HOST OS:Windows Server 2012 x64
GUEST OS:Windows 7 Pro x86
代码:
#include <windows.h> #pragma comment(linker, "/entry:main") int filter(EXCEPTION_POINTERS * pException, BOOL *pFound) { if(*(BYTE*)pException->ContextRecord->Eip == 0x90) { *pFound = FALSE; } return EXCEPTION_EXECUTE_HANDLER; } BOOL cpuid_test() { BOOL bFound = TRUE; __try { __asm { mov eax, 0x12345678 pushfd; or DWORD ptr [esp], 0x100; popfd; cpuid; nop; int 3; } } __except(filter(GetExceptionInformation(), &bFound)) { ; } return bFound; } int main() { if(cpuid_test()) { MessageBoxA(0, "Hyper-v Found !!", 0, 0); } else { MessageBoxA(0, "Hyper-v not Found!!", 0, 0); } ExitProcess(0); return 0; }
此段代码,用 cl AntiVM.c 编译生成 AntiVM1.exe
放到GUEST OS里面,执行,会提示 “Hyper-v Found !!”。
放到一台真实机器,装有Windows 7 Pro操作系统的机器上执行,会提示 “Hyper-v not Found!!”
此时,大家可以看到Hyper-v虚拟机里面执行和真机执行后的结果是不一样的。
我们来详细解释一下这段代码的原理
此段代码是用来检测是否运行在虚拟机模式下的,前面已经提到,现代的虚拟机,几乎都是用到了VT技术(这里以INTEL的VMX为例)
VMX将CPU分为ROOT和NON-ROOT两种状态,一般来说,会将GUEST OS运行在NON-ROOT状态,但是对CPUID这条指令,是无条件进入ROOT状态的。
在ROOT状态下,Hyper-v对CPUID这条指令进行的处理,模拟掉GUEST OS执行CPUID指令的整个过程,注意,模拟完,EIP会加2(CPUID指令占2字节)
下面我们来看一下代码mov eax, 0x12345678 pushfd; or DWORD ptr [esp], 0x100; popfd; cpuid; /* 正常情况下,到这行的时候,EFLAGS的TF = 1,也就是下一条指令(CPUID)触发单步异常 如果开了Hyper-v,这里会触发vmx-exit,进入Non-root状态,然后hyper-v处理完CPUID 将EIP+2,EIP指向了下一条NOP指令 */ nop; /* 正常情况下,单步异常后EIP会停在NOP这条指令上面 如果开了Hyper-v,CPUID处理完后将EIP+2,执行到这里的时候,出现了EFLAGS的TF = 1 也就是执行完NOP 触发单步异常 */ int 3; /* 如果开了Hyper-v,单步异常触发,EIP会执行这里 */
在异常处理中,判断单步异常时的EIP是指向NOP还是指向INT 3,以此来判断是否有Hyper-v的存在
if(*(BYTE*)pException->ContextRecord->Eip == 0x90) { *pFound = FALSE; }
////////////////////////////////////华丽的分割线/////////////////////////////////////////////
难道Hyper-v的驱动开发程序员没意识到这一点吗?非也!
我们看下面的代码
#include <windows.h> #pragma comment(linker, "/entry:main") int filter(EXCEPTION_POINTERS * pException, BOOL *pFound) { if(*(BYTE*)pException->ContextRecord->Eip == 0x90) { *pFound = FALSE; } return EXCEPTION_EXECUTE_HANDLER; } BOOL cpuid_test() { BOOL bFound = TRUE; __try { __asm { mov eax, 0 pushfd; or DWORD ptr [esp], 0x100; popfd; cpuid; nop; int 3; } } __except(filter(GetExceptionInformation(), &bFound)) { ; } return bFound; } int main() { if(cpuid_test()) { MessageBoxA(0, "Hyper-v Found !!", 0, 0); } else { MessageBoxA(0, "Hyper-v not Found!!", 0, 0); } ExitProcess(0); return 0; }
和上一个版本编译方式相同,编译为AntiVM2.exe
代码也几乎一模一样,仅一处区别,将mov eax, 0x12345678改成了mov eax, 0
大家会惊讶的发现,AntiVM2.exe在Guest OS里面提示 “Hyper-v not Found!!”。也就是说CPUID指令被正确处理了。
两个例子一对比,基本上真相大白,0x12345678是个错误的cpuid指令的参数,hyper-v在处理的时候,发现是错误的参数,就没有去处理,
只是简单地 eip + 2,然后恢复non-root状态,导致了BUG的产生!!
////////////////////////////////////华丽的分割线/////////////////////////////////////////////
后记:
1,在HOST OS:Windows Server 2012上执行AntiVM1.exe和AntiVM2.exe,都提示“Hyper-v not Found!!”,说明HOST OS,其实也在NON-ROOT状态下
而且都没有处理好CPUID指令
2,VPC也有这样的BUG
3,VMWare最新版本,无此BUG!!!!!!
4,希望Hyper-v开发者能修复此BUG
- 已编辑 海风月影 2012年10月18日 8:50