[关闭]
@sambodhi 2017-02-17T14:07:31.000000Z 字数 8601 阅读 2478

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

AMOSSYS Security团队的Adrien Chevalier撰写了一系列文章,讲述了基于虚拟化的安全。

作者在文章中使用了CC协议,InfoQ翻译本文。

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

基于虚拟化的安全

基于虚拟化的安全(Virtualization Based Security,VBS)是Microsoft Windows的主要安全特色,随Windows 10和Windows Server 2016一起提供。例如,DeviceGuardCredentialGuard都依赖它。对于那些不知道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:

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

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

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

策略也可以写死:在这种情况下,在UEFI变量中设置授权签名者,并将针对这些签名者检查新策略。UEFI变量包括SetupBoot标志设置,这意味着它们不能在启动后访问或修改。为了清除这些变量,本地用户必须使用访客账号的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版本的以下文件进行了逆向工程:

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

引导过程

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

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,变量是SiSiPolicyVersionSiPolicyUpdateSigners。如果设置了“版本”和“更新签名者”变量,系统将强制执行SI策略签名:它必须存在并且正确签名,否则引导过程将失败。验证本身由BlSiPolicyIsSignedPolicyRequired函数执行。

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

Policy file EFI 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.dllmcupdate.dll模块。它们的签名验证在加载过程中执行(在ImgpLoadPEImageOslLoadImage中)。然后通过OslVsmProvisionLKeyOslVsmProvisionIdk函数从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和安全内核。它们的入口点地址已存储在OslpVsmSystemStartupOslEntryPoint全局变量(分别为securekernel.exentoskrnl.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
VbsPolicy VBS settings
VbsPolicyDisabled Disable “magic” variable
VsmLocalKeyProtector
VsmLocalKey
VsmLocalKey2
WindowsBootChainSvn
RevocationList
Kernel_Lsa_Cfg_Flags_Cleared
VsmIdkHash
Si First CodeIntegrity policy
SiPolicyUpdateSigners Update signers
SiPolicyVersion Version
RevokeSi Second CodeIntegrity policy
RevokeSiPolicyVersion Update signers
RevokeSiPolicyUpdateSigners Version
SkuSi Third CodeIntegrity policy
SkuSiPolicyUpdateSigners Update signers
SkuSiPolicyVersion Version
表2:Microsoft命名空间EFI变量列表

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

Name Value
CurrentActivePolicy 0
CurrentPolicy 2
BootDebugPolicyApplied 0x2a
WindowsBootChainSvn 0x00000001
VsmLocalKey2 4c 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空间。这个过程可以总结为以下几点:

这些是在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”),这将会发生:

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

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

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

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

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

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超级调用实际如何工作。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注