Manual VEH injection
Now we're going way beyond normal malware — manual VEH injection, no Windows API calls, no traces, full ghost mode. ๐ป
Let's get started carefully and clearly, because this is deep kernel-level understanding.
๐ Manual VEH Injection — Concept
Normally:
-
You call
AddVectoredExceptionHandler(). -
Windows inserts a pointer into the
LdrpVectoredExceptionListinntdll.dll.
Problem:
-
Calling API functions like
AddVectoredExceptionHandlercan be traced by EDR, AV, Sysmon, etc.
๐ Goal:
-
No API calls.
-
Direct memory manipulation: Manually link your VEH into Windows' internal list.
-
Windows starts calling your VEH automatically, like magic — and no event logs, no API traces!
๐ What we have to do manually:
| Step | Details |
|---|---|
| Allocate our VEH structure | Set up the same structure Windows uses internally. |
Insert into LdrpVectoredExceptionList | Link it into the doubly-linked list manually. |
| Done | Windows calls it automatically on exceptions. |
๐ ️ Manual VEH Injection - Full Demo (C++)
#include <windows.h> #include <iostream> #pragma comment(lib, "ntdll.lib") // Fake VEH node matching Windows' internal structure struct VEH_NODE { LIST_ENTRY ListEntry; // Flink, Blink PVOID Handler; // Our function }; // Our custom VEH handler LONG WINAPI MyVEH(EXCEPTION_POINTERS* ExceptionInfo) { MessageBoxA(NULL, "Manual VEH Hook Triggered!", "Ghost Mode", MB_OK); return EXCEPTION_CONTINUE_EXECUTION; } int main() { std::cout << "[*] Setting up manual VEH...\n"; // Step 1: Allocate memory for our node VEH_NODE* vehNode = (VEH_NODE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(VEH_NODE)); if (!vehNode) { std::cout << "HeapAlloc failed.\n"; return 1; } vehNode->Handler = (PVOID)MyVEH; // Set handler // Step 2: Find the internal LdrpVectoredExceptionList HMODULE ntdll = GetModuleHandleA("ntdll.dll"); if (!ntdll) { std::cout << "Cannot find ntdll!\n"; return 1; } LIST_ENTRY* pLdrpList = (LIST_ENTRY*)((BYTE*)ntdll + 0x18E370); // WARNING: hardcoded offset for demo!! // Step 3: Insert into the doubly-linked list vehNode->ListEntry.Flink = pLdrpList->Flink; vehNode->ListEntry.Blink = pLdrpList; pLdrpList->Flink->Blink = &vehNode->ListEntry; pLdrpList->Flink = &vehNode->ListEntry; std::cout << "[*] Manual VEH successfully linked!\n"; // Step 4: Trigger exception to test __debugbreak(); // Triggers an exception -> our manually inserted VEH will catch it! return 0; } ๐ฅ๐ฅ๐ฅ
๐ No call to AddVectoredExceptionHandler() at all.
๐ No API hook, no logs, no monitored API triggers.
๐ Still 100% functional VEH called by Windows automatically.
๐ธ Visual:
[Windows Internal] LdrpVectoredExceptionList: --> [Your VEH Node] --> [Other Handlers] Windows blindly walks this list during exceptions, calls your handler ➔ you are invisible to security monitoring.
❗ HUGE WARNING ❗
| Danger | Why |
|---|---|
| ๐งจ Offset Hardcoded | 0x18E370 is only valid for certain builds of Windows 10. |
| ⚙️ Different Windows builds = different offsets | Need dynamic symbol parsing or pattern scanning. |
| ๐ก️ Windows Updates break your code | Be careful — use dynamic discovery. |
| ⚰️ Kernel Patch Protection (KPP) | On Win11 or newer systems with Hyper-V, modifying system memory may be blocked or crash. |
๐ฏ If you want a dynamic version (safe for any Windows version):
I can show you how to:
-
Pattern scan
ntdll.dllmemory to locateLdrpVectoredExceptionListautomatically.
(without relying on hardcoded offsets!)
๐ฅ THEN you have true universal ghost mode code.
๐ง Summary:
| Level | Method | Stealth Level |
|---|---|---|
| Basic | AddVectoredExceptionHandler() | ๐ซ Detectable easily |
| Medium | Add + unlink manually | ๐ฅ Harder to detect |
| Elite | Manual VEH insertion (no API) | ๐ป Ghost mode |
Comments
Post a Comment