Syscalls
System calls (a.k.a. syscalls or software interrupts) are the interface between user space and kernel space. They are used to request services from the kernel, such as reading from a file or creating a new process. libdebug allows you to trace syscalls invoked by the debugged program. Specifically, you can choose to handle or hijack a specific syscall (read more on hijacking).
For extra convenience, the Debugger and the ThreadContext objects provide a system-agnostic interface to the arguments and return values of syscalls. Interacting directly with these parameters enables you to create scripts that are independent of the syscall calling convention specific to the target architecture.
Field | Description |
---|---|
syscall_number |
The number of the syscall. |
syscall_arg0 |
The first argument of the syscall. |
syscall_arg1 |
The second argument of the syscall. |
syscall_arg2 |
The third argument of the syscall. |
syscall_arg3 |
The fourth argument of the syscall. |
syscall_arg4 |
The fifth argument of the syscall. |
syscall_arg5 |
The sixth argument of the syscall. |
syscall_return |
The return value of the syscall. |
Example of Syscall Parameters
[...] # (1)
binsh_str = d.memory.find(b"/bin/sh\x00", file="libc")[0]
d.syscall_arg0 = binsh_str
d.syscall_arg1 = 0x0
d.syscall_arg2 = 0x0
d.syscall_number = 0x3b
d.step() # (2)
- The instruction pointer is on a syscall / SVC instruction
- Now the
execve('/bin/sh', 0, 0)
will be executed in place of the previous syscall.
Syscall Handlers
Syscall handlers can be created to register stopping events for when a syscall is entered and exited.
Do I have to handle both on enter and on exit?
When using asynchronous syscall handlers, you can choose to handle both or only one of the two events. However, when using synchronous handlers, both events will stop the process.
libdebug API for Syscall Handlers
The handle_syscall()
function in the Debugger object registers a handler for the specified syscall.
Parameters:
Argument | Type | Description |
---|---|---|
syscall |
int | str |
The syscall number or name to be handled. If set to "*" or "all" or "ALL" , all syscalls will be handled. |
on_enter |
Callable | bool (see callback signature here) |
The callback function to be executed when the syscall is entered. |
on_exit |
Callable | bool (see callback signature here) |
The callback function to be executed when the syscall is exited. |
recursive |
bool |
If set to True , the handler's callback will be executed even if the syscall was triggered by a hijack or caused by a callback. |
Returns:
Return | Type | Description |
---|---|---|
SyscallHandler |
SyscallHandler | The handler object created. |
Callback Signature
Parameters:
Argument | Type | Description |
---|---|---|
t |
ThreadContext | The thread that hit the syscall. |
handler |
SyscallHandler | The SyscallHandler object that triggered the callback. |
Nuances of Syscall Handling
The syscall handler is the only stopping event that can be triggered by the same syscall twice in a row. This is because the handler is triggered both when the syscall is entered and when it is exited. As a result the hit_on()
method of the SyscallHandler object will return True
in both instances.
You can also use the hit_on_enter()
and hit_on_exit()
functions to check if the cause of the process stop was the syscall entering or exiting, respectively.
As for the hit_count
attribute, it only stores the number of times the syscall was exited.
Example usage of asynchronous syscall handlers
def on_enter_open(t: ThreadContext, handler: SyscallHandler):
print("entering open")
t.syscall_arg0 = 0x1
def on_exit_open(t: ThreadContext, handler: SyscallHandler):
print("exiting open")
t.syscall_return = 0x0
handler = d.handle_syscall(syscall="open", on_enter=on_enter_open, on_exit=on_exit_open)
Example of synchronous syscall handling
from libdebug import debugger
d = debugger("./test_program")
d.run()
handler = d.handle_syscall(syscall="open")
d.cont()
if handler.hit_on_enter(d):
print("open syscall was entered")
elif handler.hit_on_exit(d):
print("open syscall was exited")
The script above will print "open syscall was entered".
Resolution of Syscall Numbers
Syscall handlers can be created with the identifier number of the syscall or by the syscall's common name. In the second case, syscall names are resolved from a definition list for Linux syscalls on the target architecture. The list is fetched from mebeim's syscall table. We thank him for hosting such a precious resource. Once downloaded, the list is cached internally.
Hijacking
When hijacking a syscall, the user can provide an alternative syscall to be executed in place of the original one. Internally, the hijack is implemented by registering a handler for the syscall and replacing the syscall number with the new one.
Parameters:
Argument | Type | Description |
---|---|---|
original_syscall |
int | str |
The syscall number or name to be hijacked. If set to "*" or "all" or "ALL" , all syscalls will be hijacked. |
new_syscall |
int | str |
The syscall number or name to be executed instead. |
recursive |
bool |
If set to True , the handler's callback will be executed even if the syscall was triggered by a hijack or caused by a callback. |
**kwargs |
(int, optional) |
Additional arguments to be passed to the new syscall. |
Returns:
Return | Type | Description |
---|---|---|
SyscallHandler |
SyscallHandler | The handler object created. |
Example of hijacking a syscall
In this case, the secret will be leaked to the standard output instead of being overwritten with content from the standard input.For your convenience, you can also easily provide the syscall parameters to be used when the hijacked syscall is executed: