RDP to RCE: When Fragmentation Goes Wrong

Remote Desktop Gateway (RDG), previously known as Terminal Services Gateway, is a Windows Server component that provides routing for Remote Desktop (RDP). Rather then users connecting directly to an RDP Server, users instead connect and authenticate to the gateway. Upon successful authentication, the gateway will forward RDP traffic to an address specified by the user, essentially acting as a proxy. The idea is that only the gateway needs to be exposed to the Internet, leaving all RDP Servers safely behind the firewall. Due to the fact that RDP is a much larger attack surface, a setup properly using RDG can significantly reduce an organization’s attack surface.

In the January 2020 security update, Microsoft addressed two vulnerabilities in RDG. The bugs, CVE-2020-0609 and CVE-2020-0610, both allow for pre-authentication remote code execution.

Looking at the diff

The first step to analyze these bugs is to look at the difference between the original and patched versions of the affected DLL.

A BinDiff of the RDG executable before and after installing the patch.

It is clear only one function has been been modified. RDG supports three different protocols: HTTP, HTTPS, and UDP. The updated function is responsible for handling the latter. Normally, one would show a side-by-side comparison of the function before and after patch. Unfortunately, the code is extremely large and there are many changes. Instead, we have opted to present a pseudo-code representation of the function, in which irrelevant code has been stripped.

Pseudo-code for the UDP handler function

The RDG UDP protocol allows for large messages to be split across multiple separate UDP packets. Due to the property that UDP is connectionless, packets can arrive out of order. The job of this function is to re-assemble messages, ensuring each part is in the correct place. Every packet contains a header containing with the following fields:

  • fragment_id: the packet’s position in the sequence
  • num_fragments: the total number of packets in the sequence
  • fragment_length: the length of the packet’s data

The message handler uses the packet headers to ensure the message is re-assembled in the correct order, and no parts are missing. However, the implementation of this function introduces some bugs which can be exploitable.

CVE-2020-0609

The packet handler's bounds checking.

memcpy_s copies each fragment to an offset within the reassembly buffer,  which is allocated on the heap. The offset for each fragment is calculated by multiplying the fragment id by 1000. However, the bounds checking does not take the offset into account. Let’s assume buffer_size is 1000, and we send a message with 2 fragments.

  • The 1st fragment (fragment_id=0) has a length of 1. this->bytes_written is 0, so the bounds check passes.
  • 1 byte is written to the buffer at offset 0, and bytes_written is incremented by 1. The 2nd fragment (fragment_id=1) has  length of 998. this->bytes_written is 1, and 1 + 998 is still smaller than 1000, so the bounds check passes.
  • 998 bytes are written to the buffer at offset 1000 (fragment_id*1000), which results in writing 998 bytes past the end of the buffer.

Something to note is that packets don’t have to be sent in order (remember, it’s UDP). So if the first packet we send has fragment_id=65535‬ (the maximum), it will be written to offset 65535*1000, a full 65534000 bytes past the end of the buffer. By manipulating the fragment_id, it’s possible to write up to 999 bytes anywhere between 1 and 65534000 after the end of the buffer. This vulnerability is much more flexible than a typical linear heap overflow. It allows us to not only control the size of the data written, but the offset to where it’s written. With the extra control, it’s easier to do more precise writes, avoiding unnecessarily data corruption.

CVE-2020-0610

The packet handler's tracking of which fragments have been received.

The class object maintains an array of 32-bit unsigned integers (one for each fragment). Once a fragment has been received, the corresponding array entry is set from 0 to 1. Once every element is set to 1, the message re-assembly is complete and the message can be processed. The array only has space for up to 64 entries, but the fragment ID can be between 0 and 65535. The only verification is that fragment_id is less than num_fragments (which can also be set to 65535). Therefore, setting the fragment_id to any value between 65 and 65535 will allow us to write a 1 (TRUE) outside the bounds of the array. Whilst being able to set a single value to 1 may seem implausible to turn into an RCE, even the tiniest modifications can have a huge impact on program behavior.

Mitigations

If for whatever reason you are unable to install the patch, it is still possible to prevent exploitation of these vulnerabilities. RDG supports the HTTP, HTTPS, and UDP protocols, but the vulnerabilities only exist in the code responsible for handling UDP. Simply disabling UDP Transport, or firewalling the UDP port (usually port 3391) is sufficient to prevent exploitation.

Remote Desktop Gateway Settings

Future work and detection

In our efforts to improve detection capabilities, some of our research includes passive and active data capabilities for scanning for vulnerabilities like CVE-2020-0609 and CVE-2020-0610. As part of our platformization of threat intelligence, we have begun adding vulnerability information to Telltale, allowing organizations to determine if they are at risk.