From URGENT/11 to “Frag/44”: Microsoft patches critical vulnerabilities in Windows’ TCP/IP stack
By Ben Seri, Yuval Sarel, Gal Levy, and Noam Afuta
Part 1: A deep dive into the vulnerabilities, and their similarities to certain URGENT/11 vulnerabilities previously discovered by Armis’ researchers.
In a recent patch Tuesday, Microsoft announced the discovery and patch of two critical remote-code-execution (RCE) vulnerabilities and an additional denial-of-service (DoS) vulnerability in the TCP/IP stack of Windows which impacts versions since Windows 7. The impacted TCP/IP stack was introduced in Windows Vista in 2007, and was called “Next Generation TCP/IP stack” at the time. Thus, these critical vulnerabilities in the networking stack seem to impact Windows releases from the last 14 years. These include Windows 7 and Windows Embedded Compact 7 (formerly known as “Windows CE”), which are no longer supported, and may impact, for example, certain embedded systems that rely on these OSes such as Workstations attached to medical devices, or Engineering Workstations that operate industrial controllers.
Vulnerabilities in widely-used TCP/IP stacks are considered a rare specimen, with a high reward for attackers, since they may enable remote take over of devices without user interaction, and without relying on any specific application of affected devices. In Windows, the occurrence of vulnerabilities in the networking stack, especially ones rated as critical, that exist in an attack surface that is enabled by default, is extremely rare. However, when it comes to embedded systems in particular, these vulnerabilities can be even more serious, since these systems remain unpatched for long periods of time. In fact, many organizations utilize Armis for the purposes of identifying vulnerable systems as well as anomalous behaviors indicative of exploits targeted at these types of TCP/IP vulnerabilities. In the following report, we will highlight a detailed account of the specific mechanisms within the Windows TCP/IP stack, the underlying cause of the vulnerabilities, and most importantly how they might be used to mount sophisticated attacks.
This blog will be part one of a two-part series, in which we will do a technical deep dive of these vulnerabilities, and try to figure out their true exploitability potential. This first part will detail one of the RCE vulnerabilities (CVE-2021-24074), which resembles one of the RCE vulnerabilities discovered by Armis researchers in URGENT/11. Contrary to common belief, this report will demonstrate that a path to RCE does indeed exist using this critical vulnerability.
From URGENT/11 to “Frag/44”
The latest patch in Windows comes after a year and a half in which multiple research initiatives have focused on the security of embedded TCP/IP stacks. In August 2019, Armis researchers were first to identify the potential security risk in embedded TCP/IP stacks when they uncovered 11 vulnerabilities in VxWorks - the most popular real-time operating system (RTOS). This set of vulnerabilities, dubbed “URGENT/11”, was shown to impact hundreds of millions of mission-critical devices. A later discovery by Armis researchers found that the underlying TCP/IP stack used by VxWorks (IPnet), was in fact supported by several additional RTOSs, used by countless applications. Amongst the impacted devices was Becton Dickinson’s Alaris Infusion Pump, a prominent life-supporting medical device with over a million units sold to hospitals as of 2017, underscoring the critical impact of URGENT/11 on medical devices.
Following URGENT/11 in 2019, multiple research initiatives took up the baton, and looked at a wide array of embedded TCP/IP stacks during 2020. A set of vulnerabilities in the Treck TCP/IP stack, dubbed “Ripple20”, was discovered by JSOF researchers, and several security vendors (Armis amongst them) identified over 30 impacted device manufacturers. Later, Forescout researchers focused on open-source TCP/IP stacks, and found a set of vulnerabilities dubbed “Amnesia:33” (at this point it seems the naming convention of URGENT/11 is now the official designation method 😎).
Microsoft’s most recent Patch Tuesday release (February 9, 2021) seems to have completed a full circle of the recent research initiatives in TCP/IP stacks. The set of vulnerabilities discovered by Microsoft originates from different bugs in the fragmentation mechanism in IPv4 and IPv6 (hence the name we’ve coined “Frag/44”, for the sake of keeping with the naming convention).
In the URGENT/11 technical whitepaper, Armis researchers had referenced the ancient WinNuke bug from 1997 -- a remote DoS bug in the TCP/IP stack used by Windows 95 and NT. The bug could be triggered simply by sending a single TCP out-of-band segment (with the URG flag set) to a Windows machine, resulting in a Blue Screen of Death (BSOD):
- TCP out-of-band data (powered by the URGENT flag, that was the basis for four of URGENT/11’s vulnerabilities).
- Routing options in IPv4 (that enabled CVE-2019-12256 in URGENT/11, and CVE-2021-24074 in Windows’ tcpip.sys).
- IP fragmentation in both IPv4 and IPv6 (all three of the latest CVEs in Windows’ TCP/IP stack stem from bugs in the implementation of IP fragmentation).
While this blog will detail a technical deep dive on one of the newly discovered vulnerabilities in Windows’ TCP/IP stack, let’s begin with a high level overview of all of the patched vulnerabilities.
As mentioned, all three of the patched vulnerabilities relate in some way or another to fragmented IP packets -- either in the IPv4 or IPv6 subsystem.
CVE-2021-24074 - Remote-Code-Execution in Parsing of Options in IPv4 Fragments
This vulnerability is a state confusion triggered when the Windows’ TCP/IP stack reassembles IPv4 fragments that contain varying IP options -- specifically while parsing Loose Source Routing IPv4 options from the reassembled fragments. The state confusion can lead to out-of-bound read and write of kernel memory that can (theoretically) lead to remote-code-execution.
CVE-2021-24094 - Remote-Code-Execution via Recursive IPv6 Fragmentation
This vulnerability lies within the IPv6 fragmentation mechanism, and can be triggered with a handful of maliciously crafted IPv6 fragment packets. This bug is categorized as an RCE since two pointers in the metadata of the IPv6 fragmentation state aren’t properly handled, resulting in a “Dangling Pointer” scenario. Theoretically, this state could possibly be exploited by an attacker, in a Use-After-Free exploit that leverages heap shaping or other primitives to reach code execution. Despite the fact this bug was shown to cause a denial-of-service condition (BSOD), it is unclear whether this bug can successfully be used to reach code execution.
CVE-2021-24086 - Denial of Service via IPv6 Fragmentation
This vulnerability is a NULL dereference in the processing of certain maliciously crafted IPv6 fragment packets, which leads to a denial-of-service condition (BSOD). Unlike the vulnerabilities mentioned above, this vulnerability can be triggered by an attacker located outside of a target’s LAN -- either in a separate subnet, or through the Internet (if the targets’ IPv6 is directly exposed to the Internet). This vulnerability can be triggered by an attacker sending approx. 50 maliciously crafted IPv6 fragments to the target machine, and it can result in a somewhat “persistent” DoS condition if the attacker sends the malicious packets to a target device indefinitely.
Technical Deep Dive on CVE-2021-24074
As mentioned in the overview above, this vulnerability is a state confusion triggered while the Windows’ TCP/IP stack reassembles IPv4 fragments that contain varying IP options (different IP options, in each of the fragments). This deep dive will attempt to understand the fundamentals of the underlying bug and piece together whether it can result in a working RCE exploit.
To find the bug, we started by analyzing the binary diff between revisions 804 (the latest) and 746 (the one prior) from build 19041 and found the following additions to the IPv4 reassembly routine (highlighted are the added chunks):
As we’ll see shortly, the addition of these lines prevents the potential state confusion. The variable reassembled_packet includes the payload of the reassembled packet, alongside the IP header (which can include IP options), and some metadata that was parsed from the IP header. In the patched version, a copy of the IP header from the first fragment will be stored in reassembled_packet->ip_header, while in the original version this field contained the IP header of the last fragment (not shown in the snippet above). In the original code, some of the fields containing metadata from the IP header and its options were only updated while the individual fragments were being parsed. This created a potential state confusion in which the metadata fields contained values obtained while parsing two different headers, while the code relies on them being in sync.
Before we can connect the dots and understand the specifics of this vulnerability, we need a brief recap on IPv4 and IP Options.
Background on IPv4 Options and Loose Source Routing
Coincidentally, the niche aspects of the IPv4 header, such as IP Options and Loose Source Routing, were also the basis for one of the RCE vulnerabilities discovered in URGENT/11 (CVE-2019-12256), and the URGENT/11 technical whitepaper can be used as a basis for understanding these mechanisms (see the section “Background - ICMP error packets, IP options and Loose Source Routing”). However, a short recap is outlined here:
Each IPv4 packet has a non-mandatory options field that can be up to 40 bytes long. The options field contains an array of “options”, which are TLV fields (type, length, value), and all of them must fit within 40 bytes.
Source Routing (Loose or Strict), is an ancient feature that allows hosts to specify the route their packet will traverse in the packet’s header. The mechanism, powered by IP options of type LSRR or SSRR (Loose or Strict Source Routing Records) is infamously known to introduce security concerns, since they enable attackers to abuse the default routing path of a packet and force a malicious route on packets containing them. Due to this concern, they are blocked by almost all routers and hosts by default. However, in most TCP/IP implementations -- and apparently in Windows’ TCP/IP stack as well, these rather complex options are still parsed before they are dropped, presenting an additional attack surface nonetheless.
This attack surface is a promising one since the source routing mechanism isn’t used by any modern application, and is thus rarely tested in actual networks, but on the other hand it performs somewhat complex in-memory operations.
Every LSRR\SSRR option has the following structure:
Windows’ TCP/IP Stack Routing Logic, Gone Wrong
As described above, the underlying issue of this bug is a state confusion that occurs when the first fragment of an IPv4 packet contains a certain IP option array, while the last fragment of that packet contains a different IP option array. This behaviour is not supported by the RFC, and the lack of validation of this peculiar scenario is the basis for the unraveling of this seemingly minor bug. As shown in the patch diff earlier, the state confusion manifests in certain metadata fields stored in the reassembly object of the fragmented packet holding values obtained while parsing the first fragment, while other fields hold values obtained while parsing the last fragment. The state confusion ultimately can allow the attacker to bypass certain input validation processes that are needed to prevent out-of-bounds conditions.
Connecting the dots and causing this state confusion to lead to these OOB conditions is not trivial, and requires many conditions to align. However, let’s start by jumping ahead, and viewing the consequence of the state confusion, once these conditions are met.
The following code snippet is a simplified version of the Ipv4pReceiveRoutingHeader function. The function is called when a packet fragment contains routing options (when offset_of_routing_option_in_packet is not 0), and when a packet is reassembled it will also process the reassembled packet, if one of the fragments contained routing options. The snippet displayed below handles the source routing logic:
- The state confusion manifests in the initial lines of code -- the function takes the payload of the first fragment (first_frag), and calculates the offset of the routing header (the strict/loose source routing IP option within the IP header) -- routing_header_first. However, as mentioned in the patch diff before, the metadata fields within the reassembly object (reassembled) -- offset_of_routing_option_in_packet and routing_header_option_size, were actually calculated when the last fragment was processed. This is the fundamental bug in the code.
- routing_header_first points to an offset within the first fragment, where a source routing header, which passed many validation steps, supposedly resides. However, due to the state confusion, the first fragment may actually contain data which is fully attacker controlled (we’ll see how later on). This allows a bypass of the validation steps of the source routing header.
- The code will now consider forwarding the packet, based on certain conditions:
- According to the RFC, If the routing header contains a Pointer that points within the routing data (relative to Length), the packet needs to be forwarded by the receiving host (the code here includes more strict conditions, we’ll detail them later on).
- Thus, the forwarder should take the dst_ip from the current entry in the routing data (where Pointer points to), place it’s own IP in on top of the current entry, and advance the Pointer to the next entry.
- Lastly, IppForwardPackets is called, attempting to forward the packet.
- The last step can enable a write OOB condition, when the target’s own IP address is written to the buffer pointed by Pointer, which is an unsigned 8-bit the attacker can control (more on that later), that can point past the end of the packet’s buffer.
A potential read OOB condition can also occur when the source IP of the packet is compared to the content of the current entry in the route data -- that is also pointed by the attacker-controlled Pointer. The outcome of the condition may translate into different ICMP error messages that will be sent back to the attacker.
Passing Through Hoops
Now that we understand how this vulnerability can lead to OOB conditions, let’s unpack the various hoops we need to jump through, to reach these conditions.
At a high level, these are the conditions that need to be met for the state confusion to occur and reach the flow in the Ipv4pReceiveRoutingHeader function shown above:
- An IPv4 with two fragments is sent to the target.
- Both fragments will contain an IP options field of equal size.
- The first fragment will contain IP options that require little to no validation steps (we’ll see an example of this later on). It will not hold any source routing options.
- The last fragment will contain a valid source routing option (LSRR), with a Pointer that will point past the end of the routing data -- indicating to the receiver the packet has reached its final destination.
- The source routing option that will be parsed from the first fragment (where as mentioned above, it doesn’t actually reside), will need to have a Pointer pointing to the last entry in the routing data, which contains the source IP of the sender. However, both Pointer and Length fields can point out-of-bounds, into kernel memory.
Validation of IP options is done in the function Ipv4pProcessOptions, and is performed when processing each of the individual IP fragments -- but not when a packet has been fully reassembled. The validation process also updates the metadata fields of the offset and length of a source routing header (if it exists) in the reassembly object:
The various limitations described above will be detailed in the next section.
Requirement #1 - both fragments need to have IP options of equal length
The requirement that both fragments will contain IP options with equal size is a side effect of the state confusion, that manifests in the Ipv4pReceiveRoutingHeader function:
Requirement #2 - The last fragment needs to contain a certain LSRR option
The requirement that the last fragment, which contains a valid LSRR option has a Pointer that points past the end of the routing data, is needed so the stack fully reassembles the packet, and then process it again, with the metadata fields now being out-of-sync (and leading to the OOB conditions). This indicates the packet has reached its final destination, and is validating in the following code snippet:
Requirement #3 - The reassembled packet needs to contain a certain LSRR option
Lastly, to reach the write OOB condition, we want the final, reassembled packet to actually be considered as one that needs to be forwarded by the stack, so it will attempt to place the target’s IP address at the last routing entry, which will be pointed out-of-bounds. Remember, the state confusion leads to an LSRR option being parsed from the first fragment, as the routing header of the entire reassembled packet (and this is why this requirement can coincide with the previous). The condition validating that the LSRR option is of a packet that needs to be forwarded can be viewed in the following code snippet:
Connecting the Dots
Passing the above limitations means we can start and see how the various parts fit together, and whether we can achieve an effective OOB condition with this vulnerability.
The first part of the puzzle is having a fully controlled Pointer and Length fields in the LSRR option that will be parsed from the first fragment, in the offset of the LSRR found in the last fragment.
The IP options in the first fragment will also pass through some validation, so we need to find options that have relatively lax limitations. One of the more flexible IP options, supported by the Windows’ TCP/IP stack, is the SEC option (0x82). Examining Ipv4ProcessOptionsHelper we could see that the only constraint for SEC option is size, which has to be 0xB, meaning that there are 9 bytes (header excluded) that are fully controlled.
An alternative approach can attempt to deliberately misalign the SEC and LSRR options. This can be achieved by padding the start of the IP options field in the last fragment, since (as we’ve learned) the offset of the routing header is taken from the last fragment as part of the confusion. Padding with NOP options, which are simply the byte 0x1, will tell the receiver to skip over them and continue parsing the options ahead. By using four NOPs before the LSRR option in the last fragment, we can control the Length parsed from the LSRR taken from first fragment, while maintaining a valid LSRR option in the last fragment, and keep the total length of the IP options field consistent between the fragments -- 0xB including the NOPs:
Getting from OOB to RCE
It seems the puzzle is almost complete, and by meticulously navigating through the various limitations and conditions, it is possible to reach the code flow where the OOB read and OOB write conditions occur. However, the actual primitives they allow are still limited in terms of achieving a working remote-code-execution exploit.
Let’s review the options an attacker has from leveraging these OOB primitives:
Writing 4-bytes OOB in Kernel Memory
As mentioned above, the write OOB primitive eventually leads the attacker to write the target’s own address at an offset (which he controls) maximum 0xFF bytes after the end of the packet’s buffer. If the target is configured as a DHCP client (the most likely configuration), the attacker can leverage a DHCP man-in-the-middle attack, and choose which IP address the target has. This can allow him to control both the OOB offset and the OOB content (at least to some degree -- some 32-bit integers are not valid IPv4 addresses). The only problem (and this is not a small limitation), is the attacker will need to guess a 32-bit integer from the target’s memory, and use it as his source IP.
The graphic above illustrates the memory layout of the buffers involved in the reassembly and routing logic, and how the read OOB and write OOB conditions manifest in memory. The reassembled packet that is passed to the routing function Ipv4pReceiveRoutingHeader will be the one from which the current routing entry, pointed to by the OOB Pointer will be taken. If this OOB read, from kernel memory, will contain the source IP of the packet, a new packet buffer will be allocated -- the forwarded packet, and to it the packet header will be copied, and in it the target’s own IP address will be written, out-of-bounds, using the attacker-controlled Pointer.
If the attacker is able to groom the packet’s heap memory in an effective way, he might be able to find offsets from the end of the reassembled packet’s boundary, in which a predictable 32-bit integer exists, and use that integer as his own, spoofed source IP -- to allow for the OOB read comparison condition to succeed. However, in addition to this, he would also need to groom the packet’s heap memory in such a way that the write OOB condition (that will use the same Pointer, but relative to a different buffer, of the forwarded packet) will lead to something that resembles the beginning of an remote-code-execution chain. For example, overriding part of a function pointer in the kernel’s heap, or controlling other kernel structures that may weaken the target’s defenses, or lead to new primitives that can be used to gain code execution. Although this flow isn’t impossible, it seems a bit far fetched.
Writing 1-byte OOB in Kernel Memory
Instead of writing 4-bytes, in an offset far from the packet’s buffer boundary, an attacker can settle for writing 1-byte, at a 1 byte out-of-bounds offset. This can be achieved much more reliably, since the attacker can brute force the value of the OOB comparison condition by sending the crafted packets from varying spoofed source IPs. While guessing an entire IP address has a probability of 1/232, guessing 1 byte has a much practical probability of 1/28, which can be brute forced rather quickly.
Combining this with the ability to control the target’s IP address using DHCP spoofing, means the attacker can write a controlled byte OOB. Using effective heap grooming, this can lead the attacker to control certain kernel data structures, or alter the LSB byte of a pointer, that can be used to create an RCE exploit.
Reading OOB Iteratively
The 1-byte OOB write primitive detailed above works by simple brute force. The attacker can send the maliciously fragmented packet 256 times, from 256 different spoofed source IPs, and in the most likely scenario, one of the attempts will reach the OOB write condition. However, the attacker won’t actually know which attempt worked, meaning the OOB read condition couldn’t be used to exfiltrate kernel memory data.
In the forwarding logic implemented in the Ipv4pReceiveRoutingHeader function, an ICMP error message will be sent back to the attacker, if the source IP read OOB doesn’t match the source IP of the packet. In certain cases (and it is still unclear to us exactly which), an ICMP redirect message will be sent back to the attacker from the function IppForwardPackets, when it attempts to forward the packet -- which occurs if the read OOB comparison condition had past successfully.
This means, the attacker may be able to observe the different ICMP messages being returned to him, while brute forcing through the 256 different source IPs (their lower, least significant byte), and learn which of his attempts landed on the correct byte of kernel memory. This primitive can even be expanded, and used by the attacker to move the brute-forced byte further and further away from the end of the packet’s buffer, each time iterating through an additional byte, until an ICMP redirect message is sent back to him.
This last primitive is more conjunctive than the first two, since it is unclear the exact conditions in which the different ICMP messages will be sent. However, if this primitive becomes practical, it can enable the attacker to combine it with the first primitive, writing multiple controlled bytes, out-of-bounds, at an offset he controls. This is in addition to the ability to exfiltrate kernel memory data that may be used to bypass ASLR, or gain other sensitive information from a target’s device.
As highlighted throughout the technical deep dive above, this vulnerability is a complex one to understand and exploit, with many moving parts, and many limitations. Nevertheless, it holds some powerful primitives for attackers. While the vulnerability can only be triggered from an attacker on the same LAN as the target (IP packets with source routing options are dropped by modern Internet routers), it can still be triggered without any authentication or user interaction in a true zero-click remote attack against any unpatched Windows device. Sophisticated attackers can, and will, invest considerable amounts of resources to forge such powerful primitives into full chain exploits, even when the odds are slim. The exploitation primitives detailed above demonstrate this vulnerability can turn into a working RCE exploit, despite overwhelming limitations.
In Part 2, we will focus on the intricacies of the IPv6 fragmentation mechanism in the Windows TCP/IP stack, and attempt to fully understand the details of the vulnerabilities found within. Stay tuned!