概念
硬件虚拟化技术,隐藏计算机资源物理性的一面,比如能把单个或多个物理资源,转化为一个或多个逻辑资源。
intel vt-x/或者amdv8(SVM)
VT是一个驱动(.sys),由操作系统加载后,运行在每个cpu上,推翻原来的操作系统,然后os和app-r0和r3变成GUEST,建立特权层(host,vmm,hypervisor,root,-1),然后监控GUEST执行。
这样就可以简化虚拟机开发,hook,隐藏,软件调试与反调试。
简单概括就是在os和hardware中间加一层。参考下图armv8的架构
secureworld与normalworld
之间物理隔离。
我们知道r3进r0,xp以前软中断int2E,xp及以后是sysenter,x86是kifastcall,x64及其以后syscall。
内核进入VT层,使用vmexit,就是因为guest执行某条指令,vt层感兴趣,操作系统就会抛出vmexit事件,一个异常,被特权层监控到。
原理概述
特权层就是中间的VMMhost.
应用场景是1.硬件-vmware-0s,2.硬件-VTDrv-os
VT出现前,VMWare采用进程虚拟机,作为操作系统一个进程,通过软件手段模拟硬件,为了实现多个虚拟机真是物理地址隔离,需要编程实现把客户机的物理地址翻译为真实机器的物理地址。要给不同操作系统编写不同虚拟机程序。
硬件虚拟化技术则在硬件上(ept,gpt)实现内存地址和io设备映射,就是引进直接二次寻址,从虚拟机虚拟地址到虚拟机物理地址到真实机物理地址和i/o映射的特性。
VMM在使用硬件虚拟化技术时创建出的特权层,提供给虚拟机开发者,实现虚拟硬件与真实硬件的通信和一些事件(vmm抛出的事件)
进行处理。
HOST:虚拟机宿主(VMM)即VT驱动,相当于VMWare。两者互斥。
GUEST:客户机,就是操作系统以上的东西。
VT意义:简化虚拟机开发,中断HOOK,软件调试。SSDTHOOk等。
流程
VT运行时候,VTDrv驱动做初始化,执行VMXON,开启VT,然后建立了特权层,然后执行VMLAUNCH,将当前级别切换成Guest,从root-1级别,退出vt状态,然后Guest运行时候产生VMEXIT事件,被Host捕捉到(比如事件抛出调用int2e等),重新回到host调用那个抛出事件的handle函数。调用完handle函数后,调用VMRESUME,重新回到Guest。直到再次发生抛出VMEXIT,如果想退出VM,就调用VMXOFF。
VMLAUNCH和VMRESUME区别就是lanuch初始化时候调用,以后用VMRESUME。
Guest执行时候,有可能引发VMEXIT事件,host处理这些操作,即可以修改他,也可以欺骗guest,完全取决于host,guest指令分两种,无条件陷入(只要执行就会引发VMEXIT)和有条件(必须在vt驱动注册之后)。
VT指令
VMCS区域管理指令:
VMPTRLD:激活当前VMCS(关键结构体,VThost/guest对应的上下文,每个cpu核一个),并加载内存数据到VMCS指针。
VMPTRST:存储当前VMCS指针数据到内存
VMCLEAR:将launch状态的VMCS设置为clear状态(非激活状态),与VMPTRLD相反。
VMREAD:读取VMCS中的域值
VMWRITE:写入VMCS的值域
VMX管理指令:
VMLAUNCH:启动VMCS的虚拟机
VMRESUME:从host恢复VMCS的虚拟机
VMXOFF:退出VMXROOT模式
VMON:进入CMXROOT模式
VMCALL:关闭VT时让虚拟机主动发出退出事件(NBP_HYPERCALL_UNLOAD)
其他什么RDMSR,WWRMSR,异常等就是有条件指令,必须在vt驱动中注册。
虚拟机双层地址翻译
就是虚拟机虚拟到虚拟机物理到真实机物理地址。
前置知识参考
https://blog.csdn.net/youyou519/article/details/82419163
EPT(Extended Page Table,扩展页表技术),Intel Core i7全面支持EPT
CR3:指向虚拟机页表(gpt,guest page table)虚拟机虚拟地址到虚拟机物理地址映射
EPTP:指向EPT页表(用于将虚拟机物理地址到真实机物理地址映射)
将CR3指定的Gpt通过EPTP指定的EPT翻译成真实内存中的GPT,虚拟地址通过GPT翻译成虚拟机物理地址。
虚拟机物理地址通过EPT翻译成真实地址。
两者都是4级地址翻译。
步骤:因为虚拟机虚拟地址到虚拟机物理地址要先得到gpt真实内容,所以首先把gpt表,映射成真实地址(因为他本身就是虚拟机的物理地址,并不是真的)。然后2层各4级寻址。
利用EPT页表将CR3所指向的GPT顶级地址翻译成真实物理地址,找到GPT的真实内容。即首先映射GPT到真实地址。
找到cr3的真实物理地址之后,以后再guest中拿到的虚拟机线性地址通过GPT表得到虚拟机物理地址。然后再通过EPTP找到真实地址。
X64调用约定
X64上相比x86,新增了r8-r15寄存器。统一使用fastcall,其中rbx,r12,r13,r14,r15寄存器是乱写急促安全,系统随时改写寄存器,程序自己使用需要通过栈备份。
rax,rcx,rdx,r8,r9,r10,r11是易挥发的,不用特别保护,其他寄存器比如rbx,需要保护,x86下只有eax,ecx,edx是易挥发的。
r8,r8d,r8w,r8b/低4,2,1字节。
x64不能内嵌汇编,只能专门写个asm,关于x64汇编编写注意要对汇编asm设置
接着下面一项修改成
另外X64和x86汇编的区别:
1.比ecx(rcx),edx(rdx)寄存器多了两个r8,r9
2.参数入栈对齐到8字节
3.前四个参数放到了rcx,rdx,r8,r9四个寄存器中,但栈上也会预留4个空间
4.调用者来负责堆栈平衡
5.栈整体要被16整除
6.形参不菲空间分配和初始化由调用者来完成,(为了变参)
7.局部变量空间由被调用者来分配。
VT初始化与启动流程
1.检查是否支持VT以及是否开启VT
2.分配VMX内存区域,VMXON,VMCS(来回切换的上下文环境),HOST-vMM栈区域(从guest有栈,到host无栈,需要自己分配栈区,在堆上)
3.设置Cr4.vmxe,防止别人使用vt
4.初始化VMXON(就是填一个版本号,在msr寄存器里,传给vmxon),VMXON开启VT,创建特权区
5.初始化VMCS区域:guest host虚拟机运行控制域,guest、host寄存器,rsp/rip/rflag/exithandler(exithandler是捕获exit事件时host运行代码)
6调用VMLAUNCH,返回guest,以后再进入就用resume。
检查是否支持VT和开启VT
CPUID返回的ecx的第五位,为1支持VT,
GetCpuIdInfo(0x1,&eax,&ebx,&ecx,&edx)//汇编实现的函数,调用cpuid
return((ecx&1<<5))_
CR4的第13位,vmxe是否为1,表示已经开启vt
MSR_IA32_FEATURE_CONTROL寄存器第0位Lock位是否为1,表示bios是否设置1,开启vt
MSR_IA32_FEATURE_CONTROL寄存器第2位Lock(Enable VMX outside SMX位)为1,VMxon不能执行。
分配VMX内存
1VMXON,HOST-VMM栈空间,VMCS放在一个结构体里
typedef struct _CPU
{
PVOID OriginalVmcs;
PVOID OriginalVmxonR;
PVOID VMM_Stack;
}CPU,*PCPU;
CPU g_VMXCPU[128]={0}//128核
这三个每个流程如下
1.VMXON内存
申请连续4K对齐内存空间
pCpu->OriginaVmxonR=MmAllocateContiguousMemory(2*PAGE_SIZE,PhyAddr);
PVOID MmAllocateContiguousMemory(
SIZE_T NumberOfBytes,
PHYSICAL_ADDRESS HighestAcceptableAddress//调用者可以使用的最高有效物理地址。
);
VMCS的版本号表示符写入VMXON区
*(ULONG64)pCpu->OriginaVmxonR=(__readmsr(MSR_IA32_VMX_BASIC)&0xffffffff);
VMXON指令进入虚拟机模式
PhyAddr=(MmGetphysicalAddress(pCpu->OriginaVmxonR));//拿到物理地址
__vmx_on(&PhyAddr)//开启虚拟机模式,以后不再访问这里
VMM Stack
因为host没有栈,所以要给他在堆上分配一个栈
pCpu->VMM_Stack=ExAllocatePoolWithTag(NoPagedPool,8*PAGE_SIZE,MEM_TAG);
VMM_Stack=(ULLONG_PTR)pCpu->VMM_Stack+8*PAGE_SIZE-8;//栈由高向低增长,指向空位,空栈递减(空就是栈顶指针是指向空的,x86是满,sp指向栈顶的元素)
__vmx_vmwrite(HOST_RSP,VMM_Stack);//设置VMCS区域的host rsp域
VMCS各种控制域设置
VMCS是一个4k内存区域,分为六部分:
1,GUEST-STATE域:虚拟机从root操作模式进入non-root操作模式时,处理器的状态
2.HOST-STATE域:与guest相反
3.VM执行控制域:虚拟机在non-root操作模式运行时的时候,控制处理器non-root操作模式退出到root操作模式
4.VM退出控制域:虚拟机从no-root操作模式下退出时,需要保持的信息
5.VM进入控制域:虚拟机从root到non-root模式,需要读取的信息。
6.VM退出信息域:虚拟机从non-root操作模式退出到root模式,将退出原因保存在该区域。
guest寄存器设置:cr0,cr3,cr4,dr7,gdtr,idtr,es,cs,ss,ds,fs,gs,ldtr,tr
host寄存器:同上
guest rsp/rip/rflag设置(当前系统的rsp,rip,rflag,就相当于上下文)
host rsp、rip设置(rsp为分配的VMM_STACK,rip就是下一条要执行的指令,所以为VmxVmexitHandler,写死)
VMCS类似进程EPROCESS
VMLAUNCH
__vmx_vmlaunch();//进入特权层
root切换到non-root寄存器上下文状态切换:
在进入root模式下,会通过push(HVM_SAVE_ALL_NOSEGRESGS宏)把guest非seg的寄存器传给root的exithandler使用。
处理完root模式下的事情,进入non-root模式的时候,需要设置non-root的rip并恢复guest寄存器的值
root模式的rip和rsp固定,前面申请的内存底和栈顶,不用修改
vt多核的支持
就是前面每个cpu一个独立内存和寄存器
CPU g_VMXCPU[128];
for(i=0;i<keNumberProcessors;i++){
KeSetSystemAffinityThread((KAFFINITY)(ULONG_PTR)1<<i));//设置亲和力,将该代码运行在指定processor
OldIrql=KeRaiseIrqlToDpcLevel();//提升IRQL级别,防止被打断
Status=CmSubvert(NULL);//CmSubvert就是前面一系列操作,的流程是保存所有寄存器(除了段寄存器)的内容到栈里后,盗用HvmSubverCpu
KeLowerIrql(OldIrql);
KeRevertToUserAffinityThread();
if(Status)
{
KdPrint(("HvmSwallowBluepill(): CmSubvert() failed with status 0x%08hX\n",Status));
break;
}
}
VT退出
VmCall,
传一个事件NBP_HYPERCALL_UNLOAD给Vmcall,Vmcall产生一个EXIT_REASON_VMCALL,host知道这个事件,执行_vmx_off(),撤销特权层,把guest寄存器拷贝进host这里,然后Trampoline,一组生成的汇编指令,将host的寄存器状态恢复成guest完全一样,guest_rip+len进入普通模式。
启动流程
VmxIsImplemented()(判断是否支持VT)
->StartVirtualTechlology()(初始化启动VT内部封装HvmSwallowBluepill())
->CmSubvert(NULL)(汇编)
>HbmSubvertCpu()(汇编,每个处理器都会调用,检查IA32_FEATURE_CONTROL,LOCK位,Enable VMX outside SMX位,为VMXON结构分配空间,__vmx_on(&phyaddr),为VMCS结构分配空间,为Host分配栈,set_in_cr4(X86_CR4_VMXE,防止别人再用vt))
->VmxSetupVmCS()(初始化VMCS结构)(比如guest寄存器2写入vmcs,host sp,ip也写入vmcs)
->vmx_vmlaunch()
->后面guest通过vmxexithandle陷入host,然后执行完通过vmresume返回guest此时要通过_vmwrite写入guest下一条要执行的指令,通过guesteip+当前指令长度。
卸载过程
driverUnload里StopVirtualTechlology()(封装原本HvmSpitOutBluepill())
->vmxVmCall()
->VmExitHandler
->VmxShutdown
->VmxGenerateTrampolineToGuest()申请一个弹簧床函数,恢复寄存器的
->__vmx_off()
clear_in_cr4(x85_CR4_VMXE)(说明不再占用vt资源)
比如没有欺骗前
欺骗后