安全的内核补丁保护之SKPG扩展
2022-3-5 11:50:0 Author: www.4hou.com(查看原文) 阅读量:24 收藏

本文将介绍SKPG的数据结构和组件,更具体地说,是它被激活的方式。SKPG 初始化的过程请您点击这里了解。

内部HyperGuard激活

在SKPG 初始化一文中,我介绍了HyperGuard并描述了它的不同初始化途径。无论我们怎么选择,最终都会在普通内核完成初始化时到达SkpgConnect。此时内核中所有重要的数据结构已经初始化,可以开始受到 PatchGuard 和 HyperGuard 的监控和保护。

经过几次标准输入验证后,skpgconect获得SkpgConnectionLock并检查SkpgInitialized全局变量,以告诉HyperGuard是否已经被初始化。如果设置了变量,函数将根据接收到的信息返回STATUS_ACCESS_DENIED或STATUS_SUCCESS。在任何一种情况下,它都不会做任何其他事情。

如果 SKPG 尚未被初始化,SkpgConnect 将开始对其进行初始化。首先,它计算并保存多个随机值,以便稍后在多个不同的检查中使用。然后它分配并初始化一个背景结构,保存在全局 SkpgContext 中。在我们继续讨论其他 SKPG 领域之前,有必要花点时间讨论一下 SKPG 的背景。

SKPG背景

这个SKPG背景结构在SkpgConnect中被分配和初始化,并将在所有SKPG检查中使用。它包含HyperGuard监控和保护系统所需的所有数据,如NTPTE信息、加密算法、KCFG范围等,以及另一个计时器和回调,与我们在SKPG 初始化一文看到的计时器和回调不同。不幸的是,像HyperGuard的其他部分一样,这个结构(我将称之为SKPG_CONTEXT)没有文档记录,因此我们需要尽最大努力弄清楚它包含什么以及如何使用它。

首先,需要分配背景。这个背景具有一个动态大小,它取决于从普通内核接收到的数据。因此,它是在运行时使用函数SkpgComputeContextSize计算的。结构的最小大小是0x378字节(随着背景结构获得新字段,这个数字往往会随着Windows构建的次数增加而增加),并且根据从普通内核发送的数据,该结构将被添加一个动态大小。

只有在通过PatchGuard代码路径初始化SKPG时才发送的输入数据是一个名为Extents的结构数组。这些范围描述了HyperGuard保护的不同内存区域、数据结构和其他系统组件。我将在后面的文章中更详细地介绍所有这些,但是有几个例子包括GDT和IDT、某些受保护模块中的数据部分和具有安全含义的MSR。

计算出所需的大小后,分配SKPG_CONTEXT结构,并在SkpgAllocateContext中设置一些初始字段。其中两个字段包括另一个安全计时器和一个相关的回调,其函数被设置为SkpgHyperguardTimerRoutine和SkpgHyperguardRuntime。它还设置了与PTE地址相关的字段和其他与分页相关的属性,因为很多HyperGuard检查都验证了正确的Virtual->Physical页面转换。

然后,调用SkpgInitializeContext来使用普通内核提供的范围完成背景的初始化。这基本上意味着遍历输入数组,使用数据初始化内部结构, 我将调用 SKPG_EXTENT,并将它们粘贴在 SKPG_CONTEXT 结构的末尾,我选择调用 ExtentOffset 的字段指向范围数组(请注意,这些结构都没有记录,因此所有结构和字段名称都是由组成的):

1.png

SKPG范围

有许多不同类型的范围,每个SKPG_EXTENT结构都有一个Type字段指示其类型。每个范围还具有一个哈希,在某些情况下,该哈希用于验证对所监控的内存区域没有做任何更改。然后是被监控内存的基址和字节数的字段,最后是包含每个范围类型特有数据的联合。作为参考,下面是反向工程的SKPG_EXTENT结构:

2.png

我提到过,HyperGuard使用的输入区是由普通内核中的PatchGuard初始化器函数提供的。但是SKPG会初始化另一种类型的范围,同样安全的范围。为了初始化这些,SkpgInitializeContext调用SkpgCreateSecureKernelExtents,提供SKPG_CONTEXT结构和当前范围数组结束的地址,因此可以将安全范围放置在那里。安全范围使用与常规范围相同的SKPG_EXTENT结构,并保护安全内核中的数据,例如加载到安全内核和安全内核内存范围中的模块。

3.png

范围类型

如前所述,有许多不同类型的范围,HyperGuard使用每种范围来保护系统的不同部分。然而,我们可以将他们分成几个具有相似特征的组,并以相似的方式处理。为了清晰并将正常范围与安全范围分离,我将使用命名约定SkpgExtent用于正常范围类型,SkpgExtentSecure用于安全范围类型。

我想要介绍的第一个范围非常简单,它总是被发送到SkpgInitializeContext,而不管其他输入。

初始化范围

有一个范围不属于任何组,因为它不涉及任何HyperGuard验证。这是0x1000: SkpgExtentInit,此范围不会复制到背景结构中的数组中。相反,这个范围类型是由SkpgConnect创建的,并发送到SkpgInitializeContext,以设置背景结构本身中以前未填充的一些字段。这些字段具有与热补丁相关的附加哈希值和信息,例如是否启用热补丁以及retpoline代码页的地址。它还在背景结构中设置一些标志,以反映设备中的一些配置选项。

内存和模块范围

这个组包括以下范围类型:

4.png

所有这些范围类型的共同点是,它们都表示HyperGuard要保护的内存范围。其中大多数包含正常内核中的内存范围,但是SkpgExtentSecureMemory和SkpgExtentSecureModule有VTL1内存范围和模块。尽管如此,不管内存类型或VTL如何,所有这些范围类型都以类似的方式处理,所以我将它们组合在一起。

当向SKPG Context添加普通内存范围时,将验证所有普通内核地址范围,以确保页面具有用于SKPG保护的有效映射。为了使普通内核页对SKPG保护有效,这个页是不可写的。SKPG将监控所有请求页面的更改,因此内容可以随时更改的可写页面不是此类保护的有效“候选”页面。因此,SKPG只能监控保护为“读”或“执行”的页面。显然,只有有效的页面(如PTE中的valid位所示)才能受到保护。当启用HVCI时,与某些内存范围有一些细微的差别,因为在这些条件下SKPG无法处理某些页面类型。

一旦映射和验证,每个应该被保护的内存页将被哈希,并将哈希保存到SKPG_EXTENT结构中,它将在未来的HyperGuard检查中使用,以验证页面没有被修改。

一些内存范围描述了一个通用的内存范围,还有一些,比如 SkpgExtentImagePage,描述了一个特定的内存类型,需要稍微区别对待。这种范围类型提到了普通内核中的特定映像,但是HyperGuard不应该保护整个映像,而应该保护其中的一部分。因此,输入范围包括映像基础、映像中保护应该开始的页面偏移量以及请求的大小。这里要保护的内存区域也将被哈希,哈希值将被保存到SKPG_EXTENT中,以便在将来的验证中使用。

但是写入SKPG背景的SKPG_EXTENT结构通常只描述单个内存页面,而系统可能希望保护映像中更大的区域。对于HyperGuard来说,一次处理一个页面的内存验证更简单,这样可以获得更可预测的处理时间,避免在哈希大内存范围时占用太多时间。因此,当接收到请求大小大于页面(0x1000字节)的输入范围时,SkpgInitializeContext遍历请求范围中的所有页面,并为每个页面创建一个新的SKPG_EXTENT。只有描述范围中的第一个页面的第一个范围接收类型SkpgExtentImage。描述以下页面的所有其他类型都接收不同的类型0x1014,我选择调用SkpgExtentPartialMemory,并且原始的范围类型位于SKPG_EXTENT结构中特定类型数据的前2个字节中。

数组中的每个范围都可以用不同的标志来标记。其中一个是Protected标志,它只能应用于普通的内核区,这意味着SKPG应该保护指定的地址范围不受更改。在这种情况下,SkpgInitializeContext将在请求的地址范围上调用SkmmPinNormalKernelAddressRange来固定并防止它被VTL0代码释放:

5.png

安全内存范围本质上与普通内存范围非常相似,主要区别在于它们由安全内核本身初始化以及它们所保护的内容的详细信息。

生成SkpgExtentSecureModule类型的范围来监控加载到安全内核空间中的所有映像。这是通过迭代SkLoadedModuleList全局列表来完成的,它和普通内核的PsLoadedModuleList一样,是一个KLDR_DATA_TABLE_ENTRY结构的链表,表示所有加载的模块。对于其中的每个模块,都调用SkpgCreateSecureModuleExtents来生成范围。

为此,SkpgCreateSecureModuleExtents 一次接收一个加载的 DLL 的 KLDR_DATA_TABLE_ENTRY,验证它是否存在于 PsInvertedFunctionTable(包含所有加载的 DLL 的基本信息的表,主要用于快速搜索异常处理程序),然后枚举模块。安全模块中的大多数部分都使用 SKPG_EXTENT 进行监控,但不受修改保护。只有一个部分受到保护,TABLERO 部分:

6.png

TABLERO 部分是仅存在于少数二进制文件中的数据部分。在普通内核中,它存在于 Win32k.sys 中,其中包含 win32k 系统服务表。在安全内核中,securekernel.exe 中存在 TABLERO 部分,其中包含全局变量,例如 SkiSecureServiceTable、SkiSecureArgumentTable、SkpgContext、SkmiNtPteBase 等:

7.png

当 SkpgCreateSecureModuleExtents 遇到 TABLERO 部分时,它会调用 SkmmProtectKernelImageSubsection 将部分页面的 PTE 从默认的读写更改为只读。

然后,对于每个范围,无论其类型如何,都会创建一个类型为SkpgExtentSecureModule的范围。如果段是可执行的,则每个内存区域将被哈希成范围标记中的一个标志。每个范围生成的范围数量可能不同:如果在计算机上启用了hotpatch,将为受保护映像范围中的每个页面生成单独的范围。否则,每个受保护的部分会生成一个可能覆盖多个页面的范围,所有这些范围都使用SkpgExtentSecureModule类型:

8.png

如果启用了hotpatch,则为每个安全模块创建最后一个安全模块区。变量SkmiHotPatchAddressReservePages将指示在模块末尾有多少个页面被预留给HotPatch使用,并为每个页面创建一个范围。与前面描述的普通内核模块范围的方式类似,每个范围描述一个页面,范围类型是SkpgExtentPartialMemory,类型SkpgExtentSecureModule被放置在范围的一个类型特定的字段中。

另一个安全范围类型是SkpgExtentSecureMemory。这是一个通用范围类型,用于指示安全内核中的任何内存范围。但是,目前它仅用于监控由安全内核处理器块(SKPRCB)指向的GDT。这是一个内部结构,其目的类似于普通内核的KPRCB(类似地,它们的数组存在于SkeProcessorBlock中)。对于系统中的每个处理器,都有一个这种类型的范围。此外,该函数在每个KGDTENTRY64结构的Type字段中设置一个位,以表明这个条目已经被访问,并防止它在以后被修改,但是在偏移量0x40处的TSS条目将被跳过:

9.png

这基本上涵盖了内存区的初始化和使用。

参考及来源:https://windows-internals.com/hyperguard-secure-kernel-patch-guard-part-2-skpg-extents/?utm_source=rss&utm_medium=rss&utm_campaign=hyperguard-secure-kernel-patch-guard-part-2-skpg-extents如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/zYJy
如有侵权请联系:admin#unsafe.sh