INF1018 - Software Básico - 2010.1

Segundo Trabalho

Atacando a Pilha de Execução

O que é o trabalho

O objetivo deste trabalho é entender como funcionam os ataques do tipo buffer overflow, em que se corrompe a pilha de execução de um programa, muitas vezes colocando nela um novo código para ser executado em benefício do atacante.

Para entender como funcionam esses ataques, é necessário lembrar como é organizada a pilha de execução. Na base de cada registro de ativação temos o valor anterior de %ebp e o endereço de retorno, para onde o controle será desviado quando a função corrente termina. Em ataques de buffer overflow, esse endereço tipicamente é substituído por um outro, que indica uma área da própria pilha de execução que o atacante preenche com o código que deseja que seja executado. Para sobreescrever a pilha, tipicamente se explora o fato de não existir verificação de limites na manipulação de arrays em C. Se uma função declara um array como variável local, esse array será alocado na pilha, em endereços anteriores ao endereço de retorno. Se esse array (o buffer do nome do ataque) for preenchido para além de seus limites, acabaremos alcançando a posição da pilha onde reside o endereço de retorno.

Neste trabalho, você deve usar a idéia de buffer overflow para:

  1. simular um ataque que faz o programa "vulnerável" parar de executar o seu código e abrir um shell do Unix;
  2. simular um ataque que faz o programa "vulnerável" parar de executar o seu código e renomear um arquivo do diretório corrente (crie um arquivo de nome meuarq no diretório corrente e renomeie o arquivo que passará a se chamar meunovoarq);
  3. simular um ataque que faz o programa "vulnerável" parar de executar o seu código e excluir um diretório (crie um diretório de nome meudir e exclua o diretório - o diretório não será excluído se contiver outros diretórios ou arquivos).

O buffer overflow> é um ataque bem conhecido, e por isso os sistemas atuais incluem várias proteções contra ele. Para fazer este trabalho, vamos precisar desabilitar algumas proteções e trabalhar dentro de algumas limitações.

Entendendo um pouco mais

Considere o programa abaixo. Execute-o e veja o resultado. Utilize o gdb para descobrir que endereço está sendo afetado pela atribuição a buffer[6]. Se já não for o endereço de retorno de function, altere o programa para que seja.
#include<stdio.h>

void function() {
  int buffer[5];
  buffer[0] = 0;
  buffer[6] = buffer[6] + 11;
}

int main (void) {
  int x = 1;
  function ();
  x = 0;
  x = x + 2;
  printf("%d\n", x);
  return 0;
}
Usando o gdb (depois de gdb <executavel>, escreva disassemble main e disassemble function), tente alterar o endereço de retorno para que depois de executar function, o controle retorne para diferentes pontos na main.

O que fazer

Leia o artigo clássico sobre como construir um ataque de buffer overflow. Lá você já vai encontrar boa parte da solução. Um problema a mais é que desde a época do artigo os sistemas operacionais criaram mais um mecanismo de proteção: a cada execução a pilha é alocada em endereços diferente ( address randomization).

O livro texto mostra, no exercício 3.38, o código aqui. Esse código vai lhe permitir fazer alguns testes entrando com diferentes strings em hexadecimal (Sugiro fortemente que você use "cut and paste" a partir de um arquivo onde edite facilmente suas diferentes tentativas.) Use-o também como base para criar o programa vulneravel.c a ser entregue (ver abaixo). Pode simplificar a vida você juntar as funções getbuf e test em uma só. Ao entrar nessa função, antes de requisitar o string ao usuário, chame uma função em assembler que retorne o valor de %ebp e mostre esse valor (com printf). Isso vai ajudá-lo a definir o novo endereço a ser armazenado como endereço de retorno.

Crie um array grande na sua função main para garantir que você tem bastante pilha para sobrescrever!

Use o gdb e o objdump para ver o código gerado para diferentes coisas, tamanhos e endereços. Para usar o gdb, execute gdb meuexecutável e depois disassemble main ou disassemble function, por exemplo. Para usar o objdump, compile seu programa usando gcc -c meuprog.c (ele Para usar o gdb, execute gdb meuexecutável e depois disassemble main ou disassemble function, por exemplo. Para usar o objdump, compile seu programa usando gcc -c meuprog.c (ele só vai gerar um meuprog.o) e faça objdump -d meuprog.o,

Entregáveis

Devem ser enviados dois arquivos: um programa vulneravel.c e um outro explicando como usá-lo e com que entradas (dados prontos para o usuário fazer cut and paste e obter o efeito pedido).

MUITA ATENÇÃO: Tudo nesse trabalho é extremamente dependente de compilador e de sistema operacional. Teste tudo no labgrad pois será a plataforma em que executaremos seu passo a passo.

Prazo

O trabalho deve ser entregue até o dia 21 de junho, por email.

Observações