description |
---|
09/21/2023 |
A syscall is a system call.
Easiest explanation that I could find:
A syscall is a function executed by the kernel.
This is how the program enters the kernel in order to carry out a specific task such as creating a process, Input and Output, and other tasks that require kernel-level access.
Check out the Linux Syscall Table:
{% embed url="https://chromium.googlesource.com/chromiumos/docs/+/HEAD/constants/syscalls.md#x86_64-64_bit" %}
You will come to notice that syscalls are very similar in appearance to libc
functions.
open()
fork()
read()
Ultimately, these functions can be summed up as wrappers around syscalls, making it easier for programmers to utilize in code.
In Linux, syscalls are triggered by using the int80
instruction.
Once called, the kernel checks the value stored in RAX
and this is equivalent to the syscall number which is what depicts WHICH syscall is ran.
The execve
syscall executes the program passed to it in the RDI register.
RSI
and RDX
hold argp
and envp
respectively.
argp
are command line argumentsenvp
are environment variables
If there is no system()
function, we can use execve
to call "/bin/sh"
instead.
This will pass in a pointer "/bin/sh"
to RDI
, populating the RSI
and RDX
with 0
since both argv
and envp
need to have the value of NULL
to grant you a shell.
Kernel-based malware and exploits require syscalls in order to intercept file read/writes, open sockets for network communications, creating files, read/write memory, etc.
In other words, you need syscalls in order to do something malicious.
A remote kernel exploit is possible.
Say there is a vulnerability in the TCP/IP stack which runs in the kernel for Linux.
This vulnerability could ultimately lead to kernel-level exploitation without any syscalls ever being invoked.
We can utilize strace
to see what we are syscalls are being invoked.
This gets really interesting.
We can see execve()
placing the value 0
into RAX
(which the kernel checks), storing the value as a parameter (0
) into RDI
and calling read()
or 0
which is the syscall number for read()
.
{% code overflow="wrap" %}
strace ./vuln execve("./vuln", ["./vuln"], 0x7fff13afab20 /* 22 vars */) = 0 read(0, "\n", 300) = 1 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x1} --- +++ killed by SIGSEGV +++
{% endcode %}