说到虚拟化、虚拟机等名词,IT领域的小伙伴肯定不陌生,业内人士或多或少的都用过VMware或其他PC虚拟机重新搭建一个系统,用来做各种高危测试,避免影响物理机的OS;10年前热炒的云计算,也是基于虚拟化技术的;多年以前,早期的虚拟化还是靠虚拟机软件模拟执行指令后返回结果。大约从2005年开始,intel推出了硬件辅助虚拟化技术Vanderpool(VT的前生),直接宣布从硬件层面开始支持虚拟化,个人认为有划时代的意义,这也直接促进了后来10多年云计算的蓬勃发展(大名鼎鼎的KVM就是基于VT的);近些年新买的电脑都支持VT(当然cpu要求是intel的),安装虚拟机的时候先在BIOS开启VT支持即可;除了虚拟化,VT在安全领域用途也很广,比如调试与反调试,无限硬件断点,无痕Hook(不会触发windwos的PG保护和被调试软件自带的CRC检测),后续会逐个分享;本文先简单介绍VT的基本概念和流程,为后续的代码理解做准备;
凡是用过虚拟机的小伙伴都知道,需要先装个虚拟机软件,最常见的是vmware和virtual box,然后就可以在虚拟机装OS了。这种场景下,虚拟机里面运行的os叫guestOS,处于VMX non-root状态;物理机运行的os叫hostOS,出于VMX root状态;虚拟机用来监控和管理guestOS的,叫VMM(virtual machine manager); 除此以外,VT还涉及到其他好些概念,诸如vmx\root\vmxon\vm entry\vmcs\vmexit\vmresume等等,先把这些概念之间的关系梳理如下:
上图也是VMX执行的顺序流程,嵌套了VMX所有重要的指令;现在来挨个解释一下VM开头的重要概念和指令:
- CPUID
用来检查当前CPU是否支持VT,也是虚拟化开启的第一步;近些年新买的电脑,只要cpu是intel,都支持VT。安装虚拟机的时候先在BIOS开启VT即可;这里要强调一下:逆向人员一般在开发的时候都在虚拟机做各种操作,蓝屏后能快速利用镜像恢复,这时需要在虚拟机开启VT选项,如下图,才能继续在虚拟机测试VT功能;由于虚拟机本身已经虚拟化了一层,再开启VT支持,相当于又虚拟化了一层;站在物理机的角度看,这时一共虚拟化了2层,像不像盗梦空间?
- VMXON
用于开启VMX(virtual machine extension);执行VMXON指令的时候需要提供一个4KB对齐的内存区间,称作VMXON region,该区域的物理地址作为VMXON指令的操作数。该内存区间用于支持host CPU的VMX功能,该区域在VMXON和VMXOFF之间一直都会被VMX硬件所使用,每个host cpu都需要一个4KB对齐的空间;至于这个4KB空间装了什么数据,intel暂未公开(从其他开发人员的资料看,可能记录了当前运行host正在运行哪个虚拟机)。代码层面需要做的就是分配这么一个空间就好,其他都不用管了;
和虚拟化相关的内存不能分页,换句话说不能被交换到磁盘,也就不能产生page fault,windows下再驱动层一般用ExAllocatePool函数分配,传入NonPagedPool参数表示申请非分页内存;
- VMCLEAR 和 VMPRTLOAD
VMCLEAR: 清空VMCS区域,为下一步初始化VMCS做准备;该指令在intel开发手册的原话是:The instruction ensures that VMCS data for that VMCS (some of these data may be currently maintained on the processor) are copied to the VMCS region in memory. It also initializes parts of the VMCS region (for example, it sets the launch state of that VMCS to clear);大概意思是确保VMCS的数据能被cpoy到内存中VMCS region,同时初始化VMCS区域的部分内容(比如设置VMCS为launch状态);
VMPRTLOAD: 加载或指定虚拟机。同一个物理CPU,可能会在不同的vCPU之间切换。再说直白一点,同一个物理核可能会在不同的虚拟机之间来回切换(就像进程、线程切换一样),切换到哪个虚拟机运行了?可以通过该指令确定;一旦VMPRTLOAD指定了虚拟机,接下面所有的VM指令都是针对该虚拟机的了;切换虚拟机的本质就是切换VMCS(就像切换进程的本质就是切换CR3,也就是PCB一样);
- 初始化VMCS
这是重点。VMX通过引入根运行模式(VMX root operation) 和非根模式(VMX non-root operation),直接让vCPU运行在物理CPU上,在软件上省去了对vCPU运行的模拟,大大提升了性能。 剩下的就是对vCPU状态的记录了,为此Intel引入了VMCS(Virtual Machine Control Structure)功能;VMCS详细的数据作用如下:
注意:VMCS块内数据的读写必须通过单独的vmread和vmwrite指令;C语言常见内存操作如memset、memcopy、char * 等方式是不行的;为啥intel要单独提供两个指令了?个人理解:VMCS结构体内部各个字段的位置不是一成不变的。
(1)不同版本可能是不一样的,如果直接用开发人员通过结构体把字段位置写死,不利于代码在不同版本之间适配和移植;
(2)VMWRITE指令可能还会影响某些寄存器
- VMLANUCH
这是一条进入新世界的指令。一旦执行,VMX随即从root模式切换到non-root模式,直白点就是切换到guest OS继续运行,避免影响guest OS在ring 3层的app;
- VMEXIT或VMCALL
上面切回到guest OS会一直运行,直到guest OS产生某些原因的异常,自身处理不了了,需要VMM帮忙处理,便会调用vmexit或vmcall退回到host;host处理这些异常的代码就需要开发人员定制写了!
- VMRESUME
host 处理完 guest产生的异常后,调用该指令回到guest OS继续执行;
以上便是VMX整体的流程;总结一下:先在guest OS检查一下当前CPU是否支持VT,然后通过VMXON开启VMX,由此衍生出了MVX的root和non-root模式,同时进入root模式的VMM去初始化VMCS结构,再通过VMLAUNCH返回guestOS运行,直到产生部分异常,guestOS hold不住了,通过vmexit退回root模式的VMM,请求VMM帮忙处理。处理完成后再通过VMRESUME重新把物理CPU的执行机会还给guestOS运行,也就是运行vCPU;整个过程,物理CPU一直都在运行,无非是运行guestOS的指令(也就是vCPU),还是运行VMM的指令(也就是host CPU,有些地方也习惯称为逻辑CPU);
用下图总结一下以上内容:
- 不同的物理CPU可以来回在不同的vCPU之间切换,所以需要VMCS来保存host和guest的各个状态(核心是各个寄存器、IDT/GDT表等),以便切回来的时候能恢复当初的状态;
- 物理CPU和vCPU是多对多的关系,由VMM把物理CPU形成一个资源池(当然这是需要开发人员通过写代码实现的,硬件不直接支持),供vCPU使用;
最后附上VMX的指令集:
指令 |
作用 |
VMPTRLD |
加载一个VMCS结构体指针作为当前操作对象 |
VMPTRST |
保存当前VMCS结构体指针 |
VMCLEAR |
清除当前VMCS结构体 |
VMREAD |
读VMCS结构体指定域 |
VMWRITE |
写VMCS结构体指定域 |
VMCALL |
引发一个VMExit事件,返回到VMM |
VMLAUNCH |
启动一个虚拟机 |
VMRESUME |
从VMM返回到虚拟机继续运行 |
VMXOFF |
退出VMX操作模式 |
VMXON |
进入VMX操作模式 |
参考: