#include <stdio.h>
int assembly(void);
int main(void)
{
assembly();
puts("Hello World!");
return 0;
}
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.
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:
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.
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:
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:
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.
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:
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:
Exemplo de uso:
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
Nome
RAX
RDI
exit
60
int status_de_saída