/images/jhin.png

Jhinxs

Intel VT x64 EPT Hook框架的实现

主要记录下X64VT以及实现EPT Hook时的一些关键点 基本的VT开启 这部分和之前32位的没什么太大的区别,主要是在填写vmc字段时,需要额外填写如IA32_EFER,IA32E_MODE这种必须要填写的,还有像RDTSCP这种在新的win10上面系统上面做引发VMEXIT需要填写的,这种主要是为了兼容性考虑。 开启EPT的话需要在SECONDARY VM execution Control Field字段里面填写对应的enable ept字段,一般来讲不是太老的CPU都是支持的。 而在X64上多核处理的话,主要是使用KeGenericCallDpc来调用我们的VT初始化或者你需要的其他需要进行多核同步的过程,这个API就是让多个CPU同时以特殊的DPC例程执行我们的代码。 其他就是一些数据类型,寄存器的更改,换成64位的模式 EPT内存管理 EPT是intel为内存虚拟化而设计的一种硬件机制,主要是是为了实现Guest对于物理内存的访问控制 因为EPT的存在,Guest对一个地址访问时,每当访问一个物理地址,都会走EPT物理地址转化的过程,这个过程和x64下的VA->PA的转换过程类似,例如访问Guest访问某个进程物理地址,首先找到CR3,此时访问CR3中的物理地址,就会触发GPA->HPA的EPT转换过程,然后访问PML4,PDE等物理地址时,都会触发一个EPT的转换,这也是VT开启EPT后性能损耗的原因之一,关于EPT各个表项的内容可以查看参考 Intel-64-ia-32–system-programming-manual-vol-3 Chapter 28.2 EPT的构建 EPT的构建,感觉就是实现一个具体的地址转换的过程,从EPTP的构建,以及其中PML4,PDPT,PDT其中各个表项的各个字段的填写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pEptState->EptPageTable = PageTable; PageTable->PML4[0].all = 0; PageTable->PML4[0].Bits.read_access = 1; PageTable->PML4[0].Bits.write_access = 1; PageTable->PML4[0].Bits.exec_access_supervisor = 1; PageTable->PML4[0].Bits.PDPTPFN = MmGetPhysicalAddress(&PageTable->PML3[0]).QuadPart >> 12; for (int i = 0; i < VMM_EPT_PML3E_COUNT; i++) { PageTable->PML3[i].all = 0; PageTable->PML3[i].

x64的一些特性

寄存器 首先是寄存器数值的宽度,除去标志寄存器和段寄存器(段寄存器长度均为96位,其中有16位的可见部分和80位的不可见部分)外,均为8个字节64位宽度,在32位平台的基础上,增加了R8~R15共8个寄存器 易变寄存器:RAX,RCX,RDX,R8-R11 非易变寄存器:RBP,RBX,RSI,RDI,R12-R15 FS与GS 32位的操作系统中,FS寄存器在3环执向TEB结构体,0环指向KPCR结构体,在X64中,则是使用GS寄存器 微软提供了IA32_FS_BASE(0xC0000100)和IA32_GS_BASE MSR(0xC0000101)寄存器用于获取Base,因为如果用段描述的方式去获取Base的话,在64位的段描述符中,是没法再放入64位的地址 FS则继续给Wow64提供服务,也就是32位的应用程序 内存布局与内存模型 x64下内存分配,理论可以使用2^64次大小的内存空间,但是实际操作系统设计时只用了其中的48位,x64采用了9-9-9-9-12分页,也就是PML4T分页,按照32位操作系统非PAE分页的套路,也即10-10-12分页,PDE-PTE-offset,总的内存1024x1024x4k=4Gb,其中用户和内核各使用了2GB,在x64下理论上支持,512x512x512x512x4k=256Tb,实际上windows支持16TB左右的空间 其中: 用户空间:0x00000000~00000000 — 0x000007fff~ffffffff 内核空间:0xfffff800~00000000 — 0xffffffff~ffffffff IA-32架构下主要内存模型为:Basic Flat Module(基本的平坦模型),Protect Flat Module(受保护的平坦模式),Multi-Segment Module(分段内存模型) Basic Flat Module(基本的平坦模型) 最为简单的一种模型,操作系统和应用程序可以访问不受限制的内存空间,没有任何的分段机制,根据inter开发手册所说,一个最简单的平坦模型,至少建立两个段描述符,一个数据,一个代码,分别指向了数据段,代码段,但是都被映射到基址为0,限长为4gb的内存空间,即便访问的地址超过了实际物理内存的最大地址,也不会报错 Protect Flat Module(受保护的平坦模式) 主要是在基本的平坦模型中,提供了内存访问保护,超出界限后会出发保护异常,同样有了特权级机制的段权限分配,也即用户模式和内核模式,这些段的基址都是从0开始,也就是说这些段在内存空间上来说都是重叠的,逻辑上划分为不同的用途 Multi-Segment Module(分段内存模型) 相较于前两种,稍稍复杂一点,这种对于不同用途的段在内存划分上也分离开来,实现真正的内存分段,对不同段,如代码段,数据段,内存访问采用seg base+offset的形式,采用了强制的硬件级的保护机制,每个应用程序可以有自己的私有段,也可以和其他程序共享,同样的,段权限检查和访问控制机制,都可以阻止如内存越界等非法访问操作 在x64中,根据inter开发手册: In 64-bit mode, segmentation is generally (but not completely) disabled, creating a flat 64-bit linear-address space.

Intel VT-x mini VT 框架的实现

Intel VT-x是Intel芯片用于支持硬件虚拟化的一种技术,个人理解是在传统的操作系统之上,创建专用于虚拟化开发者的特权级环境,这种权限甚至高于操作系统的环境,也就是常说的Hypervisor层,同样的,新的环境也会引入一些新的特权指令等新的功能。在设计架构上和SVM类似,分为VMM和VMGUEST两种角色,分别处于VMX-ROOT和VMX-NO ROOT模式下 对于虚拟机来说,Hypervisor是完全透明的,当Guest客户机发生vmexit事件后会陷入到Hypervisor,此时控制权完全交与Hypervisor,而Hypervisor同样可以通过vmlaunch运行虚拟机,或者在处理完vmexit事件后,通过vmresume指令,继续运行虚拟机 VT框架实现流程 本代码用于学习研究,运行于32位操作系统之上,在代码实现VT的时候,主要是需要遵循Intel开发手册的标准,我这里实现的时候,大概是以下的流程: 判断当前CPU是否支持VT技术,通过执行cpuid指令,获取返回信息并判断对应的标志位,这是硬件层面的要求 读取MSR_IA32_FEATURE_CONTROL MSR寄存器对应标志位,用于判断在BIOS配置中是否开启了VT 检查并设置CR4 VMXE标志位 分配4K对齐的内存,用于vmx操作,这块区域按照白皮书的定义一般叫做VMXON region 初始化VMXON region区域,主要是写入一个VMCS版本号,这个版本号通过读取 IA32_MSR_VMX_BASIC MSR寄存器获得 执行VMXON,并检查EFLAGE.CF位是否置0,判断是否执行成功 分配4K对齐的内存,用于逻辑处理器也即虚拟机的操作区域,叫做VMCS region 调用vmclear指令,将VMCS区域设置为清除状态,也即区域初始化 调用vmptrld指令,激活VMCS 为host以及guest分配内存空间 填充VMCS区域,这是最重要也是最复杂的一步 通过vmlaunch运行虚拟机 代码部分 这个代码写的也是一波三折,蓝屏N次,也是各种坑,虽然可以有参考代码,但实际上有很多因素,遇到比如像保存现场代码的优化,多核处理等等诸多问题 首先是开启VT的前期,检测阶段,首先就是通过CPUID指令,这个指令执行前,需要将EAX寄存器置为1,返回值会存在ECX寄存器中,这里CPUID置1是Intel的要求,如果EAX中是其他值,返回的信息也是不同的,只是在这里用作CPU VTZ支持检测,EAX需要置1 1 2 3 4 5 6 7 8 9 10 11 12 get_cpuid_info proc,_para:dword pushad mov eax,1H cpuid mov esi,_para mov [esi],ecx popad ret get_cpuid_info endp 然后是读取IA32_FEATURE_CONTROL MSR寄存器,如果在bios启用VT则bit1,bit2或者两者都必须为1,bit1和bit2主要用于设置是否处于SMX Mode(安全扩展模式)下,这里一般检测bit2就可以了 1 2 3 4 5 6 ULONG64 msr = __readmsr(MSR_IA32_FEATURE_CONTROL); if (!