Post

Windows Kernel Exploitation Part 2 - Arbitrary Write

Preface

I kinda skipped the over Stack Overflows with GS section since I quickly realized that doing it in x64 is near impossible (at least for me currently) with just a pure vanilla stack buffer overflow. While attempting it, I learned that Structured Exception Handlers are not stored on the stack in 64-bit systems (woohoo Microsoft!!), rather, they’re stored in a table very far away from the stack. So a simple SEH overwrite with a stack buffer overflow is not possible here. Instead, I will pivot to the pool stuff for the time being, until I decide to load the 32-bit driver and exploit it the way they intended.

Introduction

An arbitrary write primitive, or colloquially named as a write-what-where primitive, enables an attacker to arbitrarily write a value to any address we supply. It’s quite a powerful primitive and grants the attacker a lot of creativity on how they exploit the application/system. After being inspired be a Off By One Security video; as a result, my plan of attack will be as follows:

  1. Write shellcode to a code-cave in KUSER_SHARED_DATA - something like KUSER_SHARED_DATA+0x800
  2. Obtain and modify the permissions on KUSER_SHARED_DATA’s Page Table Entry (PTE)
  3. Overwrite HalDispatchTable+0x8 with the address of our shellcode
  4. Call NtQueryIntervalProfile to trigger our shellcode

HackSys and other guides wrote the user-mode address of their shellcode to HalDispatchTable+0x4 (32 bit arch). I don’t think this technique will work in modern windows due to SMEP

Alternatively, we can do something like this:

  1. Allocate an executable page of memory in user space
  2. Modify that page’s PTE and mark it as a kernel page
  3. Overwrite HalDispatchTable+0x8 with the address of our shellcode
  4. Call NtQueryIntervalProfile to trigger our shellcode

This still gets around SMEP and reduces the amount of kernel writes - it seems arguably easier too, but I’m not sure.

The Vulnerability

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
typedef struct WRITE_WHAT_WHERE {
    PULONG_PTR What;
    PULONG_PTR Where;
}  WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

(...)

    ProbeForRead((PVOID)UserWriteWhatWhere, sizeof(WRITE_WHAT_WHERE), (ULONG)__alignof(UCHAR));

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

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

(...)

    DbgPrint("[+] Triggering Arbitrary Write\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);

(...)

As the comment suggests, the issue with this code is that the developers DO NOT check if both the What and Where pointers reside in user-mode. This allows us, the attacker, to mix-match whether the What/Where pointers exist in kernel- or user-mode; providing us the ability to arbitrarily write anywhere. The ability to write to any address within a specific space is powerful already, but now being able to arbitrarily write in both spaces is like the “Golden Primitive”.

Exploit

Now we have a rough idea on how we’re going to get an EoP from our arbitrary write primitive, let’s writeout the full exploit

Acquiring Page Table Entries

Overwriting HalDispatchTable+0x8

NtQueryInttervalProfile

Win

Remarks and Future ideas

An arbitrary write primitive is quite powerful and allows you to write creative exploits. It can be used to create an arbitrary read, bypass many kernel mitigations, and

This post is licensed under CC BY 4.0 by the author.

Trending Tags