Debugging Flow of Stopping Events
Before diving into each libdebug stopping event, it's crucial to understand the debugging flow that these events introduce, based on the mode selected by the user.
The flow of all stopping events is similar and adheres to a mostly uniform API structure. Upon placing a stopping event, the user is allowed to specify a callback function for the stopping event. If a callback is passed, the event will trigger asynchronously. Otherwise, if the callback is not passed, the event will be synchronous. The following flowchart shows the difference between the two flows.
When a synchronous event is hit, the process will stop, awaiting further commands. When an asynchronous event is hit, libdebug temporarily stops the process and invokes the user callback. Process execution is automatically resumed right after.
Tip: Use cases of asynchronous stopping events
The asynchronous mode for stopping events is particularly useful for events being repeated as a result of a loop in the executed code.
When attempting side-channel reverse engineering, this mode can save a lot of your time.
Types of Stopping Events
libdebug supports the following types of stopping events:
Event Type | Description | Notes |
---|---|---|
Breakpoint | Stops the process when a certain address is executed | Can be a software or a hardware breakpoint |
Watchpoint | Stops the process when a memory area is read or written | Alias for a hardware breakpoint |
Syscall | Stops the process when a syscall is made | Two events are supported: syscall start and end |
Signal | Stops the process when a signal is received |
Multiple callbacks or hijacks
Please note that there can be at most one user-defined callback or hijack for each instance of a stopping event (the same syscall, signal or breakpoint address). If a new stopping event is defined for the same thing, the new stopping event will replace the old one, and a warning will be printed.
Internally, hijacks are considered callbacks, so you cannot have a callback and hijack registered for the same event.
Common APIs of Stopping Events
All libdebug stopping events share some common attributes that can be employed in debugging scripts.
Enable/Disable
All stopping events can be enabled or disabled at any time. You can read the enabled
attribute to check the current state of the event. To enable or disable the event, you can call the enable()
or disable()
methods respectively.
Callback
The callback function of the event can be set, changed or removed (set to None
) at any time. Please be mindful of the event mode resulting from the change on the callback parameter. Additionally, you can set the callback to True
to register an empty callback.
Hit Records
Stopping events have attributes that can help you keep track of hits. For example, the hit_count
attribute stores the number of times the event has been triggered.
The hit_on()
function is used to check if the stopping event was the cause of the process stopping. It is particularly useful when debugging multithreaded applications, as it takes a ThreadContext as a parameter. Refer to multithreading for more information.
Hijacking
Hijacking is a powerful feature that allows you to change the flow of the process when a stopping event is hit. It is available for both syscalls and signals, but currently not for other stopping events. When registering a hijack for a compatible stopping event, that execution flow will be replaced with another.
For example, in the case of a signal, you can specify that a received SIGALRM
signal should be replaced with a SIGUSR1
signal. This can be useful when you want to prevent a process from executing a certain code path. In fact, you can even use the hijack feature to "NOP" the syscall or signal altogether, avoiding it to be executed / forwarded to the processed. More information on how to use this feature in each stopping event can be found in their respective documentation.
Recursion
Mixing asynchronous callbacks and hijacking can become messy. Because of this, libdebug provides users with the choice of whether to execute the callback for an event that was triggered by a callback or hijack.
This behavior is enabled by the parameter recursive
, available when instantiating a syscall handler, a signal catcher, or their respective hijackers. By default, recursion is disabled.
Recursion Loop Detection
When carelessly doing recursive callbacks and hijacking, it could happen that loops are created. libdebug automatically performs checks to avoid these situations and raises an exception if an infinite loop is detected.
For example, the following code raises a RuntimeError
: