In August 2019 Microsoft announced it had patched a collection of RDP bugs, two of which were wormable. The wormable bugs, CVE-2019-1181 & CVE-2019-1182 affect every OS from Windows 7 to Windows 10. There is some confusion about which CVE is which, though it’s possible both refer to the same bug. The vulnerable code exist in both the RDP client and server, making it possible to exploit in either direction.
From my previous work with RDP, I believe the DecodeFormatData and SendFormatDataRequest functions to only be callable post-authentication; leaving just the Decompress function.
The original code (left) is a bit of a mess because it has been optimized into a single if statement. The updated code (right) is slightly less of a mess, and has some additional validation.
Line 24 adds code which checks if v11 + 0x2000 is smaller than v11. If the condition is true an error code is set, which later results in the function aborting. You may be asking how the check could possibly fail. How can adding 0x2000 to an integer make it smaller than before? The answer is: via an integer overflow.
An unsigned integer is 4 bytes and can hold any value between 0 and 0xFFFFFFFF (4,294,967,295). If an integer is incremented past its max value, it loops around to zero. So, if v11 was 0xFFFFFFFF, adding 0x2000 to it would result in a final value of 0x1FFF, which is smaller than the original.
v11 is used as the size for a heap allocation performed via the “new” operator. The assumption here would be that if we can overflow v11, it’d cause the allocation to be smaller than the decompressed data, leading to a heap overflow.
Some Reversing Required
To get a better understanding of how to exploit the integer overflow, I needed to know what values I control and how to call the vulnerable function. To do this, I xref’d DecompressUnchopper::DecompressUnchopper (the initializer function for the DecompressUnchopper class). Next, I continued to use “xrefs to” to walk backwards until I found the function which utilizes this class. Eventually, I came to CRdpDynVCMgr::HandleIncomingDvcData.
From my previous work with RDP (hours of reading docs), I knew that DynVC was short for “Dynamic Virtual Channel”. The DVC interface allows for communication between modules on the client and server, and supports both raw and compressed data. It’s likely the vulnerable “decompress” function can be hit by sending compressed data.
Already having a custom RDP client written for exploiting BlueKeep, I added code to open the DVC channel and send some test data.
After attaching the debugger to the RDP server process, I set a breakpoint on DecompressUnchopper::Decompress and ran my code.
rdx (2nd argument) is the test data I sent, and r8 (3nd argument) is the data length. Now knowing the function parameters, I was able to clean up the decompiled code.
Now, it’s time to see if I can trigger a crash.
Overflowing The Heap
In order to test the vulnerability by crashing the RDP server, I crafted a malicious DVC packet.
I set the uncompressedSize field to 1 – 0x2000 (0xFFFFE001), so that when 0x2000 is added it will loop around to 1. Then I set the compressed data to contain the letter ‘A’ repeated 0x200 times, which should result in the heap buffer being overflowed by 0x1FF bytes.
Awesome, now I can write arbitrary data of arbitrary size to neighboring heap allocations! This bug is powerful because object instances are stored on the same heap, making it possible to overwrite them.
For example, I changed an object’s VTable pointer so that the application would perform a ptr call via the address 0x1337133713371337.