CVE-2011-1281: A story of a Windows CSRSS Privilege Escalation vulnerability

Today, I would like to present a detailed description of the CVE-2011-1281 vulnerability [1], which was reported by me several months ago and patched today, together with four other bugs marked as the Elevation of Privileges class, on the occasion of the monthly Microsoft Patch Tuesday cycle (see Microsoft Security Bulletin MS11-056, a summary of the flaws’ origin and severity). All of the issues were present in the Windows CSRSS (Client/Server Runtime Subsystem) component, already mentioned in several of my posts [2][3][4] and articles [5][6]. Some of these problems affected every edition of the Windows NT-family systems up to Windows 2008/7, while the remaining part was only present up to Windows Vista. The latter is primarily caused by the fact, that all of the flaws were found in the Console Management code present in winsrv.dll (one of the modules used by the Windows Subsystem). Due to some major architecture changes applied in Windows 7 [7], the console support was (almost) entirely moved from the privileged CSRSS process into CONHOST.EXE, running in the context of the local user.

The blog post is meant to open up a series of technical write ups, explaining the origin and exploitation process of all the CSRSS issues just fixed. Apart from five high-impact vulnerabilities, publically announced by Microsoft, I will also present two Denial of Service bugs, which can be used to generate an unhandled Access Violation exception, resulting in the CSRSS crash and a Blue Screen of Death. A complete list of the flaws to be discussed, together with their root cause is shown below:

CVE-2011-1281 CSRSS Local EOP AllocConsole Vulnerability Lack of Sanity Check
CVE-2011-1282 CSRSS Local EOP SrvSetConsoleLocalEUDC Vulnerability Integer Signedness Error
CVE-2011-1283 CSRSS Local EOP SrvSetConsoleNumberOfCommand Vulnerability Integer Signedness Error
CVE-2011-1284 CSRSS Local EOP SrvWriteConsoleOutput Vulnerability Code Logic Error
CVE-2011-1870 CSRSS Local EOP SrvWriteConsoleOutputString Vulnerability Integer Overflow
DoS Vulnerability #1 Invalid 16-bit Integer Wrap
DoS Vulnerability #2 Integer Signedness Error

Along with the operating system internals and technical information related to the first vulnerability from the list, I am also going to cover possible exploitation vectors, which can be used to achieve reliable code execution in a real environment. The vulnerability itself is an example of a Handle-based Use-after-free condition, and is – to my best knowledge – the first publicly disclosed and documented vulnerability of this type. In order to better understand the techniques and concepts presented herein, you are strongly adviced to read the Windows Numeric Handle Allocation in Depth [8] article, providing detailed information about the handle allocation mechanisms found in the Windows NT kernel. Have fun, and stay tuned for successive blog entries! As always, comments of any kind are encouraged :-)

Note: Despite newer Windows platforms being affected as well, all exploitation considerations contained in this article are only confirmed for the Windows XP operating system. Furthermore, CSRSS not being part of the Windows kernel, its sources cannot be found in the WRK (Windows Research Kernel) package. Consequently, as the C code listings presented herein have a strictly illustrative purpose, some of them come form the ReactOS project source code (explicitly marked).

Before proceeding to technical details, you can watch an exploitation video:

The basics

One of the most elementary assumptions of the Windows console support, is the fact that a single process can be assigned a maximum of one console. The statement can be found in numerous locations thorough the MSDN documentation, e.g. the AllocConsole and AttachConsole function references:

A process can be associated with only one console, so the AllocConsole function fails if the calling process already has a console.
A process can be attached to at most one console. If the calling process is already attached to a console, the error code returned is ERROR_ACCESS_DENIED (5).

If we take a look at the kernel32.dll implementation of the AllocConsole routine, it turns out that the developers did remember about the aforementioned principle:

.text:7C87235E                 mov     eax, large fs:18h
.text:7C872364                 mov     [ebp+var_43C], eax
.text:7C87236A                 mov     eax, [eax+30h]
.text:7C87236D                 mov     eax, [eax+10h]
.text:7C872370                 cmp     [eax+10h], ebx
.text:7C872373                 jz      short loc_7C872387
.text:7C872375                 push    5               ; dwErrCode
.text:7C872377                 call    _SetLastError@4 ; SetLastError(x)
.text:7C87237C                 mov     [ebp+var_42C], ebx

The assembly code can be easily translated into the following C snippet:

if(CurrentProcessPEB()->ProcessParameters->ConsoleHandle != NULL)
{
  SetLastError(ERROR_ACCESS_DENIED);
  return (FALSE);
}

Apparently, the condition is correctly verified on the application’s (client) side. Following that code, numerous calls to internal kernel32 routines are issued, including AllocConsoleInternal – a function primarily responsible for sending the actual console creation request to the Windows Subsystem. The goal is achieved by packing the configuration data into a special shared buffer, and calling ntdll!CsrClientCallServer with the SrvAllocConsole operation code. Until now, every single part of the execution path – such as avoiding input sanitization or modifying functions’ parameters – could have been modified by the application itself.

.text:7C871F2B                 push    2Ch
.text:7C871F2D                 push    20224h
.text:7C871F32                 push    ebx
.text:7C871F33                 lea     eax, [ebp+var_BC]
.text:7C871F39                 push    eax
.text:7C871F3A                 call    ds:__imp__CsrClientCallServer@16 ; CsrClientCallServer(x,x,x,x)
.text:7C871F40                 xor     esi, esi
.text:7C871F42                 cmp     [ebp+var_9C], esi

After the console request is sent and dispatched by CSRSS, there is not much of a control, anymore. An (A)LPC message is first received by the csrsrv!CsrApiRequestThread function, and then passed to an adequate operation handler – that is, winsrv!SrvAllocConsole. Its prologue starts with the following assembly lines:

.text:75B4D1A7 ; int __stdcall SrvAllocConsole(LPHANDLE lpTargetHandle, int)
.text:75B4D1A7 _SrvAllocConsole@8 proc near            ; DATA XREF: .text:75B38A80 o
.text:75B4D1A7
.text:75B4D1A7 var_C           = byte ptr -0Ch
.text:75B4D1A7 var_4           = dword ptr -4
.text:75B4D1A7 lpTargetHandle  = dword ptr  8
.text:75B4D1A7
.text:75B4D1A7                 mov     edi, edi
.text:75B4D1A9                 push    ebp
.text:75B4D1AA                 mov     ebp, esp
.text:75B4D1AC                 sub     esp, 0Ch
.text:75B4D1AF                 push    ebx
.text:75B4D1B0                 mov     ebx, [ebp+lpTargetHandle]
.text:75B4D1B3                 push    esi
.text:75B4D1B4                 push    edi
.text:75B4D1B5                 lea     esi, [ebx+28h]
.text:75B4D1B8                 mov     eax, large fs:18h
.text:75B4D1BE                 mov     eax, [eax+3Ch]
.text:75B4D1C1                 mov     eax, [eax+20h]
.text:75B4D1C4                 mov     eax, [eax+74h]
.text:75B4D1C7                 mov     edi, ds:__imp__CsrValidateMessageBuffer@16
.text:75B4D1CD                 push    1
.text:75B4D1CF                 push    dword ptr [esi+4]

As can be seen, the routine begins from validating the input buffers, and than proceeds (not shown) straight to console allocation. (Un)surprisingly, the code doesn’t check, whether there is a text interface already associated with the client process! The above observation leads to a trivial conclusion – the only conditional jump preventing an application from creating more than one console at a time, is performed in the local context of the requestor itself!

The following function (gcc-compatible) can be used to zero out the “ConsoleHandle” field, so that multiple kernel32!AllocConsole calls do not fail anymore:

VOID ClearHandle()
{
  // fs:[0x18] points to the virtual address
  // of the Thread Environment Block (TEB) structure.
  __asm("mov eax, fs:[0x18]");

  // kd> dt _TEB
  // nt!_TEB
  //    +0x000 NtTib            : _NT_TIB
  //    +0x01c EnvironmentPointer : Ptr32 Void
  //    +0x020 ClientId         : _CLIENT_ID
  //    +0x028 ActiveRpcHandle  : Ptr32 Void
  //    +0x02c ThreadLocalStoragePointer : Ptr32 Void
  //    +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
  __asm("mov eax, [eax+0x30]");

  // kd> dt _PEB
  // nt!_PEB
  //    +0x000 InheritedAddressSpace : UChar
  //    +0x001 ReadImageFileExecOptions : UChar
  //    +0x002 BeingDebugged    : UChar
  //    +0x003 SpareBool        : UChar
  //    +0x004 Mutant           : Ptr32 Void
  //    +0x008 ImageBaseAddress : Ptr32 Void
  //    +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
  //    +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
  __asm("mov eax, [eax+0x10]");

  // kd> dt _RTL_USER_PROCESS_PARAMETERS
  // nt!_RTL_USER_PROCESS_PARAMETERS
  //    +0x000 MaximumLength    : Uint4B
  //    +0x004 Length           : Uint4B
  //    +0x008 Flags            : Uint4B
  //    +0x00c DebugFlags       : Uint4B
  //    +0x010 ConsoleHandle    : Ptr32 Void
  __asm("mov dword ptr [eax+0x10], 0");
}

Given the above procedure, one may successfully execute the following code:

AllocConsole(); // (1)
ClearConsole();
AllocConsole(); // (2)

consequently getting CSRSS to create two console windows. Even though the first one is still displayed on the screen and has its events dispatched, it is now a zombie console. Namely, it becomes impossible to reference such a window by any means, unless it has another process attached, which we assume it does not. What is even more interesting, the window remains present on the user’s desktop, even after the parent application terminates. Given the circumstances, the flaw already enables a potential attacker to consume the machine resources (physical memory), in such a way it cannot be released until system reboot – in other words, a typical Denial of Service condition.

The result of running the following code snippet:

while(1)
{
  AllocConsole();
  ClearHandle();
}

is shown below:

Going deeper

Having just achieved a reliable DoS condition, it is time to wonder if the vulnerability can be used to do anything more than that. Fortunately, it can. Being designed to manage a maximum of one console per process, CSRSS is unable to store more information that the developers originally assumed, when creating internal structure definitions. Consequently, the per-process structure held by the Windows Subsystem only contains a single field to store the console handle. The above can be confirmed by investigating the ReactOS code (an accurate copy of the original Windows sources):

typedef struct _CSRSS_PROCESS_DATA
{
  struct tagCSRSS_CONSOLE *Console;
  struct tagCSRSS_CONSOLE *ParentConsole;
  (...)
  HANDLE ConsoleEvent;
  (...)
} CSRSS_PROCESS_DATA, *PCSRSS_PROCESS_DATA;

As a direct result, it is not possible to allocate multiple consoles in the context of one process, without dealing damage to the structures’ references. Thus, associating a second text window to a process that already owns one console gets CSRSS to overwrite the information about previous allocations:

/* Set the Process Console */
ProcessData->Console = Console;

The worst thing about the behavior is that the previous console is not freed by the faulty SrvAllocConsole code – it is simply forgotten about, as if it never existed – hence, the text window’s reference count is not decremented. Normally, CSRSS decrements the console refcount upon the termination of one of the console clients; however, the operation is only performed in the context of one, single console currently assigned to the terminating application (referenced through ProcessData->Console).

This leaves us with one or more dangling consoles, which in themselves do not pose a serious problem (they only occupy the virtual space of the subsystem process). What should be noted, however, is the fact that such console objects still contain client-related information (i.e. the parent process handle), which might have turned invalid since the console allocation time. Therefore, we might try to achieve interesting results, if we were able to make CSRSS utilize this outdated information in some way. Let’s take a look at some options.

In order to implement parts of the console functionality, the CSRSS process sometimes calls back to the owner (or clients) of the console window. More precisely, callbacks are used in two specific situations:

  • A Control Event is generated in the context of the console. It can be accomplished both programatically – by using the GenerateConsoleCtrlEvent API – or manually – by issuing the CTRL+C or CTRL+Break hotkeys.
  • The user wants to set the window properties, by choosing the “Properties” / “Defaults” option from the console context menu.

The first item is a well documented functionality, as the application itself is able to register its own callback routines called upon a control event occurence (see SetConsoleCtrlHandler), while the other one is an internal solution, not referenced in any of the official documents. In order to issue a callback, the subsystem uses the information present in the console descriptor – most notably, the client process handle. The entire mechanism has been thoroughly explained in the Windows CSRSS Tips & Tricks [6] article; for us, there are two essential observations to make:

  1. The CSRSS → Client callbacks are triggered by having CSRSS create a new thread in the context of the client process:
    .text:75B48504 ; int __stdcall InternalCreateCallbackThread(HANDLE hProcess, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter)
    .text:75B48504 _InternalCreateCallbackThread@12 proc near ; CODE XREF: WowExitTask(x)+40 p
    .text:75B48504                                         ; CreateCtrlThread(x,x,x,x,x)+10E p ...
    .text:75B48504
    (...)
    .text:75B485A5                 lea     eax, [ebp+ThreadId]
    .text:75B485A8                 push    eax             ; lpThreadId
    .text:75B485A9                 push    ebx             ; dwCreationFlags
    .text:75B485AA                 push    [ebp+lpParameter] ; lpParameter
    .text:75B485AD                 lea     eax, [ebp+ThreadAttributes]
    .text:75B485B0                 push    [ebp+lpStartAddress] ; lpStartAddress
    .text:75B485B3                 push    ebx             ; dwStackSize
    .text:75B485B4                 push    eax             ; lpThreadAttributes
    .text:75B485B5                 push    [ebp+hProcess]  ; hProcess
    .text:75B485B8                 call    ds:__imp__CreateRemoteThread@28 ; CreateRemoteThread(x,x,x,x,x,x,x)
    (...)
  2. The “lpStartAddress” parameter is fully controlled by the parent process of the console.

Investigating the InternalCreateCallbackThread routine x-refs leads to the following function call chains:

  • winsrv!ProcessCtrlEvents → winsrv!CreateCtrlThread → winsrv!InternalCreateCallbackThread
  • winsrv!InitWindowClass → winsrv!ConsoleWindowProc → winsrv!PropertiesDlgShow → winsrv!InternalCreateCallbackThread

Both execution paths can be easily triggered by the attacker’s application (either by calling GenerateConsoleCtrlEvent, or SendMessage), thus an attacker is able to get CSRSS to call CreateRemoteThread with a freed process handle and a controlled start address! The described behavior can be confirmed by creating a dangling console, terminating the parent process, choosing the “Properties” option, and watching the CSRSS API calls:

kd> U
winsrv!InternalCreateCallbackThread+0x17:
001b:75b7851b ff151c13b675    call    dword ptr [winsrv!_imp__NtOpenProcessToken (75b6131c)]
001b:75b78521 85c0            test    eax,eax
001b:75b78523 7d07            jge     winsrv!InternalCreateCallbackThread+0x28 (75b7852c)
001b:75b78525 33c0            xor     eax,eax
001b:75b78527 e9c4000000      jmp     winsrv!InternalCreateCallbackThread+0xec (75b785f0)
001b:75b7852c 56              push    esi
001b:75b7852d 8b350813b675    mov     esi,dword ptr [winsrv!_imp__NtQueryInformationToken (75b61308)]
001b:75b78533 57              push    edi

kd> Dd @esp
010bfba8  0000051c 00000008 010bfbe8 01ebfa78
010bfbb8  010bfbd4 7e428dd9 00010564 000000a4
010bfbc8  010bfedc 009475b8 010bfbec 7e418b26
010bfbd8  009475b8 010bfbf8 75b617f1 00000000
010bfbe8  01ebf6d8 00000000 010bfc24 75b8f412
010bfbf8  0000051c 7c872101 00010564 00000112
010bfc08  01ec01a0 0000fff8 7e44048f 010bfe64
010bfc18  7c872101 00010564 00000000 010bfe74

kd> !handle 51c
processor number 0, process 821d2da0
PROCESS 821d2da0  SessionId: 0  Cid: 025c    Peb: 7ffd8000  ParentCid: 021c
DirBase: 09308040  ObjectTable: e15394b8  HandleCount: 2774.
Image: csrss.exe

Handle table at e1222000 with 2774 Entries in use
051c: free handle, Entry address e17fba38, Next Entry 0000030c

Given the above, we end up being able to perform critical operations on undefined HANDLE values, in the context of a SYSTEM process. That’s certainly good news; the only problem is – how the flawed behavior can be used to escalate the privileges of the local user… Let’s find out :-)

Exploitation: Stage One

At the current stage, we can get CSRSS to use a previously-freed handle, assuming it is a valid Process Object identifier. In order for the exploit to work, it is required to re-assign the handle to another, highly-privileged process. The task can be accomplished, if we are able to (indirectly) control the number, types and order of handle allocation and deallocation, performed by the subsystem process.

The exact free handle management algorithms employed by the Windows kernel has been described in [8]. To make a long story short, the operating system manages a simple LIFO (Last In, First Out) queue called the free-list, on behalf of CSRSS. Every time a new handle is requested by the process (through OpenProcess, OpenThread, or any other API /service), the first item from the queue is popped, and assigned to an object. Similarly, when the NtClose service is called from within ring-3, the freed handle value is placed at the beginning of the queue.

Due to the fact that no tools designed to investigate Windows handle free-lists could be found on the internet at the time of the research, I decided to develop a really basic utility on my own. The project is called Windows Handle Lister, and is available through the Google Code website [9]. An example output of the program is as follows:

-=*( Microsoft Windows XP SP3 Handle-Table Lister v0.0.1 by j00ru//vx )*=-
Loading driver...
Opening driver...
Querying for the driver version...
Driver Version: Microsoft Windows XP Handle-Table Lister v0.0.1 by Matt "j00ru" Jurczyk
Enter the target PID (less than 65536),
or the handle table address (greater than 65536): 592
Setting the current process identifier...
----- FREE LIST:
0x000005b8 ---> 0x00000378 ---> 0x0000016c ---> 0x00000324 ---> 0x000000cc --->
0x00000220 ---> 0x000005f4 ---> 0x000005c0 ---> 0x000005fc ---> 0x000005f8 --->
0x00000584 ---> 0x000002c8 ---> 0x000004ac ---> 0x00000600 ---> 0x00000604 --->
(...)
0x000007e8 ---> 0x000007ec ---> 0x000007f0 ---> 0x000007f4 ---> 0x000007f8 --->
0x000007fc ---> 0x00000000 ---> (null)

----- ALTERNATE FREE LIST:
0x00000000 ---> (null)

Press enter to obtain a fresh copy of the handle free-lists.
or Ctrl+C to quit.

When a new process is created in the system, a total of three handles are allocated in the context of CSRSS, in the following order and locations:

  1. Process Object handle, allocated in basesrv!BaseSrvCreateProcess by NtDuplicateObject
  2. Thread Object handle, allocated in basesrv!BaseSrvCreateThread by NtOpenThread
  3. Port Object handle, allocated in csrsrv!CsrApiHandleConnectionRequest by NtAcceptConnectPort

Consequently, the first item present on the current free-list is always assigned to the newly-spawned process. On the other hand, when a simple (single-threaded) process is terminated, the handles are freed in the following order:

  1. Thread Object handle
  2. Port Object handle
  3. Process Object handle

Although the above lists give us plenty of information required to reliably control the free-list, we must keep in mind that the internal state of CSRSS is highly dependant on the operating system state – every time a new process or thread is created or terminated, the contents of the handle queue change. What is more, (indirectly) spawning a process with the NT AUTHORITY\SYSTEM privileges might potentially require creating an unknown (but reasonably low) amount of other processes and threads. In general, it is almost impossible for an attacker to set up an ideal free-list, with the accuracy of one handle. In order to bypass the problem, I would like to propose a simple technique called Handle-spraying.

The basic concept of the solution is to fill the free-list with store large amounts of process handles (associated with consoles), instead of a single one. By spraying the queue with, say, 100 items, it doesn’t matter if the handle assigned to the privileged process is picked as the first, third, fifteenth or fifty second.

Let’s consider a queue made up of handles in the following pattern:

Free-list ⇒ 12345 → … → 1000 → ∅

After creating 100 simple processes one after another, the lists will look like the following:

Process handles 14710 → … → 298

Thread handles 25811 → … → 299

Port handles 36912 → … → 300

Free-list 301 → 302 → 303 → … → 1000 → ∅

When each of the created processes creates a single zombie console, we terminate all of them. Due to the thread / port handle swap, the queue will have the following form:

Free-list ⇒ 13246 → … → 1000 → ∅

Additionally, each of the 100 former process handles (1, 4, …) is now associated with a dangling console, and will be used as the CreateRemoteThread argument, when triggered. Now, we can manipulate the specific layout of the free-list by creating and terminating threads in an appropriate order (CSRSS keeps tracks of all the processes / threads running on the system, thus it opens a handle to every execution unit). For example, we can create 300 threads:

Thread handles ⇒ 13246 → … → 300

Free-list 301 → 302 → 303 → … → 1000 → ∅

Next then, we shall free 200 handles – the ones previously associated with threads and ports:

Thread handles ⇒ 14710 → … → 298

Free-list 235689 → … → 1000 → ∅

Finally, the remaining handles are deallocated:

Free-list ? 147101316 → … → 1000 → ∅

Thanks to the re-arrangments, we end up with a fully operational free-list, with one hundred of the initial items being handles associated with a console object. After following the above steps, we no longer have to worry if the privileged process (used in the exploitation process) will be assigned a formed Process Object identifier, or not – because it always will.

Exploitation – Stage Two

The second problem I encountered, while thinking of possible exploitation vectors, was the exact way of spawning a highly privileged program from within a restricted account. The main and mostly considered option was to initiate the creation of a service process (e.g. by using the Help and Support Center, or some other system functionality, which requires a certain service to work). After a short period of experiments, I found out that there is a much simpler solution. On Windows XP, using the win+u hotkey for the first time during the system session typically results in WINLOGON.EXE spawning several processes, one of which being UTILMAN.EXE with the Local System rights.

UTILMAN.EXE stands for “Utility Manager”, and is responsible for the Ease of Access options’ management. More specifically, it can be used to open up other helper applications, such as Magnifier, Narrator, or On-Screen Keyboard. The interesting thing about the manager is that it can be used, while the Winlogon screen (logon prompt) is active – hence the extraordinary privileges of the process. I believe that other, interesting methods of getting the OS to create a new, privileged process exist; however, the “utilman” one is perfectly valid in the context of a Proof of Concept – in order to achieve reliable exploitation in real conditions, “Stage Two” would probably have to be carried out in a different way.

Exploitation – Stage Three

The last exploitation stage which must be completed before seeing a shiny, brand new CMD.EXE with the NT AUTHORITY\SYSTEM security token, is moving the payload bytes into the memory context of UTILMAN. Obviously, it is not possible to use the OpenProcess + WriteProcessMemory API pair, due to the fact that processes running under a restricted user’s account are unable to open handles to programs with System-level rights. Trying to sneak some executable code through the environment variables is not a viable option, either; the security token difference makes it impossible to pass data through that communication channel. Not many options left…

One thing that the two processes (exploit and UTILMAN) have in common, is the desktop these two programs operate on. It turns out that WIN32K.SYS – the main graphical kernel module on Windows – manages two shared sections (a per-session and a per-desktop one), mapped in the context of every GUI process (a process becomes graphical after issuing a call to one of the WIN32K system calls). One of these sections contains the characteristics of windows present on the considered desktop, including arrays of data (e.g. unicode windows titles, editbox values and more). Consequently, a malicious application is able to store arbitrary bytes in the memory context of a highly-privileged process in the system, just by manipulating or creating basic windows on the local desktop.

In order to address ASLR (Address Space Layout Randomization), it is even possible to perform a simple form of memory spraying, by creating multiple windows with overlong titles. This way, we can set the remote thread’s StartAddress to a constant value, and simply assume that the shared section mapping will be large enough to cover the chosen memory area. For the illustrative purpose of a PoC, 40 windows with a 32kB title each, proved to guarantee almost 100% success rate on the test machine (the virtual address being hit was 0x00606060). Interestingly, the shared section mapping on Windows XP is not only readable, but also executable (see the section attributes on the above screenshot)! This fortunate coincidence settles any potential disputes about the DEP mechanism being able to disrupt the exploitation process. On the other hand, the “E” attribute has been removed from the mapping on Windows Vista, so the technique presented here would most likely cease to work on newer software configurations.

Exploitation – Final Notes

Having gone through all the stages needed to achieve a semi-reliable code execution with escalated privileges, let’s sum up the exploitation steps, needed to be taken from an exploit developer’s point of view:

  1. Spray the shared WIN32K section, by creating a sufficient amount of USER objects. The section is then going to be mapped to every process running in the context of the local desktop, thus we can perform this step at this early point,
  2. Create N instances of a process, each of which will create a single zombie console and then go idle, (*)
  3. Kill all N instanes of the processes,
  4. Create 3N local threads, (**)
  5. Kill 2N threads (in the order described in the “Second Stage” section),
  6. Kill the remaining N threads,
  7. Emulate the win+u key presses, resulting in a new instance of UTILMAN.EXE being created,
  8. Call SendMessage(HWND_BROADCAST,WM_SYSCOMMAND,0xFFF7,0), triggering the execution of CreateRemoteThread on each of the N freed handles.

* – by creating a zombie console, we also mean replacing the original PropertiesProc address (used in kernel32!AllocConsole) with a custom pointer, as described in [6].
** – the technique is very time-sensitive. If any handle is picked / stored on the free-list between steps 3 and 4, than steps 5 and 6 might not succeed in setting up the expected free-list handle layout.

Funny facts

One particurarly funny fact I have came across, is related to the AllocConsole service implementation present in the ReactOS project sources:

CSR_API(CsrAllocConsole)
{
    PCSRSS_CONSOLE Console;
    NTSTATUS Status = STATUS_SUCCESS;
    (...)
    RtlEnterCriticalSection(&ProcessData->HandleTableLock);
    if (ProcessData->Console)
    {
        DPRINT1("Process already has a console\n");
        RtlLeaveCriticalSection(&ProcessData->HandleTableLock);
        return STATUS_INVALID_PARAMETER;
    }

As can be seen, the above code doesn’t lack the essential sanity check, which should be performed at the beginning of the winsrv!SrvAllocConsole handler, and therefore is not affected by the described vulnerability. Considering that a great part of the ReactOS implementation was build based on reverse engineering of the original Windows images, the author of the above snippet must have automatically assumed that the check must be performed. Either way, I find it quite amusing that the source code of a project – meant to be a reproduction of the Windows operating system – tends to be written in a better / more secure manner, than the original components being based on :-)

Conclusion

Personally, the discussed vulnerability is an interesting example, showing that the use-after-free vulnerability class is not only characteristic to web browsers, but can also be found in regular system software. Successful exploitation is made possible thanks to various details of the CSRSS component architecture, such as the restricted program’s ability to directly control and re-arrange the subsystem’s free-list, or the fact that CSRSS performs execution-critical tasks on the process handles (creating new threads in certain situations). Interestingly, having found the vulnerability three years ago, I left it as-is, believing that is just another random local DoS issue.

As previously mentioned, the presented exploitation techniques are only confirmed to be valid for Windows XP. In order to improve the EoP reliability, or port the exploitation to Windows Vista, the developer has to invent alternate ways of performing Stage Two and Stage Three, or namely:

  • Allocate a new CSRSS handle for a highly-privileged process (either by triggering NtOpenProcess, NtDuplicateObject or other service),
  • Fill (preferably: spray) this process’es memory areas with controlled bytes – the payload (preferably: inside pages with the Executable attribute)

Thank you for reading this exploitation write-up; more interesting articles are soon going to show up, describing both already fixed issues and possibly some zero-days (e.g. a reliable way of bypassing the Driver Signature Enforcement mechanism). Stay tuned, and don’t hesitate to leave your comments, especially including new concepts regarding one of the exploitation stages! :-)

References

[1] CVE-2011-1281. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-1281
[2] Windows CSRSS Write Up: the basics (part 1/1). https://j00ru.vexillium.org/?p=492
[3] Windows CSRSS Write Up: Inter-process Communication (part 1/3). https://j00ru.vexillium.org/?p=502
[4] Windows CSRSS Write Up: Inter-process Communication (part 2/3). https://j00ru.vexillium.org/?p=527
[5] Custom console hosts on Windows 7 (Hack in the Box Magazine #4). http://magazine.hitb.org/issues/HITB-Ezine-Issue-004.pdf
[6] Windows CSRSS Tips & Tricks (Hack in the Box Magazine #5). http://magazine.hitb.org/issues/HITB-Ezine-Issue-005.pdf
[7] Windows 7 / Windows Server 2008 R2: Console Host. http://blogs.technet.com/b/askperf/archive/2009/10/05/windows-7-windows-server-2008-r2-console-host.aspx
[8] Windows Numeric Handle Allocation in Depth (Hack in the Box Magazine #6). http://magazine.hackinthebox.org/issues/HITB-Ezine-Issue-006.pdf
[9] Windows Handle Lister project @ Google Code, http://code.google.com/p/windows-handle-lister/

39 thoughts on “CVE-2011-1281: A story of a Windows CSRSS Privilege Escalation vulnerability”

  1. Hey
    I really enjoyed the post, thanks.
    One more question though: did you start your research in msdn or just had ideas or from reversing csrss itself?
    Keep it coming.

  2. @Ruben:
    @dookie2000ca:
    @Arkon:
    @synapse:
    @Shaddy:
    I am really glad you guys like it! Cheers!

    @Arkon: When it comes to this particular bug, I started my research from MSDN. It actually originated from my willingness to find a way for a process to have more than one console (which can be quite an annoying limitation).

    As for the other issues, I’ve been reversing the CSRSS internals heavily :-)

  3. Damn, I had written a PoC by your article, and it’s really works ;)
    Also, all these noisy console windows can be hidden with ShowWindow(SW_HIDE), that made exploitation 100% stealth.

  4. Pingback: IDELIT
  5. @GunnerO2:
    @Han:
    @BEO:
    @oldlamp:
    @Dreg:
    What can I say… thanks again!

    @Cr4sh:
    Oh wow, that’s really cool, good work on writing a functional exploit!
    I do realize that the process can be made invisible for a computer user; however, going into such details of a weaponized exploit in work is beyond the scope of this article ;)

  6. yes, I simulated the concept(poc), it worked pretty well.

    :) handle spraying and csrss dug-in – brilliant.

    but, i think spawning “utilman” is very un-deterministic approach [maybe beyond scope of this poc]

    @all, suggestions for further improvements (thinking of vista/7)?

  7. @nullBox: Cool, good work on creating an exploit :)

    I do agree with you that spawning a SYSTEM-privileged process is the weakest point of the exploitation process. However, I don’t really have too many ideas of other techniques, which could be used here (probably spawning a new service or subsystem?).

    Cheers!

  8. Yaa, ur write.. though I have located few default windows services .. which can be evoked .. ;) and truly the exploit work flawlessly with almost 99% success on all vers of windows XP.. cheers !!!

    However, what I think, the major problems are with Vista n upwards, where ASLR defeats the memory patching part { replacing the original PropertiesProc address (used in kernel32!AllocConsole) with a custom pointer, as described in [6] / PEB or TEB} and moving of console support from the csrss to conhost in win7 [para 1 ].

    @all ;) any hacks?

  9. correction to previous post :) aslr may not be an issue in vista patching because of “GetProcAddress()+offset”.

  10. @nullBox: Nice. How about sharing the windows services and the specific ways of invoking them as a Guest?

    As you already noted, since it is a local vulnerability (not even involving the kernel), ASLR is not an issue here. When it comes to Windows 7 and its design changes, well, that’s a more complicated case. The fact that the bug even affected Windows 7 was caused by the fact that another issue was internally found in the CSRSS<=>CONHOST implementation (see last paragraph of http://blogs.technet.com/b/srd/archive/2011/07/12/ms11-056-vulnerabilities-in-the-client-server-runtime-subsystem-and-console-host.aspx), which resulted in the ability of an unprivileged application to communicate with administrative conhost.exe.

    I haven’t performed much research in the area of Windows 7 exploitation of the CSRSS vulnerabilities though, so you’re on your own investigating the subject (drop me a line if you figure out anything interesting).

  11. you indeed do a really great job!do you created 40 windows with a title of a string of 32kb in order to put shellcode in the address space of csrss.exe?

Comments are closed.