description | cover | coverY | layout | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
06/09/2024 |
../../.gitbook/assets/Screenshot 2024-06-09 at 1.15.47 AM.png |
0 |
|
As previously mentioned, syscalls are mechanisms that allow user-accessed and controlled applications to request services from the OS's kernel. There are two main methods for making syscalls.
- Direct
- Indirect
{% embed url="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Priv_rings.svg/600px-Priv_rings.svg.png" fullWidth="false" %} There's lots of talk between user-land and kernel-land. Hopefully this clears it up. Also, you may hear me addressing various layers as "rings". {% endembed %}
This method involves the application making a direct call to the kernel using the corresponding syscall number. Remember, this syscall number is a unique identifier for the service/function requested from the underlying kernel. Direct syscalls are implemented directly through Assembly code via Assembly instructions.
Direct syscalls work by preparing arguments for the syscall within the specific numbers that are required by the syscall convention for the corresponding architecture. Be sure to follow the link above for abiding by the convention.
Next, load the syscall number into the specific register.
Next, trigger the syscall using a special instruction or a software interrupt, this will transition control to the kernel. User-mode -> Kernel-mode.
Kernel Execution: The kernel will then catch the interrupt or special instruction, perform a lookup on the syscall number received, and execute the kernel function from the corresponding syscall number.
Lastly, the kernel function will return to user-mode the moment the kernel-level function completes its work.
// connect(sockfd, &sockaddr, 16)
mov x0, x4 // sockfd
adr x1, sockaddr // pointer to address, port
mov x2, #16
mov w8, #203 // x8 = 203 (connect)
svc #0 // call syscall -> kernel-mode
An indirect syscall will involve an intermediary, such as a dynamic linker or a library that will abstract the details of making the syscall; hence, indirect. However, the syscalls origins and underlying implementations are still occurring, just abstracted.
🚨 This is why malware loves indirect syscalls. These are "hard" to keep track of.
First, the application call will perform a high-level function provided by a library (libc).
Next, the library function within library code will prepare the arguments and perform the duties of the direct syscall on behalf of the user-controlled application.
Syscall Execution: The direct syscall is executed as described in the direct syscall section.
Return to Application: The result is returned to the library function, which may process it further before returning it to the application.
Here is an example C program that uses the libc library to write to the STDOUT file descriptor and exit.
example.c
:
#include <unistd.h>
#include <stdlib.h>
int main() {
const char *msg = "Hello, World!\n";
write(STDOUT_FILENO, msg, 14); // Indirect syscall to sys_write
exit(EXIT_SUCCESS); // Indirect syscall to sys_exit
}
write
is a high-level, abstracted function, that is provided by libc that performs the write
syscall.
{% embed url="https://man7.org/linux/man-pages/man2/write.2.html" %} Documentation {% endembed %}
exit
is another, that is provided by the libc library that internally handles and performs the exit
syscall.
{% embed url="https://man7.org/linux/man-pages/man2/exit.2.html" %} Documentation {% endembed %}
Direct syscalls are way more efficient because there is no intermediary layer and can be called, well, directly. However, a deeper knowledge and understanding of lower-level technologies is required.
Indirect syscalls are easier to use because they are provided by abstracted higher-level libraries such as libc. This will require additional performance overhead, compared to direct Assembly syscall functionality.
{% embed url="https://redops.at/en/blog/direct-syscalls-vs-indirect-syscalls" %} Awesome Read {% endembed %}