从头开始了解和使用Hypervisor(第5部分) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

从头开始了解和使用Hypervisor(第5部分)

gejigeji web安全 2019-12-27 10:11:39
582816
收藏

导语:毫不夸张地说,学习完本文,你完全可以创建自己的虚拟环境,并且可以了解VMWare,VirtualBox,KVM和其他虚拟化软件如何使用处理器的函数来创建虚拟环境。

从头开始了解和使用Hypervisor(第1部分)

从头开始了解和使用Hypervisor(第2部分)

从头开始了解和使用Hypervisor(第3部分)

从头开始了解和使用Hypervisor(第4部分)

配置VMCS

收集VMCS的计算机状态

为了配置客户状态和主机状态,我们需要了解有关当前系统状态的详细信息,例如全局描述符表地址,中断描述符表地址和读取所有段寄存器。

这些函数描述了如何收集所有这些数据。

GDT基地址:

Get_GDT_Base PROC     LOCAL   gdtr[10]:BYTE     sgdt    gdtr     mov     rax, QWORD PTR gdtr[2]     ret Get_GDT_Base ENDP

CS段寄存器:

GetCs PROC     mov     rax, cs     ret GetCs ENDP

DS段寄存器:

GetDs PROC     mov     rax, ds     ret GetDs ENDP

ES段寄存器:

GetEs PROC     mov     rax, es     ret GetEs ENDP

SS段寄存器:

GetSs PROC     mov     rax, ss     ret GetSs ENDP

FS段寄存器:

GetFs PROC     mov     rax, fs     ret GetFs ENDP

GS段寄存器:

GetGs PROC     mov     rax, gs     ret GetGs ENDP

LDT:

GetLdtr PROC     sldt    rax     ret GetLdtr ENDP

TR(任务寄存器):

GetTr PROC     str rax     ret GetTr ENDP

中断描述符表:

Get_IDT_Base PROC     LOCAL   idtr[10]:BYTE     sidt    idtr     mov     rax, QWORD PTR idtr[2]     ret Get_IDT_Base ENDP

GDT限制:

Get_GDT_Limit PROC     LOCAL   gdtr[10]:BYTE     sgdt    gdtr     mov     ax, WORD PTR gdtr[0]     ret Get_GDT_Limit ENDP

IDT限制:

Get_IDT_Limit PROC     LOCAL   idtr[10]:BYTE     sidt    idtr     mov     ax, WORD PTR idtr[0]     ret Get_IDT_Limit ENDP

RFLAGS:

Get_RFLAGS PROC     pushfq     pop     rax     ret Get_RFLAGS ENDP

设置VMCS

本节从定义一个称为Setup_VMCS的函数开始。

 BOOLEAN Setup_VMCS(IN PVirtualMachineState vmState, IN PEPTP EPTP);

此函数负责配置与VMCS相关的所有选项,当然还包括客户状态和主机状态,这些任务需要一条称为“VMWRITE”的特殊指令。

VMWRITE,将主源操作数(寄存器或内存)的内容写入VMCS中的指定字段。在VMX根操作中,指令将写入当前VMCS。如果以VMX非根操作执行,则指令将写入当前VMCS中由VMCS链接指针字段引用的VMCS。

VMCS字段由包含在寄存器辅助源操作数中的VMCS字段编码指定,以下枚举包含VMWRITE和VMREAD指令所需的大部分VMCS字段。更新的处理器会添加更新的字段:

enum VMCS_FIELDS { GUEST_ES_SELECTOR = 0x00000800, GUEST_CS_SELECTOR = 0x00000802, GUEST_SS_SELECTOR = 0x00000804, GUEST_DS_SELECTOR = 0x00000806, GUEST_FS_SELECTOR = 0x00000808, GUEST_GS_SELECTOR = 0x0000080a, GUEST_LDTR_SELECTOR = 0x0000080c, GUEST_TR_SELECTOR = 0x0000080e, HOST_ES_SELECTOR = 0x00000c00, HOST_CS_SELECTOR = 0x00000c02, HOST_SS_SELECTOR = 0x00000c04, HOST_DS_SELECTOR = 0x00000c06, HOST_FS_SELECTOR = 0x00000c08, HOST_GS_SELECTOR = 0x00000c0a, HOST_TR_SELECTOR = 0x00000c0c, IO_BITMAP_A = 0x00002000, IO_BITMAP_A_HIGH = 0x00002001, IO_BITMAP_B = 0x00002002, IO_BITMAP_B_HIGH = 0x00002003, MSR_BITMAP = 0x00002004, MSR_BITMAP_HIGH = 0x00002005, VM_EXIT_MSR_STORE_ADDR = 0x00002006, VM_EXIT_MSR_STORE_ADDR_HIGH = 0x00002007, VM_EXIT_MSR_LOAD_ADDR = 0x00002008, VM_EXIT_MSR_LOAD_ADDR_HIGH = 0x00002009, VM_ENTRY_MSR_LOAD_ADDR = 0x0000200a, VM_ENTRY_MSR_LOAD_ADDR_HIGH = 0x0000200b, TSC_OFFSET = 0x00002010, TSC_OFFSET_HIGH = 0x00002011, VIRTUAL_APIC_PAGE_ADDR = 0x00002012, VIRTUAL_APIC_PAGE_ADDR_HIGH = 0x00002013, VMFUNC_CONTROLS = 0x00002018, VMFUNC_CONTROLS_HIGH = 0x00002019, EPT_POINTER = 0x0000201A, EPT_POINTER_HIGH = 0x0000201B, EPTP_LIST = 0x00002024, EPTP_LIST_HIGH = 0x00002025, GUEST_PHYSICAL_ADDRESS = 0x2400, GUEST_PHYSICAL_ADDRESS_HIGH = 0x2401, VMCS_LINK_POINTER = 0x00002800, VMCS_LINK_POINTER_HIGH = 0x00002801, GUEST_IA32_DEBUGCTL = 0x00002802, GUEST_IA32_DEBUGCTL_HIGH = 0x00002803, PIN_BASED_VM_EXEC_CONTROL = 0x00004000, CPU_BASED_VM_EXEC_CONTROL = 0x00004002, EXCEPTION_BITMAP = 0x00004004, PAGE_FAULT_ERROR_CODE_MASK = 0x00004006, PAGE_FAULT_ERROR_CODE_MATCH = 0x00004008, CR3_TARGET_COUNT = 0x0000400a, VM_EXIT_CONTROLS = 0x0000400c, VM_EXIT_MSR_STORE_COUNT = 0x0000400e, VM_EXIT_MSR_LOAD_COUNT = 0x00004010, VM_ENTRY_CONTROLS = 0x00004012, VM_ENTRY_MSR_LOAD_COUNT = 0x00004014, VM_ENTRY_INTR_INFO_FIELD = 0x00004016, VM_ENTRY_EXCEPTION_ERROR_CODE = 0x00004018, VM_ENTRY_INSTRUCTION_LEN = 0x0000401a, TPR_THRESHOLD = 0x0000401c, SECONDARY_VM_EXEC_CONTROL = 0x0000401e, VM_INSTRUCTION_ERROR = 0x00004400, VM_EXIT_REASON = 0x00004402, VM_EXIT_INTR_INFO = 0x00004404, VM_EXIT_INTR_ERROR_CODE = 0x00004406, IDT_VECTORING_INFO_FIELD = 0x00004408, IDT_VECTORING_ERROR_CODE = 0x0000440a, VM_EXIT_INSTRUCTION_LEN = 0x0000440c, VMX_INSTRUCTION_INFO = 0x0000440e, GUEST_ES_LIMIT = 0x00004800, GUEST_CS_LIMIT = 0x00004802, GUEST_SS_LIMIT = 0x00004804, GUEST_DS_LIMIT = 0x00004806, GUEST_FS_LIMIT = 0x00004808, GUEST_GS_LIMIT = 0x0000480a, GUEST_LDTR_LIMIT = 0x0000480c, GUEST_TR_LIMIT = 0x0000480e, GUEST_GDTR_LIMIT = 0x00004810, GUEST_IDTR_LIMIT = 0x00004812, GUEST_ES_AR_BYTES = 0x00004814, GUEST_CS_AR_BYTES = 0x00004816, GUEST_SS_AR_BYTES = 0x00004818, GUEST_DS_AR_BYTES = 0x0000481a, GUEST_FS_AR_BYTES = 0x0000481c, GUEST_GS_AR_BYTES = 0x0000481e, GUEST_LDTR_AR_BYTES = 0x00004820, GUEST_TR_AR_BYTES = 0x00004822, GUEST_INTERRUPTIBILITY_INFO = 0x00004824, GUEST_ACTIVITY_STATE = 0x00004826, GUEST_SM_BASE = 0x00004828, GUEST_SYSENTER_CS = 0x0000482A, HOST_IA32_SYSENTER_CS = 0x00004c00, CR0_GUEST_HOST_MASK = 0x00006000, CR4_GUEST_HOST_MASK = 0x00006002, CR0_READ_SHADOW = 0x00006004, CR4_READ_SHADOW = 0x00006006, CR3_TARGET_VALUE0 = 0x00006008, CR3_TARGET_VALUE1 = 0x0000600a, CR3_TARGET_VALUE2 = 0x0000600c, CR3_TARGET_VALUE3 = 0x0000600e, EXIT_QUALIFICATION = 0x00006400, GUEST_LINEAR_ADDRESS = 0x0000640a, GUEST_CR0 = 0x00006800, GUEST_CR3 = 0x00006802, GUEST_CR4 = 0x00006804, GUEST_ES_BASE = 0x00006806, GUEST_CS_BASE = 0x00006808, GUEST_SS_BASE = 0x0000680a, GUEST_DS_BASE = 0x0000680c, GUEST_FS_BASE = 0x0000680e, GUEST_GS_BASE = 0x00006810, GUEST_LDTR_BASE = 0x00006812, GUEST_TR_BASE = 0x00006814, GUEST_GDTR_BASE = 0x00006816, GUEST_IDTR_BASE = 0x00006818, GUEST_DR7 = 0x0000681a, GUEST_RSP = 0x0000681c, GUEST_RIP = 0x0000681e, GUEST_RFLAGS = 0x00006820, GUEST_PENDING_DBG_EXCEPTIONS = 0x00006822, GUEST_SYSENTER_ESP = 0x00006824, GUEST_SYSENTER_EIP = 0x00006826, HOST_CR0 = 0x00006c00, HOST_CR3 = 0x00006c02, HOST_CR4 = 0x00006c04, HOST_FS_BASE = 0x00006c06, HOST_GS_BASE = 0x00006c08, HOST_TR_BASE = 0x00006c0a, HOST_GDTR_BASE = 0x00006c0c, HOST_IDTR_BASE = 0x00006c0e, HOST_IA32_SYSENTER_ESP = 0x00006c10, HOST_IA32_SYSENTER_EIP = 0x00006c12, HOST_RSP = 0x00006c14, HOST_RIP = 0x00006c16, };

好的,让我们继续我们的配置。

下一步是配置主机段寄存器:

    __vmx_vmwrite(HOST_ES_SELECTOR, GetEs() & 0xF8); __vmx_vmwrite(HOST_CS_SELECTOR, GetCs() & 0xF8); __vmx_vmwrite(HOST_SS_SELECTOR, GetSs() & 0xF8); __vmx_vmwrite(HOST_DS_SELECTOR, GetDs() & 0xF8); __vmx_vmwrite(HOST_FS_SELECTOR, GetFs() & 0xF8); __vmx_vmwrite(HOST_GS_SELECTOR, GetGs() & 0xF8); __vmx_vmwrite(HOST_TR_SELECTOR, GetTr() & 0xF8);

请记住,以HOST_开头的字段与hypervisor在VM-Exit发生时设置的状态有关,以GUEST_开头的字段与hypervisor在执行VMLAUNCH时为客户设置的状态相关。

& 0xF8的目的是Intel提到必须清除三个较低有效位,否则当你执行带有无效主机状态错误的VMLAUNCH时,它将导致错误。

VMCS_LINK_POINTER应该为 0xffffffffffffffff。

    // Setting the link pointer to the required value for 4KB VMCS. __vmx_vmwrite(VMCS_LINK_POINTER, ~0ULL);

接下来,我打算在计算机的当前状态下执行VMX指令,因此客户机和主机配置必须相同。在以后的部分中,我们将其配置为单独的客户布局。

现在,让我们配置GUEST_IA32_DEBUGCTL。

IA32_DEBUGCTL MSR提供位域控件,以启用调试跟踪中断、调试跟踪存储,启用跟踪消息,在分支上单步执行,最后分支记录记录,以及控制LBR堆栈的冻结。

简而言之:LBR是一种为处理器提供一些寄存器记录的机制。

我们不使用它们,而是将它们配置为当前计算机的MSR_IA32_DEBUGCTL,你会看到__readmsr是RDMSR的固有函数。

    __vmx_vmwrite(GUEST_IA32_DEBUGCTL, __readmsr(MSR_IA32_DEBUGCTL) & 0xFFFFFFFF); __vmx_vmwrite(GUEST_IA32_DEBUGCTL_HIGH, __readmsr(MSR_IA32_DEBUGCTL) >> 32);

在配置TSC时,你应该修改以下值,我对此没有确切的解释,因此将它们设为零。注意,我们加0的值可以被忽略,如果你不修改它们,就好像你加了0一样。

    /* Time-stamp counter offset */ __vmx_vmwrite(TSC_OFFSET, 0); __vmx_vmwrite(TSC_OFFSET_HIGH, 0); __vmx_vmwrite(PAGE_FAULT_ERROR_CODE_MASK, 0); __vmx_vmwrite(PAGE_FAULT_ERROR_CODE_MATCH, 0); __vmx_vmwrite(VM_EXIT_MSR_STORE_COUNT, 0); __vmx_vmwrite(VM_EXIT_MSR_LOAD_COUNT, 0); __vmx_vmwrite(VM_ENTRY_MSR_LOAD_COUNT, 0); __vmx_vmwrite(VM_ENTRY_INTR_INFO_FIELD, 0);

这一次,我们将为我们的主机配置段寄存器和其他GDT(当vm退出时)。

    GdtBase = Get_GDT_Base(); FillGuestSelectorData((PVOID)GdtBase, ES, GetEs()); FillGuestSelectorData((PVOID)GdtBase, CS, GetCs()); FillGuestSelectorData((PVOID)GdtBase, SS, GetSs()); FillGuestSelectorData((PVOID)GdtBase, DS, GetDs()); FillGuestSelectorData((PVOID)GdtBase, FS, GetFs()); FillGuestSelectorData((PVOID)GdtBase, GS, GetGs()); FillGuestSelectorData((PVOID)GdtBase, LDTR, GetLdtr()); FillGuestSelectorData((PVOID)GdtBase, TR, GetTr());

上面在为我们的VMCS收集信息的过程中定义了Get_GDT_Base。

FillGuestSelectorData负责为vmc设置客户选择器、属性、限制和基础。具体实施过程如下:

void FillGuestSelectorData( __in PVOID GdtBase, __in ULONG Segreg, __in USHORT Selector ) { SEGMENT_SELECTOR SegmentSelector = { 0 }; ULONG            uAccessRights; GetSegmentDescriptor(&SegmentSelector, Selector, GdtBase); uAccessRights = ((PUCHAR)& SegmentSelector.ATTRIBUTES)[0] + (((PUCHAR)& SegmentSelector.ATTRIBUTES)[1] << 12); if (!Selector) uAccessRights |= 0x10000; __vmx_vmwrite(GUEST_ES_SELECTOR + Segreg * 2, Selector); __vmx_vmwrite(GUEST_ES_LIMIT + Segreg * 2, SegmentSelector.LIMIT); __vmx_vmwrite(GUEST_ES_AR_BYTES + Segreg * 2, uAccessRights); __vmx_vmwrite(GUEST_ES_BASE + Segreg * 2, SegmentSelector.BASE); }

GetSegmentDescriptor函数对象:

BOOLEAN GetSegmentDescriptor(IN PSEGMENT_SELECTOR SegmentSelector, IN USHORT Selector, IN PUCHAR GdtBase) { PSEGMENT_DESCRIPTOR SegDesc; if (!SegmentSelector) return FALSE; if (Selector & 0x4) { return FALSE; } SegDesc = (PSEGMENT_DESCRIPTOR)((PUCHAR)GdtBase + (Selector & ~0x7)); SegmentSelector->SEL = Selector; SegmentSelector->BASE = SegDesc->BASE0 | SegDesc->BASE1 << 16 | SegDesc->BASE2 << 24; SegmentSelector->LIMIT = SegDesc->LIMIT0 | (SegDesc->LIMIT1ATTR1 & 0xf) << 16; SegmentSelector->ATTRIBUTES.UCHARs = SegDesc->ATTR0 | (SegDesc->LIMIT1ATTR1 & 0xf0) << 4; if (!(SegDesc->ATTR0 & 0x10)) { // LA_ACCESSED ULONG64 tmp; // this is a TSS or callgate etc, save the base high part tmp = (*(PULONG64)((PUCHAR)SegDesc + 8)); SegmentSelector->BASE = (SegmentSelector->BASE & 0xffffffff) | (tmp << 32); } if (SegmentSelector->ATTRIBUTES.Fields.G) { // 4096-bit granularity is enabled for this segment, scale the limit SegmentSelector->LIMIT = (SegmentSelector->LIMIT << 12) + 0xfff; } return TRUE; }

另外,还有一个名为IA32_KERNEL_GS_BASE的MSR,用于设置内核GS基地址。每当你运行诸如SYSCALL之类的指令并输入到环0时,都需要更改当前的GS寄存器,并且可以使用SWAPGS进行更改。该指令将IA32_KERNEL_GS_BASE的内容复制到IA32_GS_BASE中,现在当你要重新项目用户模式时,已在内核中使用它,你应该更改用户模式GS 基地址。另一方面,MSR_FS_BASE没有内核基地址,因为它在32位模式下使用,而在64位(长模式)内核下使用。

GUEST_INTERRUPTIBILITY_INFO & GUEST_ACTIVITY_STATE如下所示:

    __vmx_vmwrite(GUEST_INTERRUPTIBILITY_INFO, 0); __vmx_vmwrite(GUEST_ACTIVITY_STATE, 0);   //Active state

现在,我们项目VMCS的最重要部分,它是CPU_BASED_VM_EXEC_CONTROL和SECONDARY_VM_EXEC_CONTROL的配置。

这些字段启用和禁用客户的一些重要函数,例如,你可以将VMCS配置为每当检测到执行HLT指令时(客户中)导致VM-Exit,请检查上面的VM执行控制部分以获取详细说明。

    __vmx_vmwrite(CPU_BASED_VM_EXEC_CONTROL, AdjustControls(CPU_BASED_HLT_EXITING | 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_EPT*/, MSR_IA32_VMX_PROCBASED_CTLS2));

如你所见,我们设置了CPU_BASED_HLT_EXITING,它将导致HLT上的VM退出,并使用CPU_BASED_ACTIVATE_SECONDARY_CONTROLS激活辅助控件。

在辅助控件中,我们使用了CPU_BASED_CTL2_RDTSCP,现在使用CPU_BASED_CTL2_ENABLE_EPT进行注释,因为在这一部分中我们不需要处理EPT。在以后的部分中,我将介绍如何使用在以前中配置的EPT或扩展页表。

上面提供了PIN_BASED_VM_EXEC_CONTROL,VM_EXIT_CONTROLS和VM_ENTRY_CONTROLS的描述,但现在将它们清零。

    __vmx_vmwrite(PIN_BASED_VM_EXEC_CONTROL, AdjustControls(0, MSR_IA32_VMX_PINBASED_CTLS)); __vmx_vmwrite(VM_EXIT_CONTROLS, AdjustControls(VM_EXIT_IA32E_MODE | VM_EXIT_ACK_INTR_ON_EXIT, MSR_IA32_VMX_EXIT_CTLS)); __vmx_vmwrite(VM_ENTRY_CONTROLS, AdjustControls(VM_ENTRY_IA32E_MODE, MSR_IA32_VMX_ENTRY_CTLS));

另外,AdjustControls的定义如下:

ULONG AdjustControls(IN ULONG Ctl, IN ULONG Msr) { MSR MsrValue = { 0 }; MsrValue.Content = __readmsr(Msr); Ctl &= MsrValue.High;     /* bit == 0 in high word ==> must be zero */ Ctl |= MsrValue.Low;      /* bit == 1 in low word  ==> must be one  */ return Ctl; }

下一步是为客户和主机设置控制寄存器,我们使用内部函数将它们设置为相同的值。

    __vmx_vmwrite(GUEST_CR0, __readcr0()); __vmx_vmwrite(GUEST_CR3, __readcr3()); __vmx_vmwrite(GUEST_CR4, __readcr4()); __vmx_vmwrite(GUEST_DR7, 0x400); __vmx_vmwrite(HOST_CR0, __readcr0()); __vmx_vmwrite(HOST_CR3, __readcr3()); __vmx_vmwrite(HOST_CR4, __readcr4());

下一部分是为客户设置IDT和GDT的基本和限制。

    __vmx_vmwrite(GUEST_GDTR_BASE, Get_GDT_Base()); __vmx_vmwrite(GUEST_IDTR_BASE, Get_IDT_Base()); __vmx_vmwrite(GUEST_GDTR_LIMIT, Get_GDT_Limit()); __vmx_vmwrite(GUEST_IDTR_LIMIT, Get_IDT_Limit());

设置RFLAGS:

    __vmx_vmwrite(GUEST_RFLAGS, Get_RFLAGS());

如果要在客户中使用SYSENTER,则应配置以下MSR。在x64 Windows中设置这些值并不重要,因为Windows在x64版本的Windows中不支持SYSENTER,而是使用SYSCALL,对于32位进程,首先将当前执行模式更改为长模式,但是在32位处理器中,这些字段是必填字段。

    __vmx_vmwrite(GUEST_SYSENTER_CS, __readmsr(MSR_IA32_SYSENTER_CS)); __vmx_vmwrite(GUEST_SYSENTER_EIP, __readmsr(MSR_IA32_SYSENTER_EIP)); __vmx_vmwrite(GUEST_SYSENTER_ESP, __readmsr(MSR_IA32_SYSENTER_ESP)); __vmx_vmwrite(HOST_IA32_SYSENTER_CS, __readmsr(MSR_IA32_SYSENTER_CS)); __vmx_vmwrite(HOST_IA32_SYSENTER_EIP, __readmsr(MSR_IA32_SYSENTER_EIP)); __vmx_vmwrite(HOST_IA32_SYSENTER_ESP, __readmsr(MSR_IA32_SYSENTER_ESP));

不要忘记配置HOST_FS_BASE,HOST_GS_BASE,HOST_GDTR_BASE,HOST_IDTR_BASE,HOST_TR_BASE。

    GetSegmentDescriptor(&SegmentSelector, GetTr(), (PUCHAR)Get_GDT_Base()); __vmx_vmwrite(HOST_TR_BASE, SegmentSelector.BASE); __vmx_vmwrite(HOST_FS_BASE, __readmsr(MSR_FS_BASE)); __vmx_vmwrite(HOST_GS_BASE, __readmsr(MSR_GS_BASE)); __vmx_vmwrite(HOST_GDTR_BASE, Get_GDT_Base()); __vmx_vmwrite(HOST_IDTR_BASE, Get_IDT_Base());

下一个重要部分是在VMLAUNCH执行时设置客户的RIP和RSP,从你在此部分中配置的RIP开始,在发生VM退出时,设置主机的RIP和RSP。很明显,主机RIP应该指向负责根据返回码管理VMX事件,并决定执行VMRESUME或使用VMXOFF关闭hypervisor的函数。

    // left here just for test __vmx_vmwrite(0, (ULONG64)VirtualGuestMemoryAddress);     //setup guest sp __vmx_vmwrite(GUEST_RIP, (ULONG64)VirtualGuestMemoryAddress);     //setup guest ip __vmx_vmwrite(HOST_RSP, ((ULONG64)vmState->VMM_Stack + VMM_STACK_SIZE - 1)); __vmx_vmwrite(HOST_RIP, (ULONG64)VMExitHandler);

HOST_RSP指向我们上面分配的VMM_Stack,而HOST_RIP指向VMExitHandler(如下所述的程序集编写函数)。 GUEST_RIP指向VirtualGuestMemoryAddress(我们在EPT初始化期间配置的全局变量),而GUEST_RSP则指向零,因为我们没有放置任何使用堆栈的指令,因此在实际示例中,它应指向可写的不同地址。

只要我们在客户状态中具有相同的CR3,将这些字段设置为主机地址就不会引起问题,因此所有地址都映射为与主机完全相同。

此时,VMCS就准备完了。

检查VMCS布局

不幸的是,检查VMCS布局不如其他部分那么直接,你必须控制Intel 64和IA-32体系结构软件开发人员手册的[CHAPTER 26] VM ENTRIES中描述的所有清单,包括以下部分:

26.2检查VMX控制和主机状态区域;

26.3检查和加载客户状态;

26.4加载MSRS;

26.5事件注入;

26.6虚拟机输入的特殊函数;

26.7加载客户状态期间或之后的VM发生故障;

26.8 VM项目期间的计算机检查事件;

此过程中最难的部分是,你不知道VMCS布局的不正确部分,或者你忘记了最终导致故障的内容。

这是因为Intel只是提供了一个错误号码,而没有提供有关VMCS布局中确切错误的任何更多详细信息。

错误如下所示。

29.png

为了解决这个问题,我创建了一个名为VmcsAuditor的用户模式应用程序。正如它的名字所描述的,如果你有任何错误,并且对解决问题没有任何想法,那么它可以是一个选择项。

请记住,VmcsAuditor是基于Bochs仿真器对VMX的支持的工具,因此所有检查都来自Bochs,并且它不是解决所有问题的100%可靠工具,因为我们不知道处理器内部到底发生了什么,但是可以真正有用且省时。

源代码和可执行文件,你可以点此获取。

VM-Exit处理程序

当我们的客户软件退出并将句柄交还给主机时,可以在以下定义中定义其VM-exit原因。

#define EXIT_REASON_EXCEPTION_NMI       0 #define EXIT_REASON_EXTERNAL_INTERRUPT  1 #define EXIT_REASON_TRIPLE_FAULT        2 #define EXIT_REASON_INIT                3 #define EXIT_REASON_SIPI                4 #define EXIT_REASON_IO_SMI              5 #define EXIT_REASON_OTHER_SMI           6 #define EXIT_REASON_PENDING_VIRT_INTR   7 #define EXIT_REASON_PENDING_VIRT_NMI    8 #define EXIT_REASON_TASK_SWITCH         9 #define EXIT_REASON_CPUID               10 #define EXIT_REASON_GETSEC              11 #define EXIT_REASON_HLT                 12 #define EXIT_REASON_INVD                13 #define EXIT_REASON_INVLPG              14 #define EXIT_REASON_RDPMC               15 #define EXIT_REASON_RDTSC               16 #define EXIT_REASON_RSM                 17 #define EXIT_REASON_VMCALL              18 #define EXIT_REASON_VMCLEAR             19 #define EXIT_REASON_VMLAUNCH            20 #define EXIT_REASON_VMPTRLD             21 #define EXIT_REASON_VMPTRST             22 #define EXIT_REASON_VMREAD              23 #define EXIT_REASON_VMRESUME            24 #define EXIT_REASON_VMWRITE             25 #define EXIT_REASON_VMXOFF              26 #define EXIT_REASON_VMXON               27 #define EXIT_REASON_CR_ACCESS           28 #define EXIT_REASON_DR_ACCESS           29 #define EXIT_REASON_IO_INSTRUCTION      30 #define EXIT_REASON_MSR_READ            31 #define EXIT_REASON_MSR_WRITE           32 #define EXIT_REASON_INVALID_GUEST_STATE 33 #define EXIT_REASON_MSR_LOADING         34 #define EXIT_REASON_MWAIT_INSTRUCTION   36 #define EXIT_REASON_MONITOR_TRAP_FLAG   37 #define EXIT_REASON_MONITOR_INSTRUCTION 39 #define EXIT_REASON_PAUSE_INSTRUCTION   40 #define EXIT_REASON_MCE_DURING_VMENTRY  41 #define EXIT_REASON_TPR_BELOW_THRESHOLD 43 #define EXIT_REASON_APIC_ACCESS         44 #define EXIT_REASON_ACCESS_GDTR_OR_IDTR 46 #define EXIT_REASON_ACCESS_LDTR_OR_TR   47 #define EXIT_REASON_EPT_VIOLATION       48 #define EXIT_REASON_EPT_MISCONFIG       49 #define EXIT_REASON_INVEPT              50 #define EXIT_REASON_RDTSCP              51 #define EXIT_REASON_VMX_PREEMPTION_TIMER_EXPIRED     52 #define EXIT_REASON_INVVPID             53 #define EXIT_REASON_WBINVD              54 #define EXIT_REASON_XSETBV              55 #define EXIT_REASON_APIC_WRITE          56 #define EXIT_REASON_RDRAND              57 #define EXIT_REASON_INVPCID             58 #define EXIT_REASON_RDSEED              61 #define EXIT_REASON_PML_FULL            62 #define EXIT_REASON_XSAVES              63 #define EXIT_REASON_XRSTORS             64 #define EXIT_REASON_PCOMMIT             65

VMX退出处理程序应为纯汇编函数,因为调用已编译的函数需要进行一些准备和某些寄存器修改,而VMX处理程序中最重要的事情是保存寄存器状态,以便你可以继续。

我创建了一个示例函数来保存寄存器并返回状态,但是在此函数中,我们称为另一个C函数。

PUBLIC VMExitHandler EXTERN MainVMExitHandler:PROC EXTERN VM_Resumer:PROC .code _text VMExitHandler PROC     push r15     push r14     push r13     push r12     push r11     push r10     push r9     push r8             push rdi     push rsi     push rbp     push rbp ; rsp     push rbx     push rdx     push rcx     push rax mov rcx, rsp ;GuestRegs sub rsp, 28h ;rdtsc call MainVMExitHandler add rsp, 28h pop rax     pop rcx     pop rdx     pop rbx     pop rbp ; rsp     pop rbp     pop rsi     pop rdi      pop r8     pop r9     pop r10     pop r11     pop r12     pop r13     pop r14     pop r15 sub rsp, 0100h ; to avoid error in future functions JMP VM_Resumer VMExitHandler ENDP end

主VM-Exit处理程序是一个切换用例函数,它对VMCS VM_EXIT_REASON和EXIT_QUALIFICATION具有不同的决策。

在这一部分中,我们仅对EXIT_REASON_HLT执行一项操作,然后打印结果并重启以前的状态。

从以下代码中,你可以清楚地看到是什么事件导致了VM退出。请记住,只有在VMCS的控制执行字段(如上所述)允许的情况下,某些原因才导致VM退出。例如,如果基于主处理器的VM执行控制的第7位允许,则在客户软件中执行HLT将导致VM退出。

VOID MainVMExitHandler(PGUEST_REGS GuestRegs) { ULONG ExitReason = 0; __vmx_vmread(VM_EXIT_REASON, &ExitReason); ULONG ExitQualification = 0; __vmx_vmread(EXIT_QUALIFICATION, &ExitQualification); DbgPrint("\nVM_EXIT_REASION 0x%x\n", ExitReason & 0xffff); DbgPrint("\EXIT_QUALIFICATION 0x%x\n", ExitQualification); switch (ExitReason) { // // 25.1.2  Instructions That Cause VM Exits Unconditionally // The following instructions cause VM exits when they are executed in VMX non-root operation: CPUID, GETSEC, // INVD, and XSETBV. This is also true of instructions introduced with VMX, which include: INVEPT, INVVPID,  // VMCALL, VMCLEAR, VMLAUNCH, VMPTRLD, VMPTRST, VMRESUME, VMXOFF, and VMXON. // case EXIT_REASON_VMCLEAR: 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_VMLAUNCH: { break; } case EXIT_REASON_HLT: { DbgPrint("[*] Execution of HLT detected... \n"); // DbgBreakPoint(); // that's enough for now ;) Restore_To_VMXOFF_State(); break; } case EXIT_REASON_EXCEPTION_NMI: { break; } case EXIT_REASON_CPUID: { break; } case EXIT_REASON_INVD: { break; } case EXIT_REASON_VMCALL: { break; } case EXIT_REASON_CR_ACCESS: { break; } case EXIT_REASON_MSR_READ: { break; } case EXIT_REASON_MSR_WRITE: { break; } case EXIT_REASON_EPT_VIOLATION: { break; } default: { // DbgBreakPoint(); break; } } }

重启下一条指令

如果发生VM退出(例如,客户执行了CPUID指令),则客户RIP保持不变,并且取决于你是否更改客户RIP,因此,如果你没有用于管理这种情况的特殊函数,则可以执行VMRESUME,就像执行CPUID和VMRESUME的无限循环一样,因为你没有更改RIP。

为了解决此问题,你必须读取一个称为VM_EXIT_INSTRUCTION_LEN的VMCS字段,该字段存储导致VM退出的指令的长度,因此你必须首先读取客户当前的RIP,然后读取VM_EXIT_INSTRUCTION_LEN,然后将其添加到客户 RIP 。现在,你的客户 RIP指向下一条说明,你就可以开始了。

为此,需要以下函数。

VOID ResumeToNextInstruction(VOID) { PVOID ResumeRIP = NULL; PVOID CurrentRIP = NULL; ULONG ExitInstructionLength = 0; __vmx_vmread(GUEST_RIP, &CurrentRIP); __vmx_vmread(VM_EXIT_INSTRUCTION_LEN, &ExitInstructionLength); ResumeRIP = (PCHAR)CurrentRIP + ExitInstructionLength; __vmx_vmwrite(GUEST_RIP, (ULONG64)ResumeRIP); }

VMRESUME

VMRESUME类似于VMLAUNCH,但它用于重启客户端。

如果当前VMCS的启动状态不是“clear,则VMLAUNCH失败。如果指令成功,它将启动状态设置为“已启动”。

如果当前VMCS的启动状态未“启动”,则VMRESUME失败。

因此很明显,如果你之前执行过VMLAUNCH,则无法再使用它来重启客户代码,并且在这种情况下将使用VMRESUME。

下面的代码是VMRESUME的实现过程。

VOID VM_Resumer(VOID) { __vmx_vmresume(); // if VMRESUME succeed will never be here ! ULONG64 ErrorCode = 0; __vmx_vmread(VM_INSTRUCTION_ERROR, &ErrorCode); __vmx_off(); DbgPrint("[*] VMRESUME Error : 0x%llx\n", ErrorCode); // It's such a bad error because we don't where to go ! // prefer to break DbgBreakPoint(); }

让我们测试一下!

好了,我们已经完成了配置,现在是时候使用OSR驱动程序加载器来运行我们的驱动程序了。

65.png

从上面的图片(在启动VM区域中)可以看到,首先我们将当前逻辑处理器设置为0,然后使用VMCLEAR指令清除VMCS状态,然后设置VMCS布局,最后执行VMLAUNCH指令。

现在,执行客户代码,并配置VMCS,使其在执行HLT(CPU_BASED_HLT_EXITING)时退出,因此它已成功执行,并调用了我们的VM-EXIT处理函数,然后调用了主VM-Exit处理程序并称为VMCS退出原因是0xc(EXIT_REASON_HLT),我们的VM-Exit处理程序在客户中检测到HLT的执行,现在它已经捕获了执行进程。

然后执行机器状态保存机制,我们使用VMXOFF成功地关闭了hypervisor,并返回到第一个调用者的成功状态(RAX = 1)。

至此,我们就完全熟悉配置虚拟机控制结构并最终运行了我们的客户代码。在下一篇文章中,我们将对该配置进行增强,例如项目保护模式,中断注入,页面修改日志记录,虚拟化当前计算机等。

下面,我将尝试为你介绍通过配置VMCS来虚拟化当前正在运行的系统,然后使用监控函数来检测一些重要指令的执行,例如CPUID并从用户和内核模式更改CPUID的结果,检测不同控制寄存器上的修改,描述不同微体系结构上的VMX函数,谈论MSR位图以及许多其他有趣的事情。

请确保在第七代Intel处理器上测试hypervisor,如果你的处理器不支持某些函数,并且如果没有远程内核调试器(不是本地内核调试器),你可能会看到你的系统停止运行或蓝屏死机。

VMX 0设置和VMX 1设置

在前面的部分中,我们实现了一个称为AdjustControl的函数。这是每个hypervisor的重要组成部分,因为你可能希望在具有不同微体系结构的许多不同处理器上运行hypervisor,因此你应该了解自己的处理器函数,以避免未定义的行为和VM项错误。

ULONG AdjustControls(IN ULONG Ctl, IN ULONG Msr) { MSR MsrValue = { 0 }; MsrValue.Content = __readmsr(Msr); Ctl &= MsrValue.High;     /* bit == 0 in high word ==> must be zero */ Ctl |= MsrValue.Low;      /* bit == 1 in low word  ==> must be one  */ return Ctl; }

如上所述,我们在4种情况下使用了上面的函数。

    __vmx_vmwrite(CPU_BASED_VM_EXEC_CONTROL, AdjustControls(CPU_BASED_HLT_EXITING | CPU_BASED_ACTIVATE_SECONDARY_CONTROLS, MSR_IA32_VMX_PROCBASED_CTLS)); __vmx_vmwrite(SECONDARY_VM_EXEC_CONTROL, AdjustControls(CPU_BASED_CTL2_RDTSCP, MSR_IA32_VMX_PROCBASED_CTLS2)); __vmx_vmwrite(PIN_BASED_VM_EXEC_CONTROL, AdjustControls(0, MSR_IA32_VMX_PINBASED_CTLS)); __vmx_vmwrite(VM_EXIT_CONTROLS, AdjustControls(VM_EXIT_IA32E_MODE | VM_EXIT_ACK_INTR_ON_EXIT, MSR_IA32_VMX_EXIT_CTLS)); __vmx_vmwrite(VM_ENTRY_CONTROLS, AdjustControls(VM_ENTRY_IA32E_MODE, MSR_IA32_VMX_ENTRY_CTLS));

在Intel VMX中,某些控件是保留的,必须设置为处理器决定的特定值(0或1)。保留控件必须设置的特定值是其默认设置。这些类型的设置因处理器和微体系结构而异,但一般来说,有三种类型的类:

1.始终灵活:这些从未被保留。

2.Default0:这些(或已被保留)默认设置为0。

3.Default1:保留(或已保留)它们的默认设置为1。

现在,针对基于pin的VM执行控件,基于主处理器的VM执行控件,VM项目控件,VM退出控件和基于辅助处理器的VM执行控件具有单独的函数MSR。

这些MSR为:

MSR_IA32_VMX_PROCBASED_CTLS MSR_IA32_VMX_PROCBASED_CTLS2 MSR_IA32_VMX_EXIT_CTLS MSR_IA32_VMX_ENTRY_CTLS MSR_IA32_VMX_PINBASED_CTLS

在所有上述MSR中,位31:0表示允许这些控件的0设置。如果将MSR中的位X清除为0,则VM项允许控件X(位X)为0;否则,该值为0。如果MSR中的位X设置为1,则如果控件X为0,则VM项目失败。同时,位63:32表示允许这些控件的1设置。如果MSR中的位32 + X设置为1,则VM项允许控件X为1;否则,控件X为1。如果将MSR中的位32 + X清除为0,则如果控件X为1,则VM项目失败。

现在,你应该了解AdjustControls的用途,因为它首先读取与VM执行控件相对应的MSR,然后调整0设置和1设置并返回最终结果。

我强烈建议你查看专门针对MSR_IA32_VMX_PROCBASED_CTLS和MSR_IA32_VMX_PROCBASED_CTLS2的AdjustControls的结果,因为你可能会无意中将某些位设置为1,因此你应该制定一个计划,根据你的特定处理器处理某些VM退出。

CR0和CR4中的VMX固定位

对于CR0, IA32_VMX_CR0_FIXED0 MSR (index 486H)和IA32_VMX_CR4_FIXED0 MSR (index 488H)和IA32_VMX_CR4_FIXED1 MSR (index 489H)表示在VMX操作中如何设置CR0和CR4中的位。如果IA32_VMX_CRx_FIXED0中的第X位是1,那么VMX操作中的第X位是1。类似地,如果第X位在IA32_VMX_CRx_FIXED1中为0,则在VMX操作中该CRx位为0。通常情况下,如果第X位在IA32_VMX_CRx_FIXEDx中是1,那么第X位在IA32_VMX_CRx_FIXED1中也是1。

  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务