系统程序员的虚拟化






4.92/5 (50投票s)
好奇如何创建虚拟机管理程序应用程序?
- Github 链接: https://github.com/WindowsNT/asm
(现在包含 VS 解决方案以及 bochs 的自动编译/ISO 生成)
臭名昭著的三部曲:第二部分
本文面向已经阅读过我的汇编教程(https://codeproject.org.cn/Articles/45788/The-Real-Protected-Long-mode-assembly-tutorial-for)并希望了解虚拟化工作原理的读者。您想创建自己的 VMWare 工作站吗?让我们开始吧!
背景
所需物品
- 完全理解 CPU 在保护模式和长模式下的工作方式 - 阅读我的文章:https://codeproject.org.cn/KB/system/asm.aspx。
- Bochs 源代码 - 重新编译并启用 VMX 扩展。VMWare(或其他虚拟化软件)将无法工作。另外,我的代码很可能使用了您 CPU 版本中不存在的功能 - 但您可以尝试。哦,如果您真的有勇气,也可以在原始 DOS PC 中进行测试。github 源代码为了方便您,已包含 Bochs。
- =非常好的= 汇编知识
- Flat Assembler (http://flatassembler.net/)
- FreeDos (或您有许可的任何其他 DOS)。源代码为了方便您,已包含 FreeDos 软盘镜像。
- 大量的耐心
如果您是初级程序员,请立即退出。
如果您是高级程序员,请立即退出。
如果您是专家程序员,请立即退出。当您开始阅读本文时,您 anyway 会感到自己像个初学者。
但是,由于我也是初学者,所以您最终还是能够读懂我想说的话,因为我在阅读虚拟化手册后也有同样的感受。所以请继续阅读!
启动
我们将创建一个应用程序,该应用程序将为 CPU 准备虚拟化环境,创建虚拟机,进入虚拟机并退出。所有这些都将在 x64 模式下完成,以简化操作。x86 模式下也是可能的,但我们将专注于 x64 架构以避免代码中的不必要开销。
代码仅演示了基本的 VMX 功能,并且可能在您的 CPU 上无法工作。但是,您可以使用启用虚拟化功能的 bochs,这样您就可以测试我的代码。
术语
- VMM (虚拟机监视器): 宿主应用程序
- VM (虚拟机): 客户机应用程序
- 根操作 (Root Operation): VMM 运行的代码/上下文
- 非根操作 (Non Root Operation): VM 运行的代码/上下文
- VMX 转换 (VMX Transition): 从宿主到客户机 (VMEntry) 或从客户机到宿主 (VMExit)
- VMCS: 用于控制 VM 和 VMX 转换的结构。
- VM Entry (虚拟机进入): 从宿主应用程序到客户机的转换。
- VM Exit (虚拟机退出): 由于某种原因从客户机到宿主的转换。
VMX 操作的生命周期
- VMM 检查 CPU 虚拟化支持 (CPUID) 并启用它 (CR4 和
VMXON
) - VMM 为每个 VM 初始化一个控制结构,称为 VMCS。通过使用
VMPTRST
和VMPTRLD
指示 CPU 该指针的位置。使用VMREAD
、VMWRITE
和VMCLEAR
进行 VMCS 的读/写。 - VMM 使用
VMLAUNCH
或VMRESUME
进入 VM - VM 通过
VMEXIT
退出到 VMM - 反复执行上述所有操作
- VMM 最终会关闭自身
VMXOFF
我的 CPU 支持虚拟化吗?
是的 (否则您现在也不会在这里阅读了),但如果您仍然想确认,可以在执行 CPUID
指令并将 EAX
设置为 1 后,检查 ECX 寄存器的第 5 位。
mov eax,1
cpuid
bt ecx,5
jc VMX_Supported
jmp VMX_NotSupported
在知道您的 CPU 支持 VMX 操作后,您应该检查 IA32_VMX_BASIC MSR
(索引 0x480) 来获取您 CPU 的特定实现信息。
mov ecx, 0480h
rdmsr
这个 64 位 MSR 包含大量信息,但目前我们感兴趣的是 2 个字段
- 位 0 - 31: 32 位 VMX 修订号
- 位 32 - 44:
VMXON
区域或VMCS
的字节数 (最多 4096)。
VMX 修订号 (4 字节) 应放入每个 VMCS
/VMXON
结构中,以便处理器知道用于存储数据的格式。每个 VMCS
/VMX
结构的大小应恰好是位 32-44 指示的字节数 (最大 4096)。
启用 VMX 操作
- 进入长模式。
- 将 CR4 的第 13 位设置为 1。此位启用
VMX
操作。 - 将 CR0 的第 5 位设置为 1 (NE) - 这对于
VMXON
成功是必需的。 - 初始化一个
VMXON
区域。 - 执行
VMXON
指令。
VMCS
是一个 4KB 对齐的内存区域,用于支持 VM 操作。它包含 3 个字段:4 字节用于存储修订号 (0x480 MSR 寄存器返回值),4 字节用于 VMX 异常数据 (稍后详述),其余部分是用于控制 VM 操作的六个字段集合。
VMXON
区域是单个 VMCS 区域,您只需要初始化修订号。VMXON 区域的初始化需要放入正确的修订号 (前 4 个字节),如上面 0x480 MSR 寄存器返回值所示。
VMXON
指令需要一个地址 (例如,VMXON
[rdi])。此地址应包含 VMXON
区域的 64 位物理地址 (4KB 对齐),并且该区域的前 4 个字节应包含 VMX 修订号。
File: VMX.ASM
Func: VMX_Enable
为 VMX 操作设置的 CR4 位
mov rax,cr4
bts rax,13
mov cr4,rax
启用 VMX
mov [rdi],ebx ; Put the revision. Rdi holds the VMCS address and ebx holds the revision
VMXON [rsi] ; Assuming rsi holds the address of the VMCS
VMCS 组
到目前为止都很简单,但真正的地狱从这里开始。VMCS 的其余部分 (也就是说,在最初的 8 个字节 (修订号 + VMX 异常) 之后) 被分成 6 个子组
- 客户机状态 (Guest State)
- 宿主状态 (Host State)
- 非根控件 (Non root controls)
VMExit
控件 (VMExit
controls)VMEntry
控件 (VMEntry
controls)VMExit
信息 (VMExit
information)
以上每个字段都包含关于 VM 如何启动 (VMEntry
后的状态)、VMExit 后的宿主状态、VMExit 何时发生等重要信息。
File: VMX.ASM
Func: VMX_TryGuest and VMX_TryGuest2
客户机状态 (The Guest State)
这包含以下信息 (括号中为位号)
- CR0,CR3,CR4,DR7,RSP,RIP,RFLAGS, (各 64 位)
- 对于 CS,SS,DS,ES,FS,GS,LDTR,TR 中的每一个
- 选择符 (Selector) (16 位)
- 基址 (Base address) (64 位)
- 段限长 (Segment limits) (32 位)
- 访问权限 (Access rights) (32 位)
- 对于 GDTR 和 IDTR
- 基址 (Base address) (64 位)
- 限长 (Limit) (32 位)
IA32_DEBUGCRTL
(64)IA32_SYSENTER_CS
(32)IA32_SYSENTER_ESP
(64)IA32_SYSENTER_EIP
(64)IA_PERF_GLOBAL_CTRL
(64)IA32_PAT
(64)IA32_EFER
(64)SMBASE
(32)- 活动状态 (Activity State) (32 位) - 0 活动, 1 暂停 (执行 HLT), 2 发生三重故障, 3 等待启动 IPI (SIPI)。
- 中断性状态 (Interruptibility state) (32 位) - 定义了在 VM 中应阻止的某些功能的的状态 - 稍后详述。
- 待处理的调试异常 (Pending debug exceptions) (64 位) - 为了方便使用 DR7 进行硬件断点 - 稍后详述。
- VMCS 链接指针 (VMCS Link pointer) (64 位) - 保留,设置为
0xFFFFFFFFFFFFFFFF
。 - *VMX 预emption计时器值 (VMX Preemption timer value) (32 位) - 稍后详述。
- *页目录指针表条目 (Page Directory pointer table entries) (4x64 位) - 指向页的指针 - 稍后详述。
客户机状态描述了 CPU 在 VMEntry
之后寄存器的值。由于您可以完全控制寄存器,因此可以使 VM 以任何模式启动 (实模式、保护模式、长模式等)。但是,即使您要启动一个实模式 VM (如我的代码所示),也必须像普通的 p-mode 选择器一样初始化段寄存器,并带有正确的限长、访问权限等。
用于段寄存器的值 (限长、基址、选择符、访问权限和标志) 与普通保护模式下使用的值相同,因此,例如,您会看到我的代码为 DS 读/写数据段添加 0x92 访问标志。
宿主状态 (The Host State)
这包含以下信息 (括号中为位号)
- CR0,CR3,CR4,RSP,RIP (各 64 位)
- CS,SS,DS,ES,FS,GS,TR 选择符 (各 16 位)
- FS,GS,TR,GDTR,IDTR 基址 (各 64 位)
IA32_SYSENTER_CS
(32)IA32_SYSENTER_ESP
(64)IA32_SYSENTER_EIP
(64)*IA32_PERF_GLOBAL_CTRL
(64)*IA32_PAT
(64)*IA32_EFER
(64)
宿主状态告诉 CPU 在 VMExit
后如何返回 VMM。
执行控制字段 (Execution Control Fields)
这些字段基本上告诉 CPU 在 VM 中什么可以执行,什么不可以执行。所有不允许的操作都会导致 VMExit
。这些部分是:
- Pin-Based (32 位) : 中断
- Processor-Based (2x32 位)
- 主要: 单步执行、TSC HLT INVLPG MWAIT CR3 CR8 DR0 I/O 位图
- 次要: EPT、描述符表更改、无限制客户机等
- 异常位图 (Exception bitmap) (32 位): 每个异常对应一位。如果位为 1,则该异常会导致
VMExit
。 - I/O 位图地址 (I/O bitmap addresses) (2x64 位): 控制 IN/OUT 何时导致
VMExit
。 - 时间戳计数器偏移 (Time Stamp Counter offset)
- CR0/CR4 客户机/宿主掩码 (CR0/CR4 guest/host masks)
- CR3 目标 (CR3 Targets)
- APIC 访问 (APIC Access)
- MSR 位图 (MSR Bitmaps)
我的代码为了简化,只使用了 Pin-Based 和 Processor-Based,但这些字段是您真正的瑞士军刀;您可以完全控制 VM 被允许或不允许执行的操作。
VM-Exit 控制字段 (VM-Exit Control Fields)
这些字段告诉 CPU 在 VMExit
时要加载什么以及要丢弃什么。
VMExit
控件 (32 位)VMExit
MSR 控件
VM-Entry 控制字段 (VM-Entry Control Fields)
VMEntry
控件 (32 位)VMEntry
MSR 控件VMEntry
事件注入控件 (VMEntry
Controls for event injection)
这种事件注入是您的第二个武器。当 VM 退出时,您可以注入一个事件,让 VM 认为该异常是由其代码生成的。是的,VMM 可以变得非常强大。
VM-Exit 信息 (只读) 字段 (VM-Exit Information (Read only) Field)
- 基本信息
- 退出原因 (Exit Reason) (32 位)
- 退出限定符 (Exit Qualification) (64 位)
- 客户机线性地址 (Guest Linear Address) (64 位)
- 客户机物理地址 (Guest Physical Address) (64 位)
- 向量化退出信息 (Vectored exit information)
- 事件传递退出 (Event delivery exits)
- 指令执行退出 (Intstruction execution exits)
- 错误字段 (Error field)
VMCS 初始化 (The VCMS Initialization)
为了允许使用 VMREAD
或 VMWRITE
进行后续读写,您需要先将 VMCS
的前 4 个字节初始化为修订号 (与上面的 VMXON
结构一样),然后执行 VMPTRLD
并传入其地址。
Intel 手册 3B 的附录 H 列出了所有索引。例如,客户机 RIP 的索引是 0x681e
。要将值 0
写入该字段,我们将使用
mov rax,0681eh
mov rbx,0
vmwrite rax,rbx
这意味着,在成功的 VM Entry 之后,客户机将从 RIP 设置为 0
开始。
为您的 VM 分配内存 (Giving Memory to your VM)
您以为就完了?哈哈。没那么快。您必须为您的新虚拟机分配内存,并配置 EPT。EPT 是一种将宿主物理地址转换为客户机物理地址的机制。幸运的是,它与已知的长模式分页机制完全相同,因此您可以在我的文章中回顾它。
最初,CPU 的 VMX 功能要求客户机必须在分页保护模式下启动,而 VMM 应用程序通常会将虚拟机放入 VM86 模式,以允许期望干净的实模式启动的操作系统正常工作。很快,它们引入了“无限制客户机”(Unrestricted Guest) 标志 (次要退出控件中的位 7),允许客户机在实模式下启动。但是,将虚拟机放入实模式意味着我们必须映射低 640KB 内存,因此我们必须使用 EPT。
如果您的 CPU 不支持“无限制客户机”模式,那么您可以使用类似的 C 代码设置一个保护模式客户机,因为我的代码无论如何都会创建保护模式风格的段。使用 Bochs 的 github 项目会自动创建一个保护模式客户机。
当然,根据客户机的初始状态 (例如,如果您想以长模式启动客户机),您还需要配置 Guest PAE、分页、正确的 CR4 等。但我们的这个小程序将配置一个实模式客户机,因此它需要将一部分宿主内存映射到客户机物理地址。EPT 转换使用低 48 位 (正如当今的 CPU 那样 - 并非使用整个 64 位范围)。
该代码目前测试的是一个保护模式客户机。此 VM 的初始化在 VMX_Initialize_Guest2
中。这次,CR0 设置为保护模式分页模式,CR4 加载了页目录 (与普通保护模式使用的相同,因为我们的 EPT 是透明的)。文件 guest32.asm 是我们保护模式客户机的入口点,这次选择器已准备就绪。它只是设置了一个标志,然后使用 VMCall
退出到 VMM。
启动它!(Launch It!)
在正确初始化 VMCS 后 (好吧,这是个玩笑,但我不得不说“正确”——准备好在这里会遇到大量失败),VMLAUNCH
指令将开始执行虚拟机 (从 VMCS
客户机设置的 CS:XIP
开始)。如果进入失败,VMLAUNCH
执行后 Z 标志将立即设置。
这就是 BOCHS 将为您提供帮助的地方。在 VMLAUNCH
失败后,bochs 调试器窗口将显示一条消息,说明出了什么问题,这样您就能知道要在 VMCS
中修复什么。
如果 VMLAUNCH
成功,控制将不会返回到宿主,直到发生 VM Exit。当发生 VM Exit 时,控制将转移到 VMM 的退出例程 (如 VMCS 宿主状态字段中所配置)。VMExit
仅检查 VMEntry
设置的标志,以了解 VMEntry
代码是否成功执行。
请注意,即使 VMLAUNCH
成功,启动 VM 也可能立即导致 VMExit
,原因可能是任何故障 (页面故障、EPT 配置错误等)。这样,VMLAUNCH
会成功,但控制会立即返回到您的退出例程,而 VMEntry
代码不会被执行。
已启动,然后呢?(Launched, Now What?)
什么也没有。VM 就像什么都没存在一样执行,除非您使其存在。您现在需要实现自己的 BIOS,并将其复制到虚拟内存的正确地址 (以便执行从 0xFFFF:0xFFF0
开始),以及您的驱动程序,用于在实际硬件和实际内存与您可能允许在 VM 内使用的虚拟硬件和内存之间传输数据。是的,这就是为什么 VMWare Workstation 有 500MB 的大小;它包含 BIOS、驱动程序和通信协议,允许例如虚拟屏幕 (在客户机看来是一个实际驱动程序) 在您实际屏幕上的窗口中显示。USB 硬件也是如此,它被从实际系统复制到虚拟系统。
作为一个简单的测试,人们可能会认为复制实际的 BIOS 到虚拟内存应该很容易,例如,DOS 可以启动。对,但 DOS 会从哪个设备启动,因为 VM 中没有任何设备?这就是为什么您需要使用自定义 BIOS 将实际设备复制到 VM 中,以便通过特定协议与宿主通信,然后模拟允许的设备,以便 VM 正常工作。
我的应用程序只是以透明的方式转发内存,因此调用 VM 中的 BIOS 和 DOS 是可能的。但在现实生活中,您不想这样做,因为这样 VM 就可以破坏 VMM,因为它们共享相同的内存。
在现实生活中,如果“无限制客户机”不被允许,您必须在 VM86 分页保护模式下启动客户机,并且如果客户机希望将其设置为保护模式/长模式 (就像操作系统一样),您就必须捕获 VMExit
(当客户机软件尝试执行 LGDT 时会发生此情况),并模拟所有将否则会失败的调用 (LGDT
、LIDT
、CR0
、分页初始化等),以便客户机可以认为其操作成功。
VM Exits (虚拟机退出)
VMExit
可能发生各种原因,可能是因为您在 VMCS
控制/退出字段中指定了 VMExit
原因,或者 VM 实际上进入了关机状态 (例如,ring 0 崩溃),如果 CPU 在实际的非虚拟化状态下运行,这将导致 CPU 重置,或者其他任何原因。执行将在 VMCS
宿主状态保存处 (CS:XIP
) 恢复,您可以读取 VMCS
退出信息 (只读) 来检测退出的原因。
使用 VMRESUME
在退出后恢复 VM。
VMCall (虚拟机调用)
有些系统知道它们运行在虚拟化环境中 (例如,VMWare 驱动程序),它们确实想跳回宿主以交换信息。VMCall
指令会导致 VMExit
到宿主,虚拟化系统可以与宿主交换信息。我的代码也使用 VMCall
退出到宿主。
当然,如果在非 VMX-non-root 环境中执行 VMCall
,则会抛出一个未识别的操作码异常。
控制 MSRs (Control MSRs)
为了简化,我的代码不检查所有功能 (这很可能是它在您的原始 DOS 中无法工作的最可能原因),但在测试它们之前,您应该检查 VMX MSRs 以获取可用功能。Intel 的 3B 附录 G 包含了所有这些 MSR。要加载 MSR,请将其编号放入 RCX 并执行 rdmsr
指令。结果在 RAX 中。
IA32_VMX_BASIC
(0x480): 基本 VMX 信息,包括修订号、VMCS 大小、内存类型等。IA32_VMX_PINBASED_CTLS
(0x481): Pin-based VM 执行控件的允许设置。IA32_VMX_PROCBASED_CTLS
(0x482): Processor-based VM 执行控件的允许设置。IA32_VMX_PROCBASED_CTLS2
(0x48B): Secondary Processor-based VM 执行控件的允许设置。IA32_VMX_EXIT_CTLS
(0x483): VM Exit 控件的允许设置。IA32_VMX_ENTRY_CTLS
(0x484): VM Entry 控件的允许设置。IA32_VMX_MISC MSR
(0x485): 杂项数据的允许设置,例如RDTSC
选项、无限制客户机可用性、活动状态等。IA32_VMX_CR0_FIXED0
(0x486) 和IA32_VMX_CR0_FIXED1
(0x487): 指示在 VMX 操作中 CR0 中允许为 0 或为 1 的位。IA32_VMX_CR4_FIXED0
(0x488) 和IA32_VMX_CR4_FIXED1
(0x489): CR4 的情况相同。IA32_VMX_VMCS_ENUM
(0x48A): VMCS 的枚举辅助。IA32_VMX_EPT_VPID_CAP
(0x48C): 提供有关 VPID 和 EPT 功能的信息。
创建 Hypervisor 病毒 (Creating the Hypervisor Virus)
到目前为止,我们对 VM 的科学都很感兴趣,但程序员的灵魂总是包含一些不祥的感觉,比如杀戮、复仇、欺骗、破解以及所有这些事情。
现在,我们将考虑您是邪恶的 (否则您也不会走到这里),并讨论 Blue Pill。Blue Pill 是一种控制整个操作系统的虚拟机病毒。要做到这一点,您只需将整个内存映射为透明的,然后启动带有 Windows 的 VM,同时配置几乎所有内容来触发 VMExit
。现在,无论 Windows 尝试做什么,都会通过 VMExit
报告给您的 hypervisor,并且使用注入技术,您可以伪造任何响应 - 而且由于 Intel 没有 (已知的) 方法来检测应用程序是否在虚拟化环境中运行,您将永远不会被发现。永远?谁知道呢 - 但如果您被抓到了,我保证我对此一无所知。:)
等等!CR4 的第 13 位在虚拟机内部应该是 1,所以如果该位是 0,您肯定知道您没有被虚拟化!但是如果这个位是 1,您真的知道您是否在一个 VMM 下吗?谁知道。如果有人得到了 Windows Loader 的源代码,并发现一个 mov eax,cr4 - test eax 0x2000 - jz WE_ARE_OWNED
序列在那里,请告诉我。
另一个可能的检测方法是测试 VMCall
,它会引发一个异常,您可以捕获它。但是,有人确保过您吗,没有发生退出,并且您的宿主注入了一个异常让您捕获并假定您是自由的?
另一种可能的方法是测试 CPU 是否不支持无限制客户机,以及您是否在 VM86 模式下启动。如果您看到您正在 VM86 模式下运行,那么您很可能被虚拟化了。但是等等 - 我们是不是忘了 EMM386 exe
?但基于 NT 的操作系统不会加载任何 DOS 驱动程序,所以如果 NT 加载并检查 VM86 并且它已启用,它可能会假定它正在虚拟化。
结论
正如您所见,虚拟化最初并不是一个非常复杂的主题,但要创建一个真正起作用的东西,您需要实现 BIOS、驱动程序等。这就是为什么不是很多程序员真正尝试这样做,这也是为什么只有少数应用程序支持虚拟化。VMware 完成了大量工作,使其 Workstation 能够真正地完成工作。
代码是从我之前的文章中导入的,并组织在 6 个文件中。它相当混乱,但它能工作。
尝试一下,然后告诉我。如果它不起作用,请告诉我并帮助我改进它。无论哪种方式,您能读到这里的事实都值得赞赏。
如果您到现在还没有感到失望,我强烈建议您申请像 VMWare 这样的虚拟化软件公司职位 - 您会做得很好。告诉他们您已经阅读了我的文章,他们也可能会聘用我。:)
祝您好运!(GOOD LUCK!)
参考文献
- 虚拟化博客 - http://virtualizationtechnologyvt.blogspot.com/
- Intel 手册 - http://www.intel.com/products/processor/manuals/
- 真正的保护模式长模式 - https://codeproject.org.cn/KB/system/asm.aspx
- 分页 - http://wiki.osdev.org/Paging
- Blue Pill - http://en.wikipedia.org/wiki/Blue_Pill_(malware)
历史
- 2018-12-31: 新年快乐,将 VMX 代码包含在主 github 项目中
- 2015-01-11: 新年快乐,一些格式调整
- 2012-07-02: 添加了保护模式客户机并修复了一些最小的 bug
- 2011-06-26: 首次发布