Windows 8 Kernel Memory Protections Bypass
Recently,
MWR intern Jérémy Fetiveau (
@__x86)
conducted a research project into the kernel protections introduced in
Microsoft Windows 8 and newer. This blog post details his findings, and
presents a generic technique for exploiting kernel vulnerabilities,
bypassing
SMEP and
DEP. Proof-of-concept code is provided which reliably gains
SYSTEM
privileges, and requires only a single vulnerability that provides an
attacker with a write-what-where primitive. We demonstrate this issue by
providing a custom kernel driver, which simulates the presence of such a
kernel vulnerability.
Introduction
Before diving into the details of the bypass technique, we will
quickly run through some of the technologies we will be breaking, and
what they do. If you want to grab the code and follow along as we go,
you can get the zip of the files
here.
SMEP
SMEP (Supervisor Mode Execution Prevention) is a mitigation that aims to prevent the
CPU from running code from user-mode while in kernel-mode.
SMEP
is implemented at the page level, and works by setting flags on a page
table entry, marking it as either U (user) or S (supervisor). When
accessing this page of memory, the
MMU can check this flag to make sure the memory is suitable for use in the current
CPU mode.
DEP
DEP (Data Execution Prevention) operates
much the same as it does in user-mode, and is also implemented at the
page level by setting flags on a page table entry. The basic principle
of
DEP is that no page of memory should be both writeable and executable, which aims to prevent the
CPU executing instructions provided as data from the user.
KASLR
KASLR (Kernel Address Space Layout
Randomization) is a mitigation that aims to prevent an attacker from
successfully predicting the address of a given piece of memory. This is
significant, as many exploitation techniques rely on an attacker being
able to locate the addresses of important data such as shellcode,
function pointers, etc.
Paging 101
With the use of virtual memory, the
CPU
needs a way to translate virtual addresses to physical addresses. There
are several paging structures involved in this process. Let’s first
consider a toy example where we only have page tables in order to
perform the translation.
For each running process, the processor will use a different page
table. Each entry of this page table will contain the information
“virtual page X references physical frame Y”. Of course, these frames
are unique, whereas pages are relative to their page table. Thus we can
have a process A with a page table PA containing an entry “page 42
references frame 13” and a process B with a page table PB containing an
entry “page 42 references frame 37”.
If we consider a format for virtual addresses that consists of a page
table field followed by an offset referencing a byte within this page,
the same address 4210 would correspond to two different physical
locations according to which process is currently running (and which
page table is currently active). For a 64-bit x86_64 processor, the
virtual address translation is roughly the same.
However, in practice the processor is not only using page tables, but
uses four different structures. In the previous example, we had
physical frames referenced by PTEs (page table entries) within PTs (page
tables). In the reality, the actual format for virtual addresses looks
more like the illustration below:
The cr3 register contains the physical address of the PML4. The PML4
field of a virtual address is used to select an entry within this PML4.
The selected PML4 entry contains (with a few additional flags) the
physical address of a
PDPT (Page Directory Pointer Table). The
PDPT field of a virtual address therefore references an entry within this
PDPT. As expected this
PDPT
entry contains the physical address of the PD. Again, this entry
contains the physical address of a PD. We can therefore use the PD field
of the virtual address to reference an entry within the PD and so on
and so forth. This is well summarized by Intel’s schema:
It should be now be clearer how the hardware actually translates
virtual addresses to physical addresses. An interested reader who is not
familiar with the inner working of x64 paging can refer to the section
4.5 of the volume 3A of the
Intel manuals for more in-depth explanations.
Previous Exploitation Techniques
In the past, kernel exploits commonly redirected execution to memory allocated in user-land. Due to the presence of
SMEP,
this is now no longer possible. Therefore, an attacker would have to
inject code into the kernel memory, or convince the kernel to allocate
memory with attacker-controlled content.
This was commonly achieved by allocating executable kernel objects containing attacker controlled data. However, due to
DEP,
most objects are now non executable (for example, the “NonPagedPoolNx”
pool type has replaced “NonPagedPool”). An attacker would now have to
find a way to use a kernel payload which uses return-oriented
programming (
ROP), which re-uses existing executable kernel code.
In order to construct such a payload, an attacker would need to know
the location of certain “ROP gadgets”, which contain the instructions
that will be executed. However, due to the presence of
KASLR,
these gadgets will be at different addresses on each run of the system,
so locating these gadgets would likely require additional
vulnerabilities.