VT 入门篇——最小 VT 实现(上)
写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。
华丽的分割线
概述
在学习如何实现最小VT框架的时候,我们先看一下流程图:

本篇我们介绍进入虚拟机模式前这部分内容,剩下的部分我们在下一篇继续。
如下是更清晰的一些流程,以后我们重点看下面的图:

如上实现都必须在具有0环的权限才可以,最方便的当然是在驱动内实现,如何写驱动我就不赘述了,自己回头复习去。如下是我们驱动代码的基本框架:
#include <ntddk.h>
#include <intrin.h>
#define DbgPrintLine(X) DbgPrint(X##"\n")
NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
DbgPrintLine("Unloaded Successfully!");
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = UnloadDriver;
DbgPrintLine("Loaded Successfully!");
return STATUS_SUCCESS;
}
intrin.h这个头文件的作用我就不说了,但是提前说一句,并不是所有的指令在32位都是包装好的,这些指令都是内联函数。有些包装好的指令是在64位才能用的,比如__vmx_on等需要传QWORD参数的函数我们需要自己实现,微软并没有帮我们实现该功能。当然在64位的情况下,我们就可以用更多的指令了,由于我们是32位的,就自己实现好了,虽然有点小麻烦。
话不多说,开始进入正题。
VT 支持启用检测
在之前的老的CPU,它是不支持VT的。现在的新的支持VT的CPU,也是有个开关的。就算CPU支持VT,如果被关掉禁用了,你也没法用,就需要我们进行检测是否能够支持启用VT,故写了一个函数,如下是完整代码,后续详细讲解:
BOOLEAN CheckVTEnabled()
{
//此内部函数将指令返回的受支持功能和 CPU 信息存储在 cpuInfo 中,
// cpuInfo 是一个由四个 32 位整数组成的数组,其中依次填充了 EAX、EBX、ECX 和 EDX 寄存器的值。
int CPUInfo[4];
__cpuid(CPUInfo, 1); //调用 CPUID 需要一个参数,参数就是1,通过 ECX 的值的索引5位就是 VMX
int Info = CPUInfo[2];
if (!(Info & VMXBit))
{
DbgPrintLine("Error : CPUID");
return FALSE;
}
ULONGLONG CONTROL_MSR = __readmsr(IA32_FEATURE_CONTROL_MSR);
if (!(CONTROL_MSR & IA32_FEATURE_CONTROL_MSR_Lock))
{
DbgPrintLine("Error : FEATURE CONTROL MSR");
return FALSE;
}
ULONG cr0 = __readcr0();
if (!((cr0 & CR0_PE) && (cr0 & CR0_NE) && (cr0 & CR0_PG)))
{
DbgPrintLine("Error : CR0");
return FALSE;
}
ULONG cr4 = __readcr4();
if (cr4 & CR4_VMXE)
{
DbgPrintLine("VT Has Been Occupied!");
return FALSE;
}
return TRUE;
}
__cpuid是对CPUID汇编指令的封装,我们先看看Intel是怎样说明该函数的:
CPUID returns processor identification and feature information in the EAX, EBX, ECX, and EDX registers. The instruction’s output is dependent on the contents of the EAX register upon execution (in some cases, ECX as well).
现在的CPU一般都支持CPUID,如果实在不放心的话可以检测EFLAGS的索引21二进制位是否可以修改设置,如下是白皮书说明:
The ID flag (bit 21) in the EFLAGS register indicates support for the CPUID instruction. If a software procedure can set and clear this flag, the processor executing the procedure supports the CPUID instruction.
这里我认为现在使用的CPU都支持CPUID指令。该指令是一个非常复杂的指令,具体可以查看白皮书的第764页,有关eax这个参数每个值的含义,具体看白皮书的第765页,我们使用的参数是1,我们可以看一下它的内容:

代码注释我明确说明用到的是ecx,我们看一下为什么:

这只是表格的一部分,但对于我们有用的就足够了。注意我们的VMX位,就在这个里面。通过这条CPUID指令我们只是判断CPU是否支持VT,但通过vmxon指令启用VT还有一些必备条件的。

白皮书开头说我们的CR4的VMXE位需要置1,并且在MSR的MSR_IA32_FEATURE_CONTROL成员的索引0位必须是1,否则使用vmxon指令启用VT会触发通用保护异常。这个只能在BIOS内进行设置,否则也会触发,通过这个我们就可以判断VT是否被禁用了,如下是相关的中文说明:

当然这些条件远远不够,如下是白皮书的说明:
The first processors to support VMX operation require that the following bits be 1 in VMX operation: CR0.PE, CR0.NE, CR0.PG, and CR4.VMXE. The restrictions on CR0.PE and CR0.PG imply that VMX operation is supported only in paged protected mode (including IA-32e mode). Therefore, guest software cannot be run in unpaged protected mode or in real-address mode.
也就是说,必须在带有分页的保护模式下才能正常使用VT,为了简单处理我们不使用虚拟机嵌套,所以CR4.VMXE这个位如果是1,说明我再启用就是套娃了,不跟你套。
如上是我写的函数的所有细节了,我们来做个实验,在做实验之前我们的驱动入口代码修改为如下:
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = UnloadDriver;
DbgPrintLine("Loaded Successfully!");
if (CheckVTEnabled())
{
DbgPrintLine("MiniVT : VT Support!");
}
return STATUS_SUCCESS;
}
然后编译,拖到虚拟机里进行加载,通过DbgView我们就可以得到如下结果,表示成功:

VMXON
上面我们实现了VT是否支持启用的检测函数,下面我们再实现两个函数,实现VT技术的启用和关闭,它的函数原型如下:
BOOLEAN StartVT();
BOOLEAN StopVT();
由于__vmx_on微软并没有在32位帮我们封装起来,需要我们自行实现,函数如下:
BOOLEAN __vmx_on(DWORD32 LVMXONRegionPA, DWORD32 HVMXONRegionPA)
{
_asm
{
push[HVMXONRegionPA];
push[LVMXONRegionPA];
_emit 0xF3;
_emit 0x0F;
_emit 0xC7;
_emit 0x34;
_emit 0x24; // vmxon qword ptr [esp]
add esp, 8;
}
UINT32 eflags = __readeflags();
if (eflags & EFLAG_CF)
{
return FALSE;
}
return TRUE;
}
使用_emit是因为编译器并不支持vmxon编译,所以只能内嵌了。vmxon这个指令并不是一定会成功的,如果失败会放到EFLAG的CF位,是0表示成功,如下是白皮书的说明:
Execute VMXON with the physical address of the VMXON region as the operand. Check successful execution of VMXON by checking if EFLAGS.CF = 0.
好,我们开始实现启用VT的代码,具体代码如下:
BOOLEAN StartVT()
{
if (CheckVTEnabled())
{
DbgPrintLine("MiniVT : VT Support!");
__writecr4(__readcr4() | CR4_VMXE);
PVOID pVMXONRegion = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'vmx');
if (!pVMXONRegion)
{
DbgPrintLine("Error : vmx Alloc Error");
return FALSE;
}
RtlZeroMemory(pVMXONRegion, 0x1000);
*(UINT32*)pVMXONRegion = (UINT32)__readmsr(MSR_IA32_VMX_BASIC)&0x7FFFFFFF;
g_VMXCPU.pVMXONRegion = pVMXONRegion;
g_VMXCPU.pVMXONRegion_PA = MmGetPhysicalAddress(pVMXONRegion);
return __vmx_on(g_VMXCPU.pVMXONRegion_PA.LowPart, g_VMXCPU.pVMXONRegion_PA.HighPart);
}
return FALSE;
}
在解释之前,我们来看看白皮书是咋说的:

前两个小黑点我们已经做完了,下面继续,它让我们申请一个4KB对齐的VMXON Region,至于到底多大呢?我们需要查阅IA32_VMX_BASIC_MSR这个寄存器,这个寄存器的信息说明如下:

然后我们注意到这一句话:
Bits 44:32 report the number of bytes that software should allocate for the VMXON region and any VMCS region. It is a value greater than 0 and at most 4096 (bit 44 is set if and only if bits 43:32 are clear).
你要4KB对齐,又最大4KB,那我直接申请这么大不就行了?
Initialize the version identifier in the VMXON region (the first 31 bits) with the VMCS revision identifier reported by capability MSRs. Clear bit 31 of the first 4 bytes of the VMXON region.
上面的几句话说明前4个字节位说明版本号,以让CPU如何处理VT,这个同样在IA32_VMX_BASIC_MSR这个寄存器,前31位就是版本号。对于剩下的字节,需要清0。
Execute VMXON with the physical address of the VMXON region as the operand.
我们使用vmxon指令需要的是它的物理地址,而不是线性地址,所以需要转化一下,最后调用我们封装好的__vmx_on函数,就开启了VT。
既然开启了,我们也得会关闭,如下是关闭VT的代码,实现不难,就不细说了。
BOOLEAN StopVT()
{
__vmx_off();
__writecr4(__readcr4() & ~CR4_VMXE);
ExFreePool(g_VMXCPU.pVMXONRegion);
return TRUE;
}
到现在,我们需要略微修改驱动的加载和卸载函数代码,以做实验验证是否成功,具体代码如下:
NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
DbgPrintLine("Unloaded Successfully!");
return StopVT() ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = UnloadDriver;
DbgPrintLine("Loaded Successfully!");
return StartVT() ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL;
}
如果成功的话,它的输出和我们VT支持启用检测的调试输出是一样的,并且驱动加载是成功并且不会蓝屏。对于后面的部分,下一篇继续。
下一篇
VT 入门篇——最小 VT 实现(下)
VT 入门篇——最小 VT 实现(上)的更多相关文章
- VT 入门番外篇——初识 VT
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
- 羽夏看Win系统内核—— VT 入门番外篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
- Intel VT入门
前言 传说中的VT貌似很神秘的样子,关于VT入门的资料又很少,于是研究了一番 由于资源有限,自身水平亦有限,并且是闭门造车之作,如有错误的地方请指正,不胜感激! 关于VT可以先参考海风月影写的 ...
- Apache Maven 入门篇 ( 上 )
作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法. 这个入门篇分上下两篇.本文着重动手,用 mav ...
- [转]Apache Maven 入门篇 ( 上 )
原文地址:Apache Maven 入门篇 ( 上 ) 作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这 ...
- 【Tools】Apache Maven 入门篇 ( 上 )
作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法.这个入门篇分上下两篇.本文着重动手,用 mave ...
- Maven 入门篇 ( 上 )
写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法.这个入门篇分上下两篇.本文着重动手,用 maven 来构建运行 hello ...
- 【opencv入门篇】 10个程序快速上手opencv【上】
导言:本系列博客目的在于能够在vs快速上手opencv,理论知识涉及较少,大家有兴趣可以查阅其他博客深入了解相关的理论知识,本博客后续也会对图像方向的理论进一步分析,敬请期待:) PS:官方文档永远是 ...
- [JavaEE] Apache Maven 入门篇(上)
http://www.oracle.com/technetwork/cn/community/java/apache-maven-getting-started-1-406235-zhs.html 作 ...
随机推荐
- SQL 语句实战演练
1 创建数据库.删除数据库 备注:关键字不一定要大写. CREATE DATABASE sql_testDROP DATABASE sql_test 2 新建表 CREATE TABLE `emp` ...
- python 小兵(12)模块1
序列化 我们今天学习下序列化,什么是序列化呢? 将原本的字典.列表等内容转换成一个字符串的过程就叫做序列化. 为什么要有序列化模块: 比如,我们在python代码中计算的一个数据需要给另外一段程序使用 ...
- Ubuntu更换镜像源
不同的源 当修改sources.list文件时,我们需要将下面任意一个镜像源的代码复制粘贴到该文件中. 阿里源 # 阿里镜像源 deb http://mirrors.aliyun.com/ubuntu ...
- 「IOI2009」旅行商
题目传送门 首先,看到这道题感觉就像dp(然鹅没什么用). 一个美好的设想 假如没有两个展销会在同一天开展:前途光明 暴力dp,复杂度o(\(n^2\)). 没有同一天的展销会 暴力dp慢,是因为本质 ...
- Java协变、逆变、类型擦除
协变.逆变 定义 Java中String类型是继承自Object的,姑且记做String ≦ Object,表示String是Object的子类型,String的对象可以赋给Object的对象.而Ob ...
- samb建立共享文件夹,windows报无法访问没有访问权限
一.首先确保你已经成功安装上了samba 二.在修改smb.conf文件之前,先拷贝一个备份,然后输入下面的命令修改smb.conf gedit /etc/samba/smb.conf将smb.con ...
- JspSmartUpload 简略中文API文档
感谢原文作者:~数字人生~ 原文链接:https://www.cnblogs.com/mycodelife/archive/2009/04/26/1444132.html 一.JspSmartUplo ...
- webpack热更新 同时导出文件到本地
webpack 配置热更新后,文件配置导出到本地 安装 npm i webpack-dev-server-output --save-dev 引入 const WebpackDevServerOutp ...
- Mysql批量删除和修改某个前缀的表
1.批量删除某个前缀的表名,首先选出这些个表. select concat( 'drop table ', table_name, ';' ) from information_schema.tabl ...
- java中静态代码块初始化顺序
(一)java 静态代码块 静态方法区别 一般情况下,如果有些代码必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的;需要在项目启动的时候就初始化,在不创建对象的情况下, ...