# CVE-2024-43639: Remote Code Execution in Microsoft Windows KDC Proxy
March 04, 2025 | Trend Micro Research Team
_In 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._, 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.