写在前面

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

你如果是从中间插过来看的,请仔细阅读 羽夏看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. 为什么 Redis 的查询很快, Redis 如何保证查询的高效

    Redis 如何保证高效的查询效率 为什么 Redis 比较快 Redis 中的数据结构 1.简单动态字符串 SDS 对比 c 字符串的优势 SDS可以常数级别获取字符串的长度 杜绝缓冲区溢出 减少修 ...

  2. 004 Linux 揭开神器 vim 面纱

    01 开篇初识 vim vim 功能吊炸天,但我们掌握一些常用的命令即可应对日常的使用了,不记流水账! Linux 中最常用的编辑器是什么? vim ! vi 跟 vim 啥区别? vim 就是 vi ...

  3. 在build中配置resources,来防止我们资源导出失败的问题

    <!--在build中配置resources,来防止我们资源导出失败的问题--> <build> <resources> <resource> < ...

  4. K8s配置配置存活、就绪和启动探测器

    kubelet 使用存活探测器来知道什么时候要重启容器. 例如,存活探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤). 这样的情况下重启容器有助于让应用程序在有问题的情况下更可用. ...

  5. JVM专题3: GC 垃圾回收

    合集目录 JVM专题3: GC 垃圾回收 什么是GC? 为什么要有 GC? Garbage Collection, 用于内存回收. 简述一下 Java 垃圾回收机制? 那些内存需要回收 虚拟机中程序计 ...

  6. SQL解析器详解

    1.概述 最近,有同学留言关于SQL解析器方面的问题,今天笔者就为大家分享一下SQL解析器方便的一些内容. 2.内容 2.1 SQL解析器是什么? SQL解析与优化是属于编辑器方面的知识,与C语言这类 ...

  7. C编译器中“不是所有的控件路径都返回值”报错

    编译器的判断逻辑是是否在所有的分支中都返回了值,即if不成立时也必须返回值.编译器认为如果三个if都不成立则此函数可能没有返回值,故报错.需要将第三个if改为else或者去掉if体直接return.

  8. IDEA Debug常用快捷键

    快捷键 介绍 F7 步入:进入到方法内部执行.一般步入自定义的方法.区别于强行步入 F8 步过:不会进入到方法内部,直接执行. F9 恢复程序:下面有断点则运行到下一断点,否则结束程序. Shift+ ...

  9. Html 项目使用自定义字体文件问题

    感谢大佬:https://zhidao.baidu.com/question/652711582735059245.html 1.首先在项目过程中新建文件夹fonts将准备好的ttf字体文件复制该文件 ...

  10. 重力感应 加速计- By严焕培

    //  加速计-传统用法 // //  Created by 严焕培 on 15-05-19. //  Copyright (c) 2015年 sibu. All rights reserved. / ...