(Collaborative post by Mateusz “j00ru” Jurczyk & Gynvael Coldwind)

Early Sunday morning discussion has resulted in j00ru coming up with an idea to mitigate some variants of kernel exploitation techniques by introducing a CPU feature that would disallow execution control transfers in kernel-mode to code residing in user memory area pages (e.g. addresses < 0x80000000 on a 32-bit Windows with default settings). The idea was that the system would mark every page as either being allowed to execute code in ring-0 or not. And hey, guess what… Intel has already proposed such a feature a month ago! Furthermore, it seems that this exact idea was already described in 2008 by Joanna Rutkowska, and two days ago she has published a follow up post on her blog.

The feature is called Supervisor Mode Execution Protection (SMEP for short) and it is documented in the newest release (May 2011) of the Intel manual 3A sections 2.5 (CR4.SMEP flag) and 4.6 (per memory page settings). For convenience reasons, we have quoted the related parts at the end of this post. Also, Dan Rosenberg has already (May 16, 2011) discussed, how this feature will interact with Linux and its specifics. To contribute to the discussion, we decided to do a similar write-up, and describe the implications of SMEP on Windows (and possible ways of bypassing it).

SMEP support on Windows

Let’s start with saying that SMEP is not supported on Windows, yet – but that is fine, since there are no CPU’s supporting SMEP on the market anyway. The obvious question is – how much time will it take for Windows to fully support the feature, after new CPUs are released. Looking back at the NX-bit (introduced by AMD as “Enhanced Virus Protection” (sic!) in 2003; support for Windows appeared in 2004 with XP SP2) we don’t think we’ll have to wait too long.

Personally, we can see two potential issues here for Microsoft:

1. It is currently an Intel-only feature currently, so Microsoft might want to wait for AMD’s reply (well, AMD must implement the exact functionality, or its equivalent in order to stay competitive) just to make sure their implementation is similar / identical.

2. QA and backwards compatibility with faulty drivers that for some reason do callbacks with ring0 privileges to code lying in user memory area. These things will crash – but this is actually a good thing! These drivers should not be used in the first place, since calling code in user memory areas should always be considered a vulnerability. However, we actually believe that there aren’t many drivers of this kind (hopefully – none).

Of course, for the purpose of this article, we assume that this feature is going to be implemented.

SMEP vs kernel-mode exploits

While discussing kernel exploitation, we usually talk about three classes of vulnerability exploitation outcomes: Elevation of Privilege (be it local or remote), Information Disclosure and Denial of Service. SMEP of course addresses only the first class or, to be more precise, the case where an exploit is constructed as following:

Prepares a shellcode in user memory, Redirects execution to the prepared payload, by exploiting a kernel/driver security flaw.

This is probably the simplest form of EoP-class exploit, and most commonly used as well. The shellcode residing in user memory results in its address being well known, which saves one the need to calculate/derive/predict its address in the somewhat opaque kernel memory area. And this is where SMEP comes in – if one cannot jump to user memory, one cannot make use of this scenario.

However, there is still a number of other techniques which might be employed to successfully bypass SMEP. Let’s start with…

Return-Oriented Programming

The first and most intuitive idea of how to execute ring-0 payload without the need to reference user-mode memory pages is to make use of a commonly known user-mode technique called ROP. The general concept of the technique relies on constructing the final payload, using tiny assembly code snippets called gadgets, with an execution-transferring instruction (such as ret or pop+jmp) at the end. The method (being an actual extension of ret2libc) has been extensively used as a great measure to bypass hardware-enforced DEP protection, preventing the attacker from executing memory pages marked as Non-Executable.

In this case we would like to make use of existing code snippets somewhere within the executable pages residing in kernel-mode area. The code doesn’t necessarily have to be found in the core NT kernel image (ntoskrnl.exe or equivalent), but any PE executable (or more generally – executable page) present in the high memory regions. The only potential problem one might stumble upon is how to actually obtain the virtual addresses of the kernel-mode gadgets from within a ring-3 application. As present in numerous articles, the above matter is not an actual problem, since any application (with no specific privileges in the system) is able to retrieve the base address of any kernel module. This can be accomplished by making use of either an undocumented NtQuerySystemInformation NT API together with the SystemModuleInformation information class, or a well-documented EnumDeviceDrivers function from the Process Status API interface.

Now, in order to find appropriate gadgets within for example, win32k.sys, one has to complete the following steps:

Load win32k.sys into the local, user-mode address space (e.g. using LoadLibraryEx with DONT_RESOLVE_DLL_REFERENCES), Search for suitable code snippets within the loaded module, Obtain the ring-0 base address of the module, Kernel Gadget Address = User Gadget Address – Local win32k.sys base + Kernel win32k.sys base

Consequently, the technique is considered feasible on every Microsoft Windows version and edition (be it 32- or 64-bit). What should be noted, however, is that Return-Oriented Programming can only be utilized if the kernel-mode stack contents are controlled by an attacker, which is the case, when:

A stack-based buffer overflow vulnerability is successfully triggered, The attacker is able to modify the stack pointer, so that it points to controlled memory (e.g. into user-mode memory areas, which are not restricted from being read / written).

Modifying nt!MmUserProbeAddress

Another concept of how the protection can be subverted is an equivalent of overwriting addr_limit with ULONG_MAX, described by Dan Rosenberg. This time the attacker’s goal is to overwrite the nt!MmUserProbeAddress global variable, which is used in order to verify whether a user-specified address resides within user-mode memory areas, or not (i.e. the essential ProbeForRead and ProbeForWrite routines make use of the value, usually set to 0x7fff0000).

If it was possible to set the value to a value larger than the usual user-kernel address boundary (which is often true in case of typical write-what-where conditions), the attacker would gain an ability to read from and write to arbitrary kernel memory, thus making him able to directly store the payload in ring-0 pages, and later execute it through a device-driver vulnerability, without ever executing user-mode pages under CPL=0.

Additionally, what makes the technique even more feasible, is the fact that nt!MmUserProbeAddress is an exported symbol, thus its virtual address can be easily obtained by any user-mode application (see steps 3 and 4 in the previous section).

Windows Reserve Objects

Last, but not least, it is also possible to take advantage of a special type of objects introduced in the Windows 7 kernel, called Reserve Objects, and thoroughly described in a Hack In The Box Magazine #3. As presented, these objects allow any user to allocate the following structure in the Non-Paged kernel memory areas (more precisely, on NonPagedPool):

nt!_KAPC +0x000 Type : Int2B +0x002 Size : Int2B +0x004 Spare0 : Uint4B +0x008 Thread : Ptr32 _KTHREAD +0x00c ApcListEntry : _LIST_ENTRY +0x014 KernelRoutine : Ptr32 void +0x018 RundownRoutine : Ptr32 void +0x01c NormalRoutine : Ptr32 void +0x020 NormalContext : Ptr32 Void +0x024 SystemArgument1 : Ptr32 Void +0x028 SystemArgument2 : Ptr32 Void +0x02c ApcStateIndex : Char +0x02d ApcMode : Char +0x02e Inserted : UChar

Four successive 32-bit fields in the structure can be fully controlled by the attacker, which means that a total of 16 arbitrary bytes can be stored in kernel-mode (of course, an application can create more than one such an object). What is most important for us, is that even though the allocation is not made on pages marked as executable, it is marked as non-pageable. Consequently, any code present in such an allocation can be freely executed, since DEP does not protect against running code from non-pageable memory pages, either on 32- or 64-bit Windows editions:

DEP is also applied to drivers in kernel mode. DEP for memory regions in kernel mode cannot be selectively enabled or disabled. On 32-bit versions of Windows, DEP is applied to the stack by default.This differs from kernel-mode DEP on 64-bit versions of Windows, where the stack, paged pool, and session pool have DEP applied.

In the HITB article, the author suggested that special chunks of reserve objects might be used, in order to store the entirety of the desired payload. It turns out, however, that it would be usually enough to simply disable the SMEP processor feature by flipping a proper bit in the CR4 register, and then execute the remaining shellcode from within user-mode pages (as it is currently done). Hence, the entire ring-0 code could be limited to the following snippet:

mov eax, cr4 btr eax, 20 mov cr4, eax jmp 0x0baaaaad

The above instructions result in clearing the 20th (SMEP) bit in CR4 and performing a direct jump to attacker-defined address. Fortunately, the payload is 15-bytes long (23 on 64-bit platform), so it can be successfully stored in just a single reserve object. To sum up, successful exploitation of a kernel vulnerability would require the following steps:

Store the payload in user-mode memory (e.g. at the 0x0baaaaad address), Call NtQueueApcThreadEx with proper arguments, holding the kernel-mode payload bytes, Obtain the address of the object structure, by calling NtQuerySytemInformation(SystemHandleInformation), Trigger the vulnerability, and jump to the NormalRoutine (first controlled) field of the KAPC structure.

As previously mentioned, one of the most serious limitations of the technique is that it only works on Windows 7, thus cannot be utilized on neither Windows XP nor Vista (the Reserve Object functionality doesn’t exist on older platforms). Apart from that, we consider the technique very elegant and reliable as long as Data Execution Prevention is not extended to other areas of the kernel memory (such as Non Paged Pool); this is, however, very unlikely to happen.

Other techniques

Apart from the above three example approaches, there are probably plenty of other techniques, which could be reliably used to store the desired payload in the kernel-mode executable (or not DEP-protected) memory, at a known address. This is primarily caused by the following facts:

Ring-0 modules are expected to be passed various types of input data from user-mode. For obvious security reasons, the data has to be moved into kernel memory areas before being processed.

The Windows architecture allows kernel-information leakage by its design (modules’ image base, objects’ addresses, kernel stacks’ addresses, not to mention leaks caused by WIN32K.SYS). Interestingly, Microsoft has even created documented API interfaces for some of these functionalities, see e.g. EnumDeviceDrivers from PSAPI.

Given the above, any method which results in the storage of attacker-supplied bytes at a known location (most suitably, non-pageable memory pages) is going to effectively bypass SMEP. The statement remains true, for as long as Microsoft doesn’t eliminate all information leakage issues present in the system, which – in our opinion – is never going to happen (especially, as the Intel architecture allows such disclosure through SIDT / SGDT / SLDT). A good example of the vendor’s attitude towards the impact of kernel-related leaks has been presented in the recent “Subtle information disclosure in WIN32K.SYS syscall return values” blog entry.

Summary

While SMEP does introduce some additional level of security, it is still quite easy to bypass. The add-on value SMEP brings to the game is an increased level of complexity for exploit developers and the fact that old exploits, which use the return to user memory technique, will simply cease to work.

Appendix

Intel Manual 3A, 2.5 Control Registers

SMEP-Enable Bit (bit 20 of CR4) — Enables supervisor-mode execution prevention (SMEP) when set. See Section 4.6, “Access Rights”.

Intel Manual 3A, 4.6 Access Rights

For accesses in supervisor mode (CPL < 3): [...] Instruction fetches. • For 32-bit paging or if IA32_EFER.NXE = 0, access rights depend on the value of CR4.SMEP: — If CR4.SMEP = 0, instructions may be fetched from any linear address with a valid translation. — If CR4.SMEP = 1, instructions may be fetched from any linear address with a valid translation for which the U/S flag (bit 2) is 0 in at least one of the paging-structure entries controlling the translation. • For PAE paging or IA-32e paging with IA32_EFER.NXE = 1, access rights depend on the value of CR4.SMEP: — If CR4.SMEP = 0, instructions may be fetched from any linear address with a valid translation for which the XD flag (bit 63) is 0 in every paging-structure entry controlling the translation. — If CR4.SMEP = 1, instructions may be fetched from any linear address with a valid translation for which (1) the U/S flag is 0 in at least one of the paging-structure entries controlling the translation; and (2) the XD flag is 0 in every paging-structure entry controlling the translation.

For accesses in user mode (CPL = 3): [..] Instruction fetches. • For 32-bit paging or if IA32_EFER.NXE = 0, instructions may be fetched from any linear address with a valid translation for which the U/S flag is 1 in every paging-structure entry controlling the translation. • For PAE paging or IA-32e paging with IA32_EFER.NXE = 1, instructions may be fetched from any linear address with a valid translation for which the U/S flag is 1 and the XD flag is 0 in every paging-structure entry controlling the translation.