在 Windows 系统的安全防护体系中,驱动程序强制签名(Driver Signature Enforcement,DSE)是一项重要的安全机制,它用于防止攻击者将未签名的驱动程序加载到内核中,从而保护内核免受恶意代码的攻击。然而,随着攻击者技术的不断发展,针对 DSE 的绕过技术也逐渐出现。同时,微软推出的基于虚拟化的安全(Virtualization-Based Security,VBS)技术试图加强对内核的保护,但在未结合管理程序代码完整性(Hypervisor-Protected Code Integrity,HVCI)的情况下,仍然存在被绕过的可能。本文将详细介绍在没有开启 HVCI 的 VBS 环境下进行 DSE 绕过。
在高版本的 Windows 系统中,DSE 由 ci.dll 内核模块实现,该模块中存在一个名为g_CiOptions的全局变量,将该变量设置为0可以完全禁用 DSE。但在 Windows 10 引入 VBS 机制后,这种直接修改变量的方法失效了,于是我们就看到了下面这一幕:
VBS 是 Windows 10 和 11 系统中默认启用的安全机制,它提供了一个受管理程序保护的环境,运行第二个 “安全内核”,传统的 Ring-0 级别的内核无法触及该环境。VBS 通过内核数据保护(Kernel Data Protection,KDP)技术来保护g_CiOptions变量。
我们使用 Ghidra 导入 ci.dll,找到CipInitialize函数,我们可以看到有许多函数回调例程,我们点击CiInitializePolicy如图所示
我们在CiInitializePolicy里一直向下翻找,可以看到g_CiOptions全局变量的地址传给了MmProtectDriverSection函数,如图所示
该函数的具体用法可以参考 MSDN,下面我就简单的列一下函数原型
该函数使用 VSM 管理的 SLAT 表(二级地址转换)保护内核中数据段的物理内存。而参数AddressWithinSection是指向要保护有效数据的内存地址,且在受保护期间,无法删除此保护。
要绕过 DSE,我们需要找到合适的位置进行补丁,我们可以在 CI!CipImageGetImageHash处下个断点,然后加载驱动,输出如下:
从调试结果可知,加载任意驱动程序后,系统会进入 ci.dll 并调用 CiValidateImageHeader函数执行签名验证。因此,我们可在 CiValidateImageHeader函数起始位置写入一条机器指令,使其直接返回STATUS_SUCCESS,以此绕过签名验证流程。
需注意的是,内核空间中无法直接向可执行内存写入数据,需先修改虚拟地址对应的页表项(PTE),将目标内存区域设置为可写状态,再执行代码热修补操作。
通过 Ghidra 对 RTCore64.sys 漏洞驱动进行逆向分析,可提取出其内核内存读、写接口,如下图所示:
相关接口定义如下:
调用漏洞驱动读写接口的实现代码基于上述接口,可编写如下代码调用漏洞驱动的内核内存读、写功能:
寻找CiValidateImageHeader函数的地址
由于 Windows 的版本差异,CiValidateImageHeader函数的特征码可能不同,我们需要找一个相对稳定的特征码和相对稳定的搜索模式进行匹配。在这期间,我花费了大量时间去分析不同版本 Windows 系统中的 ci.dll 文件,尝试了多种特征码组合,最终才找到一个相对稳定的特征码和匹配模式。
具体实现代码如下:
该函数的工作原理如下:
通过修改 PTE 项进行热修补操作找到 CiValidateImageHeader 函数后,我们需要修改其对应的页表项(PTE)权限,然后实施热修补。这是整个绕过过程的核心步骤,具体实现如下:
获取 MiGetPteAddress 函数信息我们需要反汇编 MiGetPteAddress 函数逻辑来找到计算参数和 PTE 基址。从之前的分析中,我们知道该函数的汇编代码如下:
从代码中可以看到:
在我们的实现中,通过以下代码获取这些关键信息:
通过反汇编分析,我们可以准确提取出计算 PTE 地址所需的参数,这是实现内存权限修改的关键步骤。
计算 CiValidateImageHeader 函数的 PTE 地址
可以借助 WinDbg 的!pte [Address]指令来确定 PTE 地址是否正确。
修改 PTE 权限
实施热修补
写完要马上恢复原始状态,否则会触发 PatchGuard 蓝屏
最终效果成功执行代码后,系统的DSE保护机制将被临时绕过,此时可以加载未签名的驱动程序。执行效果截图如下:
防护策略HVCI 通过 Hypervisor 利用 SLAT/EPT/NPT 提供的硬件虚拟化能力,在比普通内核更高的特权级别(Ring -1) 上管控内存页权限,普通内核态代码无法修改 Hypervisor 维护的页表。在启用HVCI的环境下可执行的内存页会被标记为只读且可执行(禁止写入),可写入的内存页(如数据区)会被标记为可写但不可执行。当我们尝试在启用 HVCI 的 VBS 环境下使用这种方法绕过 DSE,就会发现:
在启用VBS(基于虚拟化的安全)但未开启HVCI(管理程序代码完整性)的环境下,通过代码热修补技术可绕过DSE(驱动签名强制)。
在VBS环境下,g_CiOptions全局变量被内核数据保护(KDP)技术保护,无法直接修改。因此,我们采用了一种间接的方法:通过修改CiValidateImageHeader函数,使其在执行签名验证前直接返回STATUS_SUCCESS,从而绕过整个签名验证流程。
漏洞驱动利用 :利用RTCore64.sys漏洞驱动实现对内核内存的读写操作,这是整个绕过过程的基础。
内存映射 :通过映射内核镜像和ci.dll镜像到用户空间,便于分析和定位关键函数。
特征码搜索 :使用特征码搜索技术定位MiGetPteAddress和CiValidateImageHeader等关键函数,提高了代码在不同Windows版本上的兼容性。
PTE操作 :通过分析MiGetPteAddress函数,提取计算PTE地址所需的参数和基址,实现对页表项的精准定位和权限修改。
热修补技术 :在修改页表项权限后,对CiValidateImageHeader函数进行热修补,写入xor rax, rax; ret指令使其直接返回STATUS_SUCCESS。
HVCI限制 :此方法仅在未开启HVCI的VBS环境下有效。当HVCI开启时,内核代码会被进一步保护,无法通过此方法修改。
蓝屏风险 :在修改页表项权限后,需要在用户空间等待用户输入后恢复原始值,否则会导致 PatchGuard 蓝屏。
NTSTATUS MmProtectDriverSection (
[in] PVOID AddressWithinSection,
[in] SIZE_T Size,
[in] ULONG Flags
) ;
0 : kd> bu CI !CipImageGetImageHash
0 : kd> g
Breakpoint 0 hit
CI !CipImageGetImageHash :
fffff803`242e6f64 488bc4 mov rax,rsp
6: kd> k
# Child-SP RetAddr Call Site
00 ffffb080` c3529408 fffff803`242e6de5 CI!CipImageGetImageHash
01 ffffb080` c3529410 fffff803`243283f0 CI!CipCalculateImageHash+0xa5
02 ffffb080` c3529490 fffff803`242d6eaa CI!CipValidateFileHash+0x290
03 ffffb080` c3529570 fffff803`242a7ef2 CI!CipValidateImageHash+0x112
04 ffffb080` c3529660 fffff803`9274e661 CI!CiValidateImageHeader+0xaa2
05 ffffb080` c3529840 fffff803`9274df8f nt!MiValidateSectionCreate+0x641
...
0e ffffb080` c352a0d0 fffff803`927ccd42 nt!MmLoadSystemImageEx+0x110
0f ffffb080` c352a1b0 fffff803`928bc453 nt!IopLoadDriver+0x262
10 ffffb080` c352a380 fffff803`9211e74b nt!IopLoadUnloadDriver+0x83
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 14小时前
被漫雾.编辑
,原因: