Windows KUSER_SHARED_DATA 结构随机化技术解析
Windows 10 在内核地址空间布局随机化(KASLR)方面进行了多项改进,显著提高了漏洞利用成本,特别是针对远程代码执行漏洞。许多内核虚拟地址空间(VAS)位置,包括内核栈、内存池、系统页表项(PTE)等,都已实现随机化。一个众所周知的例外是 KUSER_SHARED_DATA 结构,这是一个传统上始终在内核中固定虚拟地址映射的内存页。本文重点介绍了我们为加强 Windows 中的 KASLR 所做的工作,通过缓解内核中最后一个已知的盲写目标结构,该结构可被远程代码执行漏洞利用。
背景
KUSER_SHARED_DATA 结构是内存中的单个页面(4096 字节),在内核和用户端的 VAS 中均映射到固定的硬编码地址。KUSER_SHARED_DATA 被映射到每个进程中,并提供了一种快速机制,用于从内核获取频繁需要的全局数据(中断时间、版本、调试器状态、处理器扩展等),而无需通过系统调用或中断进行用户-内核模式切换。在 64 位 Windows 上,该页面的内核模式虚拟地址为 0xFFFFF78000000000,并映射为读写权限。用户模式映射位于地址 0x7FFE0000,页面权限为只读。NT 内核及相关驱动程序在启动期间和启动后使用内核模式映射动态写入 KUSER_SHARED_DATA 结构中的各个字段,用户模式代码可以使用用户模式映射读取这些值。
尽管 KUSER_SHARED_DATA 结构不包含任何指针(得益于先前的工作),但它映射到硬编码虚拟地址这一事实对远程内核模式漏洞利用非常有用。当远程攻击者通过触发内核中的漏洞获得“写”原语时,KASLR 是一个障碍,他们必须准确知道要写入的位置。这种盲写原语通常迫使攻击者利用另一个漏洞来泄露信息,以披露内核模式结构的内存地址,最好该结构的内容可以由攻击者远程控制。但作为绕过利用链中另一个漏洞的方法,攻击者可以使用相同的盲写原语,并在 KUSER_SHARED_DATA 中写入一些特殊构造的数据结构。通过这种方式,他们可以使用此数据的地址(跨重启固定)作为指向内核中另一个代码路径的指针。在某些情况下,这可以使攻击者将初始获得的盲写原语(通过触发漏洞)扩展到读取内核 VAS 的任意区域,从而导致 KASLR 被破坏。Ricerca Security 的一篇文章详细介绍了这种技术,通过在 KUSER_SHARED_DATA 中写入有效负载(伪造的 MDL 结构)并将此有效负载的地址提供给特定代码路径,该代码路径期望合法的 MDL 结构指针,从而按照有效负载的描述从任意内存读取。这允许将盲写原语扩展为任意内存读取原语,并在 CVE-2020-07096 修补的远程代码执行漏洞的概念验证利用过程中使用。
缓解设计
KUSER_SHARED_DATA 是一个向后兼容的结构,自 Windows 早期版本以来一直存在。页面内的字段保持在固定偏移量,并且多年来添加了一些额外字段。因此,许多在 Windows 上运行的第三方内核模式代码依赖于从该页面的固定地址读取。我们如何在不破坏共享页面静态性假设的情况下随机化它?
为确保兼容性,我们决定将页面保留在当前硬编码虚拟地址(0xFFFFF78000000000),将此映射的页面保护更改为只读,并在随机化地址创建相同底层物理页的新虚拟映射。此新随机化映射具有读写权限。本质上,对实际页面的任何写入都必须通过此新随机化虚拟地址进行,而旧的固定地址只能读取而不能写入。
此设计确保我们不破坏地址的现有使用,但缓解了之前提到的技术,即远程内核模式漏洞利用可以利用固定虚拟地址的可写性来绕过 KASLR。
以下图示说明了这一变化:
图 1:缓解前
图 2:缓解后
让我们简要看一下这种随机化和页面保护在 Windows 中是如何实现的。
从图 2 可以看出,该设计需要修改 Windows 中所有依赖旧设计的代码以保持兼容。这涉及更改 Windows 内核模式代码中所有当前写入 KUSER_SHARED_DATA 结构的位置,以使用新的随机化虚拟地址。
有一个新的全局变量 MmWriteableSharedUserData,用于存储新映射的随机化地址。此变量未导出,仅对 NT 内核内的代码可见。由于此全局变量位于 NT 内核内部,而 NT 内核本身是随机化的,因此其位置是随机化的。
在启动早期“阶段 0”初始化之前,此新全局变量保存固定(旧)KUSER_SHARED_DATA 结构的地址。一旦操作系统进入阶段 0 初始化(初始化内存管理分页、系统页表项等),处理保护 KUSER_SHARED_DATA 映射的函数是 _MiProtectSharedUserPage。此函数先前仅修改现有 KUSER_SHARED_DATA 页面权限以使其不可执行。_MiProtectSharedUserPage 现在执行以下操作(未按相同顺序列出,流程已为清晰度而更改):
- 修改现有静态映射的 KUSER_SHARED_DATA 页面权限,使其为只读(无写入或执行权限)。当页面在启动早期首次映射时,它具有 RWX 权限。
- 创建底层 KUSER_SHARED_DATA 物理页的新随机化映射。将此映射的权限设置为读写。
- 新随机化映射的虚拟地址写入仅限 NTOS 的全局变量 MmWriteableSharedUserData。
此缓解措施破坏了远程攻击者使用 KUSER_SHARED_DATA 结构利用盲写原语的能力,因为可写地址现在是随机化的,而静态地址是只读的。
应用程序兼容性
KUSER_SHARED_DATA 结构仅应由核心操作系统和内部驱动程序更新。外部第三方驱动程序不应依赖写入或更新此结构。我们一直在与外部供应商和驱动程序开发人员合作,他们在 Windows Insiders 版本上测试其代码,以确保其产品保持兼容。依赖写入当前静态 KUSER_SHARED_DATA 地址的不兼容第三方驱动程序将导致系统崩溃。如果您为 Windows 开发驱动程序,请确保针对当前 Windows Insider Preview 版本进行测试,并更新任何旧代码以与最新操作系统保持兼容!
发布
此功能仅在 64 位 Windows(AMD64 和 ARM64)上启用,您目前可以使用 Windows Insider Program 版本进行测试(感谢外部安全研究人员已经开始注意到这些变化!)。我们打算在未来的 Windows 版本中包含此缓解措施。
- Rohit Mothe,Microsoft Security Response Center (MSRC) 特别感谢 Joe Bialek 和 Gov Maharaj