CVE-2024-43639: Remote Code Execution in Microsoft Windows KDC Proxy
March 04, 2025 | Trend Micro Research TeamIn this excerpt of a Trend Micro Vulnerability Research Service vulnerability report, Simon Humbert and Guy Lederfein of the Trend Micro Research Team detail a recently patched code execution vulnerability in the Microsoft Windows Key Distribution Center (KDC) Proxy. This bug was originally discovered by k0shl and Wei in Kunlun Lab with Cyber KunLun. Successful exploitation could result in arbitrary code execution in the security context of the target service. The following is a portion of their write-up covering CVE-2024-43639, with a few minimal modifications.
An integer overflow has been reported for Microsoft Windows KDC Proxy. The vulnerability is due to a missing check for Kerberos response length.
A remote, unauthenticated attacker could direct KDC proxy to forward a Kerberos request to a server under their control, which would then send back a crafted Kerberos response. Successful exploitation could result in arbitrary code execution in the security context of the target service.
The Vulnerability
The Microsoft Windows operating system implements a default set of authentication protocols, including Kerberos, NTLM, Transport Layer Security/Secure Sockets Layer (TLS/SSL), and Digest, as part of an extensible architecture. For authentication within an Active Directory domain, Windows uses Kerberos.
Kerberos is a computer-network authentication protocol that works on the basis of tickets to allow nodes communicating over a non-secure network to prove their identity to one another in a secure manner. Kerberos builds on symmetric-key cryptography and requires a trusted third party, the key distribution center (KDC), that shares a key with all other parties in the authentication realm. Clients and services exchange Kerberos messages with the KDC. Kerberos messages can be transported over either UDP or TCP on port 88. When sent over TCP however, each request and response is preceded by the length of the message as 4 octets in network byte order.
The Microsoft Windows Server operating systems implement the Kerberos version 5 authentication protocol. Each Active Directory domain controller runs an instance of the Kerberos KDC, which uses the domain's directory service database as its security account database. To authenticate, a client must have network connectivity to a domain controller.
Although this is generally the case for machines located within an organization's network, this may not be true for clients using remote connections. To enable remote workloads, notably services such as RDP Gateway and DirectAccess, it is possible to proxy Kerberos traffic over HTTPS using a KDC Proxy.
A KDC Proxy is an HTTP-based server that implements the Kerberos KDC Proxy Protocol (KKDCP). Clients wrap their Kerberos request in a KDC proxy message and send it in the body of an HTTPS POST request where the Request-URI is set to /KdcProxy. KDC proxy messages are defined using Abstract Syntax Notation One (ASN.1). ASN.1 is a standard interface description language (IDL) for defining data structures that can be serialized and deserialized in a cross-platform way. The full specification of ASN.1 including its lexical units, separators, recursive definitions, native data types, whitespace, production rules, etc. can be found here.
Here is the structure of KDC Proxy messages:
Where:
· kerb-message is a Kerberos message, including the 4 octet message length prefix.
· target-domain is a DNS or NetBIOS domain name that represents the realm to which the Kerberos message must be sent (target-domain is required for KDC proxy requests but is not used for KDC proxy responses).
· dclocator-hint is an optional field that contains additional data used to find a domain controller.
KDC Proxy messages are encoded using the Distinguished Encoding Rules (DER). DER is a type-length-value encoding system, each DER-encoded field has the following structure:
Identifier Octets encode the type of the Contents Octets. Generally, it consists of a single octet with the following structure:
The Class field can be one of Universal (bits: 00), Application Specific (bits: 01), Context-specific (bits: 10), or Private (bits: 11). The P/C field specifies whether the field is a primitive data type (bit: 0) such as INTEGER, or a constructed data type (bit: 1), i.e. whose Content Octets contain other primitive or constructed data types. If the Class field is Universal, then the specification defines several standard Tag Numbers such as BOOLEAN
(\x1), INTEGER (\x2), OCTET STRING (\x4), UTF8STRING (\x0C), SEQUENCE (\x10), IA5STRING (\x16), GeneralString (\x1B), etc. In the case of non-Universal classes, there are rules for encoding Tag Numbers larger than 30.
In DER, there are two ways to encode Length Octets. In the short form, a single Length Octet is used with the most significant bit set to 0, and the 7 remaining bits represent the number of Content Octets. In the long form, the most significant bit of the first Length Octet is set to 1, and the 7 remaining bits encode the number of subsequent Length Octets, which themselves contain the number of Content Octets. The long form is typically used only when necessary. All multibyte integers are in big-endian format.
As an example, here is how a KDC proxy request would look like after being encoded:
Indentation shows the relationship between constructed and primitive data types. Please note that the tag number for SEQUENCE is \x10
, however, the SEQUENCE field is a constructed data type with the P/C field is set to 1. Therefore, the Identifier Octet for SEQUENCE is 0x30
. The SEQUENCE items are assigned explicit tags from 0 to 2, and when encoded, they are encapsulated in an EXPLICIT tag data type. In the Identifier Octet for the EXPLICIT tag, the Class field is set to Context-Specific (bits: 10), and the P/C field is set to 1. Finally, Kerberos realms are encoded as KerberosString, which is an alias for GeneralString.
Upon reception of a KDC proxy request, the KDC proxy extracts the target-domain and locates a domain controller for that realm. First the KDC proxy queries the DNS SRV record for the name _kerberos._tcp.Default-First-Site-Name._sites.dc._msdcs.<target_domain>, and resolves matching A records if needed. Then the KDC proxy sends an LDAP ping to the resulting set of IP addresses. An LDAP ping is a connection-less LDAP (CLDAP) rootDSE search for the Netlogon attribute, used to verify the aliveness of a domain controller, and check whether it matches a specific set of requirements. The domain controller returns a little-endian byte string that encodes a NETLOGON_SAM_LOGON_RESPONSE_EX structure.
Finally, the KDC proxy extracts kerb-message from the KDC proxy request and forwards it to the domain controller. Please note that KDC proxy only forwards Kerberos requests over TCP. Please also note that, while it can only be run on domain-joined machines, KDC proxy will proxy Kerberos requests for arbitrary domains. When KDC Proxy receives a Kerberos response from the domain controller, it wraps it in a KDC proxy message (which only contains the kerb-message field), and returns it to the client in the body of an HTTPS 200 OK response.
An integer overflow has been reported for Microsoft Windows KDC Proxy. The vulnerability is due to a missing check for the length of Kerberos responses.
After sending the Kerberos request to the domain controller, the KDC proxy reads 4 bytes from the network socket to get the Kerberos response length. Then, it attempts to read as many bytes as required to get the full response. A number of functions are involved in reading the Kerberos response, all of them are passed a pointer to a _KPS_IO structure as argument. _KPS_IO structures have a size of 0x120
bytes, here is a partial definition below (all structure definitions in this section were determined by reverse engineering; most structure and field names were chosen by us):
Whenever subsequent bytes are read from the socket, function KpsSocketRecvDataIoCompletion() in DLL file kpssvc.dll is called. It checks if enough bytes were read to get the full response, and if yes calls the function KpsPackProxyResponse(), passing a pointer to the _KPS_IO structure as an argument. KpsPackProxyResponse() first calls function KpsCheckKerbResponse() that validates the Kerberos response. Notably, if the byte that immediately follows the message length prefix is set to `0x7E` or `0x6B`, KpsCheckKerbResponse() verifies that the response is a properly constructed Kerberos message. If it is not the case, it does not perform any validation and returns without error.
KpsPackProxyResponse() local variables include a structure of type ASN1_KDC_PROXY_MSG. ASN1_KDC_PROXY_MSG structures have a size of `0x28` bytes, here is a partial definition below:
After calling KpsCheckKerbResponse(), KpsPackProxyResponse() initializes the structure as such: ASN1_KDC_PROXY_MSG.buf is set to _KPS_IO.recvbuf, and ASN1_KDC_PROXY_MSG.len is set to _KPS_IO.bytesread. Then, for wrapping the Kerberos response in a KDC proxy response it calls the function KpsDerPack(), passing the address of the ASN1_KDC_PROXY_MSG structure as an argument. From this moment on, the code flow alternates between functions from the DLL file kpssvc.dll implementing the KDC proxy server and functions from the Microsoft ASN.1 library msasn1.dll. The latter are subsequently referred to as "MSASN.1" functions.
KpsDerPack() calls MSASN.1 function ASN1_CreateEncoder(), which allocates a structure of type ASN1_encoder. ASN1_encoder structures have a size of `0x50` bytes, here is a partial definition below:
KpsDerPack() then calls MSASN.1 function ASN1_Encode(), passing pointers to the ASN1_encoder and ASN1_KDC_PROXY_MSG structures as arguments. ASN1_Encode() calls function ASN1Enc_KDC_PROXY_MESSAGE(). ASN1Enc_KDC_PROXY_MESSAGE() calls the MSASN.1 function ASN1BEREncExplicitTag(), passing a pointer to the ASN1_encoder structure as an argument. ASN1BEREncExplicitTag() is called twice, to encode the SEQUENCE and EXPLICIT fields.
Encoded data is appended to ASN1_encoder.buf, and the buffer is allocated then re-allocated as fields are being encoded. To do this, MSASN.1 functions call ASN1EncCheck(), passing the needed size as an argument. For the initial allocation, ASN1EncCheck() allocates space in the heap by calling the Windows API function LocalAlloc(). The size of the initial allocation is at least 1,024 bytes. During subsequent invocations, ASN1EncCheck() reallocates the buffer if it cannot fit the needed size. In that case, it adds the current size of the buffer and the needed size, then passes the result as an argument to the Windows API function LocalReAlloc().
ASN1BEREncExplicitTag() calls MSASN.1 function ASN1BEREncTag(). ASN1BEREncTag() encodes the Identifier Octets, first by calling ASN1EncCheck() to make sure ASN1_encoder.buf has enough space, then writing the Identifier Octets at the address ASN1_encoder.current, and finally incrementing ASN1_encoder.current. At this
stage, the length of constructed fields is not known, as it depends on the length of other constructed and primitive fields that are yet to be encoded. So ASN1BEREncExplicitTag() reserves a single byte for the Length Octets in ASN1_encoder.buf by calling ASN1EncCheck() with size 1, and incrementing ASN1_encoder.current by 1.
ASN1Enc_KDC_PROXY_MESSAGE() then calls MSASN.1 function ASN1DEREncOctetString() to encode the kerb-message OCTET STRING field, passing as arguments a pointer to the ASN1_encoder structure as well as ASN1_KDC_PROXY_MSG.buf and ASN1_KDC_PROXY_MSG.len. ASN1DEREncOctetString() is an alias for the function ASN1BEREncCharString(). ASN1BEREncCharString() first calls ASN1BEREncTag() to encode the Identifier Octets, then it calls ASN1BEREncLength(), passing ASN1_KDC_PROXY_MSG.len as argument.
ASN1BEREncLength() first computes the number of bytes required for encoding the Length Octets, adds ASN1_KDC_PROXY_MSG.len, and then passes the resulting value as an argument to ASN1EncCheck(). This ensures that ASN1_encoder.buf has enough space for both the Length Octets and the Contents Octets. ASN1BEREncLength() then writes the Length Octets at address ASN1_encoder.current, and finally increments ASN1_encoder.current by the size of Length Octets. Finally, ASN1BEREncCharString() calls the Windows API function memcpy() to copy ASN1_KDC_PROXY_MSG.len from address ASN1_KDC_PROXY_MSG.buf to address ASN1_encoder.current.
However, MSASN.1 functions do not always handle unexpected inputs properly, notably, they don't check for possible integer overflows when handling large length values. Furthermore, KpsSocketRecvDataIoCompletion() does not check the length of the Kerberos response before calling KpsPackProxyResponse(). Finally, the Kerberos response validation in KpsCheckKerbResponse() can be bypassed by setting the byte immediately following the message length prefix to any value other than 0x7E
or 0x6B
. As a consequence, it is possible for a malicious domain controller to send a large Kerberos response that will cause memory corruption errors.
Integer overflows and memory corruption errors occur when encoding the kerb-message OCTET STRING field. At this point, both SEQUENCE and EXPLICIT fields have already been encoded, ASN1_encoder.buf points to a buffer of size 1,024, and ASN1_encoder.current points at the address ASN1_encoder.buf + 4. The maximum size for Kerberos responses accepted by KDC Proxy is 4,294,967,295. If sending a Kerberos response with a length from 4,294,967,291 to 4,294,967,295 (inclusive), ASN1BEREncLength() will find that 5 bytes are required to encode the Length Octets, then add the length of the Kerberos response. However, the addition result is stored in a 4-byte unsigned variable that overflows. As a consequence, the size passed as an argument to ASN1EncCheck() is very small. ASN1EncCheck() does not reallocate the ASN1_encoder.buf buffer and later, when ASN1BEREncCharString() calls memcpy() a heap buffer overflow occurs.
Alternatively, when sending a Kerberos response with a length from 4,294,966,267 to 4,294,967,290 (inclusive), ASN1BEREncLength() calls ASN1EncCheck(). As the current ASN1_encoder.buf buffer is too small, ASN1EncCheck() proceeds to reallocate it. It adds the current size of the buffer (1,024) to the length of the Kerberos response. However, the addition result is stored in a 4-byte unsigned variable that overflows. As a consequence,
LocalReAlloc() actually decreases the size of the buffer. Later when ASN1BEREncCharString() calls memcpy() an out-of-bounds write or a heap buffer overflow occurs. As an interesting edge case, it is possible to pass 0 as the new size to LocalReAlloc(). LocalReAlloc() returns a memory address, not an error, however, the memory is not actually allocated, and an access violation occurs when attempting to write to that address.
A remote, unauthenticated attacker could direct KDC proxy to forward a Kerberos request to a server under their control, which would then send back a crafted Kerberos response. Successful exploitation could result in arbitrary code execution in the security context of the target service.
Note: to reach the vulnerable code, it is not enough to send a short Kerberos response with a large message length prefix value in the first four bytes. The Kerberos response length must actually match the prefix value.
Detection Guidance
To detect an attack exploiting this vulnerability, the detection device must monitor and parse traffic on UDP port 389 and TCP port 88. Kerberos messages can be transported over either UDP or TCP on port 88. However, when sent over TCP, each request and response is preceded by the length of the message as 4 octets in network byte order.
The detection device must inspect Kerberos responses. Please note that KDC Proxy only uses TCP port 88 for Kerberos traffic (not UDP). Therefore, the device does not need to fully parse Kerberos responses. It just needs to parse the 4-byte message length prefix and be able to isolate responses within a TCP stream. If a Kerberos response is 0x80000000 (2,147,483,648) bytes or longer the traffic should be considered suspicious, an attack exploiting this vulnerability is likely underway.
Note: The detection guidance above is based on section 7.2.2 of the Kerberos V5 RFC. It mentions that, in the 4 octets message length prefix, the high bit must be set to 0. So, according to the RFC, the maximum length of Kerberos messages transmitted over TCP is 0x7FFFFFFF.
Questions About the Patch
Our research shows that the vulnerability lies in the ASN.1 library, however, the Microsoft advisory mentions the KDC Proxy server. Furthermore, the vulnerability was addressed by adding a length check in the KDC Proxy KpsSocketRecvDataIoCompletion() function. It is unclear why Microsoft chose this approach. It is possible that the ASN.1 library is known to have bugs, and that is expected for invoking software to check its inputs. It is also unclear whether any other software components can be used to trigger the vulnerability in the ASN.1 library. As such, the present report focuses on the KDC Proxy server.
Conclusion
This vulnerability was patched by the vendor in November. To date, no attacks have been detected in the wild. Microsoft doesn’t provide any mitigations for this bug, but they do note only servers configured as a KDC server are affected. Domain controllers are not impacted by this issue. They also note that since the vulnerability exists in the KDC Proxy Server service (KDCSVC), you are only vulnerable if you are already using KPSSVC in your environment. If you do not have it configured in your environment, then this vulnerability is not exploitable. We recommend all instances of KPSSVC server be patched immediately.
Special thanks to Simon Humbert and Guy Lederfein of the Trend Micro Research Team for providing such a thorough analysis of this vulnerability. For an overview of Trend Micro Research services please visit http://go.trendmicro.com/tis/.
The threat research team will be back with other great vulnerability analysis reports in the future. Until then, follow the team on Twitter, Mastodon, LinkedIn, or Bluesky for the latest in exploit techniques and security patches.