写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看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的。现在的新的支持VTCPU,也是有个开关的。就算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还有一些必备条件的。

  白皮书开头说我们的CR4VMXE位需要置1,并且在MSRMSR_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这个指令并不是一定会成功的,如果失败会放到EFLAGCF位,是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 实现(上)的更多相关文章

  1. VT 入门番外篇——初识 VT

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  2. 羽夏看Win系统内核—— VT 入门番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  3. Intel VT入门

    前言     传说中的VT貌似很神秘的样子,关于VT入门的资料又很少,于是研究了一番 由于资源有限,自身水平亦有限,并且是闭门造车之作,如有错误的地方请指正,不胜感激! 关于VT可以先参考海风月影写的 ...

  4. Apache Maven 入门篇 ( 上 )

    作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法. 这个入门篇分上下两篇.本文着重动手,用 mav ...

  5. [转]Apache Maven 入门篇 ( 上 )

    原文地址:Apache Maven 入门篇 ( 上 ) 作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这 ...

  6. 【Tools】Apache Maven 入门篇 ( 上 )

    作者:George Ma 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法.这个入门篇分上下两篇.本文着重动手,用 mave ...

  7. Maven 入门篇 ( 上 )

    写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法.这个入门篇分上下两篇.本文着重动手,用 maven 来构建运行 hello ...

  8. 【opencv入门篇】 10个程序快速上手opencv【上】

    导言:本系列博客目的在于能够在vs快速上手opencv,理论知识涉及较少,大家有兴趣可以查阅其他博客深入了解相关的理论知识,本博客后续也会对图像方向的理论进一步分析,敬请期待:) PS:官方文档永远是 ...

  9. [JavaEE] Apache Maven 入门篇(上)

    http://www.oracle.com/technetwork/cn/community/java/apache-maven-getting-started-1-406235-zhs.html 作 ...

随机推荐

  1. python利用正则表达式提取文本中特定内容

    正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配. Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式. re 模块使 Python ...

  2. Arduino+ESP32 之 驱动GC9A01圆形LCD(一),基于Arduino_GFX库

    最近买了一块圆形屏幕,驱动IC是GC9A01,自己参考淘宝给的stm32的驱动例程, 在ubuntu下使用IDF开发ESP32,也在windows的vscode内安装IDF开发ESP32,虽然都做到了 ...

  3. 微服务架构 | 10.2 使用 Papertrail 实现日志聚合

    目录 前言 1. Papertrail 基础知识 1.1 Papertrail 特点 1.2 Papertrail 是什么 2. 使用 Papertrail 进行日志聚合的示例 2.1 创建 Pape ...

  4. Codeforces Round #742 (Div. 2)

    A. Domino Disaster 思路 按照题意模拟即可 如果是 对应关系为R --> R L --> L U --> D D --> U AC_CODE inline v ...

  5. .net core部署到ubuntu 上传文件超过30MB

    默认的上传文件不能超过30MB,需要修改几个地方 一.web.config中添加配置 <requestLimits maxAllowedContentLength="214748364 ...

  6. JS中的堆内存与栈内存

    在js引擎中对变量的存储主要有两种位置,堆内存和栈内存. 和java中对内存的处理类似,栈内存主要用于存储各种基本类型的变量,包括Boolean.Number.String.Undefined.Nul ...

  7. docker容器编排 (4)

    容器编排 我们的项目可能会使用了多个容器,容器多了之后管理容器的工作就会变得麻烦.如果要对多个容器进行自动配置使得容器可以相互协作甚至实现复杂的调度,这就需要进行容器编排.Docker原生对容器编排的 ...

  8. MAC上安装HEAAN库

    介绍 HEAN是一个软件库,它实现支持定点运算的同态加密(HE),此库支持有理数之间的近似运算.近似误差取决于某些参数,与浮点运算误差几乎相同.该库中的方案发表在"近似数算术的同态加密&qu ...

  9. 了解Java格式化输出printf,一篇就够了

    格式化详解 格式化输出 转换符 常用转换符 日期转换 搭配标志 了解C语言的都知道,C语言的输出语句printf();可以对里面的内容格式化然后输出.那么在Java中也给我们提供了相关的方法.两者十分 ...

  10. Byobu安装与使用

    机子为Ubuntu18 Byobu安装 sudo apt-get install byobu Byobu安装后默认禁用,需要启用Byobu,之后每次登陆自动启用Byobu byobu-enable 还 ...