Chapter 13: Windows Kernel Security

The kernel is the colonel of the operating system. It’s the software that allows the Operating System (OS) to link applications to hardware, translating application requests into instructions for the CPU. In fact, it’s hard to distinguish an operating system per se from its kernel; it is the heart of the OS. A bug in a user’s application may cause crashes, instability, slowness, and so on, but a bug in the kernel can crash the entire system. An even more devastating potential is arbitrary code execution with the highest privileges available on the OS. Kernel attacks are a hacker’s dream.

Absolutely everything in an OS works with the kernel in some form. As the core of the OS, the kernel requires isolation from the less-privileged processes on the system; without isolation, it could be corrupted, and a corrupt kernel renders the system unusable. This isolation is accomplished by rendering the kernel’s space in memory as off-limits to processes on the user side. Despite this, full isolation would make the computer useless for users and their applications – interfaces are a necessity. These interfaces create doorways for the attacker into the highest privilege level possible on a Windows computer.

An in-depth discussion of the Windows NT kernel is out of scope for this chapter, but we’ll introduce kernel security concepts and step through a Metasploit exploit module against the Windows kernel to better understand how it works. We’ll provide a hands-on introduction to exploiting a kernel vulnerability to elevate privileges on a Windows target. 

In this chapter, we’ll cover the following:

  • An overview of kernel concepts and attacks
  • The concept of pointers to illustrate null pointer flaws
  • Code from the Metasploit module to exploit the CVE-2014-4113 vulnerability
  • A demonstration of leveraging this module for privilege escalation after gaining a foothold on a Windows 7 target

Technical requirements

The technical requirements for this chapter are as follows:

  • Kali Linux
  • A Windows 7 target PC or virtual machine
  • WinDbg for further debugging study (not necessary to complete the exercise)
  • The IDA disassembler for analyzing binaries and drivers (not necessary to complete the exercise)

Kernel fundamentals – understanding how kernel attacks work

A crucial philosophical point to remember is that the kernel is a computer program. It’s a construct that can be rather intimidating for us lowly noobs, so it helps to remember the true nature of the beast. The casual flaws you learn about in ordinary programming can all occur in kernel code. The kernel occupies memory, just like any ordinary program, so the potential to put something where it doesn’t belong and execute it exists. If this is the case, what makes the kernel so special? The kernel manages all low-level functions by interfacing the hardware of a computer and the software of an OS. There are many, many different programs running on a modern instance of Windows, and they all want to use one processor at the same time. The programs can’t decide who gets how much time, and the processor dumbly completes operations – it can’t decide, either. It’s the kernel that functions as the cop, managing all the high-level interactions with the lowest-level structures of the system. The next time you’re marveling at the multitasking ability of a computer that isn’t actually capable of multitasking, thank the kernel for providing that illusion to you.

Windows is an example of an OS that uses a dual-mode architecture – user and kernel (sometimes called user and supervisor). Thus, the memory space is split into two halves, and user mode cannot access kernel space. Kernel mode, on the other hand, has the highest authority and can access any part of the system and hardware. The kernel is ultimately the mediator between the actual hardware and the OS. In Windows, the interface with hardware is provided by the Hardware Abstraction Layer (HAL), which, as the name suggests, creates a layer of abstraction to, for instance, normalize differences in hardware. Kernel mode drivers provide interfaces for applications requesting access to hardware; even something taken for granted such as an application wishing to display data on the screen must work with a kernel mode driver. The beauty of these structures is they create a layer of abstraction and a single familiar environment for applications to work with. A Windows developer doesn’t need to worry about the different monitors that may be displaying their program to the user:

Figure 13.1 – How Windows interacts with hardware

Figure 13.1 – How Windows interacts with hardware

Kernel attack vectors

The security implications of the kernel are both profound in the sense of potential impact and the extremely low-level activity happening within the kernel, and also straightforward in the sense that the kernel is software written by people (say no more). Some attack vectors that we consider when examining the kernel concept are as follows:

  • APIs: If the kernel doesn’t allow some means for applications to access its functionality, there’s no point in a computer and we might as well all go home. The potential exists via the APIs for arbitrary code to be executed in kernel mode, giving an attacker’s shellcode all the access it needs for total compromise. 
  • Paddling upstream from hardware: If you examine the design of the Windows OS, you’ll notice that you can get intimate with the kernel in a more direct way from the hardware side of the system hierarchy. Malicious driver design can exploit the mechanisms that map the hardware device into virtual memory space.
  • Undermining the boot process: The OS has to be brought up at boot time, and this is a vulnerable time for the system. If the boot flow can be arbitrarily controlled, it may be possible to attack the kernel before various self-protections are initialized. 
  • Rootkits: A kernel-mode rootkit in Windows typically looks like a kernel-mode driver. Successful coding of such malware is a very delicate balancing act due to the nature of the kernel’s code; couple that with modern protections such as driver signing, and this is getting harder and harder to pull off. It isn’t impossible though, and regardless, older OSs are still a reality in many environments. It’s important for the pen tester to be aware of the attacks that the security industry likes to describe as on their way out the door.

The kernel’s role as a time cop

There are various pieces of magic that a modern OS needs to perform, and the kernel is the magician. One example is context switching, which is a technique that allows numerous processes to share a single CPU. Context switching is the actual work of putting a running thread on hold and storing it in memory, getting another thread up and running with CPU resources, and then putting the second thread on hold and storing it in memory before recalling the first thread. There’s no way around the fact that this takes time to do, so some of the latency in a processor is found in context switching; one of the innovations in OSs is developing ways to cut this time down as much as possible.

Of course, we’re rarely fortunate enough to have to worry about just two little threads trying to run on the same processor – there are often dozens waiting, so the task of prioritizing becomes necessary. Prioritizing threads is a part of the work of the scheduler. The scheduler decides who gets what slice of time with the processor and when. What if a process doesn’t want to give up its time with the processor? In a cooperative multitasking OS, the process needs to be finished with resources before they will be released. On the other hand, in a preemptive multitasking OS, the scheduler can interrupt a task and resume it later. I’m sure you can imagine the security implications of an OS that’s unable to context switch with a thread that refuses to relinquish resources. Thankfully, modern OSs are typically preemptive. In fact, in the case of Windows, the kernel itself is preemptive – this simply means that even tasks running in kernel mode can be interrupted.

Even young children can grasp one of the fundamental rules of existence – events don’t always happen at once, and you often have to wait for something to happen. You have to go to school for a whole week before the fun of the weekend starts. Even at the extraordinarily small scale of the tiny slices of time used in context switching and scheduling, sometimes we have to wait around for something to happen before we can proceed. Programmers and reverse engineers alike will see these time-dependent constructs in code:

  1. Grab the value of the VAR variable; use an if/then statement to establish a condition based on this fetched value.
  2. Grab the value of the VAR variable; use it in a function according to the condition(s) established in step 1.
  3. Grab the value of the VAR variable; use it in a function according to the condition(s) established in step 1 and step 2, and so on.

Imagine if we could create a condition that would cause these dependencies to occur out of their prescribed order. For example, what if I could cause step 2 to happen first? In this case, the code is expecting a condition to have been established already. An attacker may thus trigger an exploit by racing against the established order – this is called a race condition.

It’s just a program

From a security perspective, one of the most crucial points to understand about the kernel is that it’s technically a program made up of code. The real distinction between a flaw in the kernel and a flaw in code on the user side is the privilege; any piece of code running at the kernel level can own the system because the kernel is the system.

Crashing the kernel results in an irrecoverable situation (namely, it requires a reboot), whereas crashing a user application just requires restarting the application – so, exploring kernel attacks is more precarious and there is far less room for mistakes. It’s still just a computer program, though. I emphasize this because we can understand the kernel attack in this chapter from a programmer’s perspective. The kernel is written in a mix of assembly and C (which is useful due to its low-level interface ability), so let’s take a look at a basic programming concept from a C and assembly point of view before we dive into exploiting our Windows target.

Pointing out the problem – pointer issues

Programming languages make use of different data types: numeric types such as integers, Boolean types to convey true and false, sets and arrays as composite data types, and so on. Pointers are yet another kind of data type – a reference. References are values that refer to data indirectly. For example, suppose I have a book with a map of each of the states of the United States on each page. If someone asks me where I live, I could say page 35 – an indirect reference to the data (the state map) on that particular page. References as a data type are, in themselves, simple, but the datum to which a reference refers can itself be a reference. Imagine the complexity that is possible with this cute little object.

Dereferencing pointers in C and assembly

Pointers, as a reference data type, are considered low-level because their values are used as memory addresses. A pointer points at a datum, and the actual memory address of the datum is therefore the value of the pointer. The action of using the pointer to access the datum at the defined memory address is called dereferencing. Let’s take a look at a sample C program that plays around with pointers and dereferencing, and then a quick peek at the assembly of the compiled program:

#include <stdio.h>
int main(int argc, char **argv)
{
    int x = 10;
    int *point = &x;
    int deref = *point;
    printf("
Variable x is currently %d. *point is %d.

", x, deref);
    *point = 20;
    int dereftwo = *point;
    printf("After assigning 20 to the address referenced by point, *point is now %d.

", dereftwo);
    printf("x is now %d.

", x);
}

The compiled program generates this output:

Figure 13.2 – The output of our pointer program

Figure 13.2 – The output of our pointer program

Our following assembly examples are 64-bit (hence, for example, RBP), but the concepts are the same. However, we’re sticking with Intel syntax despite working in Linux, which uses AT&T syntax – this is to stay consistent with the previous chapter’s introduction to assembly. Remember, source and destination operands are reversed in AT&T notation!

Take a look at what happens at key points in the assembled program. Declaring the x integer causes a spot in memory to be allocated for it. int x = 10; looks like this in assembly:

mov    DWORD PTR [rbp-20], 10

Thus, the 10 value is moved into the 4-byte location at the base pointer, minus 20. Easy enough. (Note that the actual size of the memory allocated for our variable is defined here – DWORD. A double word is 32 bits, or 4 bytes, long.) But now, check out what happens when we get to int *point = &x; where we declare the int pointer, *point, and assign it the actual memory location of x:

lea    rax, [rbp-20]
mov    QWORD PTR [rbp-8], rax

The lea instruction means load effective address. Here, the RAX register is the destination, so what’s really being said here is to put the address of the minus 20 base pointer into the RAX register. Next, the value in RAX is moved to the quadword of memory (8 bytes) at the minus 8 base pointer. So far, we set aside 4 bytes of memory at the minus 20 base pointer and placed the 10 integer there. Then, we took the 64-bit address of this integer’s location in memory and placed that value into memory at the minus 8 base pointer. In short, the x integer is now at RBP - 20, and the address at RBP - 20 is now stored as a pointer in RBP - 8.

When we dereference the pointer with int deref = *point;, we see this in assembly:

mov    rax, QWORD PTR [rbp-8]
mov    eax, DWORD PTR [rax]
mov    DWORD PTR [rbp-12], eax

To understand these instructions, let’s quickly review the registers. Remember that EAX is a 32-bit register in IA-32 architecture; it’s an extension of the 16-bit AX. In x64 architecture, RAX is a 64-bit register, but remember that being backward-compatible, it follows the same principle – RAX is an extension of EAX:

Figure 13.3 – 64-bit registers

 

Figure 13.3 – 64-bit registers

The square brackets, [ ], distinguish the contents of a memory location or register. So first, we’re putting the quadword value pointed to by RBP - 8 into the RAX register, then we’re loading the DWORD value that RAX is pointing to into the EAX register, and finally, the DWORD in EAX is placed in a DWORD-sized chunk of the memory at the minus 12 base pointer.

Remember that RBP - 8 contained the address of our integer, x. So, as you can see in the assembly code, we managed to get that integer stored in another place in memory by pointing to a pointer that was pointing at our integer.

Understanding NULL pointer dereferencing

Now that we’ve reviewed pointer basics, we can define NULL pointer dereferencing – it’s when a program uses a pointer to access the memory location to which it points (dereference), but the pointer’s value is NULL. If you try to recall from our introduction to shellcoding, our program tried to access 0x7a7a7a7a when we overwrote the return with the ASCII letter z, so in the case of a NULL pointer, an invalid location in memory is trying to be accessed. The difference is that we aren’t overwriting the pointer value with arbitrary bytes; it’s NULL – an address that simply doesn’t exist. The result is always some sort of a fault, but the resulting behavior can be unpredictable. With this being the case, why are we concerned with NULL pointer dereferencing? 

I know what the hacker in you is saying, it’s pretty obvious that exploiting a NULL pointer dereference vulnerability results in a denial of service. Perhaps, grasshopper, but it’s a little more complicated than that. For one, the memory addresses starting at 0x00000000 may or may not be mapped – that is, if a NULL pointer’s value is literally zero, it may be possible to end up in a legitimate memory location. If it isn’t a valid memory location, we get a crash; but if it is valid, and there’s some tasty shellcode waiting there, then we have ourselves code execution. Another scenario to consider is that the pointer is not properly validated before being dereferenced. The actual value may not be NULL in this case, but the attack is effectively the same. For our analysis, we’ll pick on a well-known Windows vulnerability from 2014 – CVE-2014-4113.

Probably the most common way of referring to known vulnerabilities is with their Common Vulnerabilities and Exposures (CVE) designation. The CVE is a catalog of software-based threats sponsored by the US federal government. Vulnerabilities are defined as flaws that can give an attacker direct access to systems or data, whereas an exposure is a flaw that allows indirect access to systems or data. The CVE convention is CVE-<year>-<ID number>

The Win32k kernel-mode driver

CVE-2014-4113 is also known by its Microsoft security bulletin designation, MS14-058. It is an Elevation of Privilege (EoP) vulnerability in the kernel-mode driver Win32k.sys. I don’t know if the name Win32k.sys makes this apparent, but a bug in this particular driver is very bad news for a Windows system.

The Win32k.sys driver is the kernel side of some core parts of the Windows subsystem. Its main functionality is the GUI of Windows; it’s responsible for window management. Any program that needs to display something doesn’t talk to graphics hardware directly. Instead, it interfaces via the Graphics Device Interface (GDI), which is managed by Win32k.sys. User mode window management talks to Win32k.sys through User32 DLLs from the Client/Server Runtime Subsystem (CSRSS) user-side service. Drivers provide access for entities to their functionality via entry points, and Win32k.sys has about 600 of them. This highly complex interaction and core functionality make security a bit of a nightmare for something like Win32k.sys.

This is a highly simplified depiction of the place of Win32k.sys in the Windows kernel and its relationship to userland: 

Figure 13.4 – Win32k.sys interaction with the kernel

Figure 13.4 – Win32k.sys interaction with the kernel

Note that this depiction also physically relates to memory, as userland is the lower portion of memory (at the top of the figure), and kernel land occupies the upper portion. 0x00000000 to 0x7FFFFFFF is user space, and application virtual memory spaces occupy certain regions within it; the remainder, 0x80000000 to 0xFFFFFFFF, is the almighty kernel. Windows design is not dumb – you can’t just arbitrarily execute something in kernel land:

Figure 13.5 – Exploiting Win32k.sys

Figure 13.5 – Exploiting Win32k.sys

What we hope to accomplish is tricking code running in kernel mode to execute our payload within user space. We don’t need to trespass in the kernel’s backyard to get something running with the kernel’s high privileges.

Passing an error code as a pointer to xxxSendMessage()

There’s a lot of complexity in Win32k.sys, and we don’t have time to even scratch the surface, so let’s hone in on the vulnerable structures that we will be attacking with our module in the next section. Remember that Win32k.sys is largely responsible for window management, including handling requests from applications to output something to a display. There’s a function inside Win32k.sys called xxxMNFindWindowFromPoint() that is used to identify the window that is occupying a particular location on the screen (a point, given in X and Y coordinates). This function will return the memory address of a C++ structure called tagWND (WND means window; this is all window management), but if there’s an error, the function returns error codes – -1 and -5. In a classic programming oversight, the caller of this function does check for the return of -1, but there isn’t a check for -5. As long as the zero flag isn’t set when the following simple comparison is executed – cmp ebx,0FFFFFFFFh – the program happily continues, knowing that it has a valid memory pointer returned from the called function. The invalid pointer vulnerability is born.

Let’s take a look at the flow of execution through Win32k.sys with IDA. In my IDA session with the driver, I identify sub_BF8B959D as the xxxSendMessage() function (sub stands for subroutine). The critical moment is visible in loc_BF9392D8 (loc for location in memory):

cmp    ebx, 0FFFFFFFFh
jnz    short loc_BF9392EB

The value in the EBX register is checked against the -1 value (note the hexadecimal value is a signed integer; hence 0xFFFFFFFF is equal to -1). jnz jumps if the zero flag is not set; remember, that’s just assembly talk for a jump to the specified location if the two compared values are not the same.

Let’s do a quick review of conditional jumps in assembly. The principles of jump if zero or jump if not zero refer to the result of a comparison. Suppose you have the x and y variables. It’s a plain logical statement that x - x = 0. Therefore, if x - y = 0, then we know that x = yjnz and jz will check the zero flag in the flags register to check the result of the comparison. 

So, if the value in EBX is not -1, then we jump to loc_BF9392EB:

push    0
push    [ebp+arg_8]
push    1Edh
push    ebx
call    sub_BF8B959D

Let’s take a look at this in IDA.

Figure 13.6 – A crucial test in IDA

Figure 13.6 – A crucial test in IDA

Recall that in my specific IDA session here, sub_BF8B959D is the xxxSendMessage function. The simplest way to put this is that xxxSendMessage will be called if EBX contains anything other than -1. The -5 value is not checked against EBX before the call. By returning -5 into the flow at this point, we can pass it to the xxxSendMessage function as a parameter. -5 represented as a hexadecimal value looks like 0xFFFFFFFB. In this particular parameter, xxxSendMessage is expecting a pointer. If the exploit works, execution will try to jump to the memory location, 0xFFFFFFFB. Part of the exploit’s job is to land us on the NULL page with an offset. The exploit will have already mapped some space in the NULL page before this point, so ultimately, execution jumps to shellcode waiting in user space. (As is often the case, Windows allows NULL page mapping for backward-compatibility reasons.) Now, I know what the hacker in you is saying: It seems like disabling NULL page mapping would stop this attack right in its tracks. A job well done as you’d be right, and Microsoft thought of that – NULL page mapping is disabled by default, starting in Windows 8.

There aren’t enough pages to do a deep dive into this particular vulnerability, but I hope I’ve given you enough background to try this out – get on your vulnerable Windows 7 VM and nab the driver (it’s in System32), open it up in IDA, and follow the flow of execution. See if you can understand what’s happening in the other functions in play here. Try keeping a running map of the registers and their values, and use the push and pop operations to understand the stack in real time. IDA is the perfect tool for this analysis. I have a feeling you’ll be hooked.

Metasploit – exploring a Windows kernel exploit module

Now that we have a little background, we’re going to watch the attack in action with Metasploit. The exploit module specific to this vulnerability is called exploit/windows/local/ms14_058_track_popup_menu (recall that MS14-058 is the Microsoft security bulletin designation for this flaw). Note that this exploit falls under the local subcategory. The nature of this flaw requires that we are able to execute a program as a privileged user – this is a local attack, as opposed to a remote attack. Sometimes, you’ll see security publications discuss local exploits with phrases such as the risk is limited by the fact that the attacker must be local to the machine. The pen tester in you should be chuckling at this point because you know that the context of distinguishing local from remote essentially removes the human factor sitting at the keyboard. If we can convince the user to take some action, we’re as good as local. These local attacks can become remotely controlled with just a little finesse.

Before we get to the fun stuff, let’s examine the Metasploit module in detail so that we understand how it works. As always, we need to take a look at the include lines so that we can review the functionality that’s being imported into this module:

require 'msf/core/post/windows/reflective_dll_injection'
class MetasploitModule < Msf::Exploit::Local
    Rank = NormalRanking
    include Msf::Post::File
    include Msf::Post::Windows::Priv
    include Msf::Post::Windows::Process
    include Msf::Post::Windows::FileInfo
    include Msf::Post::Windows::ReflectiveDLLInjection

So, we have several Windows post-exploit modules loaded here: File, Priv, Process, FileInfo, and ReflectiveDLLInjection. I won’t bog you down by dumping the code from all five post modules here, but you should always consider a proper review of the included modules as a requirement. Recall that the include statement makes those modules mixins whose parameters are directly referenceable within this parent module.

Back to the parent module – we’re going to skip over the first two defined methods, initialize(info={}) and check. You will remember that the info initialization provides useful information for the user, but this isn’t necessary for the module to function. The most practical purpose of this is making keywords available to the search function within msfconsole. The check method is also not strictly necessary, but it makes this module available to the compatibility checking functionality of Metasploit. When a target is selected, you can load an exploit and check whether the target is probably vulnerable. Personally, I find the check functionality to be nifty and potentially a timesaver, but in general, I would never recommend relying on it.

Now, at long last – the exploit method. Please note that the method starts with some error checking that we’re skipping over; it makes sure we aren’t already SYSTEM (just in case you’re still racing after crossing the finish line!), and it checks that the session host architecture and the options-defined architecture match:

def exploit
    print_status('Launching notepad to host the exploit...')
    notepad_process = client.sys.process.execute('notepad.exe', nil, {'Hidden' => true})
    begin
        process = client.sys.process.open(notepad_process.pid, PROCESS_ALL_ACCESS)
        print_good("Process #{process.pid} launched.")
  rescue Rex::Post::Meterpreter::RequestError
        print_error('Operation failed. Trying to elevate the current process...')
        process = client.sys.process.open
    end

The method starts with an attempt to launch Notepad. Note that the {'Hidden' => true} argument is passed to execute. This ensures that Notepad will execute but the friendly editor window won’t actually appear for the user (which would certainly tip off the user that something is wrong). We then handle the successful launch of Notepad and nab the process ID for the next stage of the exploit; alternatively, rescue comes to the rescue to handle the failure to launch Notepad and instead nabs the currently open process for the next stage.

DLLs are the Windows implementation of the shared library model. They are executable code that can be shared by programs. For all intents and purposes, they should be regarded as executables. The main difference from EXE files is that DLLs require an entry point that is provided by a running program. From a security perspective, DLLs are very dangerous because they are loaded in the memory space of the calling process, which means they have the same permissions as the running process. If we can inject a malicious DLL into a privileged process, this is pretty much game over.

And now, our big finale – reflective DLL injection. DLLs are meant to be loaded into the memory space of a process, so DLL injection is simply forcing this with our chosen DLL. However, since a DLL is an independent file in its own right, DLL injection typically involves pulling the DLL’s code off of the disk. Reflective DLL injection allows us to source code straight out of memory. Let’s take a look at what our module does with reflective DLL injection in the context of our Win32k.sys exploit:

    print_status("Reflectively injecting the exploit DLL into #{process.pid}...")
   if target.arch.first == ARCH_X86
        dll_file_name = 'cve-2014-4113.x86.dll'
    else
        dll_file_name = 'cve-2014-4113.x64.dll'
    end
    library_path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2014-4113', dll_file_name)
    library_path = ::File.expand_path(library_path)
    print_status("Injecting exploit into #{process.pid}...")
    exploit_mem, offset = inject_dll_into_process(process, library_path)
    print_status("Exploit injected. Injecting payload into #{process.pid}...")
    payload_mem = inject_into_process(process, payload.encoded)
    print_status('Payload injected. Executing exploit...')
    process.thread.create(exploit_mem + offset, payload_mem)
    print_good('Exploit finished, wait for (hopefully privileged) payload execution to complete.')
end

Let’s examine this step by step and skip over the status printouts:

  • First, the if...else target.arch.first == ARCH_X86 statement. This is self-explanatory – the module is pulling an exploit DLL from the Metasploit DataExploits folder, and this check allows for the architecture to be targeted correctly.
  • library_path allows the module to find and load the exploit DLL from the attacker’s local disk. I hope your creative side has kicked in and you just realized that you could modify this module to point at any DLL you like.
  • exploit_mem, offset = inject_dll_into_process() is the first slap across the target’s face. Note that inject_dll_into_process() is defined in the included ReflectiveDLLInjection module. This particular method takes the target process and the DLL’s local path as arguments and then returns an array with two values – the allocated memory address and the offset. Our module takes these returned values and stores them as exploit_mem and offset respectively.
  • payload_mem = inject_into_process() is the second slap across the target’s face. payload.encoded is our shellcode (encoded as needed). This method returns only one value – the location of the shellcode in the target process’s memory. So, as you can see, at this point in our attack, payload_mem is now the location in our target’s memory where our shellcode begins.
  • If those first two instance methods for DLL injection were the slaps in the face, then process.thread.create(exploit_mem + offset, payload_mem) is our coup de grâce. We’re passing two parameters to process.thread.create(): first, exploit_mem with our offset added to it, and then the location of our shellcode in memory, payload_mem.

So, why are we injecting a DLL into a process? The vulnerable kernel-mode driver, Win32k.sys, has more than 600 entry points that allow its functionality to be accessed; it handles a lot of useful tasks. As previously covered in this chapter, Win32k.sys is responsible for window management. Win32k.sys represents a necessary evil of this OS design – the blend of its needed power and accessibility to user-mode programs.

Practical kernel attacks with Kali

We have enough background to sit down with Kali and fire off our attack at a vulnerable Windows target. At this point, you should fire up your Windows 7 VM. However, we’re doing two stages in this demonstration because the attack is local. So far, we’ve been examining attacks that get us in. This time, we’re already in. To the layperson, this sounds like the game is already won, but don’t forget that modern OSs are layered. There was a golden age when remote exploits landed you full SYSTEM privilege on a target Windows box. These days, this kind of remote exploit is a rare thing indeed. The far more likely scenario for today’s pen tester is that you’ll get some code executed, a shell pops up, and you feel all-powerful – until you realize that you only have the privileges of the lowly user of the computer who needs permission from the administrator to install software. You have your foothold – now, you need to escalate your privileges so that you can get some work done.

An introduction to privilege escalation

The kernel attack described in this chapter is an example of privilege escalation – we’re attacking a flaw on the kernel side after allocating memory on the user side and injecting code into it. Accordingly, did you notice the big difference between the module we just reviewed and the remote attacks we examined in previous chapters? That’s right – there was no option for specifying a target IP address. This is a local attack; the only IP address you’ll define is the return of your reverse TCP connection to the handler.

To complete this demo, you’ll need to establish the foothold first! As we’re challenging you with a little self-study in order to follow along, we’re sticking with our old-school Windows 7 target.

New OS, Old Problems – the Vulnerable OEM Driver

Once you’re comfortable with the theory and practice on the older Windows 7, start exploring modern kernel exploits with Metasploit. Check out the amazing post module called dell_memory_protect. A driver provided by Dell on their laptops called DBUtilDrv2.sys had a critical kernel-level write-what-where vulnerability in versions 2.5 and 2.7. Metasploit allows us to conduct the bring your own vulnerable driver attack on any Windows box, Dell or otherwise. The driver is easy to find online, so grab it, use the module to install it and disable LSA protections, and enjoy your SYSTEM access. Extra credit goes to those who tear apart the driver in IDA!

Escalating to SYSTEM on Windows 7 with Metasploit

At this point, you’ve just received your Meterpreter connection back from the target – your foothold payload did the trick. We command getuid to see where we stand. Hmm – the username FrontDesk comes back. It doesn’t concern us that this user may or may not be an administrator; what’s important is that it isn’t SYSTEM, the absolute highest privilege possible. Even an administrator can’t get away with certain things – that account is still considered user mode.

I type background to send my Meterpreter session into the background so that I can work at the msf prompt. Although the multi/handler exploit is still in use, I can simply replace it. This time, we prepare our kernel attack with use exploit/windows/local/ms14_058_track_popup_menu:

Figure 13.7 – Managing our foothold in Metasploit

Figure 13.7 – Managing our foothold in Metasploit

In our screenshot examples, we aren’t displaying the options available to us; so, try that out with show options. When you establish the exploit and run this command, you’ll see the sessions option. This is specific to the Meterpreter sessions you’ve already established. Out in the field, you may have a foothold on dozens of machines; use this option to direct this attack at a specific session. At the msf prompt, use sessions -l to identify the session you need. sessions -i <id> will take you back into a session, so you can issue getuid to verify your privilege:

Figure 13.8 – Launching the attack inside our established session

Figure 13.8 – Launching the attack inside our established session

This can be a little confusing to set up, as you’re just coming back from configuring your handler with a payload. You need to set the payload to be used by the kernel exploit. In my example, I’m issuing set payload windows/meterpreter/reverse_tcp to create a connect-back Meterpreter shellcode payload.

When you’re ready, fire off run and cross your fingers. This is an interesting attack; by its nature, the escalation could fail without killing your session. You’ll see everything on your screen suggesting a successful exploit, complete with a new Meterpreter session indicating that the shellcode was indeed executed – and yet, getuid will show the same user as before. This is why the module author put in the fingers-crossed status message, hopefully privileged:

Figure 13.9 – Exploit complete – we are now SYSTEM

Figure 13.9 – Exploit complete – we are now SYSTEM

In our demo, our Windows 7 Ultimate host was indeed vulnerable. We are now running as SYSTEM. Game over.

Summary

In this chapter, we explored Windows kernel attacks. First, we reviewed the theory behind how the kernel works and what attackers try to leverage to pull off these attacks. Included in this theoretical discussion was a review of the low-level management role of the kernel and the security implications of these tasks, including scheduling interrupts. We picked a vulnerability type, the NULL or invalid pointer dereference vulnerability, and studied it in detail to understand how exploiting the kernel in this way gives the attacker full control of the system. We started with a review of pointers in C code and then examined the compiled assembly instructions to understand how the processor deals with the pointer concept. This review prepared us to understand what NULL pointers are and how they can cause problems in software. We then introduced a specific kernel-mode driver, Win32k.sys, and did a low-level review of its pointer flaw. We wrapped up this discussion with a review of the Metasploit exploit module, designed to attack this particular kernel-mode driver. Finally, we wrapped up the chapter with a hands-on demonstration of escalating privileges from an initial foothold by leveraging this attack against the vulnerable kernel-mode driver.

In the next chapter, we’ll wrap up the programming fundamentals with a review of fuzzing. In this book, you’ve already played around with fuzzing and may not even be aware of it. We’ll review the underlying principles and get hands-on with fuzz testing.

Questions

Answer the following questions to test your knowledge of this chapter:

  1. The ______ rests between the NT kernel and hardware.
  2. A ______ kernel can interrupt kernel-mode threads; cooperative OSs must wait for the thread to finish.
  3. In C, the ampersand operator before a variable references __________. 
  4. How many DWORDS fit into three quadwords?
  5. AX is the lower ________ of the 64-bit RAX.
  6. It is not possible to dereference an invalid pointer – true or false?
  7. My hexadecimal-to-decimal calculator says that ffffffff is equal to 4,294,967,295. Why does the xxxSendMessage() function think it’s -1?
  8. What’s the difference between DLL injection and reflective DLL injection?

Further reading

For more information regarding the topics that were covered in this chapter, take a look at the following resources:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.153.224