[2021]基于hypervisor的HIPS架构 从0到1 三 (进入虚拟化方式总结) huoji 虚拟机,hypervisor,hips 2021-05-13 727 次浏览 0 次点赞 这部分是我在2021年的某场信息安全作品赛时候时候所写的。考虑到以后可能这个东西比赛完就没价值了.于是放在这里做备份吧. # 进入INTEL虚拟化 为了激活Intel CPU的虚拟化,我们需要按照INTEL SDM[9]说明按部就班进行如下操作 1.申请一个连续内存空间用于存放我们的虚拟机上下文数据 2.申请一个pool内存用于存放我们的单个核心的上下文数据 3.设置Msr Bitmap,设置一些特定msr寄存器的访问权限,以便我们能控制特定寄存器的访问、读写 4.每个核心都同步执行进入虚拟化代码 5.修改CR4寄存器里面的VMX位,从而打开VM功能 6.初始化VMXON区域,用于HOST机存放指令 7.初始化VMCS区域内存、并且填充对应字段,设置虚拟机权限(比如接收什么vmexit事件等). 8.让核心进入虚拟化 以上这些步骤将会在下文进行详细介绍. ## 1 申请共享内存 为了能存放虚拟机上下文数据与单个核心的上下文数据,我们需要申请两块内存用于存放这些数据。 虚拟机上下文数据由于vmexit handle函数会在DPC LEVEL执行,因此我们必须申请一个连续的内存空间,非连续的空间会造成PF中断从而崩溃虚拟机. 而单个CPU核心上下文数据用于存放虚拟机的共享信息,这个是可中断的.所以使用pool内存即可 其中,申请连续内存使用微软的API名字叫做 MmAllocateContiguousMemory 而申请pool内存使用另外一个API名字叫做ExAllocatePoolWithTag。 ![](https://key08.com/usr/uploads/2021/05/1104342689.png) ## 2 设置MSR控制位视图 Patch guard通过rdmsr指令判断msr_lstar是否被修改,虚拟化一大核心是可以控制此类寄存器访问权限,但是用于MSR寄存器非常之多.如果全部拦截会造成性能下降,因此INTEL-VT技术设置了一个msr权限位图,通过修改位图可以实现只拦截指定的msr寄存器的访问.Intel SDM手册24.6.9 MSR-Bitmap Address[9] 对此进行了介绍. 如图2-2-1所示. Intel的MsrBitmap分2区段 低位MSR在0x00000000 - 0x00001FFF里(大小是1024 * 8), 高位在0xC0000000 - 0xC0001FFF里面(大小是1024 * 8),msr bitmap空间需要自己申请一个连续的空间,置位后即可控制对应的msr 我们控制两个低位msr寄存器ia32_feature_control(0x3A)、ia32_debug_control(0x1D9)与两个高位msr寄存器ia32_lstar(0xC0000082)、ia32_efer(0xC0000080) 与VMX功能msr寄存器(禁止客户机访问vmx功能寄存器) ![](https://key08.com/usr/uploads/2021/05/375790746.png) ![](https://key08.com/usr/uploads/2021/05/2409582127.png) ## 3 核心同步执行与捕获上下文 进入虚拟化需要所有核心同步执行对应命令,否则会造成虚拟化不全等异常问题,因此需要使用微软的KeGenericCallDpc函数同步所有核心执行进入虚拟化操作,同时进入虚拟化之前必须保存上下文,在虚拟化完成后guest机会继续从捕获的上下文位置执行 ![](https://key08.com/usr/uploads/2021/05/3189576641.png) ## 4 修正CR4.VMX位 根据INTEL SDM手册ENABLING AND ENTERING VMX OPERATION[9]介绍在进入VMX操作之前, 需要通过设置CR4.VMXE [bit 13] = 1来启用VMX. 然后通过执行VMXON指令进入VMX操作.如果CR4.VMXE = 0执行,但继续执行VMXON指令, VMXON会导致无效操作码异常(#UD).一旦进入VMX操作,就无法清除CR4.VMXE。系统软件通过执行VMXOFF指令退出VMX操作.执行VMXOFF后,可以在VMX操作之外清除CR4.VMXE.(更多详情见附件2 -CR4.VMX位) 因此进入虚拟化之前需要修正CR4.VMX位.此外还需要分别给CR0去除寄存器ia32_vmx_cr0_fixed1、ia32_vmx_cr0_fixed0的值与CR4去除寄存器ia32_vmx_cr4_fixed0、ia32_vmx_cr4_fixed1的值 ![](https://key08.com/usr/uploads/2021/05/1671710336.png) ## 5 设置vmxon区域 根据INTEL SDM手册LIFE CYCLE OF VMM SOFTWARE[9]介绍在打开VMX指令支持之前需要提供一个4KB对齐的内存空间(VMXON region),并且VMXON会一直被使用,直到VMXOFF指令将其关闭.详情可见 附件3 -VMXON区域介绍 VMXON所在的内存区域需要如下特性: * VMXON指针必须对齐4 KB(bit 11:0必须为零) * VMXON指针不得设置超出处理器物理地址宽度的任何位 * 需要写入VMCS revision identifier * 连续内存 为了初始化VMXON区域,我们需要申请一个4kb对齐的连续物理内存.使用微软函数MmAllocateContiguousMemory即可申请. 此外我们需要读取一次msr_vmx_basic寄存器(此寄存器是VM功能寄存器,值为0x480).读出后将vmxon的header参数设置为vmx_basic中的vmcs_revision_identifier值并且我们需要得到申请的内存区域的物理内存地址作为备用. 创建VMXON区域完毕后我们需要使用VMX_ON指令打开VMX支持,我们将使用汇编编写此处指令(为了兼容LLVM编译器).vmxon指令需要RCX寄存器即参数1为vmxon的物理地址. 初始化VMXON区域核心代码如图2-5-1所示,使用VMXON指令打开VMX支持代码如图2-5-2所示 ![](https://key08.com/usr/uploads/2021/05/2349495651.png) ![](https://key08.com/usr/uploads/2021/05/4141018220.png) ## 6 初始化VMCS区域 Vmcs全称叫做”virtual-machine control data structures”,即控制虚拟机状态的内存区域,是虚拟机极其重要的一部分.VMM可以为其支持的每个虚拟机使用不同的VMCS。对于具有多个逻辑处理器的虚拟机,VMM可以为每个虚拟处理器使用不同的VMCS。逻辑处理器将内存中的区域与每个VMCS关联。该区域称为VMCS区域 ![](https://key08.com/usr/uploads/2021/05/1835371320.png) 其中,向VMCS区域写入内容需要用到VMWRITE命令,rcx寄存器作为offset、rdx寄存器作为值汇编核心代码如图 ![](https://key08.com/usr/uploads/2021/05/1566562240.png) 为了成功初始化VMCS区域.我们需要进行如下的流程: ① 申请VMCS区域内存 ② 设置VMCS中MSR控制视图地址为之前的我们申请的地址 ③ 设置VMCS中异常接收,设定接受的异常 ④ 初始化GUEST机的各种必要的寄存器、段信息 ⑤ 初始化HOST机的各种必要的寄存器、段信息 我们将会在下文逐步详细讨论这些步骤. ① 申请VMCS区域内存 VMCS区域要自己申请一个连续的大小为一个page_size即0x1000的内存空间,另外我们是root虚拟机,根据intel sdm手册[9],我们需要设置vm shadow选项为0,详情可见附件4 -vm shadow选项介绍.此外我们需要给vmcs的header字段设置为vmcs_revision_identifier字段 申请VMCS区域内存核心代码如图 ![](https://key08.com/usr/uploads/2021/05/820058870.png) ② 设置VMCS中MSR控制视图地址为之前我们申请的地址 初始化空间完毕后,我们需要填充相关内容,首先是msr控制位图,这将允许我们控制对应的msr寄存器.我们之前已经申请并且设置了msr控制位视图.因此在此处我们只需要设置VMCS区域内的msr_bitmap字段为我们之前设置的msr控制位视图的物理地址.核心代码如图 ![](https://key08.com/usr/uploads/2021/05/2466719724.png) ③ 设置异常位图 我们需要设置异常位图用于让特定的异常产生vmexit事件从而绕过PatchGuard的检测,异常位图介绍如下,INTEL SDM[9]手册24.6.3 Exception Bitmap,详情介绍可看附件5-INTEL异常位图介绍. 异常位图原理与msr权限位图相似,只有在设置接收指定异常信息后这些异常信息才会导致VMEXIT退出,我们设置debug_exception异常位图,用于不被PatchGuard检测。核心代码如图 ![](https://key08.com/usr/uploads/2021/05/1887127504.png) ④ 初始化GUEST机的各种必要的寄存器、段信息 之后我们需要初始化guest机的各种寄存器信息,INTEL SDM[9]手册24.4.1 Guest Register State描述了我们需要设置的信息列表: * 控制寄存器 CR0, CR3, and CR4 * 调试寄存器DR7 * .RSP, RIP与 RFLAGS * CS, SS, DS, ES, FS, GS, LDTR, 与TR 详情可见附件6-guest机信息设置 本作品使用NoirVisor[10]的代码得到上下文,读取各类寄存器并且保存到eax的内存区域中之后将这些信息写入guest机状态 读入代码如图2-6-6所示,写入guest机状态如图 ![](https://key08.com/usr/uploads/2021/05/3682097462.png) ![](https://key08.com/usr/uploads/2021/05/1486503834.png) 至此,guest机信息初始化完毕 ⑤ 初始化HOST机的各种必要的寄存器、段信息 根据INTEL SDM手册,我们需要设置HOST机如下信息 * CR0 CR3 CR4 * RSP RIP * 段寄存器(如CS、SS、DS等) * FS、GS、TR、GDTR、IDTR * EFER、PAT、EFRF_GLOBAL_CTRL 详情可见附件7- HOST机信息设置 其中核心代码如图 ![](https://key08.com/usr/uploads/2021/05/3638016899.png) 此外值得注意的几点: * 由于我们驱动一开始是由软件启动的.cr3在软件内因此host机需要切换CR3到系统的CR3而不是软件的CR3 * 我们需要初始化host机栈,我们host机栈会放入一个上下文结构用来得到guest机上下文,站大小为一个内核栈大小(KERNEL_STACK_SIZE),因此计算方式为: 申请的栈地址 + 栈地址大小 - 上下文结构体大小 而host机的RIP指针将会指向我们的vm入口点(即vmm_entrypoint地址) ## 7 初始化虚拟机控制权限 此部分是设置vm功能寄存器的值来设置我们的虚拟机的权限.虚拟机的权限如要接收什么的退出等行为是受到VMX功能MSR寄存器控制的.因此我们需要设置对应字段,设置方法如下: 激活msr bitmap功能才能使用msr访问控制的功能,他是在Intel SDM[9] Primary Processor-Based VM-Execution Controls结构里.结构如图 ![](https://key08.com/usr/uploads/2021/05/3926879221.png) 设置寄存器代码如图2-7-2所示,只需调整结构中的关键字段为true即可: ![](https://key08.com/usr/uploads/2021/05/98029232.png) 此外我们需要控制我们接受什么样的vmexit事件,将会控制虚拟机接受什么类型的vmexit, windows10上必须接受rdtscp 、xsave_xrstor、invpcid三者的退出,否则会造成#ud错误导致蓝屏 这些操作是在processor_base设置的,关键结构参考Intel SDM[9] 24.6.2 Processor-Based VM-Execution Controls ![](https://key08.com/usr/uploads/2021/05/3090532028.png) 设置完毕后.我们需要确保VMCS也有这些信息,如图 ![](https://key08.com/usr/uploads/2021/05/355646920.png) ## 8 进入虚拟化 以上信息设置完毕后,即可使用vmlanuch指令进入虚拟化,这里作品使用汇编编写,vmlaunch不需要设置参数.代码如图 ![](https://key08.com/usr/uploads/2021/05/2231170475.png) 使用vmlaunch进入虚拟化后,由于我们的host RIP设置成为了vmm_entrypoint地址,因此当发生VMEXIT事件的时候会走到vmm_entrypoint函数的位置,同时我们需要保存上下文到栈中方便我们的vmexit控制: 在rsp上开辟一个0x8的空间用来存放RtlCaptureContext函数所捕获的上下文 ![](https://key08.com/usr/uploads/2021/05/1607628350.png) 之后跳转到我们的vmexit_handler,此函数会负责控制vmexit事件,首先保存一下客户机的信息,然后根据相应的vmexitcode来模拟、修改对应的操作,请注意,这些vmexit事件均是我们之前设置接收后才会产生(CPUID事件除外).如图 ![](https://key08.com/usr/uploads/2021/05/2415392896.png) ![](https://key08.com/usr/uploads/2021/05/1400489370.png) 执行完vmexit后,我们需要恢复堆栈,并且跳转到vm_resume命令处,在执行完vmresume后将会等待下一次vmexit事件产生.如图 ![](https://key08.com/usr/uploads/2021/05/1249195741.png) 至此INTEL虚拟化进入完毕 # AMD AMD的CPU使用了与INTEL的CPU类似却不相同的虚拟化技术全称”AMD Virtualization”.也叫做SVM.两者在架构上大体相同但是实现上有很多差别.为了使AMD的CPU的电脑也能进入虚拟化,需要单独为AMD写一套进入虚拟化的源码。 为了实现SVM,我们需要做如下步骤 ① 设置共享内存 ② 建立shadow page table ③ 设置MSR控制位 ④ 修正EFER.SVM位 ⑤ 设置虚拟机权限 ⑥ 设置VMCB区域 ⑦ 进入虚拟化 下文将会对以上步骤做逐步讲解 ## 1 申请共享内存 与intel的VT类似,AMD的SVM的栈也需要一个连续的内存空间, 如果非连续的内存则会造成崩溃,此外AMD需要准备一个guest_vmcb区域与host_vmcb区域,根据《AMD64 ArchitectureProgrammer’s Manual Volume 2: System Programming》[11] 与《AMD64 Virtualization Codenamed“Pacifica Technology Secure Virtual Machine Architecture Reference Manual》[12], VMCB全称”Virtual machine control block”,是存放guest机与host机控制属性的地方,作用与INTEL的VMCS区域类似.图3-1-1是申请区域并且得到物理地址代码.与VT一样,使用微软的MmAllocateContiguousMemory API申请连续空间 而栈则使用ExAllocatePoolWithTag申请pool内存。 ![](https://key08.com/usr/uploads/2021/05/413945256.png) ## 2 建立shadow page tables 根据AMD-V Nested Paging[13]描述, AMD-V虚拟机需要启用nested page机制,对guest机与host机做一层页表交换,称作shadow page tables,如图 ![](https://key08.com/usr/uploads/2021/05/549431324.png) 为了实现此机制,我们需要先自己建立一个shadow page tables,之后再将NCR3字段设置为我们建立的页表的物理地址,自己的页表空间大小为2MB,会控制512G的内存地址交换,控制的内存量对于常规PC已经足够了.核心代码如图 ![](https://key08.com/usr/uploads/2021/05/1201186035.png) ## 3 设置MSR控制位图 与INTEL-VT一样,AMD64也有类似的位图机制,根据《AMD64 Virtualization Codenamed“Pacifica Technology Secure Virtual Machine Architecture Reference Manual》[12] 中的”MSR Intercepts”描述: MSR权限位图包含四个单独的位向量,每个位向量的大小分别为16 Kbit(2 KB)。每个16 Kbit向量控制来宾访问定义的8K MSR范围。每个MSR都有两个位,这些位定义了guest读取和写入访问权限。这两位的lsb控制对MSR的读取访问,而msb控制对写入的访问。值为1表示该操作已被拦截。四个单独的位向量必须打包在一起,并位于两个连续的物理内存页中。如果MSR_PROT拦截器处于活动状态,则任何尝试读取或写入MSRPM未涵盖的MSR的尝试都会自动导致拦截. 因此,如果要拦截特定的msr寄存器的访问.需要设置对应的位图(msr_bitmap): Msr bitmap关系如下: 000h–7FFh 0000_0000h–0000_1FFFh 800h–FFFh C000_0000h–C000_1FFFh 1000h–17FFh C001_0000h–C001_1FFFh 1800h–1FFFh Reserved 比如IA32_MSR_EFER的msr值是C0000080h, 那么就在800h - FFFh 之间 要控制IA32_MSR_EFER 计算出在bitmap里面的偏移,然后置位为1 公式如下: (IA32_MSR_EFER - C0000000h) * 2(控制位) + 800h * 8(CHAR_BIT) 详情可见附件8-AMD MSR位图 我们设置拦截efer和lstar寄存器的服务 因此给efer和lstar的读、写位分别置1 核心代码如图 ![](https://key08.com/usr/uploads/2021/05/1034168191.png) ## 4 修正EFER.SVME位 根据《AMD64 Virtualization Codenamed“Pacifica Technology Secure Virtual Machine Architecture Reference Manual》[12] 中的”Canonicalization and Consistency Checks” 介绍,如果efer.svme位为0 则在启动svm的时候会抛出 VMEXIT_INVALID异常, 因此可以理解为,AMD的svm功能有一个开关,在efer寄存器上,efer寄存器结构如图,其中第12位即svme控制的SVM开关,EFER结构如图 ![](https://key08.com/usr/uploads/2021/05/1995013864.png) 激活开关代码如图 ![](https://key08.com/usr/uploads/2021/05/3387346782.png) ## 5 设置虚拟机控制权限 与intel-VT相似,我们可以控制虚拟机的权限,但与intel不同的是intel是在msr vmx功能寄存器里完成的权限控制操作,而SVM是在guest VMCB区域完成的权限控制操作,相比INTEL比较简化易用. 根据《AMD64 Virtualization Codenamed“Pacifica Technology Secure Virtual Machine Architecture Reference Manual》[12] 中的”VMCB Layout, Control Area” 介绍,结构如图 ![](https://key08.com/usr/uploads/2021/05/925902713.png) 我们需要拦截msr访问(可选)、cpuid访问(必选)、vmrun、vmcall访问(必选),因此代码如图 ![](https://key08.com/usr/uploads/2021/05/2592244135.png) 设置完中断后,我们需要Ncr3设置为我们前面设置的Shadow Page Table的物理地址以及msrpmbase设置为我们的msr bitmap的物理地址 由于我们是top1虚拟机 所以guestAsid设置为1.核心代码如图 ![](https://key08.com/usr/uploads/2021/05/2654788276.png) 之后我们要让我们的虚拟机设置拦截debug异常,因此需要设置vmcb.exception字段 ![](https://key08.com/usr/uploads/2021/05/3984553111.png) ## 6 设置GUEST.VMCB状态 根据《AMD64 Virtualization Codenamed“Pacifica Technology Secure Virtual Machine Architecture Reference Manual》[12] 中的”VMRUN Instruction” 介绍,在启动SVM之前必须设置客户机如下字段信息,结构如图3-6-1所示,分别设置CS、RIP、RFLAGS、RAX、SS、RSP、CR0/CR2/CR3/CR4寄存器 ![](https://key08.com/usr/uploads/2021/05/2637466228.png) 对应代码如图3-6-2所示 ![](https://key08.com/usr/uploads/2021/05/1680458565.png) 这里有需要注意的地方,此处我们没有手动切换CR3到系统CR3,因此我们待会需要手动切换CR3到系统的CR3. 最后的最后,根据手册,我们需要设置一个放置host机状态的物理地址, 因此设置host_state的物理地址到amd64_hsave_pa寄存器,同时我们需要接管系统的kisystemcall64,AMD可以直接设置guest的Lstar值为自己指定的值.因此对比INTEL,我们可以直接在初始化VMCB的时候设置guest机的Lstar为我们的Lstar地址 ## 7 进入虚拟化 VMCB设置完毕后,我们可以进入虚拟化了,进入虚拟化之前,需要设置host rsp也就是主机栈,: 主机栈的结构此处参考了SimpleSvm[14]的结构,如图所示 ![](https://key08.com/usr/uploads/2021/05/1132505174.png) 启动虚拟化前,我们需要把我们申请的VM stack作为参数传入我们的启动参数,代码如图所示 ![](https://key08.com/usr/uploads/2021/05/3131862050.png) 在launch_svm函数内,我们做的第一件事是切换CR3为系统CR3而不是某个软件的CR3,因此我们需要用r8寄存器作为临时寄存器,将系统CR3的值放入R8,再将其赋值给当前CR3,代码如图所示 ![](https://key08.com/usr/uploads/2021/05/1007752110.png) 之后,将VMstack地址(也就是参数1也就是RCX也就是我们之前给的host rsp地址)设置为栈,使用mov rsp,栈地址命令切换栈,如图 ![](https://key08.com/usr/uploads/2021/05/3227195905.png) 此时的栈结构如图3-7-5所示,0x0访问guest的vmcb地址,0x8访问到host_vmcb地址(因为ptr占8字节) ![](https://key08.com/usr/uploads/2021/05/2807145624.png) 由于进入虚拟化的命令vmrun需要第一个参数为rax,因此将栈地址移动到rax,如图 ![](https://key08.com/usr/uploads/2021/05/2950980449.png) 之后使用vmload命令加载guest_vmcb里面的数据后就可以vmrun rax运行虚拟机等待vmexit事件 ![](https://key08.com/usr/uploads/2021/05/1230100396.png) Vmrun成功后,guest机的信息就会被恢复到我们设置的DPC CALLBACK上,如图所示的恢复位置 ![](https://key08.com/usr/uploads/2021/05/1929343716.png) Vmrun不会停止,直到触发vmexit事件(接受什么事件由我们前面决定). 一旦有vmexit事件发生,我们需要第一时间将各个寄存器信息push到栈上方便我们访问与控制 PushAQ函数代码如图3-7-9所示,压入的栈结构如图3-7-10所示 ![](https://key08.com/usr/uploads/2021/05/49646719.png) ![](https://key08.com/usr/uploads/2021/05/1116905391.png) 一个寄存器长度为8 我们一共push 15个寄存器到栈上 也就是8 * 15 要访问我们的原来的栈公式就是RSP + 8 * 16 = 原来的栈,因此我们把原来的栈的数据当做参数1,guest机寄存器参数放到参数2,原始栈放到参数3作为备用,如图3-7-11所示 ![](https://key08.com/usr/uploads/2021/05/1465988727.png) 备用后 即可跳转到我们的svm_vmexit_hanlder代码: 与VT一样,一开始保存各种信息.值得注意的是我们现在的原始栈寄存器是RAX是不对的(因为前面VMRUN用掉了RAX,现在RAX存放的数据是我们的guest_vmcb数据),因此我们需要跳转寄存器里面的Rax的值为guest.vmcb里面的rax数据(真实guest机的rax数据),之后与VT一样 等待各类退出事件.代码如图3-7-12与3-7-13所示 ![](https://key08.com/usr/uploads/2021/05/1649867997.png) ![](https://key08.com/usr/uploads/2021/05/2462447830.png) 如要退出我们返回一个true代表虚拟机即将退出,退出我们要恢复栈、寄存器信息以及设置CR3从系统到软件,在汇编代码处一旦有退出我们跳转到退出处理集,否则我们继续循环VMRUN.代码如图3-7-14与3-7-15所示 ![](https://key08.com/usr/uploads/2021/05/530327182.png) ![](https://key08.com/usr/uploads/2021/05/465013214.png) 退出处理集负责恢复原始栈、恢复rcx寄存器,跳转到客户机的RIP上.即可完成退出 至此AMD虚拟化进入完毕 本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 0
还不快抢沙发