Windows TCP/IP Stack Internals (tcpip.sys)
Last updated: 2026-04-19
Related: Architecture, Primitives, Integer Overflows, Cve 2024 38063, Cve 2022 34718, Cve 2021 24086, Cve 2021 24094, Cve 2020 16898, Cve 2022 21907
Tags:kernel-mode,tcpip,integer-overflow,oob-write,remote,ipsec
Summary
tcpip.sys is the Windows kernel-mode driver implementing the full IPv4/IPv6 TCP/IP network stack. It is a premier attack surface for remote zero-click exploitation: it is always loaded, processes all network packets before any user-mode code runs, and handles complex stateful protocols (fragmentation, reassembly, options parsing, IPsec) in ring-0. This page documents tcpip.sys internals relevant to exploitation, derived from reverse engineering CVE-2020-16898, CVE-2021-24086, CVE-2021-24094, CVE-2022-34718, and CVE-2024-38063.
Core Data Structures
NET_BUFFER / NET_BUFFER_LIST (NDIS)
All network packet data is stored in NDIS structures, documented because NDIS is a public extension interface:
kd> dt NDIS!_NET_BUFFER
+0x000 Next : Ptr64 _NET_BUFFER
+0x008 CurrentMdl : Ptr64 _MDL ← current MDL in chain
+0x010 CurrentMdlOffset : Uint4B ← byte offset into CurrentMdl
+0x018 DataLength : Uint4B ← total data length (EXPLOITABLE: CVE-2024-38063 zeroes this)
+0x020 MdlChain : Ptr64 _MDL ← full MDL chain
+0x028 DataOffset : Uint4B
+0x030 ChecksumBias : Uint2B
...
kd> dt NDIS!_NET_BUFFER_LIST
+0x000 Next : Ptr64 _NET_BUFFER_LIST ← linked list of NBLs
+0x008 FirstNetBuffer : Ptr64 _NET_BUFFER
+0x08c Status : Int4B ← set to STATUS_DATA_NOT_ACCEPTED (0xC000021B)
by IppSendError() when packet has error
...
WinDbg commands: !ndiskd.nb <addr> and !ndiskd.nbl <addr> to dump NET_BUFFER/NET_BUFFER_LIST with MDL chains.
Packet_t (Undocumented, Reconstructed)
Internal packet wrapper used throughout IPv4/IPv6 parsing:
struct Packet_t {
Packet_t *Next; // linked list (coalesced packet list)
NET_BUFFER_LIST *NetBufferList; // backing NDIS structure
UINT8 NextHeader; // current protocol/extension header type
UINT32 NextHeaderPosition;// byte offset of Next Header field in packet
ipv6_header_t *ip_header; // pointer to parsed IPv6 header (UAF in CVE-2021-24094)
PVOID dst_ip; // destination IP pointer (UAF in CVE-2021-24094)
UINT32 packet_size; // (EXPLOITABLE: CVE-2024-38063 zeroes via IppSendError)
UINT32 flags; // bit 4 = "created by reassembly" flag
// ... many more fields
};
Reassembly_t (Undocumented, Reconstructed)
Per-fragment-group reassembly state, keyed by (src_ip, dst_ip, frag_id):
struct Reassembly_t {
// Hash table linkage
RTL_DYNAMIC_HASH_TABLE_ENTRY HashEntry;
// Reassembly data
ipv6_header_t Ipv6; // copy of first fragment's IPv6 header
UINT32 UnfragmentableLength;// total size of extension headers before Fragment Header
UINT32 DataLength; // size of reassembled fragmentable part
UINT16 packet_length; // 16-bit! (CVE-2024-38063 exploits 16-bit underflow here)
UINT16 fragment_size; // set to underflowed value 0xFFD0 in CVE-2024-38063
// Fragment list
struct FragList *fragments; // linked list of received fragments
// Offset tracking
UINT32 offset_of_last_next_hdr; // byte offset of Next Header field to fix up
UINT8 frag_next_hdr; // Next Header value from Fragment Header
// ...
};
Reassembly objects are stored in a global hash table Ipp6ReassemblyHashTable (IPv6) or Ipp4ReassemblyHashTable (IPv4), protected by Ipp6ReassemblyHashTableLock spinlock.
Hash function: IppReassemblyHashKey(iface, identification, packet) = RtlCompute37Hash(src_ip) ⊕ RtlCompute37Hash(dst_ip) ⊕ RtlCompute37Hash(frag_id) | 0x80000000
Protocol_t and Demuxer_t
The IPv6 packet parser uses a dispatch table (Protocol_t.Demuxers[277]) indexed by NextHeader value (0–255 + extra):
struct Demuxer_t {
void (*Parse)(Packet_t *); // parsing callback
BOOL IsExtensionHeader; // if true, Parse is called in extension header loop
// ...
};
Key dispatch entries:
NextHeader = 0 (IPPROTO_HOPOPTS) → Ipv6pReceiveHopByHopOptions
NextHeader = 43 (IPPROTO_ROUTING) → Ipv6pReceiveRoutingHeader
NextHeader = 44 (IPPROTO_FRAGMENT) → Ipv6pReceiveFragmentList → Ipv6pReceiveFragment
NextHeader = 58 (IPPROTO_ICMPV6) → Icmpv6ReceiveDatagrams
NextHeader = 59 (IPPROTO_NONE) → no further headers
NextHeader = 60 (IPPROTO_DSTOPTS) → Ipv6pReceiveDestinationOptions → Ipv6pProcessOptions
Packet Processing Pipeline
Top-Level: IppReceiveHeaderBatch
Incoming packets (NET_BUFFER_LIST chain)
↓
IppReceiveHeaderBatch(packet_list, protocol)
├── For each packet: IppReceiveHeadersHelper(packet)
│ └── Parse IPv6 header → set Packet->NextHeader
├── Loop while IsExtensionHeader:
│ └── Protocol->Demuxers[NextHeader].Parse(packet)
├── IppProcessDeliverList() ← deliver to transport layer
└── IppFreePacket() + NetioDereferenceNetBufferListChain()
IPv6 Options Processing: Ipv6pProcessOptions → IppSendErrorList
The entry point for Destination Options and Hop-by-Hop Options headers. When an invalid option is encountered:
Ipv6pProcessOptions(coalesced_packet_list)
↓
IppDiscardReceivedPackets() == 0 AND var != 0?
↓ YES
IsEnabledDeviceUsage_3 = Feature_2660322619__private_IsEnabledDeviceUsage_3()
├── IsEnabledDeviceUsage_3 == 0: IppSendErrorList(packet_list) ← VULNERABLE
└── IsEnabledDeviceUsage_3 != 0: IppSendError(packet) ← PATCHED (CVE-2024-38063 fix)
IppSendErrorList behavior (vulnerable):
void IppSendErrorList(Packet_t *list) {
for (Packet_t *p = list; p; p = p->Next)
IppSendError(p); // calls IppSendError on EVERY packet in list, not just erroneous one
}
IppSendError side effect (when always_send_icmp = true, triggered by option type > 0x80):
// Deep inside IppSendError:
packet->packet_size = 0; // zeroes packet_size field
// ... sends ICMP error back to sender
When IppSendErrorList processes a coalesced list containing both the malformed options packet AND subsequent fragment packets, all packets in the list get packet_size = 0.
IPv6 Fragment Reassembly: Ipv6pReceiveFragment
Ipv6pReceiveFragment(packet)
├── IppReassemblyHashKey() → look up or create Reassembly_t
├── Record unfragmentable part (first fragment only):
│ UnfragmentableLength = packet->unfragmentable_size
│ frag_next_hdr = fragment_header->next_header
│ offset_of_last_next_hdr = (offset in unfragmentable where NH points to frag header)
├── fragment_size = LOWORD(packet->packet_size) - 0x30 ← 16-bit arithmetic!
│ (CVE-2024-38063: if packet_size=0, LOWORD(0)-0x30 = 0xFFD0 = 65488)
├── Store fragment payload
└── If last fragment received: Ipv6pReassembleDatagram()
Reassembly Completion: Ipv6pReassembleDatagram
Ipv6pReassembleDatagram(packet, reassembly)
├── HeaderAndOptionsLength = UnfragmentableLength + sizeof(ipv6_header_t)
├── NetioRetreatNetBuffer(FirstNetBuffer, uint16_t(HeaderAndOptionsLength), 0)
│ ← uint16_t TRUNCATION (CVE-2021-24086: undersized MDL if > 0xFFFF)
├── Buffer = NdisGetDataBuffer(FirstNetBuffer, HeaderAndOptionsLength, NULL, ...)
│ ← if uint16_t(H) != H: NdisGetDataBuffer returns NULL (CVE-2021-24086 crash)
├── *Buffer = reassembly->Ipv6 ← NULL deref crash (CVE-2021-24086)
├── [PATCH addition]: new_header = alloc(); *new_header = incoming_packet->ip_header
│ reassembled->ip_header = new_header ← CVE-2021-24094 fix
└── If reassembled->flags & BIT4:
IoQueueWorkItem(IppReassembledReceive, reassembled) ← deferred (CVE-2021-24094 UAF path)
Else:
IppReceiveHeaderBatch(reassembled)
Reassembly Timeout: Ipv6pReassemblyTimeout
Called 60 seconds after first fragment received if reassembly is incomplete. Site of CVE-2024-38063 overflow:
// Allocation size (16-bit DX arithmetic):
DX = fragment_list->net_buffer_length // ≈ 0x38
DX += reassembly->packet_length // 0x38 + 0xFFD0 = 0x10008 → overflow → DX = 0x0008
DX += 0x28 // 0x0008 + 0x28 = 0x30 → ~48 bytes
alloc = ExAllocatePool(DX); // allocates ~48 bytes
// Copy size (full 32/16-bit reassembly->packet_length = 0xFFD0 = 65488):
memmove(alloc, reassembly->payload, reassembly->packet_length); // copies 65,488 → OVERFLOW
IPsec ESP Processing (CVE-2022-34718 Context)
Security Association Requirement
IPsec ESP (Encapsulating Security Payload) packets require a Security Association (SA) — a shared set of cryptographic parameters (SPI, algorithm, key) negotiated via IKEv1/IKEv2. Without an SA, tcpip.sys drops ESP packets before parsing. Establishing an SA requires:
- Domain environment with group policy, OR
- Pre-shared key (PSK), OR
- Certificate-based authentication
This is the primary prerequisite for CVE-2022-34718 — it is remote but not fully zero-click (SA must be negotiated first, which requires some initial connectivity setup).
ESP Packet Processing: Ipv6pReassembleDatagram (CVE-2022-34718)
When an IPv6 fragmented packet is carried inside an ESP payload, after decryption tcpip.sys processes the embedded IPv6 packet through the normal reassembly pipeline. In the vulnerable code:
// Offset into NetIoProtocolHeader2 is calculated as:
offset = sizeof(PayloadData) + sizeof(Padding) + sizeof(PaddingLength);
// → Can be > 0x38 (past end of object)
// OOB write: 1 byte, value = Next Header field from ESP tail (= 0x2c for Fragment Header)
*(NetIoProtocolHeader2 + offset) = NextHeader; // OOB!
The patch adds two guards:
Ipv6pReassembleDatagram: bounds check on the offset calculation- ESP handler: discard ESP packets where embedded extension header type ≤ 0x2c (blocks Fragment=0x2c, Routing=0x2b, Hop-by-Hop=0x00)
NetIoProtocolHeader2
The NetIoProtocolHeader2 is a kernel paged pool object used for protocol header processing in the IPv6 reassembly path. Key facts:
- Pool tag: investigate with
!poolused/dt tcpip!NetIoProtocolHeader2 - Size: > 0x38 bytes (exact size not public; the bug writes past offset 0x38)
- Corruption of 1 byte at offset > 0x38 does not reliably crash immediately — depends on what adjacent pool object is present
Attack Surface Summary
| Attack Vector | Component | Bug Class | CVEs |
|---|---|---|---|
| ICMPv6 Router Advertisement with malformed RDNSS option | tcpip!Icmpv6ReceiveDatagrams | Heap OOB write | CVE-2020-16898 |
| IPv6 nested fragments with huge ext. headers | tcpip!Ipv6pReassembleDatagram | uint16_t truncation → NULL deref | CVE-2021-24086 |
| IPv6 recursive reassembly state confusion | tcpip!Ipv6pReassembleDatagram | UAF + type confusion | CVE-2021-24094 |
| IPv6 fragment inside IPsec ESP payload (SA required) | tcpip!Ipv6pReassembleDatagram | OOB write → NetIoProtocolHeader2 | CVE-2022-34718 |
| IPv6 coalesced malformed opts + fragment | tcpip!Ipv6pProcessOptions + Ipv6pReassemblyTimeout | 16-bit underflow → heap overflow | CVE-2024-38063 |
| HTTP/1.1 requests to IIS | http!UlFastSendHttpResponse | Uninitialized MDL → invalid unmap | CVE-2022-21907 |
Key tcpip.sys Reversing Techniques
Extracting Patch Diffs
1. Download pre/post-patch tcpip.sys from winbindex.m417z.com
(index at: https://winbindex.m417z.com/?file=tcpip.sys)
2. Load both in IDA, generate .idb files
3. BinDiff: File → BinDiff → diff the two .idb files
4. Focus on functions with similarity < 0.9 or "changed" status
For CVE-2024-38063: exactly ONE function changed (Ipv6pProcessOptions), one line.
For CVE-2021-24086: 2 functions changed (Ipv6pReassembleDatagram, Ipv6pReceiveFragment).
Key Debugging Commands
// Set breakpoint on IPv6 fragmentation reassembly:
bp tcpip!Ipv6pReassembleDatagram
// Dump NET_BUFFER_LIST chain:
!ndiskd.nbl <addr>
// Dump NET_BUFFER:
!ndiskd.nb <addr>
// Watch packet coalescing (CVE-2024-38063):
bp tcpip!Ipv6pProcessOptions "dt @rcx; g"
// Dump reassembly hash table:
dt tcpip!Ipp6ReassemblyHashTable
Scapy IPv6 Extension Header Construction
from scapy.all import *
# Malformed destination options (CVE-2024-38063 trigger):
# Option type 0x81 > 0x80 → always_send_icmp = true in IppSendError
pkt = Ether(dst=mac) / IPv6(dst=ip, nh=60) / \
raw(struct.pack('BBBB', 44, 0, 0x81, 0) + b'\x00'*4)
# Fragment packet (Next Header from IPv6 header → 44 = fragment):
frag = Ether(dst=mac) / IPv6(dst=ip, nh=44) / \
IPv6ExtHdrFragment(m=1, id=0x1337, nh=59) / (b'A'*100)
# Nested fragments for CVE-2021-24086 (0x1ffa routing headers = 0xFFD0 bytes):
routes = raw(IPv6ExtHdrRouting(addresses=[], nh=43)) * (0xffd0//8 - 1)
routes += raw(IPv6ExtHdrRouting(addresses=[], nh=44))
Mitigation Relevance
| Mitigation | Effect on tcpip.sys exploits |
|---|---|
| Disable IPv6 | Eliminates all IPv6-based attack surface; breaks IPv6 connectivity |
| KASLR | Required for reliable RCE; not defeated by DoS-only PoCs |
| Windows Firewall | Ineffective against CVE-2020-16898 and CVE-2021-24086 (processed below firewall) |
| Segment Heap (kernel pool) | Raises bar for heap grooming; does not prevent overflow |
| HVCI | Does not help directly (no shellcode; overflow corrupts data structures) |
| Patch | Only reliable mitigation |
Exploit Relevance
tcpip.sys vulnerabilities represent the most impactful class of Windows kernel bugs: zero-click, pre-authentication, wormable, remote ring-0 code execution. The IPv6 attack surface (extension headers, fragmentation, reassembly, NDP/ICMPv6) has proven repeatedly vulnerable across multiple patch cycles (2020–2024). The complexity of the IPv6 specification — nested fragments, extension header chains, coalescing — provides fertile ground for state confusion and arithmetic errors.
Key lesson for auditors: focus on:
- 16-bit arithmetic on size fields (
fragment_size,packet_length,HeaderAndOptionsLength) - List processing functions that operate on multiple packets (coalesced lists)
- Recursive state machines (nested reassembly) where “first packet” semantics are complex
- Delayed execution paths (WorkItems, timers like
Ipv6pReassemblyTimeout) that operate on state set by earlier code - Protocol layering interactions — code paths triggered only when one protocol (e.g., ESP) delivers another (e.g., IPv6 fragments) may miss validation steps checked in the normal direct path (CVE-2022-34718 pattern)
References
- Axel “0vercl0k” Souchet, “Reverse-engineering tcpip.sys: mechanics of a packet of the death”, doar-e.github.io, April 2021
- Francisco Falcon, “Analysis of a Windows IPv6 Fragmentation Vulnerability: CVE-2021-24086”, blog.quarkslab.com, 2021
- Marcus Hutchins, “CVE-2024-38063 - Remotely Exploiting The Kernel Via IPv6”, malwaretech.com, August 2024
- Armis Research Team, “From URGENT/11 to Frag/44”, armis.com, April 2021
- pi3, “CVE-2020-16898 – Bad Neighbor”, blog.pi3.com.pl, October 2020
- chompie1337 (IBM X-Force), “Dissecting and Exploiting TCP/IP RCE Vulnerability ‘EvilESP’”, IBM Security Intelligence, 2023 — https://www.ibm.com/think/x-force/dissecting-exploiting-tcp-ip-rce-vulnerability-evilesp
- Numen Cyber Labs, “TCP/IP Vulnerability CVE-2022-34718 PoC Restoration and Analysis”, 2022 — https://www.numencyber.com/tcp-ip-vulnerability-cve-2022-34718-poc-restoration-and-analysis/
- Microsoft NDIS Documentation,
NET_BUFFER,NET_BUFFER_LIST, WDK - winbindex.m417z.com — index of Windows binaries by build
