Anti-Debug: Exceptions
Contents
- 1. UnhandledExceptionFilter()
- 2. RaiseException()
- 3. Hiding Control Flow with Exception Handlers
- Mitigations
Exceptions
The following methods deliberately cause exceptions to verify if the further behavior is not typical for a process running without a debugger.
1. UnhandledExceptionFilter()
If an exception occurs and no exception handler is registered (or it is registered but doesn’t handle such an exception), the kernel32!UnhandledExceptionFilter() function will be called. It is possible to register a custom unhandled exception filter using the kernel32!SetUnhandledExceptionFilter(). But if the program is running under a debugger, the custom filter won’t be called and the exception will be passed to the debugger. Therefore, if the unhandled exception filter is registered and the control is passed to it, then the process is not running with a debugger.
x86 Assembly (FASM)
C/C++ Code
2. RaiseException()
Exceptions such as DBC_CONTROL_C or DBG_RIPEVENT are not passed to exception handlers of the current process and are consumed by a debugger. This lets us register an exception handler, raise these exceptions using the kernel32!RaiseException() function, and check whether the control is passed to our handler. If the exception handler is not called, the process is likely under debugging.
C/C++ Code
3. Hiding Control Flow with Exception Handlers
This approach does not check whether a debugger is present, but it helps to hide the control flow of the program in the sequence of exception handlers.
We can register an exception handler (structured or vectored) which raises another exception which is passed to the next handler which raises the next exception, and so on. Finally, the sequence of handlers should lead to the procedure that we wanted to hide.
Using Structured Exception Handlers:
C/C++ Code
Using Vectored Exception Handlers:
C/C++ Code
Mitigations
- During debugging:
- For debugger detection checks: Just fill the corresponding check with NOPs.
- For Control Flow hiding: You have to manually trace the program till the payload.
- For anti-anti-debug tool development: The issue with these type of techniques is that different debuggers consume different exceptions and do not return them to the debugger. This means that you have to implement a plugin for a specific debugger and change the behavior of the event handlers which are triggered after the corresponding exceptions.