Introduction:
The following text outlines a potential path for exploitation of CVE-2019-0708 (BlueKeep). It is
certain that some people will disagree with releasing this text. Reasons why I am releasing:
■ It is released in the spirit of open knowledge.
■ It is an attempt to give back to the hacker community from whom I have learned so much
thanks to their willingness to share information.
■ The information within here is largely already available within the Chinese hacker
community [1].
■ The attack path that follows is geared towards Windows XP, while it may be technically
possible on Windows 7 or Server 2008, it is more likely to BSOD.
■ No exploit code or ring 0 to ring 3 shellcode is shared within this text. (RDP connection
code is available at [2])
■ Details are left out.
Technical Analysis
CVE-2019-0708 affects Windows XP through Windows Server 2008. A use after free (UAF)
condition exists within the termdd.sys RDP kernel driver. A remote, unauthenticated attacker
can exploit this vulnerability by establishing an RDP connection to the target server, opening an
MS_T120 virtual channel, and sending crafted data to it. Successful exploitation will result in the
attacker executing arbitrary code with kernel-level privileges or causing a denial-of-service. For
a full detailed analysis how to trigger the UAF condition consult [3]. The following analysis will
assume a base level of knowledge from the previous article.
A first step after understanding how to trigger the UAF is to understand how the dangling pointer
is used after it is freed. Due to our knowledge from [3] we know that the dangling pointer is
returned by IcaFindChannel within the IcaChannelInputInternal function within termdd.sys. A
good place to start then is analyzing the code after the IcaFindChannel call. To disassemble
termdd.sys I will be utilizing radare2 [4]. This will not be a radare2 tutorial, but the following
commands will help you get started if you would like to follow along (for more information on
using radare2 see [5]):
Open termdd.sys with radare2:
# r2 termdd.sys
Download the debugging symbols:
> idpd
Load the debugging symbols:
> idp
Run analysis:
> aaa
After seeking to IcaChannelInputInternal and reviewing the code after IcaFindChannel we see
the following:
We first see that the dangling pointer (in eax after return from IcaFindChannel) is moved into
edi . Thus for now we are largely concerned with instructions that deal with edi . After reviewing
this set of instructions something very interesting stands out immediately. At 0x11779 we see
the instruction mov, eax, dword [edi + 0x50] , and then 8 instructions later at 0x1178b
we see call dword [eax] . Already we can see how we might control EIP!
It is useful to take a step back and think about the vulnerability class and how we might be able
to exploit this instance of it. A use after free is exactly its name – memory is used after it has
been freed. Our dangling pointer in edi is pointing to memory that has been returned (freed) to
the memory manager. The memory manager can now allocate that same memory to another
requestor. In other words, edi is referencing invalid data within the context of the
IcaChannelInputInternal function. This will inevitably cause a blue screen of death, or arbitrary
code execution if we have anything to do about it :).
Given this information we formulate a high level attack plan:
1. Establish an RDP connection with the MS_T120 virtual channel.
2. Send specific data on MS_T120 virtual channel to free channel control structure.
3. Invoke an allocation with data controlled by us to occupy the freed channel control
structure memory space.
4. Control EIP
To accomplish step 3 we need to first understand a few things. Namely, what is the size of the
channel control structure and what type of memory it is (paged or non-paged). This is best
accomplished using Windbg. We set a breakpoint on IcaFindChannel within
IcaChannelInputInternal and send data on the MS_T120 channel. We see our sent data is at
ebp+18 , and the channel control structure pointer is 0x8238ccb8 .
Next we use the handy !pool command to find more about this allocated memory:
…
From this output we can see that the allocated memory region is non-paged pool. Further down
we see our channel control structure memory. It starts at 0x8238ccb0 and is size 0x98 .
We have our size and memory type, but how actually to allocate the memory? Allocation
requests for pool memory are typically serviced through the function call ExAllocatePoolWithTag
(see [6] for more information on windows pool). We need to locate this function call, but not any
one will do. We have very strict requirements:
1. Must allocate non-paged memory.
2. Must be able to allocate an arbitrary size controlled by us.
3. Must eventually contain data controlled by us.
The code base for RDP is huge and built upon many layers. There are lots of places we could
potentially look. However, it is best to start simple and start the search in termdd. Going back to
radare2 we first locate ExAllocatePoolWithTag:
Next we want to find all the references to it:
One stands out as particularly interesting, and that is the reference within
IcaChannelInputInternal. Let’s review the code:
Reviewing the arguments for ExAllocatePoolWithTag [7]:
Promising, as neither the size nor the pool type are hardcoded. Let’s place a breakpoint on this
call within IcaChannelInputInternal and send variable size data on our virtual channel:
Here we see we are within IcaChannelInputInternal with the data we sent. PoolType is 0
(non-paged) and size is 0x84 . This call then meets requirement 1. Going to the next packet we
sent:
The next size is 0x98. It is looking very likely that we directly control the size of this allocation.
This allocation is looking very promising, to find out more about how the allocated memory is
used let’s place a read/write breakpoint on it and continue execution:
A few instructions later our breakpoint is hit within the same function:
Looking at the previous instructions rep movs byte ptr es:[edi], byte ptr [esi]
stands out as it is used to copy memory from one buffer to another. Let’s check our pointer that
was returned from the ExAllocatePoolWithTag call:
That checks off requirement #3. IcaChannelInputInternal is truly a function sent by the RDP
exploit gods. It contains everything we need for RCE.
Further filling out the attack plan we now have:
1. Establish an RDP connection with the MS_T120 virtual channel.
2. Send specific data on MS_T120 virtual channel to free channel control structure.
3. Invoke allocations via call to ExAllocatePoolWithTag in IcaChannelInputInternal such
that the freed memory space is occupied with our data.
4. Control EIP via vtable call by placing function pointer to our shellcode at [edi + 50]
within our fake allocated channel control structure.
5. Break the connection to trigger UAF
6. Obtain RCE
Items to think about:
1. Where is our shellcode located?
2. Can we run any plain old userspace shellcode?
3. Are we able to send data on a channel that we’ve closed?
4. If we’ve accomplished the above how do we exit cleanly such that we don’t immediately
BSOD after shellcode executes?
Source:
https://dl.packetstormsecurity.net/papers/general/analyzing-bluekeep.pdf