Position-independent executable

Explicando PIE e ASLR

Como vimos no tópico Endereçamento o processador calcula o endereço dos operandos na memória onde o resultado do cálculo será o endereço absoluto onde o operando está.

O problema disso √© que o c√≥digo que escrevemos precisa sempre ser carregado no mesmo endere√ßo sen√£o os endere√ßos nas instru√ß√Ķes estar√£o errados. Esse problema foi abordado no t√≥pico sobre MS-DOS, onde a diretiva org 0x100 precisa ser usada para que o NASM calcule o offset correto dos s√≠mbolos sen√£o os endere√ßos estar√£o errados e o programa n√£o funcionar√° corretamente.

Sistemas operacionais modernos têm um recurso de segurança chamado ASLR que dificulta a exploração de falhas de segurança no binário. Resumidamente ele carrega os endereços dos segmentos do executável em endereços aleatórios ao invés de sempre no mesmo endereço. Com o ASLR desligado os segmentos sempre são mapeados nos mesmos endereços.

Por√©m um c√≥digo que acessa endere√ßos absolutos jamais funcionaria apropriadamente com o ASLR ligado. √Č a√≠ que entra o conceito de Position-independent executable (PIE) que nada mais √© que um execut√°vel com c√≥digo que somente acessa endere√ßos relativos, ou seja, n√£o importa em qual endere√ßo (posi√ß√£o) voc√™ carregue o c√≥digo do execut√°vel ele ir√° funcionar corretamente.

Na nossa PoC eu instruí para compilar o programa usando a flag -no-pie no GCC para garantir que o linker não iria produzir um executável PIE já que ainda não havíamos aprendido sobre o assunto. Mas depois de aprender a escrever código com endereçamento relativo em Assembly fique à vontade para remover essa flag e começar a escrever programas independentes de posição.

PIE em x86-64

J√° vimos no t√≥pico Endere√ßamento que em x86-64 se tem um novo endere√ßamento relativo √† RIP. √Č muito mais simples escrever c√≥digo independente de posi√ß√£o no modo de 64-bit devido a isso.

Podemos usar a palavra-chave rel no endereçamento para dizer para o NASM que queremos que ele acesse um endereço relativo à RIP. Conforme exemplo:

mov rax, [rel my_var]

Também podemos usar a diretiva default rel para que o NASM compile todos os endereçamentos como relativos por padrão. Caso você defina o padrão como endereço relativo a palavra-chave abs pode ser usada da mesma maneira que a palavra-chave rel porém para definir o endereçamento como absoluto.

Um exemplo de PIE em modo de 64-bit:

#include <stdio.h>

char *assembly(void);

int main(void)
{
  printf("Resultado: %s\n", assembly());
  return 0;
}

Experimente compilar sem a flag -no-pie para o GCC na hora de linkar:

$ nasm assembly.asm -o assembly.o -felf64
$ gcc main.c -c -o main.o
$ gcc *.o -o test

Deveria funcionar normalmente. Mas experimente comentar a diretiva default rel na linha 2 e compilar novamente, você vai obter um erro parecido com esse:

Repare que o erro foi emitido pelo linker (ld) e não pelo compilador em si. Acontece que como usamos um endereço absoluto o NASM colocou o endereço do símbolo msg na relocation table para ser resolvido pelo linker, onde o linker é quem definiria o endereço absoluto do mesmo.

Só que como removemos o -no-pie o linker tentou produzir um PIE e por isso emitiu um erro avisando que aquela referência para um endereço absoluto não pode ser usada.

PIE em IA-32

Como o endere√ßo relativo ao Instruction Pointer s√≥ existe em modo de 64-bit, nos outros modos de processamento n√£o √© nativamente poss√≠vel obter um endere√ßamento relativo. O compilador GCC resolve esse problema criando um pequeno procedimento cujo o √ļnico intuito √© obter o valor no topo da pilha e armazenar em um registrador. Conforme ilustra√ß√£o abaixo:

funcao:
    call __x86.get_pc_thunk.bx
    add ebx, 12345  ; Soma EBX com o endereço relativo 12345
    ; ...

__x86.get_pc_thunk.bx:
    mov ebx, [esp]
    ret

Ao chamar o procedimento __x86.get_pc_thunk.bx o endereço da instrução seguinte na memória é empilhado pela instrução CALL, portanto mov ebx, [esp] salva o endereço que EIP terá quando o procedimento retornar em EBX.

Quando a instrução add ebx, 12345 é executada o valor de EBX coincide com o endereço da própria instrução ADD.

Last updated