Structured Exception Handlers
Basics
In dissecting the workings of the Vectored Exception Handler (VEH), it's imperative to delve into the intricacies of the Structured Exception Handling (SEH) model, which lays the groundwork for exception management in Windows-based systems.
Structured Exception Handling (SEH) is a robust framework that empowers developers with full control over the handling of exceptions during the program's execution. [1]
Exceptions, which are anomalous conditions that disrupt the normal flow of a program, are broadly categorized into two types: Software and Hardware. Software exceptions are triggered by the operating system or applications due to some unexpected conditions in the software logic. On the other hand, hardware exceptions arise from erroneous conditions in the hardware, such as when the CPU attempts a division by zero or tries accessing an invalid memory address space. These exceptions can materialize in both kernel-mode and user-mode code, each having distinct handling mechanisms.
Exceptions are further classified as either continuable or non-continuable. A continuable exception, once handled, allows the program to resume execution from the point of interruption. In contrast, a non-continuable exception denotes a grave error that precludes the program's continuation, necessitating its termination. Such exceptions signify insurmountable issues that render the continuation of execution illogical or impossible from the hardware's perspective.
The heart of SEH resides in the kernel, where it orchestrates a linked list of exception registration records meticulously placed on the stack. Each record encapsulates a pointer to an exception handler function dedicated to addressing specific exceptions. When an exception is thrown, the system embarks on a quest along this list to unearth a suitable handler to process the exception, commencing with the most recently registered handler and advancing to the oldest one in the hierarchy. [2]
Upon the manifestation of any exception, the operating system initiates a search for a corresponding exception handler. Failing to locate one, it takes the decisive step of terminating the process to prevent any further anomalies.
However, when an exception handler is located, it has the discretion to:
Handle the exception and continue execution.
Perform cleanup operations and then unwind the stack, passing the exception to the next handler.
Dismiss the exception and search for another handler to process it.
Additional information
It's pivotal to elucidate that stack unwinding is a procedural mechanism involving the deconstruction of objects that have been allocated on the stack within the defined scope of a local function, which is expressly demarcated by the { and } braces. This procedure is integral to the judicious management of resources during the runtime of a program, ensuring that the resources appropriated by these objects are duly relinquished, thereby averting potential resource leaks. [3]
This underlying concept is the cornerstone of a well-regarded programming idiom known as Resource Acquisition Is Initialization (RAII). The crux of RAII encapsulates the paradigm of acquiring resources during the initialization phase and correspondingly relinquishing them upon destruction. This design framework is intrinsic to maintaining code safety, readability, and ensuring deterministic resource management. [4]
The RAII idiom is especially revered in the domain of concurrent programming, where it finds prolific application in managing mutex locks within multi-threaded environments. By adhering to the RAII design principles, developers can ascertain that mutex locks are procured and released in a deterministic and exception-safe manner. This is orchestrated by acquiring the lock during the initialization of an object and releasing it upon the object's destruction, effectively mitigating the risk of deadlocks and fostering seamless synchronization amongst threads.
Transitioning to the act of returning from a function call, it's notable that the process entails popping the top frame off the stack, which in turn leaves behind a return value. This action of popping one or more frames off the stack to redirect execution flow elsewhere within the program is encapsulated in the term 'stack unwinding'. [5] Stack unwinding is instrumental in navigating through the call stack to find suitable exception handlers, and it also plays a vital role in ensuring that the program's state remains consistent and resources are properly managed across function calls and exceptions. Through stack unwinding, programs can achieve a structured and orderly method of transitioning control and resources between different scopes and contexts, thereby enhancing the robustness and predictability of software constructs.
For further examples and documentation about RAII in C++ you can check the following links: https://www.boost.org/doc/libs/1_62_0/doc/html/boost/interprocess/scoped_lock.html , https://www.boost.org/doc/libs/1_41_0/libs/smart_ptr/scoped_ptr.htm and https://learn.microsoft.com/en-us/cpp/cpp/exceptions-and-stack-unwinding-in-cpp?view=msvc-170
Internals
SEH works on a per-thread basis. We're able to define SEH handlers with the __try and __except keywords, but they are exclusive to the Microsoft C++ Compiler, therefor they are not part of the C++ standard. Although, since SEH is just part of the Microsoft ABI and not tied to a standard or a language, there's alternative ways to setup a SEH handler, without __try and __except.
In the above code we're defining MyDivisionByZero01Exception as the Exception Filter, which in a regular MSVC exception code would be inside the __except() keyword. The Exception Filter instructs the SEH engine what to do next.
This is the following structure of the TIB [10]:
_EXCEPTION_REGISTRATION_RECORD structure is the Exception Handler List of the current thread
The above structure is obviously a linked list of Exception Handler routines, as seen before, the PEXCEPTION_ROUTINE is a pointer to a user-defined function with the following signature:
The OS will traverse the linked list in order to determine which of these handlers will be used to handle the exception.
[11]
Each handler might indicate the way to handle the exception via its return value, that are enumerated in the _EXCEPTION_DISPOSITION enum, and it's values are:
ExceptionContinueExecution
ExceptionContinueSearch
ExceptionNestedException
ExceptionCollidedUnwind
If the return value is ExceptionContinueSearch, then the OS will continue to search the next handlers.
If no handler is found, the OS will execute the Unhandled Exception Filter, UnhandledExceptionFilter might be used to place a custom filter. C/C++ Runtime Library sets the __scrt_set_unhandled_exception_filter() as the Unhandled Exception Filter by default. [9]
The context of the execution of the thread or machine state in which the exception ocurred is saved in a CONTEXT structure, which being a processor-specific register data it has different structures depending on the architecture, the base model can be found here: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context, but the actual structure used by the OS is according to the architecture of the host, for example https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-arm64_nt_context
If the process is being debugged, the system notifies the debugger (called first-chance exception). If it's not, or the debugger does not handle the exception, the OS attempts to locate a frame-based exception handler by searching the stack frames of the thread in which the exception ocurred in reverse order. Then if any frame-based exception is not found, the debugger gets a second notification (called last-chance exception), if the before fails to handle the exception or there isn't a debugger attached, the OS performs a default handling, which is to terminate the process in the most cases. [6]
SafeSEH
Since Windows XP SP2 and Windows Server 2003 Microsoft introduced safe structured exception handling, the idea was to store handler's entrypoints in a read only table, so it could be verified prior control being passed to the handler to avoid execution of corrupted exception handlers, which is actually a common exploitation technique, so when an exception occurs and the address of the handler is controlled by the exploit, there's limited choices of addresses, which makes the exploitation harder.
The Safe Structured Exception Handler comes inside the binary, exactly on the Exception Directory (or IMAGE_DIRECTORY_ENTRY_EXCEPTION) inside DataDirectory found on the Optional Header of the NT headers, which is an array of RUNTIME_FUNCTION structures. [12]
The BeginAddress and EndAddress are the boundaries of the function, and the UnwindData is a pointer to the UNWIND_INFO structure, which contains the information needed to unwind the stack. [8]
SafeSEH can be inspected statically using the following script: https://gist.github.com/zeroSteiner/44c95ccefeb9dc61e323
Vectored Exception Handling
Vectored Exception Handling (VEH) is an extension to Structured Exception Handling (SEH) that allows an application to register a function to observe or handle all exceptions for the application. Unlike SEH, which is frame-based, VEH is not tied to the function call stack, and the handlers are called regardless of where the exception occurred in the code. [13]
The handlers are stored as nodes in a doubly linked list, with each node having a structure _VECTORED_NODE comprising of pointers to the next and previous nodes, a boolean to indicate if the node is allocated, and a pointer to the encoded handler function.
VEH has the priority over SEH, meaning that the vectored exception handlers are called before the structured exception handlers. This priority allows a VEH to handle an exception in a way that may prevent subsequent SEH handlers from being invoked.
References:
Karl-Bridge, v-kents, msatranjr. (2021). Structured Exception Handling. Microsoft Learn. Retrieved from https://learn.microsoft.com/en-us/windows/win32/debug/structured-exception-handling.
Karl-Bridge, v-kents, msatranjr. (2021). About Structured Exception Handling. Microsoft Learn. Retrieved from https://learn.microsoft.com/en-us/windows/win32/debug/about-structured-exception-handling
Karl-Bridge-Microsoft, v-kents, DCtheGeek, drewbatgit, msatranjr. (2021). Termination Handling. Microsoft Learn. Retrieved from https://learn.microsoft.com/en-us/windows/win32/debug/termination-handling
Multiple. (2023, October). Resource Acquisition is initialization. Wikipedia. Article. Retreived from https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
Multiple. (2023). Call Stack. Wikipedia. Article. Retrieved from https://en.wikipedia.org/wiki/Call_stack#Unwinding
Karl-Bridge-Microsoft, v-kents, DCtheGeek, drewbatgit, mijacobs, msatranjr. (2021). Exception dispatching. Microsoft Learn. Retrieved from https://learn.microsoft.com/en-us/windows/win32/debug/exception-dispatching
Karl-Bridge-Microsoft, v-kents, DCtheGeek, drewbatgit, mijacobs, msatranjr. (2021). Debugger Exception Handling. Microsoft Learn. Retrieved from https://learn.microsoft.com/en-us/windows/win32/debug/debugger-exception-handling
ReactOS Documentation. (May, 2021). SEH64. Retrieved from https://reactos.org/wiki/Techwiki:SEH64
Lim Bio Liong. (January, 2022). Understanding Windows Structured Exception Handling Part 1 – The Basics. limbioliong blog. Retrieved from https://limbioliong.wordpress.com/2022/01/09/understanding-windows-structured-exception-handling-part-1/
René Nyffenegger. (n. d). WinAPI data types and constants. Retrieved from https://renenyffenegger.ch/notes/Windows/development/WinAPI/data-types/index#winapi-datatype-NT_TIB
Mantvydas Baranauskas. (n. d). SEH Based Buffer Overflow. Retrieved from https://www.ired.team/offensive-security/code-injection-process-injection/binary-exploitation/seh-based-buffer-overflow
Peter Johnson. (2009). Yasm User Manual: 15-2 win32: Safe Structured Exception Handling. https://www.tortall.net/projects/yasm/manual/html/objfmt-win32-safeseh.html
https://learn.microsoft.com/en-us/windows/win32/debug/vectored-exception-handling
https://stackoverflow.com/questions/33428682/manually-adding-a-vectored-exception-handler
https://unprotect.it/technique/addvectoredexceptionhandler/
https://www.codereversing.com/archives/205
https://crashrpt.sourceforge.net/docs/html/exception_handling.html
https://reverseengineering.stackexchange.com/questions/14992/what-are-the-vectored-continue-handlers
Related resources:
James McNellis. (2018). Unwinding the Stack: Exploring How C++ Exceptions Work on Windows. CppCon. Retrieved from https://www.youtube.com/watch?v=COEv2kq_Ht8&ab_channel=CppCon
Matt Pietrek. (1997). A Crash Course on the Depths of Win32™ Structured Exception Handling. Retrieved from https://bytepointer.com/resources/pietrek_crash_course_depths_of_win32_seh.htm
Bill Demikarpi. (February, 2022). Exception Oriented Programming: Abusing Exceptions for Code Execution Part 1. Retrieved from https://billdemirkapi.me/exception-oriented-programming-abusing-exceptions-for-code-execution-part-1/
Andy Bowden. (n. d). The Basics of Exploit Development 2: SEH Overflows. Retrieved from https://www.coalfire.com/the-coalfire-blog/the-basics-of-exploit-development-2-seh-overflows
MDSec. (2022). Resolving System Service Numbers using the Exception Directory. Retrieved from https://www.mdsec.co.uk/2022/04/resolving-system-service-numbers-using-the-exception-directory/
(n. a). (2022). Misusing Structured Exception Handlers. Retrieved from https://unprotect.it/technique/misusing-structured-exception-handlers/
Ildre. (2022). Exception Hijacking. Retrieved from https://saza.re/posts/exception_hijack/
Interesting links:
https://www.osronline.com/article.cfm%5Earticle=469.htm
https://web.archive.org/web/20080915135659/https://msdn.microsoft.com/en-us/magazine/cc301714.aspx
http://uninformed.org/index.cgi?v=8&a=2&p=20%
Last updated
Was this helpful?