A Debugging Primer with CVE-2019–0708

By: @straight_blast ; straightblast426@gmail.com

The purpose of this post is to share how one would use a debugger to identify the relevant code path that can trigger the crash. I hope this post will be educational to people that are excited to learning how to use debugger for vulnerability analysis.

This post will not visit details on RDP communication basics and MS_T120. Interested readers should refer to the following blogs that sum up the need to know basis:

Furthermore, no PoC code will be provided in this post, as the purpose is to show vulnerability analysis with a debugger.

The target machine (debuggee) will be a Windows 7 x64 and the debugger machine will be a Windows 10 x64. Both the debugger and debuggee will run within VirtualBox.

Setting up the kernel debugging environment with VirtualBox

  1. On the target machine, run cmd.exe with administrative privilege. Use the bcdedit command to enable kernel debugging.
bcdedit /set {current} debug yes
bcdedit /set {current} debugtype serial
bcdedit /set {current} debugport 1
bcdedit /set {current} baudrate 115200
bcdedit /set {current} description "Windows 7 with kernel debug via COM"

When you type bcdedit again, something similar to the following screenshot should display:

Image for post

2. Shutdown the target machine (debuggee) and right click on the target image in the VirtualBox Manager. Select “Settings” and then “Serial Ports”. Copy the settings as illustrated in the following image and click “OK”:

Image for post

3. Right click on the image that will host the debugger, and go to the “Serial Ports” setting and copy the settings as shown and click “OK”:

Image for post

4. Keep the debuggee VM shutdown, and boot up the debugger VM. On the debugger VM, download and install WinDBG. I will be using the WinDBG Preview edition.

5. Once the debugger is installed, select “Attach to kernel”, set the “Baud Rate” to “115200" and “Port” to “com1”. Click on the “initial break” as well.

Image for post

Click “OK” and the debugger is now ready to attach to the debuggee.

Image for post

6. Fire up the target “debuggee” machine, and the following prompt will be displayed. Select the one with “debugger enabled” and proceed.

Image for post

On the debugger end, the WinDBG will have established a connection with the debuggee. It is going to require a few manual enter of “g” into the “debugger command prompt” to have the debuggee completely loaded up. Also, because the debugging action is handled through “com”, the initial start up will take a bit of time.

Image for post

7. Once the debuggee is loaded, fire up “cmd.exe” and type “netstat -ano”. Locate the PID that runs port 3389, as following:

Image for post

8. Go back to the debugger and click on “Home” -> “Break” to enable the debugger command prompt and type:

!process 0 0 svchost.exe

This will list a bunch of process that is associated with svchost.exe. We’re interested in the process that has PID 1216 (0x4C0).

Image for post

9. We will now switch into the context of svchost.exe that runs RDP. In the debugger command prompt, type:

.process /i /p fffffa80082b72a0
Image for post

After the context switched, pause the debugger and run the command “.reload” to reload all the symbols that the process will use.

Identifying the relevant code path

Without repeating too much of the public information, the patched vulnerability have code changed in the IcaBindVirtualChannels. We know that if IcaFindChannelByName finds the string “MS_T120”, it calls IcaBindchannel such as:

_IcaBindChannel(ChannelControlStructure*, 5, index, dontcare) 

The following screenshots depicts the relevant unpatched code in IcaBindVirtualChannels:

Image for post

We’re going to set two breakpoints.

One will be on _IcaBindChannel where the channel control structure is stored into the channel pointer table. The index of where the channel control structure is stored is based on the index of where the Virtual Channel name is declared within the clientNetworkData of the MCS Initial Connect and GCC Create packet.

Image for post

and the other one on the “call _IcaBindChannel” within the IcaBindVirtualChannels.

Image for post

The purpose of these breakpoints areto observe the creation of virtual channels and the orders these channels are created.

bp termdd!IcaBindChannel+0x55 ".printf \"rsi=%d and rbp=%d\\n\", rsi, rbp;dd rdi;.echo"bp termdd!IcaBindVirtualChannels+0x19e ".printf \"We got a MS_T120, r8=%d\\n\",r8;dd rcx;r $t0=rcx;.echo"

The breakpoint first hits the following, with an index value of “31”:

Image for post

Listing the call stack with “kb” shows the following:

Image for post

We can see the IcaBindChannel is called from a IcaCreateChannel, which can be traced all the way to the rdpwsx!MSCreateDomain. If we take a look at that function under a disassembler, we noticed it is creating the MS_T120 channel:

Image for post

Also, but looking at the patched termdd.sys, we know that the patched code enforces the index for MS_T120 virtual channel to be 31, this first breakpoint indicates the first channel that gets created is the MS_T120 channel.

The next breakpoint hit is the 2nd breakpoint (within the IcaBindVirtualChannel), followed by the 1st breakpoint (within IcaBindChannel) again:

Image for post

This gets hit as it observed the MS_T120 value from the clientNetworkData. If we compared the address and content displayed in above image with the one way, way above, we can see they’re identical. This means both are referring to the same channel control structure. However, the reference to this structure is being stored at two different locations:

rsi = 31, rbp = 5; 
[rax + (31 + 5) * 8 + 0xe0] = MST_120_structure
rsi = 1, rbp = 5;
[rax + (1 + 5) * 8 + 0xe0] = MS_T120_structure

In another words, there are two entries in the channel pointer table that have references to the MS_T120 structure.

Afterwards, a few more channels are created which we don’t care about:

Image for post
index 7 with offset 5
Image for post
index 0 with offset 0 and 1
Image for post
index 0 with offset 3 and 4

The next step into finding other relevant code to look at will be to set a break read/write on the MS_T120 structure. It is with certain the MS_T120 structure will be ‘touch’ in the future.

I set the break read/write breakpoint on the data within the red box, as shown in the following:

Image for post

As we proceed with the execution, we get calls to IcaDereferenceChannel, which we’re not interested in. Then, we hit termdd!IcaFindChannel, with some more information to look into from the call stack:

Image for post

The termdd!IcaChannelInput and termdd!IcaChannelInputInternal sounds like something that might process data sent to the virtual channel.

A pro tip is to set breakpoint before a function call, to see if the registers or stacks (depending how data are passed to a function) could contain recognizable or readable data.

I will set a breakpoint on the call to IcaChannelInputInternal, within the IcaChannelInput function:

Image for post
bp termdd!IcaChannelInput+0xd8
Image for post

We’re interested in calls to the IcaChannelInput breakpoint after IcaBindVirtualChannels has been called. From the above image, just right before the call to IcaChannelInputInternal, the rax register holds an address that references to the “A”s I passed over as data through the virtual channel.

I will now set another set of break on read/write on the “A”s to see what code will ‘touch’ them.

ba r8 rax+0xa 

The reason I had to add 0xA to the rax register is because the break on read/write requires an align address (ends in0x0 or 0x8 for x64 env)

Image for post

So the “A”s are now being worked in a “memmove” function. Looking at the call stack, the “memmove” is called from the “IcaCopyDataToUserBuffer”.

Image for post

Lets step out (gu) of the “memmove” to see where is the destination address that the “A”s are moving to.

Image for post

Which is here looking at it from the disassembler:

Image for post

The values for “Src”, “Dst” and “Size” are as follow:

Image for post
Src
Image for post
Dst
Image for post
Size (0x20)

So the “memmove” copy “A”s from an kernel’s address space into a user’s address space.

We will now set another groups of break on read/write on the user’s address space to see how these values are ‘touched’

ba r8 00000000`030ec590
ba r8 00000000`030ec598
ba r8 00000000`030ec5a0
ba r8 00000000`030ec5a8

(side note: If you get a message “Too many data breakpoints for processor 0…”, remove some of the older breakpoints you set then enter “g” again)

We then get a hit on rdpwsx!IoThreadFunc:

Image for post

The breakpoint touched the memory section in the highlighted red box:

Image for post

The rdpwsx!IoThreadFunc appears to be the code that parses and handle the MS_T120 data content.

Image for post

Using a disassembler will provide a greater view:

Image for post
Image for post

We will now use “p” command to step over each instruction.

Image for post

It looks like because I supplied ‘AAAA’, it took a different path.

According to the blog post from ZDI, we need to send crafted data to the MS_T120 channel (over our selected index), so it will terminate the channel (free the MS_T120 channel control structure), such that when the RDPWD!SignalBrokenConnection tries to reach out to the MS_T120 channel again over index 31 from the channel pointer structure, it will Use a Freed MS_T120 channel control structure, leading to the crash.

Based on the rdpwsx!IoThreadFunc, it appears to make sense to create crafted data that will hit the IcaChannelClose function.

When the crafted data is correct, it will hit the rdpwsx!IcaChannelClose

Image for post

Before stepping through the IcaChannelClose, lets set a breakpoint on the MS_T120 control channel structure to see how does it get affected

Image for post
fffffa80`074fcac0 is the current address for the MS_T120 structure
Image for post
A breakpoint read is hit on fffffa80`074fcac0

The following picture shows the call stack when the breakpoint read is hit. A call is made to ExFreePoolWithTag, which frees the MS_T120 channel control structure.

Image for post

We can proceed with “g” until we hit the breakpoint in termdd!IcaChannelInput:

Image for post

Taking a look at the address that holds the MS_T120 channel control structure, the content looks pretty different.

Furthermore, the call stack shows the call to IcaChannelInput comes from RDPWD!SignalBrokenConnection. The ZDI blog noted this function gets called when the connection terminates.

Image for post

We will use “t” command to step into the IcaChannelInputInternal function. Once we’re inside the function, we will set a new breakpoint:

bp termdd!IcaFindChannel
Image for post

Once we’re inside the IcaFindChannel function, use “gu” to step out of it to return back to the IcaChannelInputInternal function:

Image for post
The MS_T120 object address is different to other MS_T120 object shown above, as these images are taken aross different debugging session

The rax registers holds the reference to the freed MS_T120 control channel structure.

As we continue to step through the code, the address at MS_T120+0x18 is being used as an parameter (rcx) to the ExEnterCriticalRegionAndAcquireResourceExclusive function.

Image for post

Lets take a look at rcx:

Image for post

And there we go, if we dereference rcx, it is nothing! So lets step over ExEnterCriticalRegionAndAcquireResourceExclusive and see the result:

Image for post

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store