Windows 7 x64 Kernel Exploitation – Arbitrary Write (2/3)

In this post, we will dive into an actual example of exploitation, against an arbitrary write-what-where vulnerability.

The Vulnerability: TriggerArbitraryOverwrite

The vulnerability we are exploiting is within the TriggerArbitraryOverwrite function. In source code, we can see this in the “TriggerArbitraryOverwrite” function, as follows:

NTSTATUS TriggerArbitraryOverwrite(IN PWRITE_WHAT_WHERE UserWriteWhatWhere) {
 PULONG_PTR What = NULL;
 PULONG_PTR Where = NULL;
 NTSTATUS Status = STATUS_SUCCESS;

PAGED_CODE();

__try {
 // Verify if the buffer resides in user mode
 ProbeForRead((PVOID)UserWriteWhatWhere,
 sizeof(WRITE_WHAT_WHERE),
 (ULONG)__alignof(WRITE_WHAT_WHERE));

What = UserWriteWhatWhere->What;
 Where = UserWriteWhatWhere->Where;

DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
 DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
 DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
 DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);

#ifdef SECURE
 // Secure Note: This is secure because the developer is properly validating if address
 // pointed by 'Where' and 'What' value resides in User mode by calling ProbeForRead()
 // routine before performing the write operation
 ProbeForRead((PVOID)Where, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));
 ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));

*(Where) = *(What);
#else
 DbgPrint("[+] Triggering Arbitrary Overwrite\n");

// Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
 // because the developer is writing the value pointed by 'What' to memory location
 // pointed by 'Where' without properly validating if the values pointed by 'Where'
 // and 'What' resides in User mode
 *(Where) = *(What);
#endif
 }
 __except (EXCEPTION_EXECUTE_HANDLER) {
 Status = GetExceptionCode();
 DbgPrint("[-] Exception Code: 0x%X\n", Status);
 }

return Status;
}

We should look in the disassembly to get a more detailed understanding of what we’re exploiting. In IDA, we can see this in the function at 0x140006090:

To translate, we can trigger this vulnerability by calling this function, and passing in a structure argument which looks like the following:

typedef struct _WRITE_WHAT_WHERE{
 PULONG What;
 PULONG Where;
}WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

From here, the function will naively behave as specified in the source code, taking *what and writing it to *where.

Interlude: the Kernel, Drivers, and You

We cannot call a device driver function directly – however, Windows offers the DeviceIoControl function, which passes a controlled interface to device drivers, as a way for user interaction with drivers (and thus, hardware):

The general approach to this is as follows:

  • Use the CreateFile function, with a first argument of “\\\\.\\HackSysExtremeVulnerableDriver”
  • Use DeviceIoControl, passing in the appropriate IOCTL argument (as specified in HackSysExtremeVulnerableDriver.h as HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE), passing in our vulnerable buffer as an argument.
  • This triggers TriggerArbitraryOverwite, passing in our vulnerable buffer.

It also is responsible for coordinating actions such as device IO: an interface to this functionality is offered via the Device Driver mechanism, and functions like DeviceIoControl.

Exploiting the Vulnerability

Unfortunately, this vulnerability does not immediately allow us to control the instruction pointer (or does it?). The way we exploit this is corrupting another code path: specifically, we corrupt the code path of NtQueryIntervalProfile, by overwriting the second element of the HalDispatchTable structure. To understand this, we can delve into the NtQueryIntervalProfile function itself:

Here, we can see that the NtQueryIntervalProfile function, callable from userland, calls KeQueryIntervalProfile, which in turn straight-up calls a predictable address in the kernel. Corrupt the address, and we control the code flow.

Unfortunately, KASLR suddenly rears it’s ugly head. In this specific case, it is not feasible to simply “brute force” KASLR (even if it were brute forceable with the increase in possible entropy provided by 64-bit). Thankfully, we can resolve the real kernel base address by querying a known symbol in memory, and comparing it’s offset-to-base to a copy on disk (this is easier to understand in code – I’ve stolen someone’s example code for simplicity’s sake, but the concept is straightforward).

This is enough to get us arbitrary code exec at a given address, and can be tested by substituting the shellcode in the sample code with int 3’s.

Interlude: The Token and the Thief

In kernel-land, our payload cannot be as simple as “cmd.exe” from shellstorm.org. Given the lack of error handling in the kernel, our payload should be as simple as possible: in this case, we utilize the windows privilege model to give ourselves system privileges.

Each process in Windows is assigned a privilege token, which is visible in it’s _EPROCESS structure. These are effectively joined in a double linked list: so our shellcode is a simple token stealer, which traverses this linked list, stealing the token of the _EPROCESS strucutre with PID 4, and giving it to ourselves.

Unfortunately, due to the recent changes in Visual Studio, it is not permitted to inline assembly. FASM (or a compiler / assembler of your choice) to the rescue.

This is enough to build a complete working privilege escalation exploit.

Finishing Touches

To summarize, our exploitation technique is as follows:

  • Prepare token thief shellcode with our PID.
  • Send IOCTL to overwrite HalDispatchTable[1] with address of our shellcode
  • Call NtQueryIntervalProfile, which triggers our shellcode
  • Steal token from PID 4 to ourselves
  • Create cmd.exe, which spawns under our token (SYSTEM)

Hopefully, this should give us a shell with ntauthority\system privilege (which is what PID 4 naturally has):

Success! The exploit code is available with the download pack in Part 1. If you’re following along, you’ll quickly note that any further (legitimate) calls to NtQueryIntervalProfile / KeQueryIntervalProfile may result in strange behaviour, including blue screens. Of note, this is reliably triggered in the shutdown process: shutting down generally produces a blue screen, as we’re not replacing the original HalDispatchTable[1] after our exploit.

In the next post, we will explore using a stack overflow vulnerability.

About Norman

Sometimes, I write code. Occasionally, it even works.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s