Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-29923 — Proof-of-concept exploit for CVE-2026-29923, a BYOVD privilege escalation in pstrip64.sys. Demonstrates physical memory read/write via IOCTL to steal SYSTEM token and spawn elevated shell. | Kitploit
Tools/GitHubGitHub/athenasec16/cve-2026-29923
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary ExploitationLabs & Practice
GitHubathenasec16/cve-2026-29923

CVE-2026-29923

Proof-of-concept exploit for CVE-2026-29923, a BYOVD privilege escalation in pstrip64.sys. Demonstrates physical memory read/write via IOCTL to steal SYSTEM token and spawn elevated shell.

View Repository
2534 months agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-29923 - Local Privilege Escalation Attack via pstrip64.sys

Disclaimer: This code is provided for educational and defensive research purposes only. It was written to further the understanding of kernel exploitation and help defenders protect against similar vulnerabilities. Any unauthorized, illegal, or malicious use of this project is strictly prohibited.


Description

Hash: ab01485bb7c8bc1a9c86096eeea6d31d8fad557bf4d44072b46373d2203faa6e

Driver Name: pstrip64.sys

CVE: CVE-2026-29923

A "Bring Your Own Vulnerable Driver" (BYOVD) attack is an old but highly effective way for attackers to bypass modern Windows security protections by using a legacy driver that the operating system still officially trusts. Once the driver is loaded, the attacker weaponizes its flaws to bridge the gap between a standard, unprivileged process and total system-level control.

Earlier this week, a new vulnerability was disclosed in the pstrip64.sys driver, tracked as CVE-2026-29923. This blog post breaks down the entire lifecycle of the exploit: from my initial vulnerability research and the development of the Proof of Concept (PoC) to actionable mitigation strategies for defenders securing their environments.

The pstrip64.sys driver is a legacy kernel-mode component tied to EnTech Taiwan PowerStrip (up to version 3.90.736). While its legitimate purpose is to enable advanced graphics card display tweaking, its deep system privileges make it a highly attractive target for attackers.


The Vulnerability

When the vulnerability was first disclosed, I began by analyzing its DriverEntry function. This serves as the main initialization routine for the kernel driver, creating the \Device\PSTRIP64 device object and exposing it to user-mode applications via the \DosDevices\PSTRIP64 symbolic link. More importantly, it configures the driver's dispatch table. The entry that immediately caught my eye was at index 14 (IRP_MJ_DEVICE_CONTROL), which routes all user-supplied IOCTL requests directly into the sub_11340 handler function, our primary area of interest.

IDA DriverEntry

The sub_11340 function serves as the primary IOCTL dispatcher, interpreting requests from user-mode.

Out of all the exposed IOCTLs, 0x80002008 is undoubtedly the most interesting. While the default cases handle minor I/O port interactions, 0x80002008 acts as a gateway to sub_11000. By passing the SystemBuffer directly into this function.

IDA ioctl

This sub_11000 routine is the smoking gun. First, it uses HalTranslateBusAddress to take our user-supplied address and translate it into a valid system physical address. Then, it opens \Device\PhysicalMemory and maps it using ZwMapViewOfSection. By hardcoding the target process handle to (HANDLE)0xFFFFFFFFFFFFFFFFLL (which represents ZwCurrentProcess()), the driver maps this physical memory directly into our calling process's virtual address space. Crucially, it then writes this newly mapped virtual address back into the SystemBuffer to return to the user, officially handing our application a direct pointer to read and write physical memory.

IDA MapViewofSection

With the vulnerability fully understood and a physical read/write primitive established, I have all the puzzle pieces required. Now, it’s time to start writing the Proof of Concept.


The Proof of Concept (PoC)

Note: This PoC was specifically developed and tested on a Windows 10 22H2 environment. Because the exploit relies on raw physical memory manipulation, the kernel structure offsets and physical memory boundaries are currently hardcoded for my setup. To test this on your own machine, you must update the Windows kernel offsets and adjust the physical address scanning ranges to match your specific OS build and RAM configuration.

The first step in my exploit is establishing communication with the driver. I did this by calling CreateFileA on the driver's symbolic link (\\.\PSTRIP64). Once I had a valid handle, I needed a clean way to abuse the 0x80002008 IOCTL I analyzed earlier. I created a wrapper function called MapPhysicalMemory(). This function populates my custom PSTRIP_MAP_REQUEST struct with the target physical address and the length of the memory chunk I want to read.

I then send this struct directly to the driver via DeviceIoControl. If successful, the driver maps that physical memory directly into my user-mode application and returns the virtual base address in the OutputResult field. I can now cast this returned address to a standard C++ pointer, giving me raw, unprivileged access to the system's physical RAM.

With my physical read/write primitive fully operational, my goal was to find the kernel data structures that contain process privileges. In Windows, every running process is represented by an EPROCESS structure.

Windows allocates EPROCESS structures in the kernel pool using a specific 4-byte identifier called a Pool Tag. For processes, this tag is the string Proc (which translates to 0x636F7250 in hex). By scanning the system’s physical RAM, I could search for this exact string.

My exploit loops through the physical memory space from 0x10000000 to 0x140000000, mapping memory in 2MB chunks (STEP_SIZE = 0x200000). I cast each mapped chunk to a raw byte array and scan it in 16-byte chunks (sizeof(_POOL_HEADER)).

However, simply finding the Proc tag in physical memory is not enough. Memory is messy, that tag could be a leftover artifact from a terminated process, or just random data that happens to match the hex value. If I blindly assumed that every Proc tag was a valid EPROCESS structure and started modifying memory, I would immediately cause a BSOD.

To ensure stability, I had to validate the structure using heuristics. First, I calculate the start of the EPROCESS structure (which sits slightly offset from the pool tag). From there, I check a few known constants for a running process:

  • PriorityClass: I verify this value is 0x2 (Normal Priority).
  • ProcessLock: I ensure this value is 0x0.
  • ImageFileName: I check that the first character of the process name is a valid, printable ASCII character.

If all these heuristics pass, I can be highly confident I am looking at a valid, active process. I then read its Unique Process ID (PID). If the PID matches my own exploit process, I save the physical address of its token pointer. If the PID is 4 (the Windows System process), I extract and save the actual value of its highly privileged token.

Finally, I align the saved physical address of my process's token pointer to the nearest 4KB boundary and use MapPhysicalMemory() one last time to map just that specific page.

Next, I navigate to the exact offset and overwrite my token with the System token value. Instantly, the Windows kernel treats my exploit process as NT AUTHORITY\SYSTEM.

After unmapping the page to ensure system stability, I simply call CreateProcessA to spawn cmd.exe. Because my current process is elevated, the new command prompt inherits these top-tier privileges, successfully completing the attack!


Notes

Note: A critical detail I discovered during my early debugging phase is how the driver handles the mapped pointer. By executing SystemBuffer->LowPart = (unsigned int)BaseAddress;, the driver casts the 64-bit virtual base address down to a 32-bit value before returning it. This truncation loses the high bits of the address, which resulted in immediate access violations when I tried to dereference it in my 64-bit exploit. To cleanly bypass this issue, I simply compiled my user-mode PoC as a 32-bit application, ensuring the returned pointer remained perfectly valid.

IDA MapViewofSection - Copy

Note: During my initial testing, I encountered a fascinating edge case: my PoC successfully located my exploit process in memory, but it failed to find the System process (PID 4).

To understand why, I needed to inspect physical memory directly. I attached a kernel debugger (WinDbg) and used commands to retrieve the virtual address and the directory base of the System process. I then used !vtop to translate that virtual address into its exact physical address in RAM.

windbg kernel debbuger system physical address windbg kernel debbuger db system address

I switched back to my user-mode debugger attached to my PoC. I set a conditional breakpoint on my memory scanning loop, instructing it to pause execution the moment my MapPhysicalMemory() function grabbed the 2MB chunk containing the System process's physical address.

windbg breakpoint

Once the breakpoint was hit, I began manually inspecting the raw bytes of the mapped memory. This is where I discovered a crucial detail about Windows kernel pool allocations.

windbg eprocessBase offset 88000

When Windows allocates memory for a process, it starts with a _POOL_HEADER (containing our Proc tag), followed by an _OBJECT_HEADER, and finally the EPROCESS structure itself. For standard user-mode applications, these headers contain additional tracking data, meaning the actual EPROCESS structure starts 0x80 bytes after the pool tag.

offest for use mode process

However, inspecting the System process's memory revealed a different layout. System process lacks some of these standard tracking headers. The offset from the Proc tag to the start of the EPROCESS structure was only 0x40 bytes!

windbg db 88000 offset windbg db 88000 - 0x40 offset offset for system process

The fix was straightforward. I updated my PoC to handle both pool header sizes by looping through an array of possible offsets (0x40 and 0x80) whenever it encounters a Proc tag.

posibileoffset

Mitigation & Detection

Cybersecurity is an endless game of cat-and-mouse between attackers and defenders. While attackers constantly hunt for vulnerable drivers, modern security products and blue teams have several robust ways to detect and block this exact operation.

The most effective way to stop a BYOVD (Bring Your Own Vulnerable Driver) attack is to prevent the driver from loading in the first place.

  • Defenders should ensure that the hash of pstrip64.sys is added to their blocklists.
  • Furthermore, organizations should enforce Microsoft's Vulnerable Driver Blocklist via Windows Defender Application Control (WDAC) and enable Hypervisor-Protected Code Integrity (HVCI) to strictly limit which kernel components can be loaded.
  • Monitor for new service creation events, looking for unexpected kernel-mode driver installations.

If the driver is already loaded, security products can still detect the exploit during the token manipulation phase.

  • Advanced monitor for anomalies in process tokens. A standard user-mode process elevating its initial primary token to NT AUTHORITY\SYSTEM without a legitimate authentication chain is a massive red flag.
  • Additionally, security teams can create rules to detect when a low or medium-integrity process spawns a highly privileged child process (such as cmd.exe), especially when the parent process has no business running as SYSTEM.

Demo

poc_demo

Download Tool