首页
社区
课程
招聘
Hypervisor From Scratch – 第 2 部分:进入 VMX 操作
2024-3-30 11:12 4349

Hypervisor From Scratch – 第 2 部分:进入 VMX 操作

2024-3-30 11:12
4349

目录

二、进入VMX操作

2.1.介绍

这是名为“ Hypervisor From Scratch ”的多个系列教程的第二部分。首先,请考虑在阅读本部分之前先阅读第一部分(基本概念和配置测试环境),因为它包含您需要了解的基本知识,以便理解本教程的其余部分。在这一部分中,我们将讨论 WDK 驱动程序并最终开始启用 VT-x。

2.2.概述

在本节中,我们将了解检测处理器对虚拟机管理程序的支持,然后我们简单地配置基本操作以启用 VMX进入 VMX 操作,然后我们将了解有关Window Driver Kit (WDK) 的更多信息。

2.3.IRP主要功能

除了我们的内核模式驱动程序(“ MyHypervisorDriver ”)之外,我们还将创建一个名为“ MyHypervisorApp ”的用户模式应用程序。首先,我应该鼓励您在用户模式而不是内核模式下编写大部分代码(只要可能),这是因为您可能没有正确处理异常。因此,它会导致 BSOD,或者另一方面,在内核模式下运行较少的代码会减少出现一些令人讨厌的内核模式错误的可能性。

如果您还记得上一部分,我们创建了一个 Windows 驱动程序。现在我们想要扩展我们的项目以支持更多 IRP 主要功能。

IRP 主要功能位于为每个设备创建的传统 Windows 表中。一旦我们在 Windows 中注册了一个设备,我们就必须为这些 IRP 主要函数引入一个处理程序。

这就像每个设备都有一个主要功能表。每当用户模式应用程序调用任何这些函数时,Windows 都会找到相应的函数(如果设备驱动程序支持该 MJ 函数),然后将 IRP 传递给内核驱动程序。

2.3.1.什么是IRP

那么,什么是 IRP ? IRP 是表示 I/O 请求数据包的结构。 该数据包包含有关其调用者、参数、数据包状态等的许多详细信息。我们从 IRP 数据包中提取调用者参数。

现在,我们可以根据 IRP 提供的详细信息在内核中处理用户模式请求。

请记住,当内核驱动程序中的函数接收到 IRP 数据包时,我们的代码有责任调查调用者并检查其权限等。

2.3.2.配置IRP主要功能

注册设备后(前面已经介绍过),我们需要介绍设备的主要功能。

以下代码负责配置不同的 IRP MJ 函数并引入自定义内核模式函数作为 IRP 处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (NtStatus == STATUS_SUCCESS)
{
    for (Index = 0; Index < IRP_MJ_MAXIMUM_FUNCTION; Index++)
    {
        DriverObject->MajorFunction[Index] = DrvUnsupported;
    }
 
    DbgPrint("[*] Setting Devices major functions.");
    DriverObject->MajorFunction[IRP_MJ_CLOSE]          = DrvClose;
    DriverObject->MajorFunction[IRP_MJ_CREATE]         = DrvCreate;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DrvIoctlDispatcher;
 
    DriverObject->MajorFunction[IRP_MJ_READ]  = DrvRead;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = DrvWrite;
 
    DriverObject->DriverUnload = DrvUnload;
 
    IoCreateSymbolicLink(&DosDeviceName, &DriverName);
}
else
{
    DbgPrint("[*] There were some errors in creating device.");
}

您可以看到我们对所有功能都使用了“ DrvUnsupported ”。 该函数处理所有 MJ 函数并告诉用户它不受支持。

“ DrvUnsupported ”的主体是这样的:

1
2
3
4
5
6
7
8
9
10
11
NTSTATUS
DrvUnsupported(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    DbgPrint("[*] This function is not supported :( !");
 
    Irp->IoStatus.Status      = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}

我们还介绍了我们设备所必需的其他主要功能。 我们将在以后的部分中完成其中一些 MJ 函数的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
NTSTATUS
DrvRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    DbgPrint("[*] Not implemented yet :( !");
 
    Irp->IoStatus.Status      = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
 
NTSTATUS
DrvWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    DbgPrint("[*] Not implemented yet :( !");
 
    Irp->IoStatus.Status      = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
 
NTSTATUS
DrvClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    DbgPrint("[*] Not implemented yet :( !");
 
    Irp->IoStatus.Status      = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}

现在让我们看看 IRP MJ 函数列表和其他类型的 Windows 驱动程序工具包处理程序例程。

2.3.3.IRP主要功能列表

我们可以使用此 IRP 主要函数列表在 WDK 驱动程序中执行不同的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define IRP_MJ_CREATE                   0x00
#define IRP_MJ_CREATE_NAMED_PIPE        0x01
#define IRP_MJ_CLOSE                    0x02
#define IRP_MJ_READ                     0x03
#define IRP_MJ_WRITE                    0x04
#define IRP_MJ_QUERY_INFORMATION        0x05
#define IRP_MJ_SET_INFORMATION          0x06
#define IRP_MJ_QUERY_EA                 0x07
#define IRP_MJ_SET_EA                   0x08
#define IRP_MJ_FLUSH_BUFFERS            0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION   0x0b
#define IRP_MJ_DIRECTORY_CONTROL        0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
#define IRP_MJ_DEVICE_CONTROL           0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL  0x0f
#define IRP_MJ_SHUTDOWN                 0x10
#define IRP_MJ_LOCK_CONTROL             0x11
#define IRP_MJ_CLEANUP                  0x12
#define IRP_MJ_CREATE_MAILSLOT          0x13
#define IRP_MJ_QUERY_SECURITY           0x14
#define IRP_MJ_SET_SECURITY             0x15
#define IRP_MJ_POWER                    0x16
#define IRP_MJ_SYSTEM_CONTROL           0x17
#define IRP_MJ_DEVICE_CHANGE            0x18
#define IRP_MJ_QUERY_QUOTA              0x19
#define IRP_MJ_SET_QUOTA                0x1a
#define IRP_MJ_PNP                      0x1b
#define IRP_MJ_PNP_POWER                IRP_MJ_PNP // Obsolete....
#define IRP_MJ_MAXIMUM_FUNCTION         0x1b

每个主要函数只有在我们从用户模式调用其相应函数时才会触发。例如,有一个名为CreateFile的函数(在用户模式下) (及其所有变体,例如用于ASCII和Unicode 的CreateFileA和CreateFileW),因此每次我们调用CreateFile时,都会调用注册为IRP_MJ_CREATE的函数 ,如果我们调用ReadFile然后 IRP_MJ_READWriteFile 然后IRP_MJ_WRITE 将被触发。

您可以看到 Windows 将其设备视为文件,并且我们需要从用户模式传递到内核模式的所有内容都可以在带有IRP *类型的参数中使用,并且可以用作内核 IRP MJ 函数处理程序的缓冲区。 Windows 负责将用户模式缓冲区复制到内核模式堆栈。

不用担心;我们在项目的其余部分中经常使用它,但我们仅在这部分中支持IRP_MJ_CREATE,而其他部分则在将来的部分中实现。

还有其他术语称为“IRP 次要函数”。我们保留了这些功能,因为本系列中未使用它们。

2.4.在DbgView中查看调试信息

在“regedit.exe”中,添加一个键:

1
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter

在其下添加一个名为 IHVDRIVER 且值为 0xFFFF 的 DWORD 值。
图片描述

2.2.检查虚拟机管理程序支持

在启用VT-x之前,我们首先应该考虑发现对VMX的支持。英特尔软件开发人员手册第 3C卷第 23.6 节**“发现对 VMX 的支持”**对此进行了介绍。

如果CPUID.1:ECX.VMX[bit 5] = 1,则支持 VMX 操作,您可以使用CPUID知道 VMX 的存在。

首先,我们需要知道我们是否在基于英特尔的处理器上运行。我们可以使用CPUID指令并找到供应商字符串“ GenuineIntel ”来理解这一点。

以下函数使用指令返回供应商字符串CPUID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
std::string
GetCpuID()
{
    // Initialize used variables
    char   SysType[13]; // Array consisting of 13 single bytes/characters
    string CpuID;       // The string that will be used to add all the characters to
                        // Starting coding in assembly language
    _asm
        {
            // Execute CPUID with EAX = 0 to get the CPU producer
        XOR EAX, EAX
        CPUID
                    // MOV EBX to EAX and get the characters one by one by using shift out right bitwise operation.
        MOV EAX, EBX
        MOV SysType[0], AL
        MOV SysType[1], AH
        SHR EAX, 16
        MOV SysType[2], AL
        MOV SysType[3], AH
                // Get the second part the same way but these values are stored in EDX
        MOV EAX, EDX
        MOV SysType[4], AL
        MOV SysType[5], AH
        SHR EAX, 16
        MOV SysType[6], AL
        MOV SysType[7], AH
                // Get the third part
        MOV EAX, ECX
        MOV SysType[8], AL
        MOV SysType[9], AH
        SHR EAX, 16
        MOV SysType[10], AL
        MOV SysType[11], AH
        MOV SysType[12], 00
        }
    CpuID.assign(SysType, 12);
    return CpuID;
}

最后一步是检查VMX是否存在 。 我们可以使用以下代码来检查它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool
DetectVmxSupport()
{
    bool VMX = false;
    __asm {
        XOR    EAX, EAX
        INC    EAX
        CPUID
        BT     ECX, 0x5
        JC     VMXSupport
        VMXNotSupport :
        JMP     NopInstr
        VMXSupport :
        MOV    VMX, 0x1
        NopInstr :
        NOP
    }
 
    return VMX;
}

正如你所看到的,它检查 CPUID.1,如果第bit 5位为1,则支持VMX操作。 我们也可以在内核驱动程序中执行相同的操作。

总而言之,我们检测VMX支持的主要代码应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int
main()
{
    std::string CpuId;
 
    PrintAppearance();
 
    CpuId = GetCpuID();
 
    printf("[*] The CPU Vendor is : %s \n", CpuID.c_str());
 
    if (CpuId == "GenuineIntel")
    {
        printf("[*] The Processor virtualization technology is VT-x. \n");
    }
    else
    {
        printf("[*] This program is not designed to run in a non-VT-x environment !\n");
        return 1;
    }
 
    if (DetectVmxSupport())
    {
        printf("[*] VMX Operation is supported by your processor .\n");
    }
    else
    {
        printf("[*] VMX Operation is not supported by your processor .\n");
        return 1;
    }
 
    HANDLE hWnd = CreateFile(L"\\\\.\\MyHypervisorDevice",
                             GENERIC_READ | GENERIC_WRITE,
                             FILE_SHARE_READ |
                                 FILE_SHARE_WRITE,
                             NULL, /// lpSecurityAttirbutes
                             OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL |
                                 FILE_FLAG_OVERLAPPED,
                             NULL); /// lpTemplateFile
 
    _getch();
 
    return 0;
}

最终结果:
图片描述

2.6.启用VMX操作

如果处理器支持 VMX 操作,则需要启用它。 正如我上面告诉您的, IRP_MJ_CREATE 是应该用于启动操作的第一个函数。

在进入VMX操作之前,我们应该通过设置CR4.VMXE[bit 13] = 1来使能VMX。然后通过执行VMXON指令进入VMX操作。 如果在 CR4.VMXE = 0 的情况下执行,VMXON 会导致无效操作码异常 (#UD)。一旦进入 VMX 操作,就无法清除 CR4.VMXE。

之后,我们可以通过执行VMXOFF指令来退出VMX操作,此时CR4.VMXE可以被清零。

VMXON 还由 IA32_FEATURE_CONTROL MSR(MSR 地址 3AH)控制。 当逻辑处理器复位时,该 MSR 被清零。

让我们看看这个 MSR 的第一部分:

  • 位 0 是锁定位。 如果该位清零,VMXON 会导致一般保护 (#GP) 异常。 如果锁定位被设置,则对该 MSR 的 WRMSR 会导致一般保护异常; 在上电复位之前,MSR 无法修改。

这是什么意思? 这意味着我们可以禁用 VMX 功能,而无法再次启用。 只有系统重置后,我们才能启用VMX。

系统 BIOS 可以使用该位为 BIOS 提供设置选项以禁用对 VMX 的支持。 要在平台中启用 VMX 支持,BIOS 必须设置位 1、位 2 或两者,以及锁定位。

2.6.1.设置CR4 VMXE位

现在我们应该创建一些函数来在汇编中执行此操作。

只需在头文件(在我的例子中 Source.h )中声明您的函数:

1
extern void inline AsmEnableVmxOperation(void);

然后在汇编文件(在我的例子中为“SourceAsm.asm”)中,添加此函数(设置 CR4 的bit 13)。

AsmEnableVmxOperation PROC PUBLIC

    PUSH RAX			    ; Save the state
    
    XOR RAX, RAX			; Clear the RAX
    MOV RAX, CR4

    OR RAX,02000h	    	; Set the 14th bit
    MOV CR4, RAX
    
    POP RAX			     	; Restore the state
    RET

AsmEnableVmxOperation ENDP

另外,在 SourceAsm.asm 的上面声明您的函数。

PUBLIC AsmEnableVmxOperation

此汇编函数 应在DrvCreate中调用 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NTSTATUS
DrvCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    //
    // Enabling VMX Operation
    //
    AsmEnableVmxOperation();
    DbgPrint("[*] VMX Operation Enabled Successfully !");
 
    Irp->IoStatus.Status      = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}

最后,我们应该从用户模式调用以下函数:

1
2
3
4
5
6
7
8
9
HANDLE hWnd = CreateFile(L"\\\\.\\MyHypervisorDevice",
                         GENERIC_READ | GENERIC_WRITE,
                         FILE_SHARE_READ |
                             FILE_SHARE_WRITE,
                         NULL, /// lpSecurityAttirbutes
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL |
                             FILE_FLAG_OVERLAPPED,
                         NULL); /// lpTemplateFile

如果您看到以下结果,则您已成功完成第二部分。
图片描述

2.7.结论

在这一部分中,我们了解了创建 Windows Driver Kit 程序所需的基本知识,然后进入虚拟环境为其余部分构建基石。

在第三部分中,我们将深入了解 Intel VT-x,并使我们的驱动程序更加先进。

注意:请记住,虚拟机管理程序会随着时间的推移而发生变化,因为新功能会添加到操作系统中或使用新技术。 例如,Meltdown & Spectre 的更新对虚拟机管理程序进行了大量更改,因此,如果您想在项目、研究或其他任何用途中使用虚拟机管理程序 From Scratch,则必须使用这些教程系列最新部分中的驱动程序由于本教程正在积极更新,并且更改已应用于较新的部分(较早的部分保持不变),因此您可能会在较早的部分中遇到错误和不稳定问题,因此请确保在实际项目中使用最新的部分。

2.8.参考

[1] 英特尔® 64 和 IA-32 架构软件开发人员手册合并第 3 卷 ( https://software.intel.com/en-us/articles/intel-sdm )

[2] IRP_MJ_DEVICE_CONTROL( https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mj-device-control

[3] Windows 驱动程序工具包示例 ( https://github.com/Microsoft/Windows-driver-samples/blob/master/general/ioctl/wdm/sys/sioctl.c )

[4] 手动设置单台计算机的本地内核调试 ( https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-local-kernel-debugging-of-a-单机手动

[5]使用CPUID获取处理器制造商( https://www.daniweb.com/programming/software-development/threads/112968/obtain-processor-manufacturer-using-cpuid

[6] 即插即用次要 IRP ( https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/plug-and-play-minor-irps )

[7] _FAST_IO_DISPATCH 结构( https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_fast_io_dispatch

[8] 过滤 IRP 和快速 I/O ( https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/filtering-irps-and-fast-io )

[9] Windows 文件系统过滤器驱动程序开发 ( https://www.apriorit.com/dev-blog/167-file-system-filter-driver )


阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

最后于 2024-3-31 17:16 被zhang_derek编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (2)
雪    币: 19759
活跃值: (29377)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2024-3-31 21:30
2
1
感谢分享
雪    币: 3730
活跃值: (3967)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 2024-4-17 19:01
3
0
感谢分享
游客
登录 | 注册 方可回帖
返回