August 22, 2024 By Joshua Magri 10 min read

Vectored Exception Handlers (VEH) have received a lot of attention from the offensive security industry in recent years, but VEH has been used in malware for well over a decade now. VEH provides developers with an easy way to catch exceptions and modify register contexts, so naturally, they’re a ripe target for malware developers. For all the attention they’ve received, nobody had publicized a way to manually add a Vectored Exception Handler without relying on the built-in Windows APIs which are sometimes hooked by Endpoint Detection and Response (EDR) products. 

Back in 2015, an UnKnoWnCheaTsuser published code snippets for manipulating the VEH list, and more recently in 2024 a researcher by the name of mannyfreddy published a blog that goes into some good detail on how Vectored Exception Handlers work. Mannyfreddy’s blog also touched on how to manipulate the VEH list and even explored how to use Vectored Exception Handlers for remote process injection.

In 2022, I became interested in Vectored Exception Handlers after rad9800 published a proof-of-concept for walking the Vectored Exception Handler list and calling the RemoveVectoredExceptionHandler API on each registered handler to clear the list. This led me to develop a method for manually manipulating the VEH list and a method for using VEH to perform threadless process injection. Since information about these techniques is beginning to be shared publicly, I figured it was time to release my research in this area.

In this post, we’ll look at how to manually manipulate the Windows Vectored Exception Handler list, and how Vectored Exception Handlers can be used to evade defenses and perform process injection. You can find the accompanying code for this blog post here.

What are Vectored Exception Handlers?

Vectored Exception Handlers are a Windows mechanism that extends Structured Exception Handling (SEH). In short, they allow developers to register a function that will be called when an exception is generated in a process. This function will receive information about the exception and the state of the registers when the exception occurred.

Vectored Exception Handlers are stored in a list and when an exception is generated the first exception handler in the list will be called. Typically, you would write a VEH to look for specific exception types that you anticipate handling. If your handler is called and the error code is not one you are interested in, then you can tell the process to keep walking the list to find a handler that can handle the error. If it is an error that you would like to handle, then you can do whatever needs performed and tell the process the error has been handled, and execution will be resumed. If the entire VEH list is walked and no handler tells the process to continue executing, then the process will be terminated.

The graph below shows what the VEH looks like. The exception handler will start at the List Head and then walk through each item looking for an appropriate handler. If it arrives back at the List Head, then the process is terminated.

How do I add a Vectored Exception Handler?

You can find some example code from Microsoft here. In short, you can create a Vectored Exception Handler by creating a function that takes a pointer to an _EXCEPTION_POINTERS struct as an argument and then calls the AddVectoredExceptionHandler Windows API to register the exception handler. The arguments for the AddVectoredExceptionHandler function are below.

The first argument tells the function whether to insert your new handler at the start of the exception handler list. If you do not insert it as the first handler, then it will be inserted at the back of the list. The second argument is a pointer to your exception handler to be called.

Note that while your handler function is supposed to take an _EXCEPTION_POINTERS struct as an argument, you don’t actually need to comply with this prototype if your handler doesn’t need any arguments. This means you can have arbitrary memory addresses called as Vectored Exception Handlers. We’ll see the implications of this later.

How does EDR use Vectored Exception Handlers?

Some EDR products will register their own Vectored Exception Handlers. A common use case for this is to place PAGE_GUARD traps on certain regions of memory. When a region of memory with the PAGE_GUARD protection is accessed, it will generate an exception, and the EDR product can then inspect what generated the exception to decide if it’s malicious or not.

For example, shellcode will access the Export Address Table (EAT) for Kernel32.dll to resolve function addresses. However, the legitimate GetProcAddress function also does this. By placing a PAGE_GUARD trap on the Kernel32.dll, an EDR can analyze whether or not the access is being performed by a legitimate module, or from a region of unbacked memory. If it’s the latter, that is an indication of potential malware. Yarden Shafir discussed a similar scenario in this excellent blog post.

Since EDR vendors are using Vectored Exception Handlers, it’s in their best interest to make sure the VEH list isn’t tampered with. If you were able to add an exception handler to the front of the list, you could simply never pass execution to the EDR’s handler. In at least one popular product we’ve tested, a call to AddVectoredExceptionHandler will always result in the VEH being added at the end of the list, regardless of whether you told Windows to add it at the front of the list.

Manually manipulating the VEH list

Since calling the AddVectoredExceptionHandler API (which in turn calls RtlAddVectoredExceptionHandler) is not an option, we can simply (an overstatement) reimplement it.

As shown in the previous graphic, the Vectored Exception Handler list is stored as a doubly linked list. A doubly linked list is a data structure in which each entry has a pointer to the next entry, a pointer to the previous entry and then some data. In this case, the data is another struct containing information for the Vectored Exception Handler.

Graphic source: https://www.osronline.com/article.cfm%5Earticle=499.htm

Each individual Vectored Exception Handler looks like this.

The LIST_ENTRY item contains our Flink/Blink pointers, a reference counter, a reserved value that doesn’t really matter and lastly a pointer to the function which should be called. Except, this pointer is not actually a pointer, but rather an encoded pointer. Pointers can be encoded/decoded using the EncodePointer/DecodePointer Windows API functions.

Walking the Vectored Exception Handler list

There are two methods for locating the Vectored Exception Handler list. One relies on using heuristics like identifying a function that references the LdrpVectorHandlerList variable and reading the bytes to find the address. The second method is to register a new Vectored Exception Handler and walk through the doubly linked list until we identify a pointer to the .data section of NTDLL, which should be the head of the linked list. The latter is the method documented by rad9800, and the method which I prefer, as we don’t have to worry about offsets or byte patterns changing across Windows versions.

Inserting items into the Vectored Exception Handler list

Once we’ve identified the head of the Vectored Exception Handler list, we can begin manipulating it. We could simply hijack the VEH list by pointing the Flink and Blink entries of the List Head towards our new exception handler, visualized below. This will result in our VEH being the only entry in the list.

The danger with this approach is if an exception is raised that your exception handler cannot handle, your process will be terminated. Legitimate processes also use Vectored Exception Handlers to catch errors that they expect to be thrown, so short-circuiting the list is probably not the best approach. Instead, we can properly update the list to insert our exception handler first.

With this approach, we can handle the errors that we are interested in, and pass anything else on to the next exception handler.

Abusing Vectored Exception Handlers for process injection

As we’ve seen, implementing our own version of the AddVectoredExceptionHandler API isn’t too involved. But more importantly, it didn’t really require us to interact with the kernel, aside from calling NtProtectVirtualMemory to change memory protections on the .mrdata section of NTDLL. Since all the information the process uses when calling Vectored Exception Handlers is stored within the process, it presents a great target as a threadless process injection technique.

What is threadless process injection? Ceri Coburn covered it in their 2023 talk at Bsides Cymru, “Needles Without the Thread.” Funnily enough, this talk came out just before I was about to give a talk at an internal IBM conference demonstrating my new injection technique that didn’t require an execution primitive.

To summarize, traditional process injection techniques require a way to:

  • Allocate memory in the remote process
  • Write your code into the allocated memory
  • Protect the memory in the remote process so that it’s executable
  • Execute your code in the remote process

We can mix and match these primitives to get different techniques, and some techniques do not need all the steps. For example, if you allocate memory in the remote process as RWX, then you do not need to change the protection later. Or if you call NtMapViewOfSection then your memory is allocated and written into the remote process in the same step. But the one thing all traditional process injection techniques do require is a primitive for execution. This is typically CreateRemoteThread/QueueUserAPC/SetThreadContext (or their Nt function equivalents). As a result, these execution primitives are heavily scrutinized by security products for malicious use. Calling an execution primitive targeting unbacked memory in a remote process is a great way to get your beacon caught.

So, how about we skip the execution primitive entirely? With Vectored Exception Handlers, it works as follows:

  1. Identify the VEH list in our local process, since the address will be the same in the remote process.
  2. Allocate/Write/Protect our shellcode into the remote process with your primitives of choice.
  3. Allocate space for a new Vectored Exception Handler struct in the remote process.
  4. Call EncodeRemotePointer to get an encoded pointer for the address where you wrote your shellcode.
  5. Allocate space for a pointer and an int in the remote process (we need these for the two reserved attributes of the VEH entry).
  6. Update the new VEH entry with valid Flink/Blink attributes, update the pointer and update the two reserved attributes to point to the memory you allocated previously.
  7. Check the IsUsingVEH bit in the remote process Process Environment Block (PEB) and set it, if necessary.
  8. Set a PAGE_GUARD trap on a region of memory that will be executed by the process.

The last step is the critical one that allows us to bypass the need for an execution primitive by triggering an exception in the remote process. There are a few ways to go about this but a PAGE_GUARD trap is, in my opinion, the best way. I’ve implemented injection techniques for both new and existing processes using PAGE_GUARD traps.

If you are spawning a new process, then you can spawn the process in a suspended state and set a trap at the entry point for the process. Typically, spawning a process in a suspended state and manipulating it will get you tagged for process hollowing behavior. However, since we are not writing to any .text sections or using any execution primitives, we shouldn’t get hit with this detection. But as always, test this in your lab.

Injecting into a running process is a bit more involved, but I’ve found the easiest way is:

  1. Choose a thread in the process.
  2. Suspend the thread.
  3. Get the thread context.
  4. Set a PAGE_GUARD trap at the RIP of the thread.
  5. Resume the thread.

This technique can be a bit unstable if you are executing straight shellcode since it hijacks the thread, which can crash the process. I’ve found it more reliable to add some bootstrap shellcode that implements a proper Vectored Exception Handler that creates a new thread for your shellcode and then returns code execution back to the thread as normal. This local thread creation will not be subject to the same scrutiny as a remote thread creation.

The last consideration for either technique is whenever an error occurs in the process, your VEH will be called and your shellcode will execute. This can result in a whole bunch of beacons being created in one process and ultimately crashing it. I’ve found the solution to this problem is either the bootstrap shellcode mentioned above, which can check to ensure that the exception is a PAGE_GUARD trap, or to remove your Vectored Exception Handler from your newly spawned beacon. This can be done by executing a BOF to walk the VEH list, identify your handler (an encoded pointer to unbacked memory) and remove it through manual manipulation, or simply calling RemoveVectoredExceptionHandler on it.

Other ways to trigger remote exceptions

I believe PAGE_GUARD traps are the best method for generating remote exceptions since it’s a very straightforward NtProtectVirtualMemory call, the trap is removed after the exception is generated, and it does not require a write or execute primitive. However, there are other ways you could trigger a remote exception, for the sake of variety:

  • For a newly spawned and suspended process, set the BeingDebugged bit in the PEB to true. When the process is resumed, your exception handler will be called. You will need to use a CreateThread shellcode stub to avoid loader lock.
  • Use an execution primitive targeting an invalid address in the process. This may not trigger EDR since the execution primitive is not actually targeting your malicious memory.
  • Set non-executable page protections in a .text section of the remote process and undo it after your exception is triggered.
  • Write some invalid instructions into a .text section of the remote process.

I don’t think any of these are particularly good ideas (except maybe the first one, which I have tested successfully), but the point is you do not necessarily need to use a PAGE_GUARD trap.

Note for Windows Server 2012

As always, Windows Server 2012 does not play nicely with the techniques described above, but it’s not too hard to get it working. On Windows Server 2012, the VEH struct is missing one of the two reserved entries found on other versions of Windows. Additionally, the VEH list is not in the .mrdata section, but in the .data section.

Detection considerations

Detecting VEH manipulation can be done using the same techniques described in this post to walk the VEH list. Security products that use VEH are typically configured to ensure that they are the first entry in the VEH. If this is not the case, something malicious may have occurred. However, this may cause issues if two products are running in parallel and both expect to be the first entry in the list.

NCC Group did some excellent research on enumerating Vectored Exception Handlers across all processes and identifying any handlers that point to unbacked memory. As always, unbacked executable memory is a fairly good indicator of malicious behavior. Event Tracing for Windows Threat Intelligence (ETWTi) can also be used to identify the allocation, writing and protection of shellcode in unbacked memory. Similarly, ETWTi events for remote memory writes to the .mrdata section of a process should be a high signal/low noise indicator.

References

https://github.com/rad9800/misc/blob/main/bypasses/ClearVeh.c

https://github.com/CCob/ThreadlessInject

https://windows-internals.com/an-exercise-in-dynamic-analysis/

https://mannyfreddy.gitbook.io/ya-boy-manny

More from Offensive Security

IoT exploitation during security engagements

9 min read - During two separate security engagements, I discovered command injection vulnerabilities in two embedded devices. Discovering each vulnerability had its unique challenges. One is a classic command injection vulnerability while the other details a "blind" command injection vulnerability, which provides an interesting contrast of two vulnerability types you may commonly see in IoT systems. In addition to this technical analysis, the details surrounding the vulnerability research process, how I exploited these devices to accomplish the objective at hand and the benefits…

X-Force discovers new vulnerabilities in smart treadmill

7 min read - This research was made possible thanks to contributions from Joshua Merrill. Smart gym equipment is seeing rapid growth in the fitness industry, enabling users to follow customized workouts, stream entertainment on the built-in display, and conveniently track their progress. With the multitude of features available on these internet-connected machines, a group of researchers at IBM X-Force Red considered whether user data was secure and, more importantly, whether there was any risk to the physical safety of users. One of the most…

Evolving red teaming for AI environments

2 min read - As AI becomes more ingrained in businesses and daily life, the importance of security grows more paramount. In fact, according to the IBM Institute for Business Value, 96% of executives say adopting generative AI (GenAI) makes a security breach likely in their organization in the next three years. Whether it’s a model performing unintended actions, generating misleading or harmful responses or revealing sensitive information, in the AI era security can no longer be an afterthought to innovation.AI red teaming is emerging…

Topic updates

Get email updates and stay ahead of the latest threats to the security landscape, thought leadership and research.
Subscribe today