在Windows 操作系统中,内核与用户态之间的严格隔离是系统安全的基石。通常,内核代码只能在 Ring 0 级别执行,而用户态代码则运行在 Ring 3。为了实现这一点,Windows 内核在页表项(PTE)中设置了多种标志,其中最重要的之一就是 PTE.user。这一标志用于区分一页内存是属于用户态还是内核态。然而,在实际的 Windows 内核 API 实现中,很多检查却是依赖于硬编码的地址范围,而非严格检测 PTE.user 字段。
1.PTE简介
页表项(PTE)是操作系统内存管理中关键的数据结构之一,它记录了虚拟地址与物理内存之间映射的详细信息。每个 PTE 通常包含以下内容:
有效位(Valid Bit):指示该页是否在内存中有效。如果无效,表示该页可能在磁盘上或者尚未分配。
页帧号(Page Frame Number):指向实际物理内存中的页帧,用于将虚拟地址转换为物理地址。
保护位(Protection Bits):控制该页的访问权限,如读、写、执行权限,同时也标识是否为 Copy-On-Write(写时复制)页。
用户/内核标志(User/Supervisor Bit):决定该页是否允许用户态(Ring 3)代码访问。如果设置为用户模式,则用户程序可以访问该页;否则,仅限内核态访问。
缓存控制位、脏页位等:用于优化内存管理和维护数据一致性。
在现代操作系统中,PTE 的设计和使用不仅影响内存访问效率,还直接关系到系统安全性,因为错误或恶意修改 PTE 可能导致内核与用户态之间的隔离被突破。
2. Windows 内存保护与 PTE.user
在内存管理中,每个页表项(PTE)包含了控制访问权限的信息。PTE.user标志决定了该页是否允许用户态代码访问:
PTE.user = 1:表明这页内存被标记为用户态内存,即允许 Ring 3 代码访问。
PTE.user = 0:表示仅内核代码可以访问。
Windows 内核在处理内存操作时,如读取、写入虚拟内存、查询内存状态等,都需要判断目标地址是否在合法的用户或内核地址空间内。在这些判断中,很多 API 并非动态检测 PTE.user,而是使用了硬编码的地址边界,例如:
在 MiReadWriteVirtualMemory 中,通过比较地址是否超出预定义范围(如 0x7FFFFFFEFFFF)来决定是否拒绝访问;
在 MmQueryVirtualMemory 中,同样硬编码了地址范围检查。
这种设计决定了,只要一个地址落在“用户模式”地址范围内,系统就认为它可以由用户态代码访问。
3. 利用 PTE.user 标志进行超空间执行
在常规情况下,内核内存分配出来的内存,其 PTE.user 字段默认设置为 0,以确保只有内核代码才能访问。然而,如果能够操控内存分配过程,将分配在内核内存中的页的 PTE.user 标志人为置为 1,那么从操作系统角度看,这段内核内存就“伪装”成了用户态内存。
3.1 执行路径与地址硬编码的漏洞
如同 Windows 内核中的一些 API 实现所展示的那样,系统在判断某个地址是否为用户地址时,通常使用的是硬编码的地址边界(例如低于 0x7FFFFFFEFFFF 的地址被认为是用户内存)。因此,即使内存实际分配在内核空间,只要该内存页的 PTE.user 被置为 1,并且地址落在用户态地址范围内,那么操作系统的各项检查都会认为这是用户代码。代码这就带来了一个有趣的现象:
攻击者或研究人员可以将一段内核内存伪装为用户态内存,从而在该内存区域中执行用户态代码。
这种代码处于内核地址空间,但由于 PTE.user = 1,被系统认为是用户代码,因而不会触发正常的内核态与用户态之间的隔离检查。
3.2 超空间执行的意义与风险
超空间执行(Super-Space Execution)指的正是利用上述技术,在内核内存中执行用户态代码:
优势:
能够在不直接调用传统 Windows API 的情况下,执行绕过安全检测的代码。
隐藏于常规 API 检查之外,形成一种“超级机密”的执行环境,对系统其他部分“不可见”。
风险:
这种技术可能被恶意利用来绕过安全保护机制,例如防止内核补丁检测、绕过驱动签名验证等。
4. 安全防护与防御措施
针对这种利用 PTE.user 的超空间执行技术,防御措施主要包括:
严格的内存分配控制:确保所有分配在内核内存中的页,其 PTE.user 标志不得被非授权修改。
动态检查:内核在处理内存访问时,应增加对 PTE.user 标志的动态验证,而不仅仅依赖硬编码地址范围。
监控与日志记录:建立细粒度的内存页修改监控机制,检测异常修改内核内存页属性的行为。
原文始发于微信公众号(泾弦安全):超空间执行:从 PTE.user 看内核中隐藏的用户代码执行
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论