INF1018 - Software Básico

Invasão da Pilha de Execução

Nesse laboratório você vai usar seus conhecimentos sobre a pilha de execução para entender como são realizados ataques do tipo buffer overflow. Este tipo de ataque se aproveita da ausência de verificação de "final de array" em C para sobrescrever a pilha de execução, alterando o endereço de retorno lá presente, e possivelmente outras informações.

Vamos utilizar o programa bufbomb.c para testar alguns exemplos. Para isso, busque aqui o arquivo bufferbomb.tar. Coloque-o em uma pasta onde possa criar uma subpasta para este laboratório e em seguida execute:

tar xvf bufferbomb.tar
Isso deve criar uma pasta bufferbomb com os arquivos: Para criar o programa executável bufbomb, escreva:
make bufbomb

O programa bufbomb é bastante simples, e basicamente chama a função getbuf, definida no arquivo buf.c. Execute ./bufbomb escrevendo um texto qualquer quando o programa ficar a espera de entrada, e veja o que acontece.

Agora vamos interferir no funcionamento de bufbomb fornecendo como entrada uma string de valores "apropriados". Veja que a função getbuf declara um array de char e chama Gets para preencher esse array.

Para obtermos essa entrada "apropriada", usaremos o programa auxiliar hex2raw. Esse programa lê uma sequência de valores em formato hexadecimal e gera um arquivo binário com esses valores. Isto é, se o arquivo de entrada se contiver:

00 00 00 00 
00 00 00 01
e escrevermos:
./hex2raw < se > se.raw
o programa hex2raw vai ler o arquivo de entrada se e gerar o arquivo de saída se.raw

Podemos depois executar:

./bufbomb < se.raw
para fornecer a bufbomb a sequência de bytes equivalente ao conteúdo do arquivo mostrado. Como o programa não testa o tamanho dessa sequência, podemos dar como entrada uma string arbitrariamente grande.

Essa é a base do ataque de buffer overflow: sobrescrever a pilha de execução, alterando os endereços de retorno que estão empilhados para apontarem para um outro código que o intruso deseja que seja executado. (Nos primeiros ataques, esse outro código estava tipicamente presente nessa mesma string fornecida como entrada. Ao longo do tempo, os sistemas foram ficando mais protegidos, e não permitem a execução de instruções na área da pilha. Uma outra forma de ataque que se tornou bastante comum é desviar o controle para alguma função já existente no próprio código mas que não seria normalmente chamada.)

Vamos construir strings apropriadas para algumas tarefas:

  1. Examine o código de bufbomb.c. Veja que existe uma função danger, normalmente chamada pela função protect, que determina se o usuário tem as credenciais apropriadas para executar a chamada. Você irá criar uma string de bytes que desviará o controle para danger sem passar por protect. Para isto, a string dada como entrada para bufbomb deve sobrescrever a pilha de execução, colocando o endereço de danger no lugar do endereço de retorno de getbuf.

    Você vai ter que criar uma sequência de bytes com tamanho suficiente para ocupar desde o início do espaço ocupado por buf até o endereço de retorno de getbuf. Os valores nas posições que não correspondem ao endereço de retorno de getbuf podem ser preenchidos com qualquer valor.

    Desenhe a pilha a partir da chamada a getbuf, na main, cujo código está em buf.c. Repare que getbuf utiliza um array local. Você vai precisar de uma string que sobrescreva o endereço de retorno de getbuf! Use

    objdump -d bufbomb >bufbomb.32d
    
    para descobrir o endereço de danger (o endereço estará ao lado do label danger no arquivo bufbomb.32d). Lembre-se que esse endereço deve aparecer na pilha em little-endian!

    Verifique também no "desassembly" da função getbuf onde começa, na pilha de execução, o array buf. É comum o compilador alocar um pouco mais de espaço na pilha para poder alinhá-la em um endereço múltiplo de 16. Como o endereço do array é fornecido como parâmetro para Gets, verifique como é calculado o valor desse parâmetro (isto é, veja qual é o offset em relação ao %ebp correspondente ao endereço de buf).

    Crie um arquivo stringinvasora contendo uma sequência de valores hexa, por exemplo (quebras de linha não fazem diferença):

    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    ....
    00 00 00 00 50 86 04 08
    
    Lembre-se que a sequência tem que ter um tamanho adequado para que o endereço de danger sobrescreva o endereço de retorno de getbuf!

    Crie agora o programa auxiliar hex2raw, executando

    make hex2raw
    
    Utilize hex2raw para gerar sua string e depois forneça o arquivo resultante a bufbomb:
    ./hex2raw < stringinvasora > stringinvasora.raw
    ./bufbomb < stringinvasora.raw
    
    Você verá se seu programa fez o que você queria pelas mensagens, mas para observar o seu efeito, crie um arquivo chamado vitima (pode ser até uma pasta) dentro da pasta bufferbomb. Repare que a função danger, ao executar, remove esse arquivo!

    obs: Caso sua sequência de bytes esteja bem construída, seu programa irá chamar danger e depois deve gerar uma segmentation fault. Não se preocupe, iremos mudar isso no item seguinte.

  2. No item anterior, o programa gera um segmentation fault ao tentar retornar da função danger, pois não há um endereço de retorno no lugar esperado por ela. Corrija isto. Estenda a sua sequência de bytes de maneira a preencher a pilha com um endereço de retorno no local esperado por danger. Use o endereço da função smoke, fazendo com que o controle vá para ela depois da execução de danger. (Como smoke chama exit(), o programa agora deve terminar elegantemente.)

  3. Finalmente, vamos experimentar ativar uma função passando um argumento para ela. Esqueça agora as funções danger e protect. Gere uma sequência de bytes que faça getbuf "retornar" para a função fizz. Veja o código de fizz e tente colocar na pilha o parâmetro que ela espera, para que ela imprima a linha: fizz! You called fizz....
Obs: Ainda que com muitas simplificações, esse laboratório dá uma idéia de como ocorrem ataques do tipo buffer overflow. O objetivo é aumentar o entendimento da pilha de execução e da importância de escrever código que não esteja sujeito a esse ataque.