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

gejigeji Web安全 2019年12月27日发布
Favorite收藏

导语:毫不夸张地说,学习完本文,你完全可以创建自己的虚拟环境,并且可以了解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。

本文翻译自:https://rayanfam.com/topics/hypervisor-from-scratch-part-5/ 与 https://rayanfam.com/topics/hypervisor-from-scratch-part-6/如若转载,请注明原文地址: https://beta.4hou.com/web/22126.html
点赞 0
  • 分享至
取消

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

扫码支持

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

发表评论