So before I start designing concepts on how embedded devices with outdated OS can be RCE'ed via Bluekeep (CVE-2019-0708), I thought I could design a PoC script myself for a better understanding of the vulnerability itself.

Note: the whole analysis was done in Windows 7 32-bit environment

Before reading the post, please understand that I'm a REAL NOOB in payload generation, so if there are some parts you may not like or if there is a room for improvement, please write them in the comments and I'll thank you a hundred times.

First of all, despite having used RDP so much in my life for conveniently accessing my Windows server back at home, I had no clue how it worked, other than that the default port was 3389. A document from Microsoft really helped with giving me a better understanding in the concept, and I've found out that there were a plethora of protocols involved in the whole connection sequence.

Then, I needed to understand where the vulnerability originated. To do so, I downloaded the Windows 7 patch distributed by Microsoft and bindiffed to see what changes have been made.

Vulnerability Analysis

After downloading the .msu patch file, I unpacked it via:

expand –f:* patch.msu <destination-folder>
unpacked msu

From the 7 files, the core files are in the .cab file so again, we'll unpack it:

expand –f:* Windows6.1-KB4499175-x86.cab <destination-folder>
unpacked cab

So yeah, a whole bunch of files here that's held within these 1,573 folders which would take my eternity to bindiff. Fortunately, we know that the RDS key server drivers are located in the file "termdd.sys"

bindiff results

So IcaBindVirtualChannels is what we're gonna take a look at. (IcaRebindVirtualChannels has the same patched logic, so only looking at the bind one will do the job)

Original termdd.sys
Patched termdd.sys

So according to the patch, if an incoming RDP connection request contains a bind request to the channel "MS_T120", it would forcibly bind the channel number to 31.

Microsoft did not specify the usage of this already internally used MS_T120 channel. However, the goof they made here is that they did not block the incoming external requests from using this channel.

So when a client requests "MS_T120", two channel numbers (including #31) will point to MS_T120 at the same time, and if the client deallocates all the channels, the channel #31 will be pointing a null space, which leads to a Use After Free (UAF) vulnerability.

TBD: kd analysis

So through this, we now know that putting 2 in eax by passing the value over Channel Data will deallocate all the channels.

Proof of Concept Design

So since now we know what part triggers the vulnerability, we can now design a code that sends a RDP request packet that specifically requires MS_T120 and deallocates all the channels.

As aforementioned, I first figured out the connection sequence of RDP using this Microsoft document.

Note: I intentionally left out all optional sequences and security features since this is a mere PoC.

After creating the whole RDP request code with the Microsoft Annotated RDP Connection Sequence Example document, all left to do was adding MS_T120 to the channel list and passing over 2 via channel data to deallocate all channels.

So in clientNetworkData part of MCS Connect Initial PDU, where all the channel request sections are in, I changed the third channel rdpsnd to MS_T120 and removed all the options.

# Channel 3
buf += "4d535f5431323000"  # name = MS_T120
buf += "00000000"  # options

Then, I deallocated all the channels:

def trigger(tls):
    buf = "0300002e"  # tpktHeader, 0x2e = 46 bytes
    buf += "02f080"  # x224Data
    buf += "64000703ef7014"  # mcsSDrq - 0x14 = 20 bytes
    # optional securityHeader omitted
    buf += "0c0000000300000000000000020000000000000000000000"

    packed = bytes.fromhex(buf)
    tls.sendall(packed)

For the last, since I'm not on the level to do RCE yet, I closed the connection via:

def MCS_Disconnect_Provider_Ultimatum_PDU(tls):
    buf = "03000009"  # tpktHeader, 0x09 = 9 bytes
    buf += "02f080"  # x224Data
    buf += "2180"
    packed = bytes.fromhex(buf)

    tls.sendall(packed)

That's it for the PoC code, sending the request:

We can see that it's working as intended looking at the BSOD caused by the memory error we intentionally triggered.

I have uploaded my PoC code to Github, so don't forget to check it out, and I'm open to all suggestions and comments, feel free to leave it below.