Remote exploitation of Windows kernel

We figured out how a bug in the Windows network stack works, allowing remote access to maximum privileges in the system without any user action. We tell you how we localized the vulnerability by comparing two driver versions and created an attack scenario.


Every second Tuesday of the month, Microsoft releases Patch Tuesday, an update for Windows that fixes critical vulnerabilities. The August 13, 2024 update fixed a critical vulnerability in the network stack that could allow remote access with maximum privileges when network communication is possible over IPv6.

It was assigned the identifier CVE-2024-38063. In essence, it was a zero-click Windows TCP/IP RCE. According to the information on the official page in Microsoft Security Bulletin, the implementation of the vulnerability is related to Integer Underflow in one of the driver functions tcpip.sysresponsible for processing IPv6 packets. Due to the high criticality of this bug, our vulnerability research team started playing Patch Tuesday – Exploit Wednesday (and won only two weeks later).

Initial analysis

To understand what is causing the vulnerability, you need to compare the driver files before and after the patch. There are two main ways:

  1. Save the old file, then update and get a new one. This is the most stable method, but when using it, it is desirable that the old file is not too old. Otherwise, there is a high probability that instead of a beautiful and convenient comparison in BinDiff, you will get a huge number of changes, most of which are not related to security.

  2. Use this great site to do all your Windows related research – Winbindex. Updated files appear there with a slight delay, but this is not very critical.

For clarity, we decided to use the second method: we went to the Winbindex website and downloaded the two latest versions of the driver for Windows 10 22H2.

Here 10.0.19041.4780 is the version with the fixed vulnerability, and the previous ones are with the one that is not fixed yet. The file publication date can be determined by going to additional information or simply hovering the cursor over the patch number.

Once the files are downloaded, you can compare them using an open source utility. BinDifffor example, as a plugin for IDA Pro. When comparing the two files, we encountered a rather rare situation in the Patch Tuesday analysis: the only function subject to change was Ipv6pProcessOptionsused to handle options in IPv6 packets, such as Jumbo Payload or Hop-By-Hop.

If you look inside the function in the decompiled code, you will find the only changed section at the end.

In the July 23 version:

In the version from August 13:

Now when errors occur in IPv6 options processing, the function is used IppSendError instead of IppSendErrorListThe difference between them is that IppSendErrorList sends errors to every packet in the chain, while IppSendError — only one. You can verify this by looking at the decompiled code of the function IppSendErrorList:

Upon further analysis of the code around the modified section, the logic becomes clear: Ipv6pProcessOptions is designed to process only one packet (or fragment), while IppSendErrorList goes through all the packets in the chain. This is a clear logical error that can lead to something bad. However, we did not know what exactly, so we continued the research. But first, we decided to develop a checker to check that we understood the logic correctly.

Indeed, for three packets with errors in IPv6 options, six responses were received:

  • three errors on the first packet (Packet1 → Packet2 → Packet3);

  • two errors on the second packet (Packet2 → Packet3);

  • one error on the third packet (Packet3).

It should be noted that packets with errors will reach the sender only if Windows Firewall is disabled in the system. However, having the firewall enabled does not affect whether the sent packets will reach the vulnerable system, since they are processed at the operating system kernel level, before being processed by the firewall.

Zero to Hero

When viewing the decompiled code of a function IppSendError you can see the following section of code:

This behavior is correct, since when this function is called, the current packet is processed and an error message is sent. However, due to the fact that in the vulnerable version of the driver, this section of code is executed for each packet in the chain, including those that have not yet been processed, there is a possibility of using the field IPv6_HeaderSize when processing subsequent packets. This field is effectively equal to the size of the IPv6 header, including all nested option headers.

Realizing that this shouldn't be the case and that the size field in the working packet shouldn't be zero, we couldn't figure out for a long time where exactly to apply this primitive, so we turned to previous research on vulnerabilities in the TCP/IP network stack in Windows (1, 2).

We came across article with an analysis of a similar vulnerability related to the restructuring of packet fragments, CVE-2022-34718. It was located in the function Ipv6pReassembleDatagramusing undocumented package structures and restructuring (in the terminology of most researchers: Packet_t And Reassembly_t).

Although this function does not use the field whose value we changed in the parent Ipv6pReceiveFragment it affects the fields of the structure Reassembly_t:

Here we see that from the previously mistakenly changed field value IPv6_HeaderSize subtraction occurs 0x30since the function assumes that at this stage the size cannot be less than 48 bytes. This section of code is a great opportunity to apply the previously acquired primitive, but we still need to get here. To do this, we need to pass two checks, the first of which can be seen in the image above: this is a check that the fragment is the first in the chain. Although this condition reduces the exploitation possibilities, it is not much: it is quite possible that one packet will be enough for us.

The second condition is a check that the fragment offset is not zero, but due to processing in IppSendError (inside which is called NetioRetreatNetBufferListto return the carriage to the beginning of the packet) in fact this condition checks that zero is not equal to FlowLabel in the IPv6 header structure.

How Windows Sees tcpip.sys Code

How Windows Sees Code tcpip.sys

How it really happens

How it really happens

The field of this structure is used in the above mentioned function. Ipv6pReassembleDatagram when copying data from the buffer.

At this point we were already confident that we had fully understood the vulnerability, but the kernel did not get to executing the required code due to the following check at the beginning of the function:

Here is a variable TotalLength equals the sum of the size of the newly initialized Reassembly_t with our size in underflow (after operation it is always equal 0xffd8):

You might think that there would be an overflow in this section of code. UInt16which is UnfragmentableLengthhowever, the addition occurs with the type UInt32due to which the overflow will not occur and TotalLength there will be more than 0xfff.

Finish and Impact

Exploring other functions that use the structure Reassemblywe found a handler that is called when the next fragment timeout expires, – Ipv6pReassemblyTimeout. This function also copies data from our buffer by negative (large positive) size.

To get to this section of code, we need to put FlowLimit equal to one (due to IppSendError The driver treats this field as FragOffset), and then wait a minute. After that, the heap in the Windows kernel is overflowed with values ​​that are almost completely controlled by us, because it is quite easy to spray the necessary chunks nearby. It would seem that everything is fine?

There is always a big problem in remote exploitation of such vulnerabilities: we do not know the kernel addresses after KASLR randomization. If we were running on the attacked host itself (which is quite possible and allows raising privileges to SYSTEM), then we could use all the variety of KASLR leaks in Windows (1, 2). However, this is not our case, and the given method of using the primitive, although it provides excellent opportunities for local privilege escalation and remote BSOD, but rather strict restrictions on the size of the allocated memory area do not allow us to find out the kernel addresses, preventing it from crashing.

There are two caveats here:

  1. Any additional kernel vulnerability that allows KASLR addresses to be discovered in the chain with the presented vulnerability leads to RCE.

  2. The Windows network stack is one of the most complex parts of the OS. The exploitation primitive presented in the article can affect sections of code in further packet processing, so it is likely that there are other (extremely complex) attack methods that will lead to a KASLR leak and kernel compromise.

PoC video

https://vk.com/video-187584378_456239240?list=ln-zarPewf2vrk1X1XbVZ

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *