PIE
Position Independent Executable ou PIE é uma técnica de compilação que permite que um executável seja carregado em endereços aleatórios na memória. Isso dificulta alguns exploits que é preciso obter endereços vulneráveis no programa, já que o endereço irá mudar a cada execução.
Demonstração
#include <stdio.h>
int main()
{
printf ("%p", main);
return 0;
}Esse código apenas imprime o endereço da função main e é possível utilizar esse programa para ver o funcionamento do PIE.
Sem PIE:
Compilando esse programa usando o comando gcc -no-pie <código> -o <executável> e executando ele algumas vezes é possível observar que o endereço da main se mantém o mesmo em todas as execuções.
┌──(ata㉿vBoxKali)-[~/Documents]
└─$ gcc -no-pie main.c -o exec
┌──(ata㉿vBoxKali)-[~/Documents]
└─$ checksec exec
[*] '/home/ata/Documents/exec'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
┌──(ata㉿vBoxKali)-[~/Documents]
└─$ ./exec
0x401126
┌──(ata㉿vBoxKali)-[~/Documents]
└─$ ./exec
0x401126
┌──(ata㉿vBoxKali)-[~/Documents]
└─$ ./exec
0x401126 Com PIE:
Compilando o programa usando o comando gcc -o <executável> <código> e executando ele algumas vezes é possível observar que o endereço da main sempre altera a cada execução.
Exploit
Para podermos exploitar o PIE podemos explorar a forma em que o PIE coloca o código executável na memória. O PIE ele vai aleatorizar a página na memória que contém o executável, desse modo o endereço base (primeiro endereço do programa) sempre irá terminar com 000 e os demais endereços sempre vão ter o mesmo deslocamento em relação ao endereço base. Portanto, se conseguirmos acesso a um endereço do programa e seu deslocamento, poderemos calcular o endereço base e todos os outros endereços a partir daí.
No exemplo anterior, se observamos todos os endereços da main terminam em 0x139, e olhando o código no pwndbg conseguimos observar que o deslocamento da main em relação ao endereço base é 0x1139.
Com isso, se, por exemplo, o endereço impresso no programa for 0x5647f2a9d139 e sabemos que o deslocamento da main em relação ao endereço base é 0x1139, podemos fazer 0x5647f2a9d139 - 0x1139 e obter 0x5647f2a9c000 como o endereço base do programa.
Portanto, a ideia do exploit é obter algum endereço que sabemos o deslocamento em relação ao endereço base e então calcular o endereço base e todos os outros endereços que nos interessam.
Exemplo 1: Endereço dado pelo programa
Nesse primeiro exemplo iremos analisar como fazer o exploit em um programa que dá ao usuário o endereço da função main e pede para ele obter o endereço da função win.
Usando pwndbg conseguimos observar o deslocamento da função main e o deslocamento da função win.
Com esse valores, podemos executar o programa e realizar os cálculos necessários para conseguir obter o endereço da função win.
O endereço fornecido pelo programa foi 0x55e8b0a95159, esse é o endereço da main, como sabemos que o deslocamento da main é 0x1159, basta subtrair o endereço fornecido por esse deslocamento. Então, fazendo 0x55e8b0a95159 - 0x1159 obtemos o endereço base que é 0x55e8b0a94000. Como o deslocamento da função win é 0x11cc, só precisamos fazer 0x55e8b0a94000 + 0x11cc para obter o endereço da função win, que será 0x55e8b0a951cc. Portanto, ao colocar esse valro no programa é impresso a string da função win.
A seguir há um código em python para realizar os cálculos necessário para resolver esse exercício.
Executando esse programa, obtemos o seguinte resultado.
Exemplo 2: Format String Bug
Nesse exemplo iremos analisar um programa em que será necessário utilizar o format string bug para descobrir algum endereço para realizar o exploit e depois utilizar o valor descoberto para fazer um buffer overflow.
Analisando o programa, conseguimos ver que é chamada a função vuln na main e então é pego um input do usuário que é imprimido logo em seguida com um printf vulnerável e então depois é pego outro input do buffer com o últimos gets. Como a função vuln é chamada pela main, o endereço de retorno à main deve estar na pilha, então podemos utilizar o format string bug para vazar esse endereço da main.
O endereço de retorna de função que está na pilha irá conter o endereço depois da chamada da função vuln na main, então podemos olhar no pwndbg para saber qual o deslocamento desse endereço.
O endereço que procuramos tem o valor 0x11d7 de deslocamento, então temos que procurar algum valor na pilha que tenha valor. Então, agora temos que rodar o programa e utilizar o formato %p para imprimir os valores da pilha.
Conseguimos observar um valor 0x55f614d9f1d7 no 13° argumento que termina com o deslocamento encontrado anteriormente, então esse endereço deve ser o endereço de retorno da main. Com isso, podemos colocar um breakpoint utilizando pwndbg depois de qualquer endereço depois de imprimir o buffer fornecido pelo usuário e antes do segundo gets. Quando atingirmos esse breakpoint, podemos utilizar um comando para mostrar a pilha de execução.
Nessa pilha podemos ver vários valores 0x252070 que correspondem ao diversos %p dados como input anteriormente e o valor 0x555551d7 0x00005555, que é o valor de retorno da main. Então, podemos ver que há 56 bytes de diferença entre esse valor e o começo do buffer.
Depois de toda essa análise, podemos fazer um código em python para resolver este exemplo, como é mostrado a seguir.
Resultado mostrado após a execução do programa.
Referências
Atualizado