问题
就像常规程序一样,内核容易受到其中执行的恶意代码的影响。无论是操作系统本身存在漏洞,还是更常见的是第三方驱动程序(可以访问内核空间),最终结果都是可以在内核中执行不需要的代码。
当然,期望恶意代码永远不会在内核中执行的情况是不现实的。漏洞无处不在,而且永远都会存在。为了解决这个问题,Microsoft 提出了在其基于虚拟化的安全(VBS) 机制堆栈中添加一项新技术的想法。这项技术被称为内核数据保护(KDP),它通过利用虚拟化技术增加了一个新的内核边界,这样内存中的某些部分甚至内核本身都无法访问,正如我们稍后会发现的那样,就静态 KDP担心这不会阻止代码执行,也不是真的打算这样做。
微软有两个他们希望解决的任务。第一个是来自 CPU 的基于软件的漏洞利用。第二个是来自硬件的漏洞利用,例如通过 I/O 设备(通过设备驱动程序)和网络连接设备。
解决方案
解决方案是结合使用基于虚拟化的安全技术以及基于硬件的功能和一组新的 API,“KDP”,以便可以将内核内存的某些部分标记为只读,而不仅仅是只读,但从较低的特权级别也完全不可读/不可写。
有两种主要技术与 KDP API 结合使用来解决这个问题。第一个是二级地址转换(SLAT),它试图阻止来自 CPU 的漏洞利用(读/写原语)。第二种技术是输入输出内存管理单元(IOMMU),它试图防止漏洞(读/写原语)通过易受攻击的设备驱动程序进入。
实施
为了实施这项技术,一切都从 Hyper-V 管理程序开始。Hyper-V 使用称为 SLAT 和 IOMMU 的两种技术来有效地“隔离”内存部分,然后只能通过非常高的特权代码(VTL1 中的代码)访问这些部分。由于有相当多的技术在发展,它有助于在图表中查看这一点,此时我们可以查看每个单独的部分,以便弄清楚它们如何组合在一起以构成正常运行的 KDP API 的要求。
在经典的计算机模型中,内核直接与硬件通信。但是,在现代版本的 Windows 中,情况并非如此。相反,内核与位于硬件和内核之间的 Hyper-V 管理程序进行通信,这是一个基于软件的管理程序,最终允许所有这些其他虚拟安全技术存在和运行。
在这个模型中,如果内核想要访问一部分内存而不是直接通过硬件访问它,它会告诉管理程序它希望访问内存区域,此时管理程序将去 SLAT 检索该内存.
您会注意到内存分为两个区域,“普通内存”和“安全内存”,使用 SLAT 之类的技术可以让其以您期望的方式运行。通过将某个内存区域标记为“安全内存”,您可以将内容放入该“安全内存”存储桶中,然后使它们无法从“普通内存”访问,并且只能通过从“VTL1”调用管理程序来访问。这是内核数据保护的一个关键功能。
我们将首先了解 Hyper-V 管理程序如何允许 Windows 创建虚拟安全模式(VSM) 的概念,这是使 KDP 工作的关键技术。
虚拟安全模式
Windows 10 通过利用 Hyper-V 管理程序和 SLAT 引入了 VSM 的这一概念。VSM 创建一组称为虚拟信任级别(VTL) 的模式。这种软件级架构在 Windows 中创建了一个新的安全边界,以防止一个 VTL 中的进程访问另一个 VTL 中的进程的内存。这种额外隔离的总体目标是缓解(但不是预防)数据损坏内核漏洞。另一个目标是保护密码哈希和 Kerberos 密钥等资产。
如上图所示,有两个 VTL:VTL0 和 VTL1。这些也可以称为“VSM 正常模式”和“VSM 安全模式”,但出于我们的目的,我们将它们称为 VTL0 和 VTL1。
在启动时,管理程序使用 SLAT 为每个 VTL 分配内存。当系统运行时,这种情况会动态地继续,从而保护安全存储器 (VTL1) 免受普通存储器 (VTL0) 的影响,同样保护 VTL1 中的代码免受 VTL0 中的代码影响。因为 VTL 是分层的,所以在 VTL1 中运行的任何代码都被认为比在 VTL0 中运行的代码具有更高的特权,因此,VTL1 代码具有极高的访问级别,实际上是唯一可以对管理程序进行超级调用的 VTL。
二级地址翻译
随着 VSM 的解释,我们需要了解 SLAT 是如何工作的。正如我们已经介绍过的,在启动时,管理程序使用 SLAT 为两个 VTL 分配它们自己的内存部分(称为“正常内存”和“安全内存”),此内存映射在系统运行时动态管理。
当启用 SLAT 时,处理器将称为访客虚拟地址 (GVA) 的初始虚拟地址转换为称为访客物理地址 (GPA) 的中间物理地址。这种转换仍然由由 NT 内核管理的 CR3 控制寄存器寻址的页表管理。转换的最终结果为处理器生成 GPA,并在访客页面表 (GPT) 中指定访问保护。只有在内核模式下运行的软件才能与页表交互,例如 rootkit 可以修改中间物理页的保护。
管理程序帮助 CPU 使用嵌套页表(NPT) 转换 GPA。在非 SLAT 系统上,当转换后备缓冲区(TLB) 中不存在虚拟地址时,处理器需要查阅层次结构中的所有页表以重建最终物理地址。虚拟地址分为四个部分。每个部分代表层次结构页表的索引。初始Page-Map-Level-4的物理地址(PML4) 表由 CR3 寄存器指定。这就是为什么处理器总是能够转换地址并获得层次结构中下一个表的下一个物理地址的原因。重要的是要知道,在层次结构的每个页表条目中,NT 内核通过一组属性指定页保护。只有在每个页表条目中指定的保护总和允许时,才能访问最终的物理地址。
在启用 SLAT 的系统上,客户的 CR3 寄存器中指定的中间物理地址需要转换为真正的系统物理地址 (SPA)。在这种情况下,管理程序将表示当前正在执行的 VM 的活动虚拟机控制块 (VMCB) 的 nCR3 字段配置为 NPT 的物理地址。NPT 的构建方式与标准页表类似,因此处理器需要扫描整个层次结构以找到正确的物理地址。在图中,我们会注意到我们有“n”和“g”,它们分别表示 NPT 和 GPT。其中 NPT 由管理程序管理,GPT 由 NT 内核管理。
如上所示,GVA 到 SPA 的最终转换经过两种转换类型:GVA 到 GPA,由 NT 内核配置,GPA 到 SPA,由管理程序配置。
NPT 条目指定了多个访问保护属性,这允许管理程序进一步保护 SPA(NPT 只能由管理程序本身访问)。当处理器尝试读取、写入或运行 NPT 不允许访问的地址时,会引发 NPT 违规并生成 VM 退出。
输入输出内存管理单元
然而,SLAT 只解决了 CPU 问题,它没有解决硬件问题,即基于设备的攻击,例如直接内存访问攻击(DMA 攻击)。为了解决这个问题,Windows 利用了 IOMMU,它是一种内存管理单元 (MMU)。IOMMU 将支持 DMA 的I/O 总线连接到主存储器。它的目的是通过页表将设备可见的虚拟地址映射到内存中的物理地址,这样当设备想要访问内存时,它必须通过 IOMMU 才能访问它。该页表存储内存地址以及与这些内存地址相关的访问权限。
设备驱动程序位于 VTL0 中,但它们需要对连接的 I/O 设备进行编程,以告诉它们要访问哪些内存区域。但是,这种模型导致的问题是设备驱动程序可以告诉设备访问 VTL1 中的内存。通过将 IOMMU 放在设备和设备驱动程序之间,它可以限制设备可以访问的内存。
由于管理程序负责 IOMMU 的编程,并且管理程序只能从 VTL1 进行管理,这防止了攻击者可以使用易受攻击的驱动程序代码执行 DMA 攻击并访问它不应访问的内存的情况。
静态 KDP 实现
SLAT 是 KDP 用来运行的主要保护原理。动态和静态 KDP 实现类似,都由安全内核 (VTL1) 管理。安全内核是唯一能够将超级调用发送ModifyVtlProtectionMask
到管理程序的实体。此调用修改映射到较低 VTL0 中的物理页面的 SLAT 访问保护。
对于静态 KDP,NT 内核 (VTL0) 会验证驱动程序不是会话驱动程序或映射到大页面。如果这些条件之一为真,或者如果该部分是可丢弃部分,则无法应用静态 KDP。如果调用 API 的实体MmProtectDriverSection
没有请求可卸载目标映像,则 NT 内核执行第一次调用安全内核,该调用固定与驱动程序关联的正常地址范围 (NAR)。此固定操作可防止驱动程序的地址空间被重用,从而使驱动程序不可卸载。
然后,NT 内核将属于该段的所有页面带入内存,并将它们设为私有。然后,这些页面在叶页表条目 (PTE) 结构中被标记为只读。在这个阶段,NT 内核终于可以调用 Secure 内核,通过 SLAT 来保护底层物理页面。安全内核分两个阶段应用此保护:
- 通过在数据库中添加适当的普通表地址并更新属于 VTL1 的底层安全 PFN 来注册属于该部分的所有物理页面并将它们标记为“由 VTL0 拥有”。这允许安全内核跟踪仍然属于 NT 内核的物理页面。
- 对 VTL0 SLAT 表应用只读保护。
目标图像的部分现在受到保护,VTL0 中的任何实体都无法写入属于该部分的任何页面。
尽管微软似乎将此作为保护代码执行的营销手段,但实际上它根本没有这样做。正如它的名字所暗示的那样,它是“数据保护”,这个特性所做的就是保护数据不被从一个内核访问到另一个内核(NT->Secure)。这样做的全部目的是,如果您设法在 NT 内核中执行代码,您将无法读取已定义为存储在 VTL1 内存空间中的数据或执行 VTL1 中的代码。您也不能对 VTL1 内存空间中的数据执行任何形式的内存损坏。这并不能阻止内核漏洞利用。您仍然可以在 VTL0 中运行它们。1
这在保护方面做得很好的一件事是Hypervisor-Protected Code Integrity (HVCI) 不会被禁用。过去曾发生过一些攻击,其中有人发现了一个易受攻击的驱动程序软件,然后利用该漏洞关闭了 HVCI。但是,这是 VBS 的总体目标,而不仅仅是 KDP。KDP 只是帮助实现这一目标的一组 API。
缓解的有用性
就缓解而言 - 静态 KDP 并没有那么有用。事实上,它构成的整个技术堆栈通常更倾向于数据隔离,而不是内核执行预防。
您的观点是,攻击者肯定可以在其他地方使用写入原语,这是完全正确的。这不会停止代码执行,也不会完全停止读/写原语。它只能防止对应用了静态 KDP 的内存区域(例如 HVCI)进行此类攻击。不幸的是,鉴于 Windows 是封闭源代码,很难知道默认情况下哪些内存区域。
我认为将 Static KDP 称为 Linux Kernel Runtime Guard (LKRG) 的等价物并不公平,事实上,LKRG 更类似于 Hypervisor-Protected Code Integrity。尽管 KDP 确实在给定的内存区域上有效地实施了写保护,但您的断言是正确的。
威胁模型
微软对此的主要威胁模型被称为:“保护密钥和哈希”以及 HVCI 不被低权限读取/被低权限覆盖。
微软曾多次指出,他们计划在未来更多地使用这项技术,以更好地保护自己免受基于驱动程序的漏洞的侵害,但他们没有具体指出未来的计划、设计或他们打算如何做到这一点。只是他们将使用这项技术来做到这一点。
1 - 这种技术,特别是静态 KDP 看起来有点像安全剧院。使用它几乎没有什么好处。这不会停止代码执行,也不会阻止或减轻易受攻击的驱动程序代码。它所做的只是让操作系统说:“Kerberos 密钥需要更高的安全性,将它们放在 VTL1 中,这样如果 VTL0 被 pwned,它们就不会得到密钥”。尽管该名称标榜自己为“内核数据保护”,但 Microsoft 博客将此功能宣传为在一定程度上防止了易受攻击的代码执行,即使它没有...
使用的术语
- SLAT:二级地址翻译
- IOMMU:输入输出内存管理
- VBS:基于虚拟化的安全性
- KDP:内核数据保护
- HVCI:Hyper Visor 代码完整性
- IUM:隔离用户模式
- VTL:虚拟信任级别
- VSM:虚拟安全模式
- NT内核:VTL0内核
-安全内核:VTL1 内核
-内存:VTL0 内存
-安全内存:VTL1 内存
- PTE:页表条目
- GVA:访客虚拟地址
- GPA:客人实际地址
- TLB:翻译后备缓冲区
- NPT:嵌套页表
- SPA:系统物理地址
- VMCB:虚拟机控制块
- DMA:直接内存访问
延伸阅读+资料
- https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-kernel-dma-protection
- https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/vsm
- https://docs.microsoft.com/en-us/windows/win32/procthread/isolated-user-mode--ium--processes
- https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-vbs
- https://www.microsoft.com/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/
- https://channel9.msdn.com/Blogs/Seth-Juarez/Windows-10-Virtual-Secure-Mode-with-David-Hepkin
- https://channel9.msdn.com/Blogs/Seth-Juarez/Isolated-User-Mode-in-Windows-10-with-Dave-Probert
- https://docs.microsoft.com/en-us/windows-hardware/drivers/bringup/device-guard-and-credential-guard