1444 words
7 minutes

Windows Kernel Exploitation 練習

WinDBG 基本操作 - 練習手動把 VA 轉成 PA#

Page Table示意圖

Virtual Address的組成:

PML4 IndexPDPT IndexPD IndexPT IndexPage Offset
Bits 47-39Bits 38-30Bits 29-21Bits 20-12Bits 11-0
9 bits9 bits9 bits9 bits12 bits

隨便拿一個位址做轉換的目標:

2: kd> lm m nt
Browse full module list
start end module name
fffff806`77e00000 fffff806`79250000 nt (pdb symbols) c:\symbols\ntkrnlmp.pdb\0385A3E51169BA7F84716B1C792977F71\ntkrnlmp.pdb

讀取 CR3#

2: kd> r cr3
cr3=00000000001ae002

分別拿四個 Index#

2: kd> ? (fffff806`77e00000 >> c) & 1ff
Evaluate expression: 0 = 00000000`00000000
2: kd> ? (fffff806`77e00000 >> 15) & 1ff
Evaluate expression: 447 = 00000000`000001bf
2: kd> ? (fffff806`77e00000 >> 1e) & 1ff
Evaluate expression: 25 = 00000000`00000019
2: kd> ? (fffff806`77e00000 >> 27) & 1ff
Evaluate expression: 496 = 00000000`000001f0
  • PT Index: 0
  • PD Index: 0x1bf
  • PDPT Index: 0x19
  • PML4 Index: 0x1f0

爬Page Table#

先爬 PML4:

  1. CR3 + PML4_Index * 8
  2. !dq 抓 PML4 的內容
  3. 爬出來的 & 0ffffffffff000 清掉最低三位
2: kd> ? 00000000001ae002 + (00000000`000001f0 * 8)
Evaluate expression: 1765250 = 00000000`001aef82
2: kd> !dq 00000000`001aef82 L1
# 1aef80 00000000`001d0063
2: kd> ? 00000000`001d0063 & 0ffffffffff000
Evaluate expression: 1900544 = 00000000`001d0000

再用抓出來的結果當作 base 去爬 PDPT:

2: kd> ? 00000000`001d0000 + (00000000`00000019 * 8)
Evaluate expression: 1900744 = 00000000`001d00c8
2: kd> !dq 00000000`001d00c8 L1
# 1d00c8 00000000`0025a063
2: kd> ? 00000000`0025a063 & 0ffffffffff000
Evaluate expression: 2465792 = 00000000`0025a000

再用相同的步驟爬 PD:

2: kd> ? 00000000`0025a000 + (00000000`000001bf * 8)
Evaluate expression: 2469368 = 00000000`0025adf8
2: kd> !dq 00000000`0025adf8 L1
# 25adf8 8a000001`004000a1

注意到 PD 的內容, a11010 0001, bit7為1, 因此為 Large Page, 用 Large Page 的轉換方法:

(PTE&0ffffffffff000)+(VA&1fffff)({PTE} \&\quad {0ffffffffff000}) + (VA \&\quad {1fffff})
2: kd> ? (8a000001`004000a1 & 0fffffffffe00000) + (fffff806`77e00000 & 1fffff)
Evaluate expression: 720575944678440960 = 0a000001`00400000

最後可以用記憶體內容檢查轉換結果:

2: kd> db fffff806`77e00000
fffff806`77e00000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
fffff806`77e00010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
fffff806`77e00020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
fffff806`77e00030 00 00 00 00 00 00 00 00-00 00 00 00 18 01 00 00 ................
fffff806`77e00040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
fffff806`77e00050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
fffff806`77e00060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
fffff806`77e00070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
2: kd> !db 000001`00400000
#100400000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
#100400010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
#100400020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#100400030 00 00 00 00 00 00 00 00-00 00 00 00 18 01 00 00 ................
#100400040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
#100400050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
#100400060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
#100400070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......

Windows Programming 練習 - 列舉 User 可使用的所有 Device#

使用 Claude 生成, 各家 LLM 生出來的結果應該不會相差太大, 都是使用 pNtOpenDirectoryObject 去開 \\Device 然後用 pNtQueryDirectoryObject 配合 TryOpenDevice 去戳對應的 Device:

bool EnumerateDevices(DeviceStats& stats) {
HANDLE hDirectory = NULL;
NTSTATUS status;
// Open \Device directory
UNICODE_STRING dirName;
pRtlInitUnicodeString(&dirName, L"\\Device");
OBJECT_ATTRIBUTES objAttr;
InitializeObjectAttributes(&objAttr, &dirName, OBJ_CASE_INSENSITIVE, NULL, NULL);
status = pNtOpenDirectoryObject(&hDirectory, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &objAttr);
if (!NT_SUCCESS(status)) {
printf("[-] Failed to open \\Device directory. NTSTATUS: 0x%08X\n", status);
return false;
}
printf("[+] Successfully opened \\Device directory\n");
printf("[*] Enumerating devices...\n\n");
// Buffer for directory query
const ULONG bufferSize = 0x10000;
BYTE* buffer = new BYTE[bufferSize];
ULONG context = 0;
ULONG returnLength = 0;
BOOLEAN restartScan = TRUE;
while (true) {
status = pNtQueryDirectoryObject(
hDirectory,
buffer,
bufferSize,
FALSE, // Return multiple entries
restartScan,
&context,
&returnLength
);
restartScan = FALSE;
if (!NT_SUCCESS(status)) {
if (status == STATUS_NO_MORE_ENTRIES) {
break;
}
printf("[-] NtQueryDirectoryObject failed: 0x%08X\n", status);
break;
}
// Process entries
POBJECT_DIRECTORY_INFORMATION info = (POBJECT_DIRECTORY_INFORMATION)buffer;
while (info->Name.Buffer != nullptr) {
std::wstring name = UnicodeToWString(info->Name);
std::wstring type = UnicodeToWString(info->TypeName);
// Only process Device objects
if (type == L"Device") {
stats.totalDevices++;
DWORD error = TryOpenDevice(name);
stats.errorCounts[error]++;
// Print each device with result
wprintf(L"[%4d] \\Device\\%-40s ", stats.totalDevices, name.c_str());
switch (error) {
case 0:
stats.successCount++;
printf("[OK]\n");
break;
case ERROR_ACCESS_DENIED:
stats.accessDeniedCount++;
printf("[ACCESS_DENIED]\n");
break;
case ERROR_SHARING_VIOLATION:
stats.sharingViolationCount++;
printf("[SHARING_VIOLATION]\n");
break;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
stats.notFoundCount++;
printf("[NOT_FOUND]\n");
break;
case ERROR_INVALID_NAME:
stats.invalidNameCount++;
printf("[INVALID_NAME]\n");
break;
default:
stats.otherErrorCount++;
printf("[ERROR: %lu]\n", error);
break;
}
}
info++;
}
}
delete[] buffer;
CloseHandle(hDirectory);
return true;
}

Windows Programming 練習 - 與 Driver 互動#

安裝 Driver#

開啟 Test Mode 之後先安裝 Lab 的 Driver:

C:\Windows\System32>sc create vul binPath=C:\VulDriver.sys type=kernel
[SC] CreateService SUCCESS
C:\Windows\System32>sc start vul
SERVICE_NAME: vul
TYPE : 1 KERNEL_DRIVER
STATE : 4 RUNNING
(STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
PID : 0
FLAGS :
C:\Windows\System32>

安裝 Debug Symbol#

先在 Host 上隨便開一個資料夾, 把 VulDriver.pdb 拷進去之後加到 WinDbg 的 symbol path:

0: kd> .sympath srv*C:\Symbols*https://msdl.microsoft.com/download/symbols;C:\LocalSymbols
Symbol search path is: srv*C:\Symbols*https://msdl.microsoft.com/download/symbols;C:\LocalSymbols
Expanded Symbol search path is: srv*c:\symbols*https://msdl.microsoft.com/download/symbols;c:\localsymbols
************* Path validation summary **************
Response Time (ms) Location
Deferred srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
OK C:\LocalSymbols
0: kd> .reload /f VulDriver.sys

可以 lm 或是直接 x VulDriver!* 看是不是有抓到 symbol:

0: kd> lm vm VulDriver
Browse full module list
start end module name
fffff805`30240000 fffff805`30248000 VulDriver (private pdb symbols) c:\localsymbols\VulDriver.pdb
Loaded symbol image file: VulDriver.sys
Image path: \??\C:\VulDriver.sys
Image name: VulDriver.sys
Browse all global symbols functions data Symbol Reload
Timestamp: Fri Dec 19 20:40:23 2025 (694547B7)
CheckSum: 0000DCD0
ImageSize: 00008000
Mapping Form: Loaded
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:

利用 DeviceIoControl 與 driver 互動#

先觀察 IOCTL_VUL_TEST , 發現需滿足以下條件:

  1. 輸入 magic: 0x13371337
  2. Output buffer 大小 >= 0x1337 bytes
case IOCTL_VUL_TEST:
{
if (irpSp->Parameters.DeviceIoControl.InputBufferLength < 4) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}
magic = *(ULONG*)Irp->AssociatedIrp.SystemBuffer;
if (magic != 0x13371337) {
status = STATUS_INVALID_PARAMETER;
break;
}
const wchar_t* msg = L"HelloWorld";
ULONG bytes = (ULONG)((wcslen(msg) + 1) * sizeof(wchar_t));
if (irpSp->Parameters.DeviceIoControl.OutputBufferLength < 0x1337) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, msg, bytes);
info = bytes;
status = STATUS_SUCCESS;
break;
}

送出對應的 IOCTL 就能收到 Driver回傳的字串了

DWORD inputBuffer = VUL_MAGIC;
const DWORD outputBufferSize = 0x1337;
BYTE* outputBuffer = new BYTE[outputBufferSize];
ZeroMemory(outputBuffer, outputBufferSize);
DWORD bytesReturned = 0;
printf("\n[*] Calling DeviceIoControl...\n");
BOOL success = DeviceIoControl(
hDevice,
IOCTL_VUL_TEST,
&inputBuffer, // Input buffer (magic value)
sizeof(inputBuffer), // Input buffer size (4 bytes)
outputBuffer, // Output buffer
outputBufferSize, // Output buffer size (0x1337)
&bytesReturned, // Bytes returned
NULL // Not overlapped
);

執行結果

VulDriver!VulDeviceControl 上設定 breakpoint, 觀察 IRP 內容:

1: kd> bp VulDriver!VulDeviceControl
1: kd> g
Breakpoint 0 hit
VulDriver!VulDeviceControl:
fffff805`30241370 4889542410 mov qword ptr [rsp+10h],rdx
1: kd> dt nt!_IRP @rdx
+0x000 Type : 0n6
+0x002 Size : 0x118
+0x004 AllocationProcessorNumber : 1
+0x006 Reserved1 : 0
+0x008 MdlAddress : (null)
+0x010 Flags : 0x60070
+0x014 Reserved2 : 0
+0x018 AssociatedIrp : <unnamed-tag>
+0x020 ThreadListEntry : _LIST_ENTRY [ 0xffffa28c`a1c0b5c0 - 0xffffa28c`a1c0b5c0 ]
+0x030 IoStatus : _IO_STATUS_BLOCK
+0x040 RequestorMode : 1 ''
+0x041 PendingReturned : 0 ''
+0x042 StackCount : 1 ''
+0x043 CurrentLocation : 1 ''
+0x044 Cancel : 0 ''
+0x045 CancelIrql : 0 ''
+0x046 ApcEnvironment : 0 ''
+0x047 AllocationFlags : 0x6 ''
+0x048 UserIosb : 0x0000002b`e6b5f470 _IO_STATUS_BLOCK
+0x048 IoRingContext : 0x0000002b`e6b5f470 Void
+0x050 UserEvent : (null)
+0x058 Overlay : <unnamed-tag>
+0x068 CancelRoutine : (null)
+0x070 UserBuffer : 0x000001d7`5ee21060 Void
+0x078 Tail : <unnamed-tag>
1: kd> dd poi(@rdx+18) L1
ffffa28c`a739e000 13371337

Systembuffer 的內容對應到 Magic 0x13371337

Token Stealing 練習#

TBD

Windows Kernel Exploitation 練習
https://blog.cyberangel.work/posts/winkrnlpwn/
Author
Ethan Lai
Published at
2026-01-12
License
CC BY-NC-SA 4.0

Comments

Table of Contents