Event calling stage
The event calling stage in HyperDbg
Starting from HyperDbg v0.5, the event calling stages mechanism is added to the debugger.
This mechanism enables us to specify the exact stage at which HyperDbg will trigger the event. For instance, we can choose whether the event should be called before or after the emulation process takes place.
Most of the events in HyperDbg are based on emulation. For example, before or after modifying the memory (as a result of the '!monitor' command). Another example is for different special instructions like RDMSR, WRMSR, CPUID, etc. These stages will notify HyperDbg whether the event should be triggered before the execution of these instructions or after the execution of these instructions.
Different Calling Stages
HyperDbg allows you to specify one of the following calling stages for an event:
All
Pre
Post
When you specify 'all' for an event, it will be triggered both before and after the emulation process. On the other hand, if you choose 'pre', the event will be triggered before the emulation, and if you select 'post', it will be triggered after the emulation. If no event stage is explicitly specified, the default behavior is to trigger events in the 'pre' calling stage.
The calling stages serve a valuable purpose in analyzing specific events such as '!cpuid', '!msrread', '!msrwrite', etc. For instance, by selecting the 'post' calling stage, you can observe and potentially modify the results of the emulation after it has occurred.
One of the primary applications of these calling stages is seen in the '!monitor' command. For instance, if you wish to be notified whenever memory is modified at a particular target address, you can utilize the 'pre' calling stage to examine the memory's content before a MOV instruction, while the 'post' calling stage reveals the content of the memory after executing the MOV, effectively showing the modified memory contents.
Calling Stages and Event Short-circuiting
An important note is, once you short-circuit or ignore an event, the 'post' calling stage will not be triggered as the emulation is completely ignored and in reality, there is no 'post' stage.
Another note is that short-circuiting events are not permitted in the 'post' stage. This is because, by the time the 'post' stage is reached, the emulation has already been performed, and there is nothing left to be ignored, making it illogical to apply short-circuiting at this point.
Using Calling Stages in Events
Event calling stages are determined by using the 'stage pre', 'stage post', or 'stage all' in the definition of events.
For example, you can specify the 'post' stage for the following event commands:
Or you can specify the 'all' stage for them.
Finding Calling Stages in Scripts
In order to determine the calling stage, a new pseudo-register called '$event_stage' is added. This pseudo-register can be either 0 which shows that the event is called in the 'pre' calling stage or 1 which shows it's called in the 'post' calling stage.
Please be aware that the event invocation stage cannot be 'all' since the event occurs either in the 'pre' or 'post' calling stage, and it cannot occupy both simultaneously.
The following example reads the '$event_stage' pseudo-register in case of an event and as the result is 1, it means the event is called in the 'post' calling stage.
In cases where the debugger is halted without being triggered by an event (such as through a breakpoint, CTRL+C, stepping, etc.), the pseudo-register '$event_stage' will display a value of 0. However, it's important not to misinterpret this as a 'pre' stage, as this pause isn't prompted by an actual event.
Finding Calling Stages in Event Breaks
Once an event is triggered, the debugger will show the event id, as well as the event calling stage, for example, the following event is triggered in the 'post' calling stage. Note the (post)
in the debugger message.
Or the following event is called the 'pre' calling stage.
Examples
Below are various examples demonstrating the usage of the event calling stages mechanism. You can apply this mechanism to events that support calling stages. Please refer to the documentation for each specific event to determine whether it supports calling stages or not.
Example 1
The following example shows how we can view the memory before and after modification by using the 'all' calling stage and how the '$event_stage' pseudo-register is used to check for different stages.
Example 2
The following example shows how we can view (or possibly modify the registers) after running a CPUID instruction.
In this example, we checked whether the '$context' or the EAX register is equal to 1 and if it's equal to it, we mask the ECX register's 5th bit (which is the indicator of support for Intel VT-x). As a result, if you use a program like CPU-Z, it no longer shows support for VT-x (just for the CPUID instruction, not disabling it completely).
Example 3
As described here, SYSCALL saves RFLAGS into R11 and then masks RFLAGS using the IA32_FMASK MSR (MSR address 0xC0000084); specifically, the processor clears in RFLAGS every bit corresponding to a bit that is set in the IA32_FMASK MSR.
Using the following example we would be sure that if somewhere in the drivers, rootkits, or Windows, some codes try to modify this MSR register, the 9th flag (Interrupt Flag) will remain untouched.
Example 4
The following example shows how we can use the 'post' calling stage to view the CR2 register as a result of a page-fault. Note that, the value of the @cr2
is not valid in the 'pre' calling stage.
Miscellaneous
Commands like '!epthook' or '!epthook2' don't support calling stages. Due to the nature of function hooking implementing calling stages in this context wouldn't be meaningful.
If a singular event causes a special EPT hook, special MSR read/write, or any other event to short-circuit, the emulation process will not take place. Consequently, the 'post' mode will be disregarded for all similar events sharing the same conditions. To illustrate, consider a scenario where multiple events are associated with a single CPUID instruction. One event resides in the 'pre' start stage, while the remaining events (with identical conditions) are situated in the 'post' stage. If the 'pre' stage event leads to a short-circuit, all subsequent 'post' events will fail to trigger.
Last updated