Jump to content
Sign in to follow this  
Nytro

A Debugging Primer with CVE-2019–0708

Recommended Posts

A Debugging Primer with CVE-2019–0708

Go to the profile of Bruce Lee
Bruce LeeFollow
May 29
 

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:

1*AIzE7lB100OxAA5TFroE5Q.png

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”:

1*wt0WogyFttm4uFebtkmpYg.png

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”:

1*Kb4_P4v7o3yA5FOBwg_04g.png

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.

1*IIbwGiQeqoVBaptY15LWJQ.png

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

1*OMaH8p1OtrHZUjkjmpcAqg.png

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

1*z89v_TrIHlATFcqteH-39Q.png

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.

1*IzFQCRqw7LOMSo_4ET8mCw.png

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

1*GeNibmgftlPi-pC-AWRajQ.png

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).

1*hnlWU3RncscxHJwGxHVmfw.png

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

.process /i /p fffffa80082b72a0
1*z75Swdx6Jr_cGj0NJnGR3Q.png

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:

1*CDCdZolftT4TP-yRanJO6g.png

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.

1*rHcNi3I9cnk6Tym-nHTPiA.png

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

1*eZIx1sqp0EBMFDjIAJ_rTA.png

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”:

1*_yEEI0wzIXW8r_3f6GZASg.png

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

1*L6U0WL7B__gcxl-9yE7eRQ.png

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:

1*dd48IRwe_beYsfd225BZTA.png

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:

1*dEvUyKCqqEHJsDJq-iDsbw.png

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:

1*0XpXhYpK3TruRMqd_Kb0lg.png
index 7 with offset 5
1*V9RHo_0uh1JSQrttxyqK4A.png
index 0 with offset 0 and 1
1*X1ocLB21TvYIq4chhKY4rg.png
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:

1*tgOiIY68k5479OoUhM4A-g.png

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:

1*NpcA5JUnj9YNeOTsC8L5Sg.png

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:

1*2At3O-wfmyE_nDfiZmKnwQ.png
bp termdd!IcaChannelInput+0xd8
1*TdI8x5qBTYDBxcC1qtGHog.png

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)

 
1*HUOcZ9-zDDUaOxCk5BG3Ww.png

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

1*XwtI06FcqJopoA5qzT62bQ.png

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

1*AwCi9Gn1BIvhWVxluy__Ng.png

Which is here looking at it from the disassembler:

 
1*ta3wPY7F3Q-pI9D8Uh2qwg.png

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

 
1*eWBvBwc4_iNUO0BcjBCShw.png
Src
 
1*CfWNhH3n1UNZaIva7QD4wg.png
Dst
 
1*h7YQ6LOr4OmWaZECw2cdsg.png
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:

1*dn2Of3cIMZYm8f8XS_l5lA.png

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

1*Z7YT6CxNBCeSzONtaCp38Q.png

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

1*OB_F6YbPzOJuCMZWQdEYiA.png

Using a disassembler will provide a greater view:

1*WetrbCoWpW1VCBYnVlqAag.png
1*b_bKe4aOQF-FSv8vwIoAJg.png

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

1*V2B4Nvt7LzeSpjx7Wc9APg.png

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

1*0BHufYT5djDQC88cxNMfbg.png

Before stepping through the IcaChannelClose, lets set a breakpoint on the MS_T120 control channel structure to see how does it get affected1*Y8TDQzqoEXXnXO0b8RVFyQ.png

fffffa80`074fcac0 is the current address for the MS_T120 structure
 
1*G3NMQC-5mtPqa19JoQFbpw.png
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.

1*fqk0hBug1IhyejA3yc5SVA.png

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

1*RQdv3tA3Rxd6Yv8blmD34g.png

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.

1*UHpyt8DixO0Xllsd5v_rZQ.png

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
1*yHbBkVqw_dCtTTrQWgoRDQ.png

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

1*Zv2KPJ_fxQ3mon0dzu26cw.png
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.1*Ynl_4KUwRTsChKia2lpmJg.png

Lets take a look at rcx:

1*LReRDcLxD5l9CkJ-GCY9zw.png

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

1*A2fxsljel8cn6KoGKn7PIQ.png

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...