none
Hyper-v虚拟机CPU执行BUG RRS feed

  • 常规讨论

  • 现代的虚拟机,都使用了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:44

全部回复