Analysis of CVE-2024-38063 - Exploiting The Kernel Via IPv6 [EN]

Furkan ÖztürkFurkan Öztürk
7 min read

Introduction

In this article, we will analyze the zero-click windows TCP/IP RCE (CVE-2024-38063) vulnerability published by Microsoft on August 13, 2024. This vulnerability is caused by unsafe calculations when setting the allocation and data copying size in a function used to clean up IPv6 packets from memory.

Analysis

Patch Diff

When we look at the functions that have changed with the new patch, we can see that only the Ipv6ProcessOptions() function has changed.

Before Patch:

After Patch:

Code Analysis

As we can see IppSendErrorList() changed to IppSendError(). The only difference between them is that the IppSendErrorList() function sends errors to every packet but IppSendError() sends them to a single packet.

When we consider that the variable v6 -the first argument of the NetioAdvanceNetBuffer() and NdisGetDataBuffer() functions- represents the fragment, we understand that the Ipv6ProcessOptions() function processes one fragment at a time. The IppSendErrorList() function within the Ipv6ProcessOptions() function, however, sends any potential error to all packets while a single fragment is being examined.

Here is the IppSendErrorList function:

According to the line v8 = (_QWORD *)*v8;, a3 is a linked list or a structure chained over a pointer.

In the IppSendError() function, an error message is sent to a single target with the IppSendControl() call. The IppSendControl() function creates an ICMP error message and sends it.

It creates the error message with the IppAllocateAndFillIcmp Header() function and sends this error with the IppSendDatagramsCommon() function.

While I was reading the IppSendError() function, I came across the following code

Googling the value assigned to the result variable, 3221226011, yields the error code STATUS_DATA_NOT_ACCEPTED (0xC000021B). This means that the package is disabled.

When I explore the IppSendError() function a little further I encounter the following code block

If we look at the packet structure that I will explain below, we set the Next Header of the IPv6 packet to 0.

Fragmentation in IPv6

In IPv6 the source device (Fragmentation in IPv4 can occur at the source or at the router) may fragment packets if it determines that the packet size is larger than the Maximum Transmission Unit (MTU) of a network along the path to the destination.

During fragmentation, in addition to the original IPv6 header, each fragment contains a Fragment Header. This header contains information about which package the fragment came from and the information needed to reassemble it.

IPv6 Header

The IPv6 header has a total length of 40 bytes (320 bits) and this header contains certain fields specific to the IPv6 protocol. These headers are:

  1. Version (4 bits): Protocol version. For IPv6, this value is 6.

  2. Network Identifier (Traffic Class) (8 bits): Determines the priority of the packet; used for QoS (Quality of Service) applications.

  3. Flow Label (20 bits): Used for storage capacity belonging to a different flow; this ensures the transmission of the transmission.

  4. Height (Payload Length) (16 bits): Specifies the duration of the payload (data) after the IPv6 header. This is important during the reassembly of fragment packets.

  5. The Next Header is an 8-bit field that specifies either the type of the first extension header (if any) or the upper-layer protocol in the payload such as TCP, UDP, or ICMPv6.

  6. Hop Limit (8 bits): A value that indicates how far the packet can travel on the network. This value is reduced by 1 each time the packet passes; when it reaches 0, the packet is dropped.

  7. Source IP Address (128 bits): The IPv6 address that is the source of the packet.

  8. Destination IP Address (128 bits): The IPv6 address that is the destination of the packet.

Fragment Header

  1. Next Header (8 bits): This field identifies the type of header present after the Fragmentation header.

  2. Reserved (8 bits): This field is currently set to zero. In the future, it may be utilized for other purposes. Additionally, there is an extra 2-bit field reserved for future use.

  3. Fragment Offset (13 bits): This field indicates the offset of the fragment within the original packet, similar to the Fragment Offset field in IPv4.

  4. More Fragments (M) (1 bit): This field indicates whether there are more fragments to follow. If this bit is set to 0, it indicates the last fragment; if set to 1, it indicates that more fragments may follow.

  5. Identification Number (32 bits): This field contains an identification number that is the same for all fragments of a specific packet. It is twice the size of the corresponding field in IPv4, which is 16 bits.

IPv6 Fragmentation

Fragmentation Analysis

So lets look at the Ipv6pReceiveFragment() function

Assembly code calculating the fragment size

packet header is 0x30 bytes so packet_size - 0x30 is the size of the fragment data.

AX represents the last 16 bits of the 32-bit register EAX. It functions as an independent 16-bit register, meaning any overflow or underflow occurring in AX will not affect other parts of the EAX register. For Example:

EAX = 0x123145678
AX = 0x5678
AX + 0xFFFF = 0x15777
EAX = 0x12345777 (CF flag triggered)

After seeing this, we are looking for methods to achieve overflow or underflow in the packet_size - 0x30 process. However, since the area to be allocated and the area to which the data will be copied are the same size, there is no overflow.

Due to the circular structure of the registers, the increment after the maximum value returns to zero and the same applies to the minimum value, this time returning to the maximum value.

Additionally, in order for these IPv6 packets to be cleared from memory, one of the following 3 conditions must be met.

  1. Incorrect Fragmentation (Invalid Fragmentation)

If there are missing fragments, conflict offsets or invalid maintenance information, the system discards fragments. For example: If the first fragment (offset=0) does not pass, the other fragments are considered invalid.

If Fragment Offset + Length > the maximum threat (MTU) of the main packet is exceeded.

  1. When the Last Fragment Arrives (More=0)

The More Fragments (MF) flag must be 0 on the last fragment. When this fragment arrives: It is checked that all fragments have arrived (are the offsets consecutive?). If there is a missing fragment, the timeout is completed. If it is OK, reblocking is done.

  1. Timeout (60 Seconds)

If the last fragment does not arrive, the system waits for 60 seconds (Windows default value). When the timeout is up, Ipv6pReassemblyTimeout() is called and all fragments are discarded.

When examining the Ipv6pReassemblyTimeout() function, the following section stands out.

Memory is allocated using the IppNetAllocate() function, and data is copied into the allocated memory at two different points in the code. In the code, the last two parameters of the IppNetAllocate() function specify the size of the memory to be allocated: the first parameter corresponds to the size of the IPv6 header + some additional space, and the second corresponds to the size of the fragment data + some additional space.

The memory allocation in the Ipv6pReassemblyTimeout() function involves a two-stage calculation. However, there is a critical inconsistency between these calculations. The area to be allocated is calculated as follows:

allocation_size = fragment_list->net_buffer_length + reassembly->packet_length + 8;

  • fragment_list->net_buffer_length: Total size of the fragment header and its data (for example, 0x38 bytes).

  • reassembly->packet_length: Value manipulated by the attacker (0xFFD0, that is 65520). And we have already manipulated this value before.

  • Total**:**
    0x38 + 0xFFD0 + 8 = 0x10010 (65552 byte)

Overflow (16-bit Register):
Since the calculation is performed on a 16-bit DX register:

  • The value 0x10010 does not fit into 16 bits (it gets truncated to 0x0010).

  • The system only allocates a 16-byte memory block (IppNetAllocate(..., 0x0010)).

There are problems not only in the memory allocation size but also in the data copy size.

  • Used Value:
    reassembly->packet_length (0xFFD0, 65520 bytes).

  • Copy Operation:

    memmove(dest_buffer, reassembly->payload, reassembly->packet_length);

    65520 bytes of data are attempted to be written into a buffer of only 16 bytes.

    Result: Buffer overflow in kernel memory and controlled data corruption.

References

0
Subscribe to my newsletter

Read articles from Furkan Öztürk directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Furkan Öztürk
Furkan Öztürk