CVE-2025-7771:通过 ThrottleStop 驱动进行任意物理内存和 I/O 端口读/写
ThrottleStop 是一个小型合法应用程序,用于监控和纠正 CPU 节流。它带有一个驱动来协助这些任务,而且该驱动大多没有任何输入验证。
| IOCTL | 漏洞 |
|---|---|
| 0x80006498 | 任意内存读取 |
| 0x8000649C | 任意内存写入 |
| 0x80006430 | 任意端口读取 |
| 0x80006434 | 任意端口写入 |
似乎还存在任意的 MSR 读和写,但我无法让它们工作。你可以自己去查看。读 MSR 位于 0x80006448,写 MSR 位于 0x8000644C。
如果你能让它工作,请提交一个 issue 描述相关步骤。
下面提到的所有 IOCTL 调用都在 Exploit.c 文件中实现。
0x80006498 IOCTL 使用 MmMapIoSpace 将任意提供的物理地址映射到内核空间,并从中读取 1、2、4 或 8 个字节。数据随后被复制回用户空间。
要触发此 IOCTL,请以物理地址作为输入、输出缓冲区大小作为读取大小(1、2、4 或 8)调用 DeviceIoControl。
驱动中此 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
该驱动尚未被添加到微软驱动阻止列表中,也未加入 loldrivers。