HyperGuard 深入解析:SKPG扩展类型与系统保护机制

本文详细探讨了HyperGuard中SKPG扩展类型的实现,包括MSR与控制寄存器扩展、安全虚拟地址转换扩展及处理器特定扩展,揭示了Windows系统如何利用这些机制防护恶意修改与攻击。

HyperGuard Part 3 – More SKPG Extents

大家好!欢迎来到HyperGuard系列博客的第三部分!

在之前的博客文章中,我介绍了SKPG扩展(extents)——这些数据结构描述了HyperGuard应监控的内存范围和系统组件。到目前为止,我只涵盖了初始化扩展和各种类型的内存扩展,但这只是开始。在本文中,我将介绍其余的扩展类型,并展示HyperGuard如何使用它们来保护系统的其他区域。

MSR与控制寄存器扩展

接下来要研究的扩展组是MSR(模型特定寄存器)和控制寄存器扩展:

这一组包含以下扩展类型:

  • 0x1003: SkpgExtentMsr
  • 0x1006: SkpgExtentControlRegister
  • 0x100C: SkpgExtentExtendedControlRegister

这些扩展类型从普通内核接收,但从不添加到SKPG_CONTEXT末尾的数组中,也不会在我将在后续文章中描述的运行时检查中进行验证。相反,它们用于SKPG初始化的另一部分。

在SkpgInitializeContext中初始化SKPG_CONTEXT后,SkpgConnect执行一个IPI(处理器间中断)。它通过调用SkeGenericIpiCall并传入目标函数和输入数据来执行此IPI,该函数将在每个处理器上调用目标函数并发送请求的数据。在这种情况下,目标函数是SkpgxInstallIntercepts,输入数据包含输入扩展的数量和匹配的数组:

我将在未来的博客文章中更详细地介绍拦截,但为了提供必要的背景:SKPG可以请求虚拟机监控程序拦截系统中的某些操作,如内存访问、寄存器访问或指令。HyperGuard利用这一能力拦截对某些MSR和控制寄存器(以及其他内容,我稍后会讨论)的访问,以防止恶意修改。HyperGuard使用输入扩展从接受的选项列表中选择要拦截的MSR和控制寄存器。

由于每个处理器都有自己的一组MSR和寄存器,HyperGuard需要在所有处理器上拦截请求的寄存器。因此,通过IPI调用SkpgxInstallIntercepts,以确保它在每个处理器的上下文中被调用。

一旦进入SkpgxInstallIntercepts,该函数会遍历输入扩展数组,并根据其中提供的数据处理此组中的三种类型。如果您还记得,每个扩展包含0x18字节的类型特定数据。对于此组,此数据包含要拦截的MSR/寄存器编号以及应在其上拦截的处理器编号。这意味着每个MSR或控制寄存器可能有多个输入扩展,每个针对不同的处理器编号。或者,如果普通内核请求,MSR和控制寄存器可能仅在某些处理器上拦截,而在其他处理器上不拦截。输入扩展中MSR和控制寄存器扩展的数据结构如下所示:

1
2
3
4
5
6
7
typedef struct _MSR_CR_DATA
{
    ULONG64 Mask;
    ULONG64 Value;
    ULONG RegisterNumber;
    ULONG ProcessorNumber;
} MSR_CR_DATA, *PMSR_CR_DATA;

在遍历扩展时,函数检查扩展类型是否属于此组中的三种之一,如果是,则检查扩展中的处理器编号是否与当前处理器匹配。如果匹配,则检查MSR或控制寄存器的编号是否与接受的之一匹配。如果扩展匹配接受的寄存器之一,则从SKPRCB中的数组获取掩码——此数组包含所有接受的MSR和控制寄存器所需的掩码,因此可以请求虚拟机监控程序拦截它们。收集所有掩码,当检查完所有扩展后,最终掩码发送到ShvlSetRegisterInterceptMasks进行安装。用于安装拦截的掩码是联合HV_REGISTER_CR_INTERCEPT_CONTROL。它有文档记录,可以在此处找到。

现在覆盖了一般过程,我们可以研究接受的MSR和控制寄存器,并理解为什么HyperGuard可能希望保护它们免受修改,从MSR开始:

SkpgExtentMsr

修补某些MSR是漏洞利用和rootkit的常见操作,允许它们执行诸如挂钩系统调用或禁用安全功能等操作。其中一些MSR已经由PatchGuard定期监控,但通过HyperGuard拦截它们有好处,我将在后面介绍。可以拦截的MSR列表随着时间的推移不断增长,并随着新功能和寄存器添加到CPU而接收新添加,例如CET的实现添加了多个可能成为攻击者目标的MSR。截至Windows 11 build 22598,可以由SKPG拦截的MSR包括:

  • IA32_EFER (0xC0000080) —— 除其他外,此MSR包含NX位,强制执行一种缓解措施,不允许在未明确标记为可执行的地址中执行代码。它还包含与虚拟化支持相关的标志。
  • IA32_STAR (0xC0000081) —— 包含x86系统调用处理程序的地址。
  • IA32_LSTAR (0xC0000082) —— 包含x64系统调用处理程序的地址——通常应指向nt!KiSystemCall64。
  • IA32_CSTAR (0xC0000083) —— 包含在兼容模式下运行时x64系统调用处理程序的地址——通常应指向nt!KiSystemCall32。
  • IA32_SFMASK (0xC0000084) —— 系统调用标志掩码。执行系统调用时在此设置的任何位将从EFLAGS中清除。
  • IA32_TSC_AUX (0xC0000103) —— 使用取决于操作系统,但此MSR通常用于存储签名,与时间戳一起读取。
  • IA32_APIC_BASE (0x1B) —— 包含APIC基地址。
  • IA32_SYSENTER_CS (0x174) —— 包含使用SYSENTER执行系统调用时环0代码的CS值。
  • IA32_SYSENTER_ESP (0x175) —— 包含使用SYSENTER执行系统调用时内核堆栈的堆栈指针。
  • IA32_SYSENTER_EIP (0x176) —— 包含使用SYSENTER执行系统调用时环0入口的EIP值。
  • IA32_MISC_ENABLE (0x1A0) —— 控制多个处理器功能,如快速字符串禁用、性能监控和XD(不可执行)位禁用。
  • MSR_IA32_S_CET (0x6A2) —— 控制内核模式CET设置。
  • IA32_PL0_SSP (0x6A4) —— 包含环0影子堆栈指针。
  • IA32_PL1_SSP (0x6A5) —— 包含环1影子堆栈指针。
  • IA32_PL2_SSP (0x6A6) —— 包含环2影子堆栈指针。
  • IA32_INTERRUPT_SSP_TABLE_ADDR (0x6A8) —— 包含指向中断影子堆栈表的指针。
  • IA32_XSS (0xDA0) —— 包含在内核模式下调用XSAVE和XRESTOR指令时使用的掩码。例如,它控制由Intel处理器跟踪(IPT)使用的寄存器的保存和加载。

SkpgExtentControlRegister

通过修改某些控制寄存器,攻击者可以禁用安全功能或获得执行控制。目前SKPG支持拦截两个控制寄存器:

  • CR0 —— 控制某些硬件配置,如分页、保护模式和写保护。
  • CR4 —— 控制不同硬件功能的配置。例如,驱动程序签名强制、SMEP和UMIP位控制安全功能,使CR4成为使用任意写入漏洞的攻击者的有趣目标。

SkpgExtentExtendedControlRegister

目前只有一个扩展控制寄存器存在——XCR0。它用于切换扩展寄存器(如AVX、ZMM和CET寄存器)的存储或加载,可以由SKPG拦截和保护。

安装拦截

现在我们知道寄存器可以被拦截以及为什么,我们可以返回并通过ShvlSetRegisterInterceptMasks查看拦截的安装。该函数接收HV_REGISTER_CR_INTERCEPT_CONTROL掩码以了解要安装哪些拦截,以及一些被拦截寄存器的值——CR0、CR4和IA32_MISC_ENABLE MSR。这些都放置在一个传递给函数的结构中,如下所示:

1
2
3
4
5
6
7
struct _REGISTER_INTERCEPT_INFORMATION
{
    HV_REGISTER_CR_INTERCEPT_CONTROL InterceptControl;
    ULONG64 Cr0Value;
    ULONG64 Cr4Value;
    ULONG64 Ia32MiscEnableValue;
} REGISTER_INTERCEPT_INFORMATION, *PREGISTER_INTERCEPT_INFORMATION;

InterceptControl掩码在遍历输入扩展时构建,CR0、CR4和IA32_MISC_ENABLE的值从SKPRCB读取(它们的值,连同所有其他可能被拦截的寄存器的值,在SkeInitSystem中放置在那里,由代码SECURESERVICE_PHASE3_INIT的安全调用触发)。

此结构发送到ShvlSetRegisterInterceptMasks,该函数又对输入结构中的四个值中的每一个调用ShvlSetVpRegister以注册拦截。通过启动代码为HvCallSetVpRegisters(0x51)的快速超级调用设置寄存器值,发送四个参数(对于任何感兴趣的人,所有超级调用值都有文档记录)。最后两个参数是HV_REGISTER_NAME和HV_REGISTER_VALUE类型——这些类型有文档记录,因此很容易看到正在设置哪些寄存器:

查看函数,我们看到它正在设置CR0、CR4和IA32_MISC_ENABLE所需的值,最后设置拦截控制的掩码,因此从此时起,所有请求的寄存器都由虚拟机监控程序拦截,对它们的任何访问都将转发到SKPG拦截例程。

安全虚拟地址转换扩展

在上一篇文章中,我介绍了安全扩展——指示要保护的VTL1内存或数据结构的扩展。我还涵盖了内存扩展,包括安全内存扩展。这里是另一种安全扩展,它们在安全内核内部初始化,而不使用来自VTL0的输入扩展。它们称为安全虚拟地址转换扩展,并在SkpgCreateSecureVaTranslationExtents内部初始化。这些扩展用于保护不同页面或内存区域的虚拟->物理地址转换,这些是攻击的常见目标:

  • 0x100B: SkpgExtentProcessorMode
  • 0x100E: SkpgExtentLoadedModule
  • 0x100F: SkpgExtentProcessorState
  • 0x1010: SkpgExtentKernelCfgBitmap
  • 0x1011: SkpgExtentZeroPage
  • 0x1012: SkpgExtentAlternateInvertedFunctionTable
  • 0x1015: SkpgExtentSecureExtensionTable
  • 0x1017: SkpgExtentKernelVAProtection
  • 0x1019: SkpgExtentSecurePool

尽管它们称为安全扩展,但它们保护的数据大多是VTL0数据,例如KCFG位图的VTL0映射或倒置函数表。完成的精确验证因类型而异:例如,零页永远不应映射,因此零页的成功虚拟->物理地址转换不应可接受,而内核CFG位图应具有有效转换,但这些页面的VTL0映射应始终为只读。

查看SkpgCreateSecureVaTranslationExtents,我们可以看到扩展初始化时没有输入数据或内存范围:

这是因为所有这些扩展都与特定的数据结构相关,这些数据结构都在其他地方初始化,因此数据不需要是扩展本身的一部分,所以类型是唯一需要设置的部分。我们还可以看到,其中一些扩展仅在启用KCFG时初始化,因为如果没有它,它们就不需要。我将在后面的博客文章中介绍对每个扩展完成的检查,该文章将描述SKPG扩展验证。

最后,如果启用HotPatching,则添加另外两个扩展,两者类型均为SkpgExtentExtensionTable:

这些扩展保护SkpgSecureExtension和SkpgNtExtension变量,这些变量跟踪HotPatching数据。

每处理器扩展

还有两个扩展是处理器特定的,因为它们保护的数据在每个处理器中单独存在。然而,与MSR和控制寄存器扩展不同,不需要安装拦截,也不需要(目前)在所有处理器上执行函数。这些扩展也从普通内核接收并添加到SKPG_CONTEXT结构的扩展数组中。为这两个扩展中的每一个接收的数据包括基地址、限制和处理器编号,因此这些扩展类型可能存在多个条目,具有不同的处理器编号:

  • 0x1004: SkpgExtentIdt
  • 0x1005: SkpgExtentGdt

这些扩展包含每个处理器上GDT和IDT表的内存范围,因此HyperGuard将保护它们免受恶意修改。

未使用的扩展

扩展类型0x1007、0x1008、0x1013和0x1018在SecureKernel.exe中的任何地方都从未初始化,似乎在任何地方都未使用。它们可能已弃用或尚未完全实现。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计