@sambodhi
2017-02-17T14:07:31.000000Z
字数 8601
阅读 2455
AMOSSYS Security团队的Adrien Chevalier撰写了一系列文章,讲述了基于虚拟化的安全。
作者在文章中使用了CC协议,InfoQ翻译本文。
本文是关于基于虚拟化的安全和设备保护功能的系列文章的第一篇。这些文章的目的是从技术角度分享对这些特征的更好理解。第一篇文章将介绍从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:
图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版本的以下文件进行了逆向工程:
因此,让我们进入VBS引导过程,从执行winload.efi到ntoskrnl.exe入口点执行。
引导过程可以总结为以下五个基本步骤:
当系统启动时,Bootmgr.efi是第一个执行的Microsoft组件。其完整性和签名已事先由Secure Boot UEFI代码验证。为了能够识别撤销的签名,检查包含已撤销的签名的DBX数据库(截止2016年底,该数据库包含71个黑名单和未知的SHA256哈希值)。在bootmgr.efi代码结束时,执行将传递到winload.efi入口点:OslpMain
/OslMain
。
OslpMain
首先调用OslpPrepareTarget
,这是winload.efi的“核心”函数:它将启动管理程序、内核等。但是首先,它使用OslSetVsmPolicy
启动VBS配置。
OslSetVsmPolicy
首先检查VbsPolicyDisabled
EFI变量值(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 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”类似。
接下来系统将跳转并执行一个参数预先设置为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入口点)中,以便进一步重用。
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变量转储示例
从OslpPrepareTarget
返回,执行流程现在已启动Hyper-V并单独切分VTL0和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”),这将会发生:
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超级调用实际如何工作。