farmpoet Profile picture
Cyberspace cowboy Infosec research

Aug 19, 24 tweets

It's time to take a closer look at CVE-2024-38063 (Windows TCPIP RCE).
I usually don't post partial analysis but since most available info is unreliable I'll do my best to try and shed some light.
This time I'll focus on my workflow and thought process as we go. 🧵

MSFT advisory pretty much tells us where to look.
Our target is the tcpip.sys kernel driver which got an update during last patch tuesday.
For the analysis I picked binaries for Windows 11 23H2, builds 3958 (pre-fix) and 4036 (post).
Diffing shows a single function has changed.

In fact, this is the best you could hope for on binary diffing.
Further inspecting the differences on Ipv6pProcessOptions() it comes down to a single change at the end.
A call to IppSendErrorList() has been replaced by IppSendError(). Couldn't get simpler than this right? 😅

Well no, not really. But to tell you why we need to start at the beginning.
Checking the cross-references to the function we have 2 possible code paths: Ipv6pValidateNetBuffer and Ipv6pReceiveDestinationOptions.
The second seems particularly helpful in showing how to reach it.

Ipv6 doubled the header size compared to ipv4 so optional data needs to be provided in extension headers to avoid further bloat.
A quick read at RFC 8200 gives us a list of possible extension headers.
Time to fire up a VM with kernel debugging and start sending some packets.

We place a breakpoint at Ipv6pProcessOptions and for testing we use scapy to send an ipv6 packet with both Hop-by-Hop and Destination Options header.
Sure enough our breakpoint is hit twice so we print the call stack to check where we're coming from.

Further experiments confirm the Hop-By-Hop is called from the validate function and each destination extension header will be hit from the receive destination options (once for each header).
We now do the proper reversing of the function and try to lift the structures used.

To do that we inspect the single argument received which is an opaque structure that contains among other info a _NET_BUFFER_LIST (more on this later).
The function retrieves the first _NET_BUFFER in the list, which contains a packet, and parses a single options header.

In summary, the function parses each option in the header using NdisAdvanceNetBufferDataStart() and NdisGetDataBuffer() to walk the buffer.
Along the way, some variables are set to keep track of what's already been parsed and remaining data in the buffer.

So ok, it parses option headers and do a bunch of sanity checks. What about the vuln?
Well, ipv6 has a feature which allows the sender to request the packet to be discarded and a notification to be sent back if the destination doesn't know how to handle a specific option.

Which gets us to the patch changes. When an option header fails the checks or a request for a packet to be discarded has been received, we have a call to IppSendErrorList (on the pre-patch version).
Now what's the difference from calling IppSendError instead?

Remember the function receives a _NET_BUFFER_LIST.
The first field of that struct is a pointer to the next _NET_BUFFER it contains.
So IppSendErrorList just iterates through that single linked list until all packets in that _NET_BUFFER_LIST have been processed.

In a low traffic ipv6 network, each list will carry a single _NET_BUFFER with the packet to be processed stored in a MDL. In such case a call to IppSendErrorList would be indifferent from calling IppSendError.
However, what happens if you saturate the link with IPv6 packets?

In that scenario, each _NET_BUFFER_LIST will carry multiple packets yet to be processed and if you insert some packets with a request to be discarded then the IppSendErrorList will also be called for the remaining packets on the list.
Lets do an experiment to check.

Each line on the image are packets arriving at the Ipv6pProcessOptions (marked +), and the calls o IppSendError (marked -).
Observe the first packet being received and being sent to the error function. By the 2nd packet however, you have a list of packets that are unrolled.

As more packets go in, the more unprocessed packets will be fed to IppSendError().
Why is this relevant? Remember that IppSendError will parse a discarded packet to send back an ICMP error to the source which involves further manipulation of the buffers.

At this point I dare to speculate the vulnerability may possibly be triggered by specially crafting packets which will cause an incorrect parsing by IppSendError.
Why? because some of the offsets tracked during parsing are stored in the opaque structure mentioned previously.

Which means (if I didn't screw up in the analysis so far) that IppSendError will operate on packets yet to be processed packets while consuming offsets stored on the opaque struct.
I haven't experimentaly verified it so feel free to check if you feel so inclined.

But what we've seen so far illustrates a very important point. That the vulnerability can't be triggered with a single packet. In fact, you'll have to flood the destination with packets just to reach the vulnerable path.
This seems to be corroborated by MSFT advisory.

The implication is important since the hope for controlled exploitation will hardly succeed. Besides the usual requirement of a kernel pointer leak, controlling the target offsets with a flood of packets (plus eventual normal traffic) seems an unsurpassable barrier.

My current assessment is that at best we're talking about a remote denial of service and full RCE is intractable in this case. Not that a pre-firewall remote DoS isn't bad but 9.8 CVE score seems over the top.

This is my best assessment at the moment.
I won't keep digging into this vuln given the limitations mentioned above but hope it may help other people trying to look into it and, as always, I'm ready to be proven wrong by someone more knowledgeable in reversing network drivers.

⚠️These brief analysis are deceptively time consuming to write so, if you enjoyed reading this please consider giving a follow and/or retweeting the first post in the thread.
Comments on what you'd like to see in future CVE analysis are also welcome.
End 🧵

@_bka_ And you're right, IppSendErrorList is called in other places such as Ipv4pProcessOptions but there you lack the complex parsing logic that exists on ipv6 and the ability to request packets to be discarded. 👍

Share this Scrolly Tale with your friends.

A Scrolly Tale is a new way to read Twitter threads with a more visually immersive experience.
Discover more beautiful Scrolly Tales like this.

Keep scrolling