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:

bits 64

section .text

global assembly
assembly:
  mov rax, 60
  mov rdi, 0
  syscall
  ret

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 <felipe.silva337@yahoo.com>"
		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:

Last updated