Saltar a la navegación Saltar al contenido principal Ir al pie de página

Detecting anomalous Vectored Exception Handlers on Windows

01 marzo 2022

By Ollie Whitehouse

tl;dr

  • At least one commercial post exploitation framework is using Vectored Exception Handling (VEH) on Microsoft Windows to hook functions in an attempt subvert hooking detection.
    • e.g. We recently released a Copy on Write Detector which is capable of detecting patches to EtwEventWrite via traditional memory patching methods without the need for byte comparison.
  • NCC Group researched a way to enumerate and identify anomalous and/or potentially malicious VEH handlers present on a host.
  • We have documented our approach and shared example code which is capable of identifying:
    • if a process is using VEH or not
    • which handlers are present and which modules they point to
    • highlight if they don’t point to a known module

Prior Work

This research was aided significantly by the prior work of others:

Problem Statement

In short we want to be able to:

  • Enumerate which processes are using VEH
  • Identify how many VEH handlers are present
  • Identify which loaded modules those VEH handlers are serviced by

Malicious VEH Usage

VEH use is a well known technique in the gaming community as a means of bypassing integrity checking code used by games publishers. As such some in the cyber research community have adopted it as a technique for similar reasons.

An example malicious VEH installation would look something akin to:

void InstallHandler()
{
LPVOID myMalHandler = NULL;
// Allocate some memory
myMalHandler = VirtualAlloc(NULL, 1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//
// Copy shellcode or similar into myMalHandler here
//
// Add the handler
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)myMalHandler);
}
view raw MalHandler.cpp hosted with ❤ by GitHub

Our objective is to detect such use.

Solution

VEH using Process Enumeration

Using the Process Environment Block (PEB) there is the CrossProcessFlags bit mask which provides an indicator if a process is or is not using VEH.

To enumerate if a process is using VEH we:

  • Open a handle to the process via OpenProcess
  • use NtQueryInformationProcess to get process basic information which include PebBaseAddress
  • use ReadProcessMemory to read the PEB and check the CrossProcessFlags bit mask for 0x4 (VEH)

This provides a performant way to do initial process triage.

VEH Handler and Handling Module Enumeration

As documented by others previously the VEH handlers are stored in a doubly linked list. The address for the start of the doubly linked list is in an unexported variable called LdrpVectorHandlerList in ntdll.dll. The address LdrpVectorHandlerList is obtainable from symbols or using a heuristic on RtlpCallVectoredHandlers without.

Once we have the address of LdrpVectorHandlerList we walk the list and its associated structure. It was at this point sadly that Dimitri’s excellent prior work did not translate to Windows 10 x64.

The structure we ended up using was:

#pragma pack(2)
struct VEH_HANDLER_ENTRY
{
	LIST_ENTRY Entry;
	PVOID UnKwn1;
	PVOID UnKwn2;
	PVOID VectoredHandler3;
};

With this structure we are able to:

  • use ReadProcessMemory using the address of LdrpVectorHandlerList
  • walk the doubly linked list extracting VectoredHandler3 from each VEH_HANDLER_ENTRY
  • decode VectoredHandler3 – it is encoded via EncodePointer – to get the handler’s virtual address
  • For each of the decoded pointers then identify via EnumProcessModules and GetModuleInformation which modules (i.e. executables or libraries) each of the handlers point to.

With this done we are able to enumerate both which processes are or are not using VEH.

But also how many handlers are present for a particular process and which modules are serving them.

Identifying Anomalous or Malicious Handlers

VEH use doesn’t seem overly common in the unscientific sample size of one host. But what does an anomalous or malicious handler look like in the output? To test the concept we used the following as a cartoon:

void InstallHandlers()
{
AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)MyUnhandledExceptionFilter);
_tprintf(TEXT("[i] Registered exception handler %p\n"), &MyUnhandledExceptionFilter);
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)MyUnhandledExceptionFilter2);
_tprintf(TEXT("[i] Registered second exception handler %p\n"), &MyUnhandledExceptionFilter2);
// Register the third
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)MyUnhandledExceptionFilter3);
_tprintf(TEXT("[i] Registered third exception handler %p\n"), &MyUnhandledExceptionFilter3);
// Register the fourth
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)MyUnhandledExceptionFilter4);
_tprintf(TEXT("[i] Registered fourth exception handler %p\n"), &MyUnhandledExceptionFilter4);
// Register the fourth lots
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)MyUnhandledExceptionFilter4);
_tprintf(TEXT("[i] Registered fourth exception handler %p\n"), &MyUnhandledExceptionFilter4);
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)MyUnhandledExceptionFilter4);
_tprintf(TEXT("[i] Registered fourth exception handler %p\n"), &MyUnhandledExceptionFilter4);
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)MyUnhandledExceptionFilter4);
_tprintf(TEXT("[i] Registered fourth exception handler %p\n"), &MyUnhandledExceptionFilter4);
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)MyUnhandledExceptionFilter4);
_tprintf(TEXT("[i] Registered fourth exception handler %p\n"), &MyUnhandledExceptionFilter4);
LPVOID myMalHandler = NULL;
// Allocate some memory
myMalHandler = VirtualAlloc(NULL, 1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE);
AddVectoredExceptionHandler(false, (PVECTORED_EXCEPTION_HANDLER)myMalHandler);
}

Using our approach we can clearly see the last ‘mal’ handler points to a region of memory which isn’t hosted by a DLL or EXE.

Further work can be done here to ensure that the exception handler pointers reside in the .text section and that the section is integral.

Summary and Conclusions

In this post we have documented the appeal of VEH use on Windows to avoid certain hooking detection techniques. We have also shown VEH use is easy to detect due to a low volume of usage coupled with high signal indicators when anomalously used – all in a performant manner.

We encourage EDR, telemetry and other observability vendors to integration these enumeration techniques into their Windows product lines to facilitate detection by cyber defense teams.

Code

The example code can be obtained from here.

However please note it is only example code , it is only known to work on Windows 10 x64

Footnotes

Some footnotes associated with the project.

x64dbg

The x64dbg team have support for dumping the VEH – however it produces incorrect pointers on x64 Windows 10 – we reported it and shared our code.

RtlpLogExceptionHandler

It was noted RtlpCallVectoredHandlers contains a call to RtlpLogExceptionHandler

It was hypothesized that this might provide a further source of telemetry / forensic artifacts about which handlers have recently been called even if the handlers were no longer present. However it was unclear how it would be retrieved. In speaking to Alex Ionescu it transpires it first needs to be enabled via gflags

After only which it can be obtained via !exrlog in WinDbg

Ollie Whitehouse

Ollie Whitehouse

Formally Group CTO for NCC Group 2017 - 2022