从头开始了解和使用Hypervisor(第6部分)
导语:毫不夸张地说,学习完本文,你完全可以创建自己的虚拟环境,并且可以了解VMWare,VirtualBox,KVM和其他虚拟化软件如何使用处理器的函数来创建虚拟环境。
捕获当前计算机的状态
在上一篇文章中,我们了解了如何配置不同的VMCS字段并最终在客户状态下执行我们的指令(HLT)。这部分内容与上一部分非常相似,只是对VMCS属性进行了一些细微的更改,让我们看看其中的区别。
首先需要了解的是,你必须为每个内核创建不同的堆栈,因为我们要同时虚拟化所有内核。只要发生VM-Exit ,就会使用这些堆栈。
// Allocate stack for the VM Exit Handler. UINT64 VMM_STACK_VA = ExAllocatePoolWithTag(NonPagedPool, VMM_STACK_SIZE, POOLTAG); vmState[ProcessorID].VMM_Stack = VMM_STACK_VA; if (vmState[ProcessorID].VMM_Stack == NULL) { DbgPrint("[*] Error in allocating VMM Stack.\n"); return; } RtlZeroMemory(vmState[ProcessorID].VMM_Stack, VMM_STACK_SIZE);
从上面的代码中可以看到,我们对每个内核分别使用了VMM_Stack(在_VirtualMachineState结构中定义)。
清除VMCS状态,加载VMCS和执行VMLAUNCH等所有其他操作与上一部分完全相同,因此我不再赘述,但是请参阅负责准备当前要虚拟化的内核的函数。
void VirtualizeCurrentSystem(int ProcessorID, PEPTP EPTP, PVOID GuestStack) { DbgPrint("\n======================== Virtualizing Current System =============================\n"); PAGED_CODE(); // Allocate stack for the VM Exit Handler. UINT64 VMM_STACK_VA = ExAllocatePoolWithTag(NonPagedPool, VMM_STACK_SIZE, POOLTAG); vmState[ProcessorID].VMM_Stack = VMM_STACK_VA; if (vmState[ProcessorID].VMM_Stack == NULL) { DbgPrint("[*] Error in allocating VMM Stack.\n"); return; } RtlZeroMemory(vmState[ProcessorID].VMM_Stack, VMM_STACK_SIZE); // Allocate memory for MSRBitMap vmState[ProcessorID].MSRBitMap = MmAllocateNonCachedMemory(PAGE_SIZE); // should be aligned if (vmState[ProcessorID].MSRBitMap == NULL) { DbgPrint("[*] Error in allocating MSRBitMap.\n"); return; } RtlZeroMemory(vmState[ProcessorID].MSRBitMap, PAGE_SIZE); vmState[ProcessorID].MSRBitMapPhysical = VirtualAddress_to_PhysicalAddress(vmState[ProcessorID].MSRBitMap); // Clear the VMCS State if (!Clear_VMCS_State(&vmState[ProcessorID])) { goto ErrorReturn; } // Load VMCS (Set the Current VMCS) if (!Load_VMCS(&vmState[ProcessorID])) { goto ErrorReturn; } DbgPrint("[*] Setting up VMCS for current system.\n"); Setup_VMCS_Virtualizing_Current_Machine(&vmState[ProcessorID], EPTP, GuestStack); DbgPrint("[*] Executing VMLAUNCH.\n"); __vmx_vmlaunch(); // if VMLAUNCH succeed will never be here ! ULONG64 ErrorCode = 0; __vmx_vmread(VM_INSTRUCTION_ERROR, &ErrorCode); __vmx_off(); DbgPrint("[*] VMLAUNCH Error : 0x%llx\n", ErrorCode); DbgBreakPoint(); DbgPrint("\n===================================================================\n"); ReturnWithoutError: __vmx_off(); DbgPrint("[*] VMXOFF Executed Successfully. !\n"); return TRUE; // Return With Error ErrorReturn: DbgPrint("[*] Fail to setup VMCS !\n"); return FALSE; }
在上面的代码中,Setup_VMCS_Virtualizing_Current_Machine是新的,因此,让我们看一下此函数的内容。
配置VMCS字段
VMCS字段不是什么新鲜事物,应该对其进行配置以管理虚拟化内核的状态。
除以下内容外,所有VMCS字段均与上一部分相同:
DbgPrint("[*] MSR_IA32_VMX_PROCBASED_CTLS : 0x%llx\n", AdjustControls(CPU_BASED_ACTIVATE_MSR_BITMAP | CPU_BASED_ACTIVATE_SECONDARY_CONTROLS, MSR_IA32_VMX_PROCBASED_CTLS)); DbgPrint("[*] MSR_IA32_VMX_PROCBASED_CTLS2 : 0x%llx\n", AdjustControls(CPU_BASED_CTL2_RDTSCP | CPU_BASED_CTL2_ENABLE_INVPCID | CPU_BASED_CTL2_ENABLE_XSAVE_XRSTORS, MSR_IA32_VMX_PROCBASED_CTLS2)); __vmx_vmwrite(CPU_BASED_VM_EXEC_CONTROL, AdjustControls(CPU_BASED_ACTIVATE_MSR_BITMAP | CPU_BASED_ACTIVATE_SECONDARY_CONTROLS , MSR_IA32_VMX_PROCBASED_CTLS)); __vmx_vmwrite(SECONDARY_VM_EXEC_CONTROL, AdjustControls(CPU_BASED_CTL2_RDTSCP | CPU_BASED_CTL2_ENABLE_INVPCID | CPU_BASED_CTL2_ENABLE_XSAVE_XRSTORS, MSR_IA32_VMX_PROCBASED_CTLS2));
对于CPU_BASED_VM_EXEC_CONTROL,我们设置了CPU_BASED_ACTIVATE_MSR_BITMAP,这样你就可以启用MSR BITMAP过滤器。此字段的设置是强制性的,由于Windows在简单的内核执行过程中会访问许多MSR,因此,如果不设置此位,则将在每次MSR访问时退出,将大大降低系统速度。
对于SECONDARY_VM_EXEC_CONTROL,我们使用CPU_BASED_CTL2_RDTSCP启用RDTSCP,使用CPU_BASED_CTL2_ENABLE_INVPCID来启用INVPCID,使用CPU_BASED_CTL2_ENABLE_XSAVE_XRSTORS启用XSAVE和XRSTORS。
这是因为我在Windows 10 1809中运行了上面的代码,并且看到Windows内部使用INVPCID和XSAVE(在支持这些函数的处理器中),因此,如果你在虚拟化内核之前未启用它们,则可能导致错误。
请注意,RDTSCP将处理器的时间戳计数器的当前值(64位MSR)读入EDX:EAX寄存器,还将IA32_TSC_AUX MSR的值(地址C0000103H)读入ECX寄存器。该指令为RDTSC添加了排序,并使性能度量比使用RDTSC更准确。 INVPCID,基于进程上下文标识符(PCID)和XSAVE,使转换后备缓冲区(TLB)和分页结构缓存中的映射无效。将处理器状态组件的全部或部分保存到位于由指定的内存地址的XSAVE区域中目标操作数。
请确保检查你在这些字段中输入的最终值,因为你的处理器可能不支持所有这些函数,因此你必须实现一些其他函数或忽略其中一些函数。
除了作为GUEST_RSP的GuestStack,没有其他的可以解释的了,我稍后会告诉你这个参数里应该添加什么。
__vmx_vmwrite(GUEST_RSP, (ULONG64)GuestStack); //setup guest sp
好了,现在的问题是,我们可以从哪里开始我们的虚拟机监控程序,我的意思是,如何保存特殊内核的状态,然后在其上执行VMLAUNCH,然后继续执行其余部分。
为此,我更改了DrvCreate例程,因此你必须从用户模式应用程序更改CreateFile(稍后再讨论),实际上,DrvCreate是负责将所有核心放在VMX状态的函数。
NTSTATUS DrvCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { DbgPrint("[*] DrvCreate Called !\n"); // Start Virtualizing Current System // Initiating EPTP and VMX PEPTP EPTP = Initialize_EPTP(); Initiate_VMX(); int LogicalProcessorsCount = KeQueryActiveProcessorCount(0); for (size_t i = 0; i < LogicalProcessorsCount; i++) { // Launching VM for Test (in the all logical processor) int ProcessorID = i; RunOnProcessor(i, EPTP, VMXSaveState); } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
我们的微型驱动程序旨在仅在一个内核中使用,或者在两三个,甚至所有内核中同时使用,因此从下面的代码中可以看到,它获得了逻辑处理器数。
int LogicalProcessorsCount = KeQueryActiveProcessorCount(0);
你可以编辑此行以虚拟化特定数量的内核或仅虚拟化特定内核,但是以上代码默认情况下虚拟化所有内核。
更改所有内核上的IRQL
有一个特殊的函数叫做RunOnProcessor,这个函数的第一个参数是处理器ID,第二个参数是初始化的EPTP指针(前面已经做了说明),第三个参数是一个名为VMXSaveState的特殊例程。RunOnProcessor设置处理器关联一个特殊的内核,然后将IRQL提升到Dispatch Level,以便Windows Scheduler无法启动以更改上下文,从而运行我们的例程,并在从VMXSaveState返回时对当前运行的内核进行虚拟化,因此它可以将IRQL降低到以前的水平,现在Windows可以在hypervisor下继续正常执行。 IRQL代表“中断请求级别”,它是Windows特定的机制,用于管理中断或按中断级别分配优先级,因此提高IRQL意味着你的例程将以比普通Windows代码(PASSIVE LEVEL和APC LEVEL)更高的优先级执行。欲了解更多信息,请点此了解。
BOOLEAN RunOnProcessor(ULONG ProcessorNumber, PEPTP EPTP, PFUNC Routine) { KIRQL OldIrql; KeSetSystemAffinityThread((KAFFINITY)(1 << ProcessorNumber)); OldIrql = KeRaiseIrqlToDpcLevel(); Routine(ProcessorNumber, EPTP); KeLowerIrql(OldIrql); KeRevertToUserAffinityThread(); return TRUE; }
VMXSaveState必须保存状态并调用我们已经实现的函数VirtualizeCurrentSystem。
我们必须在程序集文件(VMXState.asm)中扩展此函数,因为所有VMXSaveState都是在程序集中实现。
EXTERN VirtualizeCurrentSystem:PROC
VMXSaveState的实现方式如下:
VMXSaveState PROC push rax push rcx push rdx push rbx push rbp push rsi push rdi push r8 push r9 push r10 push r11 push r12 push r13 push r14 push r15 sub rsp, 28h ; It a x64 FastCall function but as long as the definition of SaveState is same ; as VirtualizeCurrentSystem so we RCX & RDX both have a correct value ; But VirtualizeCurrentSystem also has a stack so it's the third argument ; and according to FastCall it should be in R8 mov r8, rsp call VirtualizeCurrentSystem ret VMXSaveState ENDP
它首先保存所有寄存器的备份,然后由于影子空间而减去堆栈,以实现快速调用函数,然后将RSP放入r8并调用VirtualizeCurrentSystem。在x64 fastcall参数中,应按以下顺序传递:RCX,RDX,R8,R9 + Stack,这意味着我们对该函数的第三个参数是当前的RSP,此值将在我们的VMCS字段中用作客户_RSP。
如果上面的函数没有错误地运行,则我们永远不要得到“ret”指令,因为该状态稍后将在另一个名为“ VMXRestoreState”的函数中继续。
如你在最终调用Setup_VMCS_Virtualizing_Current_Machine的VirtualizeCurrentSystem中看到的那样,GUEST_RIP指向VMXRestoreState,因此在当前内核中执行的第一个函数(例程)是VMXRestoreState。该函数的定义如下:
VMXRestoreState PROC add rsp, 28h pop r15 pop r14 pop r13 pop r12 pop r11 pop r10 pop r9 pop r8 pop rdi pop rsi pop rbp pop rbx pop rdx pop rcx pop rax ret VMXRestoreState ENDP
首先,在上述函数中,我们删除影子空间并重启寄存器状态,当我们返回RunOnProcessor时,就该降低IRQL了。
此函数将基于你的逻辑内核数被调用很多次,最终,所有内核都处于VMX操作下,而现在你处于VMX非根操作下。
更改用户模式应用程序
基于上述假设,我们必须对用户模式应用程序进行一些细微的更改,以便在加载驱动程序之后,可以使用它通知内核模式代码开始和结束hypervisor。
使用CreateFile处理
在检查了供应商和hypervisor的存在之后,现在我们必须通过创建文件用户模式函数调用DrvCreate。
HANDLE handle = CreateFile("\\\\.\\MyHypervisorDevice", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, /// lpSecurityAttirbutes OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); /// lpTemplateFile if (handle == INVALID_HANDLE_VALUE) { DWORD errNum = GetLastError(); printf("[*] CreateFile failed : %d\n", errNum); return 1; }
CreateFile API为我们提供了一个可以在将来的函数中使用的句柄,但是每当你关闭应用程序或调用CloseHandle时,就会自动调用DrvClose。 DrvClose将关闭hypervisor,并将状态重启到之前的状态,即未虚拟化时的状态。
使用VMX监控函数
配置完上述所有字段之后,现在该使用VMX来使用监控函数了,你将了解在安全应用程序中这些函数的独特之处。
CR3-Target控件
VM执行控制字段包括一组4个CR3目标值和一个CR3目标计数。如果你看到我在Setup_VMCS_Virtualizing_Current_Machine中介绍的VMCS,则可以看到以下几行:
__vmx_vmwrite(CR3_TARGET_COUNT, 0); __vmx_vmwrite(CR3_TARGET_VALUE0, 0); __vmx_vmwrite(CR3_TARGET_VALUE1, 0); __vmx_vmwrite(CR3_TARGET_VALUE2, 0); __vmx_vmwrite(CR3_TARGET_VALUE3, 0);
Intel将CR3-Target控件定义为“如果VMX非根操作中的源操作数与其中一个值匹配,则在VMX非根操作中对CR3执行MOV不会导致VM退出。如果CR3目标计数为n,则仅考虑前n个CR3目标值。”
未来的处理器可能会扩展Cr3-Target计数,使用此函数的实现过程如下所示:
// Index starts from 0 , not 1 BOOLEAN SetTargetControls(UINT64 CR3, UINT64 Index) { if (Index >= 4) { // Not supported for more than 4 , at least for now :( return FALSE; } UINT64 temp = 0; if (CR3 == 0) { if (gCR3_Target_Count <= 0) { // Invalid command as gCR3_Target_Count cannot be less than zero return FALSE; } else { gCR3_Target_Count -= 1; if (Index == 0) { __vmx_vmwrite(CR3_TARGET_VALUE0, 0); } if (Index == 1) { __vmx_vmwrite(CR3_TARGET_VALUE1, 0); } if (Index == 2) { __vmx_vmwrite(CR3_TARGET_VALUE2, 0); } if (Index == 3) { __vmx_vmwrite(CR3_TARGET_VALUE3, 0); } } } else { if (Index == 0) { __vmx_vmwrite(CR3_TARGET_VALUE0, CR3); } if (Index == 1) { __vmx_vmwrite(CR3_TARGET_VALUE1, CR3); } if (Index == 2) { __vmx_vmwrite(CR3_TARGET_VALUE2, CR3); } if (Index == 3) { __vmx_vmwrite(CR3_TARGET_VALUE3, CR3); } gCR3_Target_Count += 1; } __vmx_vmwrite(CR3_TARGET_COUNT, gCR3_Target_Count); return TRUE; }
目前,我还没有很好的例子说明此控件在常规Windows中可能有什么帮助,因为每个过程都有成千上万的CR3更改,但它在一些特殊情况下被用来提高整体性能。
处理客户CPUID执行
CPUID是导致VM退出的主要指令之一,如你所知,使用CPUID是因为它允许软件发现处理器的详细信息。我看到软件使用CPUID刷新不支持RDTSCP等指令的处理器的管道,以便他们可以使用CPUID + RDTSC并以某种方式获得更好的结果。
每当具有任何特权级别的任何软件执行CPUID指令时,便会调用你的处理程序,现在你可以决定该软件显示的内容,例如,我之前发表过一篇文章“破坏恶意软件的Anti-VM技术”。本文介绍了如何通过更改CPUID指令结果的方式配置VMWare,以使具有Anti-VM技术的恶意软件无法通过执行CPUID来了解它们是在虚拟化环境中执行的。VMWare和其他虚拟环境执行与处理CPUID完全相同的机制,在以下示例中,我只是将寄存器的状态(发生VM退出后的注册状态)传递给了HandleCPUID。此函数决定所请求的CPUID应该具有修改的结果,还是仅执行CPUID并返回原始结果。
处理每个VM-Exit的默认行为(由VMX Non-root中的CPUID执行引起)是通过使用_cpuidex(它是CPUID的固有函数)来获得原始结果。
__cpuidex(cpu_info, (INT32)state->rax, (INT32)state->rcx);
因此,你可以看到VMX Non-root本身无法执行CPUID,我们可以在VMX Root模式下执行CPUID,并将结果返回给VMX Non-root模式。
现在,我们需要检查RAX(CPUID索引)是否为1,这是因为有一个指示符位显示当前计算机是否在hypervisor下运行,就像许多其他虚拟机一样,我们将HYPERV_HYPERVISOR_PRESENT_BIT设置为表明在hypervisor下运行。
关于hypervisor提供程序还需要进行第二项检查,我们将其设置为“HVFS”,以表明我们的hypervisor是[H] yper [V] isor [F] rom [S] cratch。
// Check if this was CPUID 1h, which is the features request. if (state->rax == 1) { // Set the Hypervisor Present-bit in RCX, which Intel and AMD have both // reserved for this indication. cpu_info[2] |= HYPERV_HYPERVISOR_PRESENT_BIT; } else if (state->rax == HYPERV_CPUID_INTERFACE) { // Return our interface identifier cpu_info[0] = 'HVFS'; // [H]yper[v]isor [F]rom [S]cratch }
现在,你可以轻松地在上述代码中添加更多检查,并自定义你的CPUID过滤器,例如,更改计算机供应商字符串等。
这是与hypervisor相关的常量的定义:
#define HYPERV_CPUID_VENDOR_AND_MAX_FUNCTIONS 0x40000000 #define HYPERV_CPUID_INTERFACE 0x40000001 #define HYPERV_CPUID_VERSION 0x40000002 #define HYPERV_CPUID_FEATURES 0x40000003 #define HYPERV_CPUID_ENLIGHTMENT_INFO 0x40000004 #define HYPERV_CPUID_IMPLEMENT_LIMITS 0x40000005 #define HYPERV_HYPERVISOR_PRESENT_BIT 0x80000000 #define HYPERV_CPUID_MIN 0x40000005 #define HYPERV_CPUID_MAX 0x4000ffff
最后,我们将它们放入寄存器,以便每次执行例程时,客户虚拟机都会获得适当的结果。
state->rax = cpu_info[0]; state->rbx = cpu_info[1]; state->rcx = cpu_info[2]; state->rdx = cpu_info[3];
将以上所有代码放在一起,就会具有以下函数:
BOOLEAN HandleCPUID(PGUEST_REGS state) { INT32 cpu_info[4]; // Check for the magic CPUID sequence, and check that it is coming from // Ring 0. Technically we could also check the RIP and see if this falls // in the expected function, but we may want to allow a separate "unload" // driver or code at some point. ULONG Mode = 0; __vmx_vmread(GUEST_CS_SELECTOR, &Mode); Mode = Mode & RPL_MASK; if ((state->rax == 0x41414141) && (state->rcx == 0x42424242) && Mode == DPL_SYSTEM) { return TRUE; // Indicates we have to turn off VMX } // Otherwise, issue the CPUID to the logical processor based on the indexes // on the VP's GPRs. __cpuidex(cpu_info, (INT32)state->rax, (INT32)state->rcx); // Check if this was CPUID 1h, which is the features request. if (state->rax == 1) { // Set the Hypervisor Present-bit in RCX, which Intel and AMD have both // reserved for this indication. cpu_info[2] |= HYPERV_HYPERVISOR_PRESENT_BIT; } else if (state->rax == HYPERV_CPUID_INTERFACE) { // Return our interface identifier cpu_info[0] = 'HVFS'; // [H]yper[v]isor [F]rom [S]cratch } // Copy the values from the logical processor registers into the VP GPRs. state->rax = cpu_info[0]; state->rbx = cpu_info[1]; state->rcx = cpu_info[2]; state->rdx = cpu_info[3]; return FALSE; // Indicates we don't have to turn off VMX }
某种程度上类似于对CPUID的指令级挂钩,通过在下面列出这些指令的基础上配置基于主处理器和辅助处理器的控件,你还可以对许多其他重要指令具有相同的处理函数。
导致VM退出的条件
根据VM执行控件的设置,以下指令会导致VM以VMX非root用户操作退出。
CLTS ENCLS HLT IN, INS/INSB/INSW/INSD, OUT, OUTS/OUTSB/OUTSW/OUTSD. INVLPG INVPCID LGDT, LIDT, LLDT, LTR, SGDT, SIDT, SLDT, STR LMSW MONITOR MOV from CR3/CR8, MOV to CR0/1/3/4/8 MOV DR MWAIT PAUSE RDMSR, WRMSR RDPMC RDRAND, RDSEED RDTSC, RDTSCP RSM VMREAD, VMWRITE WBINVD XRSTORS, XSAVES
控制寄存器修改检测
检测和处理控制寄存器的修改是hypervisor提供的重要安全函数之一,想象一下,如果有人利用Windows内核(或任何其他操作系统)并希望取消设置控制寄存器的某个位(例如写保护或SMEP),则hypervisor会检测到此修改并阻止进一步执行。
这些特性就是为什么使用hypervisor作为安全机制比使用单独的环(1,2)要好得多的原因。
注意,SMEP代表管理模式执行保护。CR4.SMEP允许保护页面免受主管模式指令的访存,如果CR4.SMEP = 1,则在超级用户模式下运行的软件无法从在用户模式下可访问的线性地址中获取指令,并且WP代表写保护。 CR0.WP允许保护页面免受超级用户模式写入,如果CR0.WP = 0,则允许对具有只读访问权限的线性地址进行超级用户模式写访问;否则,将不进行任何操作。如果CR0.WP = 1,则不允许这样做,无论CR0.WP的值如何,都不允许对具有只读访问权限的线性地址进行用户模式写访问。
首先,让我们阅读一下 VMCS的GUEST_CRs 和EXIT_QUALIFICATION。
__vmx_vmread(EXIT_QUALIFICATION , &ExitQualification); __vmx_vmread(GUEST_CR0 , &GuestCR0); __vmx_vmread(GUEST_CR3 , &GuestCR3); __vmx_vmread(GUEST_CR4, &GuestCR4);
如你所见,下图显示了退出指令运行的全过程。
请注意,EXIT_QUALIFCATION是一般的VMCS字段,在某些情况下,例如由无效的VMCS布局,控制寄存器修改,I/O位图和其他事件引起的VM退出。
现在,从上图可以看到,让我们基于EXIT_QUALIFICATION创建一些变量来描述这种情况。
movcrControlRegister = (ULONG)(ExitQualification & 0x0000000F); movcrAccessType = (ULONG)((ExitQualification & 0x00000030) >> 4); movcrOperandType = (ULONG)((ExitQualification & 0x00000040) >> 6); movcrGeneralPurposeRegister = (ULONG)((ExitQualification & 0x00000F00) >> 8);
每当由于某些指令(例如MOV CRx,REG)导致VM退出时,我们都必须从VMX根操作手动修改客户 VMCS的CRx。从以下代码中,你可以看到如何使用VMWRITE修改VMCS的客户_CRx字段。
if (movcrAccessType == 0) { /* CRx rax); break; case 1: __vmx_vmwrite(x, GuestRegs->rcx); break; case 2: __vmx_vmwrite(x, GuestRegs->rdx); break; case 3: __vmx_vmwrite(x, GuestRegs->rbx); break; case 4: __vmx_vmwrite(x, GuestRegs->rsp); break; case 5: __vmx_vmwrite(x, GuestRegs->rbp); break; case 6: __vmx_vmwrite(x, GuestRegs->rsi); break; case 7: __vmx_vmwrite(x, GuestRegs->rdi); break; }
否则,我们必须从客户VMCS(不是主机控制寄存器,因为它可能有所不同)读取CRx,然后放入相应的寄存器(在调用VM-Exit处理程序时保存的寄存器中),然后继续执行VMRESUME。这样,客户就认为好像成功执行了MOV reg, CRx。
把它们放在一起,我们就得到了这样一个函数:
void HandleControlRegisterAccess(PGUEST_REGS GuestRegs) { ULONG movcrControlRegister = 0; ULONG movcrAccessType = 0; ULONG movcrOperandType = 0; ULONG movcrGeneralPurposeRegister = 0; ULONG64 ExitQualification = 0; ULONG64 GuestCR0 = 0; ULONG64 GuestCR3 = 0; ULONG64 GuestCR4 = 0; ULONG64 x = 0; __vmx_vmread(EXIT_QUALIFICATION , &ExitQualification); __vmx_vmread(GUEST_CR0 , &GuestCR0); __vmx_vmread(GUEST_CR3 , &GuestCR3); __vmx_vmread(GUEST_CR4, &GuestCR4); movcrControlRegister = (ULONG)(ExitQualification & 0x0000000F); movcrAccessType = (ULONG)((ExitQualification & 0x00000030) >> 4); movcrOperandType = (ULONG)((ExitQualification & 0x00000040) >> 6); movcrGeneralPurposeRegister = (ULONG)((ExitQualification & 0x00000F00) >> 8); /* Process the event (only for MOV CRx, REG instructions) */ if (movcrOperandType == 0 && (movcrControlRegister == 0 || movcrControlRegister == 3 || movcrControlRegister == 4)) { if (movcrAccessType == 0) { /* CRx rax); break; case 1: __vmx_vmwrite(x, GuestRegs->rcx); break; case 2: __vmx_vmwrite(x, GuestRegs->rdx); break; case 3: __vmx_vmwrite(x, GuestRegs->rbx); break; case 4: __vmx_vmwrite(x, GuestRegs->rsp); break; case 5: __vmx_vmwrite(x, GuestRegs->rbp); break; case 6: __vmx_vmwrite(x, GuestRegs->rsi); break; case 7: __vmx_vmwrite(x, GuestRegs->rdi); break; } } else if (movcrAccessType == 1) { /* reg32 rax = x; break; case 1: GuestRegs->rcx = x; break; case 2: GuestRegs->rdx = x; break; case 3: GuestRegs->rbx = x; break; case 4: GuestRegs->rsp = x; break; case 5: GuestRegs->rbp = x; break; case 6: GuestRegs->rsi = x; break; case 7: GuestRegs->rdi = x; break; default: break; } } }
之所以必须强制执行诸如HandleControlRegisterAccess之类的函数,是因为处理器(甚至是最新的Intel处理器)对某些基于处理器的VM执行控件(例如CR3-Load Exiting和CR3-Store Existing)具有1设置,因此你必须管理此类VM -自行退出,但是如果处理器无需进行这些设置也可以继续运行,则强烈建议减少VM退出的数量,因为现代OS的访问控制注册很多,因此,它的性能将受到明显影响。
下一章,我会接着介绍这些函数的实现过程。
发表评论