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-freecondition, 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:

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.

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:

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:

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

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:

The CSRSS → Client callbacks are triggered by having CSRSS create a new thread in the context of the client process:

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:

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 exemplary output of the program is as follows:

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:

Thread Object handle

Port Object handle

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 an exemplary queue, made up of handles in the following pattern:

Free-list ⇒ 1 → 2 → 3 → 4 → 5 → … → 1000 → ∅

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

Process handles⇒1 → 4 → 7 → 10 → … → 298

Thread handles⇒2 → 5 → 8 → 11 → … → 299

Port handles⇒3 → 6 → 9 → 12 → … → 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 ⇒1 → 3 → 2 → 4 → 6 → … → 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 ⇒ 1 → 3 → 2 → 4 → 6 → … → 300

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

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

Thread handles ⇒ 1 → 4 → 7 → 10 → … → 298

Free-list ⇒ 2 → 3 → 5 → 6 → 8 → 9 → … → 1000 → ∅

Finally, the remaining handles are deallocated:

Free-list ? 1 → 4 → 7 → 10 → 13 → 16 → … → 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:

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,

Create N instances of a process, each of which will create a single zombie console and then go idle, (*)

Kill all N instanes of the processes,

Create 3N local threads, (**)

Kill 2N threads (in the order described in the “Second Stage” section),

Kill the remaining N threads,

Emulate the win+u key presses, resulting in a new instance of UTILMAN.EXE being created,

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:

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

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! :-)

@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 :-)

@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 ;)

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

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

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