基于虚拟化的安全性 - 第 1 篇:引导过程

本文是关于基于虚拟化的安全和设备保护功能的系列文章的第一篇。这些文章的目的是从技术角度分享对这些特征的更好理解。第一篇文章将介绍从Windows引导加载程序到VTL0启动的系统引导过程。

基于虚拟化的安全

基于虚拟化的安全(Virtualization Based Security,VBS)是Microsoft Windows的主要安全特色,随Windows 10和Windows Server 2016一起提供。例如,DeviceGuard和CredentialGuard都依赖它。对于那些不知道Windows 10的这两个关键的安全创新,DeviceGuard允许系统阻止任何东西,包括受信任的应用。对于CredentialGuard,它允许系统隔离lsass.exe进程,以阻止密码收集器(如Mimikatz)的内存读取尝试。

这个新功能的主要思想是使用硬件虚拟化技术,如Intel VT-X,以便在两个虚拟机(VM)之间提供强大的隔离,并且,在将来可能会更多。这些技术允许虚拟机管理器(Virtual Machine Manager,VMM)使用扩展页表(Extended Page Tables,EPT)在物理页上设置不同的权限。换句话说,VM可以在其页表项(Page Table Entry,PTE)中设置物理页可写(+W),并且VMM可以通过在其EPT中设置适当的访问权限来静默地授权或阻止这一点。

基于虚拟化的安全性依赖于Hyper-V技术,这将产生不同虚拟信任级别(Virtual Trust Levels,VTL)的虚拟机。Hyper-V的构成,包括一个管理程序hypervisor以及运行着任何操作系统的VM,包括物理机的操作系统Windows本身都被视为一个组件。也就是说,Hyper-V的架构是CPU之上级别的,然后才是它的核心思想——VTL的分层,每一层的权限严格限制和区分。Hyper-V信任它并接受管理订单,例如控制其他VM。其他VM可以是“开明的”,如果是这样的话,那么就向Hyper-V发送受限消息以用于它们自己的管理。

VTL被编号,编号较高的是最可信的。现在,有两个VTL:

  • VTL0,这是正常的环境,基本上都在标准的Windows操作系统。
  • VTL1,它是安全的环境,包含一个最小化的内核和安全的应用程序,称为trustlet。

图1:基于虚拟化的安全性概述

CredentialGuard安全功能利用此技术隔离VTL1信任单(lsaiso.exe,上图中的“隔离LSA”)中的关键lsass.exe进程,甚至使VTL0内核不能访问其内存。只有消息可以从VTL0转发到隔离的进程,有效地阻止内存密码和散列收集器(如Mimikatz)。

DeviceGuard安全功能允许在VTL0内核地址空间实现W^X内存缓解(物理页不能同时处于可执行和可写的状态),并接受包含授权代码签名的策略。如果VTL0内核想要使物理页可执行,它必须要求VTL1进行改变(图中的“HVCI”),这将根据其策略检查签名。对于usermode代码,目前还没有完成,VTL0内核仅仅要求签名验证。策略在引导启动期间加载,并且不能在之后修改,这强制用户重新启动以加载新策略。

策略也可以写死:在这种情况下,在UEFI变量中设置授权签名者,并将针对这些签名者检查新策略。UEFI变量包括Setup和Boot标志设置,这意味着它们不能在启动后访问或修改。为了清除这些变量,本地用户必须使用访客账号的Microsoft EFI引导加载程序重新启动,这将在用户交互(通过按键)后删除它们。为了清除这些变量,本地用户必须使用自定义的Microsoft EFI引导加载程序重新启动,这将在用户交互(通过按键)后删除它们。

因此,VBS主要依赖SecureBoot:必须检查引导加载程序的完整性,因为它负责加载策略、Hyper-V、VTL1等等。

如果您对详细的设备保护(Device Guard)概述感兴趣,可以阅读MSDN的这篇文章

您还可以看看2015年、2016年黑客大会上Alex Ionescu和Rafal Wojtczuk的演示,在这项工作中,给我们提供了很多帮助。

您还可以阅读Hyper-V Internals博客的两篇文章,了解Hyper-V更多相关技术信息:

"Hyper-V debugging for beginners" (also covers Hyper-V startup);

"Hyper-V internals"。

“初学者的Hyper-V调试”(也包括Hyper-V启动); “Hyper-V内部”。

在本文中,我们将介绍从Windows引导加载程序到VTL0启动的系统引导过程。为了分析VBS在引导过程中如何初始化,我们对Windows 10 1607版本的以下文件进行了逆向工程:

  • bootmgr.efi:EFI引导加载程序(它的一小部分);
  • winload.efi: EFI Windows加载器;
  • hvix.exe: Hyper-V(真的很小);
  • ntoskrnl.exe: NTOS内核;
  • securekernel.exe: 安全内核;
  • ci.dll: 检测VTL0代码完整性;
  • skci.dll: 检测VTL1代码完整性。

因此,让我们进入VBS引导过程,从执行winload.efi到ntoskrnl.exe入口点执行。

引导过程

引导过程可以总结为以下五个基本步骤:

  • bootmgr.efi是要加载的第一个组件。它由SecureBoot验证,然后执行;
  • bootmgr.efi加载并检查winload.efi,主要的Windows加载器;
  • winload加载并检查VBS配置;
  • winload加载并检查Hyper-V和VTL0/VTL1内核组件;
  • winload退出EFI模式,启动Hyper-V。

Bootmgr.efi

当系统启动时,Bootmgr.efi是第一个执行的Microsoft组件。其完整性和签名已事先由Secure Boot UEFI代码验证。为了能够识别撤销的签名,检查包含已撤销的签名的DBX数据库(截止2016年底,该数据库包含71个黑名单和未知的SHA256哈希值)。在bootmgr.efi代码结束时,执行将传递到winload.efi入口点:OslpMain/OslMain。

OslpMain首先调用OslpPrepareTarget,这是winload.efi的“核心”函数:它将启动管理程序、内核等。但是首先,它使用OslSetVsmPolicy启动VBS配置。

VBS策略负载

OslSetVsmPolicy首先检查VbsPolicyDisabledEFI变量值(Microsoft命名空间的值,请参见下文)。如果设置,则清除此变量(设置为0),这意味着不会加载Credential Guard配置。因此,此EFI变量允许仅禁用单引导的凭据保护(并且可以通过来自VTL0 ring3的特权调用设置)。如果不存在,则从SYSTEM注册表配置单元加载配置,并对BlVsmSetSystemPolicy执行调用,BlVsmSetSystemPolicy将根据需要读取和更新VbsPolicyEFI变量。相应的值然后存储在BlVsmpSystemPolicyglobal变量中。如果启用UEFI锁,则设置此EFI变量,并且不能由winload.efi禁用(仅仅只是没有删除它的代码,必须使用自定义EFI代码)。

函数OslpPrepareTarget也调用OslpProcessSIPolicy(它被调用两次,第一次直接调用,然后从函数OslInitializeCodeIntegrity调用)。OslpProcessSIPolicy使用三个EFI变量“池”检查SI策略签名。每个池包含三个EFI变量,第一个包含策略,第二个包含其版本,第三个包含授权的策略更新签名者。例如,对于C:\Windows\System32\CodeIntegrity\SIPolicy.p7b,变量是Si,SiPolicyVersion和SiPolicyUpdateSigners。如果设置了“版本”和“更新签名者”变量,系统将强制执行SI策略签名:它必须存在并且正确签名,否则引导过程将失败。验证本身由BlSiPolicyIsSignedPolicyRequired函数执行。

三个策略和相关联的变量总结如下:

Policy fileEFI variables
C:\Windows\System32\CodeIntegrity\

SIPolicy.p7b

Si
\EFI\Microsoft\Boot\
SIPolicy.p7b
SiPolicyVersionSiPolicyUpdateSigners
C:\Windows\System32\CodeIntegrity\

RevokeSiPolicy.p7b

RevokeSiRevokeSiPolicyVersionRevokeSiPolicyUpdateSigners
\EFI\Microsoft\Boot\
SkuSiPolicy.p7b
SkuSiSkuSiPolicyVersionSkuSiPolicyUpdateSigners

表1:SI政策和相应的EFI变量

我们没有确定“revokeSiPolicy”和“skuSiPolicy”的目的,但它们似乎与常规的“SiPolicy”类似。

Hyper-V和内核组件负载

接下来系统将跳转并执行一个参数预先设置为0的OslArchHypervisorSetup函数。第一次,它将启动Hyper-V(加载hvloader.efi并通过HvlpLaunchHvLoader执行它)。然后通过OslInitializeCodeIntegrity检查安全引导设置。

OslpPrepareTarget然后加载NTOS内核(ntoskrnl.exe),并使用OslpLoadAllModules函数加载hal.dll和mcupdate.dll模块。它们的签名验证在加载过程中执行(在ImgpLoadPEImage和OslLoadImage中)。然后通过OslVsmProvisionLKey和OslVsmProvisionIdk函数从EFI变量加载“本地密钥”和“标识密钥”。

此时,NTOS内核开始初始化但还未启动。然后使用“0”参数调用OslVsmSetup(与OslArchHypervisorSetup相同:它需要一个“step”参数),它首先检查Hyper-V是否已经启动,然后初始化OslVsmLoaderBlock全局变量(在初始化期间赋值)。然后,OslVsmSetup通过OslpVsmLoadModules函数(OslLoadImage再次用于检查其签名)加载安全内核(securekernel.exe)及其依赖(skci.dll)。然后将EFI变量OsLoaderIndications的第一位设置为1。

最后,再次调用OslVsmSetupfunction,但此时该函数的参数为1。这将触发几个OslVsmLoaderBlock参数的初始化。

当函数OslpPrepareTarget返回时,VBS参数已验证,并且加载NTOS和安全内核。它们的入口点地址已存储在OslpVsmSystemStartup和OslEntryPoint全局变量(分别为securekernel.exe和ntoskrnl.exe入口点)中,以便进一步重用。

Microsoft EFI变量

VBS EFI变量(以及更常见的微软变量)属于命名空间:{0x77FA9ABD, 0x0359, 0x4D32, 0xBD, 0x60, 0x28, 0xF4, 0xE7, 0x8F, 0x78, 0x4B}。这些变量的“Boot”和“Setup”属性已设置,因此不允许在EFI引导阶段后访问或修改它们。

然而,可以转储它们以便在分析期间帮助逆向。与VBS相关的EFI变量及其相应的用法总结如下: >

EFI variable name

Usage

VbsPolicyVBS settings
VbsPolicyDisabledDisable “magic” variable
VsmLocalKeyProtector 
VsmLocalKey 
VsmLocalKey2 
WindowsBootChainSvn 
RevocationList 
Kernel_Lsa_Cfg_Flags_Cleared 
VsmIdkHash 
SiFirst CodeIntegrity policy
SiPolicyUpdateSignersUpdate signers
SiPolicyVersionVersion
RevokeSiSecond CodeIntegrity policy
RevokeSiPolicyVersionUpdate signers
RevokeSiPolicyUpdateSignersVersion
SkuSiThird CodeIntegrity policy
SkuSiPolicyUpdateSignersUpdate signers
SkuSiPolicyVersionVersion

表2:Microsoft命名空间EFI变量列表

为了转储这些变量的内容,可以关闭安全启动和使用一个简单的EFI自定义启动加载程序(gnu-efi和VisualStudio工作相当完美)。下面给出一些变量转储作为示例:

Name

Value

CurrentActivePolicy

0

CurrentPolicy

2

BootDebugPolicyApplied

0x2a

WindowsBootChainSvn

0x00000001

VsmLocalKey24c 4b 45 89 50 4b 47 31 96 00 00 00 01 00 01 00 2c 00 00 00 01 00 01 00 01 00 00 00 b2 21 ae a7 12 86 07 a8 15 28 d5 49 33 ac 09 ac 93 c8 e0 12 61 8f 10 d6 4c 68 d1 5a 5f 00 90 0c 5a 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 50 6c 1a 00 00 00 00 00 00 00 00 00 00 00 00 00 c2 0f 94 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 03 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00

表3:EFI变量转储示例

Hyper-V和安全内核的启动

从OslpPrepareTarget返回,执行流程现在已启动Hyper-V并单独切分VTL0和VTL1空间。这个过程可以总结为以下几点:

  • winload在“第一个/最底层”的Hyper-V虚拟机里启动;
  • winload唤醒securekernel;
  • securekernel初始化,并根据写死的策略,向Hyper-V申请内存保护;
  • securekernel激活VTL1层;
  • Hyper-V允许securekernel激活VTL1,并返回ShvlpVtl1Entry函数;
  • 通过ShvlpVtlReturn函数,Hyper-V把VTL1层的securekernel状态告诉VTL0层的winload(自从它唤醒securekernel之后等待了很久);
  • 在得到securekernel已经完成安全检查(启用内存保护等动作)的消息后,winload才开始唤醒ntoskrnl。

这些是在securekernel初始化之前和之后的状态(VTL0 VM是蓝色块,VTL1是绿色块,而Hyper-V是橙色块):

图2:securekernel初始化之前和之后的状态

当遵循执行流程时,OslpMain通过调用OslFwpKernelSetupPhase1退出EFI模式,并通过步骤“1”的OslArchHypervisorSetup启动Hyper-V。Hvix64通过将RSP保存到HvlpSavedRsp全局中并通过将HvlpReturnFromHypervisor传递给hvix64来启动。当命中HvlpReturnFromHypervisor时,使用cpuid指令检查启动,并恢复RSP。我们实际上是在第一个虚拟机,这将很快成为VTL1。

OslVsmSetup最后一次被调用(步骤“2”),这将会发生:

  • 检查VBS参数;
  • 验证Hyper-V是否正确运行;
  • 修改OslVsmLoaderBlock设置;
  • 在同一块中复制OslVsmLKeyArray(Local Key)和OslVsmIdk(“idk”用于“Identification Key”);
  • 调用已存储到OslpVsmSystemStartup全局中的安全内核入口点,指定OslVsmLoaderBlock及其大小作为参数。

然后,安全内核将执行初始化,安全内核通过SkmiProtectSecureKernelPages这一特殊函数的调用来申请独占内存(以确保安全性),同时安全内核还注册了Hyper-V的事件监听例程(HyperGuard及其Skpg *前缀例程)。根据特殊模块寄存器的说明文献对以下MSR的操作,由函数SkpgxInterceptMsr拦截和处理:

  • 0x1B(APIC_BASE);
  • 0x1004(?);
  • 0x1005(?);
  • 0x1006(?);
  • 0x100C(?);
  • 0xC0000080(EFER);
  • 0xC0000081(STAR);
  • 0xC0000082(LSTAR);
  • 0xC0000083(CSTAR);
  • 0xC0000084(FMASK);
  • 0xC0000103(TSC_AUX);
  • 0x174(SEP_SEL);
  • 0x175(SEP_RSP);
  • 0x176(SEP_RIP);
  • 0x1a0(MISC_ENABLE)。

我们的假设是这些处理程序设置为捕获VTL0中的CPL转换和阻止关键的MSR修改。还有两个其他例程,SkpgxInterceptRegisters和SkpgInterceptRepHypercall。一种可能性是,第一个可能能够拦截CRXXX寄存器操作(例如,CR4写入SMEP禁用),第二个可以拦截未授权的超级调用(这仅仅是一个假设)。

关于HyperGuard,似乎VTL0完整性检查由SkpgVerifyExtents执行。这个特定的函数由SkpgHyperguardRuntime调用,它可以被定期执行(使用SkpgSetTimer)。HyperGuard的执行和回调函数被复制到了SkpgContext的全局函数中(由SkpgAllocateContext和SkpgInitializeContext初始化)。

请记住,前面的讨论只是假设,可能是错误的,因为我们现在没有在VTL1 HyperGuard/PatchGuard例程花时间研究。

在其初始化结束时,安全内核将最终执行两个超级调用:

  • 0x0F,进入ShvlEnableVpVtl,指定一个ShvlpVtl1Entry函数指针;
  • 0x12,进入ShvlpVtlCall,它不在代码的任何其他部分使用,并且使用它自己的超级调用trampoline(我们将在下一篇文章中给出关于这些超级调用trampolines的更多细节)。

ShvlpVtl1Entry结束了SkpPrepareForReturnToNormalMode,似乎这个过程实际上使Hyper-V启用VTL1和VTL0,返回到ShvlpVtl1Entry,然后返回到winload.efi到VTL0上下文。

最后,当回到winload.efi主程序时,它将通过OslArchTransferToKernel执行NTOS入口点,它使用OslEntryPoint全局调用其入口点。

然后执行下一个操作,就像Windows在正常环境中启动一样,只是NTOS内核现在知道与VBS相关的组件(如Device Guard)。

结论

基于虚拟化的安全性是Microsoft Windows 10安全功能的关键组成部分。通过覆盖VBS的安全内核初始化,我们希望本文将给予更多资源,以便更深入地了解这些功能。

在第二篇中,我们将介绍VTL0和VTL1之间的内核通信以及Hyper-V超级调用实际如何工作。

感谢魏星对本文的审校。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值