Links

Syscall no Linux

Chamada de sistema no Linux
Uma chamada de sistema, ou syscall (abreviação para system call), é algo muito parecido com uma call mas com a diferença nada sutil de que é o kernel do sistema operacional quem irá executar o código.
O kernel é a parte principal de um sistema operacional encarregada de gerenciar todo o sistema, desde o hardware até mesmo a execução do software (processos/tarefas). Ele é a base de todo o restante do sistema que roda sob controle do kernel. O Linux na verdade é um kernel, um "sistema operacional Linux" na verdade é um sistema operacional que usa o kernel Linux.
Em x86-64 existe uma instrução que foi feita especificamente para fazer chamadas de sistema e o nome dela é, intuitivamente, syscall. Ela não recebe nenhum operando e a especificação de qual código ela irá executar e com quais argumentos é definido por uma convenção de chamada assim como no caso das funções.

Convenção de syscall x86-64

A convenção para efetuar uma chamada de sistema em Linux x86-64 é bem simples, basta definir RAX para o número da syscall que você quer executar e outros 6 registradores são usados para passar argumentos. Veja a tabela:
Registrador
Uso
RAX
Número da syscall / Valor de retorno
RDI
1° argumento
RSI
2° argumento
RDX
3° argumento
R10
4° argumento
R8
5° argumento
R9
6° argumento
O retorno da syscall também fica em RAX assim como na convenção de chamada da linguagem C.
Em syscalls que recebem menos do que 6 argumentos não é necessário definir o valor dos registradores restantes porque não serão utilizados.

exit

Nome
RAX
RDI
exit
60
int status_de_saída
Vou ensinar aqui a usar a syscall mais simples que é a exit, ela basicamente finaliza a execução do programa. Ela recebe um só argumento que é o status de saída do programa. Esse número nada mais é do que um valor definido para o sistema operacional que indica as condições da finalização do programa.
Por convenção geralmente o número zero indica que o programa finalizou sem problemas, e qualquer valor diferente deste indica que houve algum erro. Um exemplo na nossa PoC:
assembly.asm
main.c
bits 64
section .text
global assembly
assembly:
mov rax, 60
mov rdi, 0
syscall
ret
#include <stdio.h>
int assembly(void);
int main(void)
{
assembly();
puts("Hello World!");
return 0;
}
A instrução ret na linha 10 nunca será executada porque a syscall disparada pela instrução syscall na linha 9 não retorna. No momento em que for chamada o programa será finalizado com o valor de RDI como status de saída.
No Linux se quiser ver o status de saída de um programa a variável especial $? expande para o status de saída do último programa executado. Então você pode executar nossa PoC da seguinte forma:
$ ./test
$ echo $?
O echo teoricamente iria imprimir 0 que é o status de saída que nós definimos. Experimente mudar o valor de RDI e ver se reflete na mudança do valor de $? corretamente.

Outras syscalls

Se quiser ver uma lista completa de syscalls x86-64 do Linux pode ver no link abaixo:
Você também pode consultar o conteúdo do arquivo cabeçalho /usr/include/x86_64-linux-gnu/asm/unistd_64.h para ver uma lista completa da definição dos números de syscall.
Além disso também sugiro consultar a man page do wrapper em C da syscall afim de entender mais detalhadamente o que cada uma delas faz. Por exemplo:
$ man 2 exit
E para simplificar a consulta de syscalls no meu Linux eu implementei e uso a seguinte função em Bash. Fique à vontade para usá-la:
function syscall() {
if [ -z "$1" ]; then
echo "Developed by Luiz Felipe <[email protected]>"
echo "See (Portuguese): https://mentebinaria.gitbook.io/assembly-x86"
echo
echo "Usage: syscall name [32|64]"
return 0
fi
name="$1"
bits="${2-64}"
number=$(grep -m1 "__NR_$name" \
"/usr/include/x86_64-linux-gnu/asm/unistd_$bits.h" \
| cut -d' ' -f3)
[ -z "$number" ] && return 1
if [ "$bits" == "64" ]; then
sysnumRegister="RAX"
arguments="RDI, RSI, RDX, R10, R8, R9"
else
sysnumRegister="EAX"
arguments="EBX, ECX, EDX, ESI, EDI, EBP"
fi
echo "Syscall number ($sysnumRegister): $number"
echo "Arguments: $arguments"
echo
echo "Synopsis:"
awkCode='
/SYNOPSIS/,/DESCRIPTION/{
if ($1 != "SYNOPSIS" && $1 != "DESCRIPTION") {
print $0
}
}
'
man 2 "$name" | awk "$awkCode"
return 0
}
Exemplo de uso: