Neste laboratório você vai usar seus conhecimentos sobre a pilha de execução para entender o que são 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. Ainda que com muitas simplificações, este laboratório mostra como é importante escrever código seguro...
Ao longo do tempo, os sistemas foram ficando mais protegidos contra esse tipo
de ataque, por isso vamos precisar desabilitar algumas
dessas proteções. Uma delas é feita pelo compilador gcc
:
a inserção de um código que verifica a integridade da pilha após uma chamada
de função. Para desabilitar essa proteção, executamos o gcc
com a opção -fno-stack-protector
.
Outro mecanismo de segurança presente nos sistemas atuais é impedir que um programa execute código que "resida" na
área de dados, ou na pilha.
Para desabilitar essa proteção, passamos ao gcc
a opção -Wa,--execstack
.
A última proteção que precisamos desabilitar é a randomização de
endereços, que impede que os endereços
dos dados de um programa (e de sua pilha) sejam sempre os mesmos, em
qualquer execução desse programa.
Para realizar esse laboratório sem essa proteção, execute na linha
de comando
setarch x86_64 -R /bin/bash
para entrar em um ambiente de execução sem randomização de endereços, e execute todos
os comandos do laboratório dentro desse ambiente.
tar xvf bufferbomb.tarIsso deve criar uma pasta
bufferbomb
com os arquivos:
bufbomb.c
- código a ser "atacado"
buf.c
- manipulação do buffer
hex2raw.c
- função auxiliar para criar as strings de entrada
Makefile
- configuração da compilação e ligação
make bufbombO utilitário
make
é uma ferramenta que constrói programas
e bibliotecas a partir de regras definidas em um arquivo denominado
Makefile
(ou makefile
).
O programa bufbomb é bastante simples: sua main
basicamente chama a função getbuf
,
definida no arquivo buf.c
.
Observe a função getbuf
:
repare que ela declara um array local e chama a função Gets
para preencher esse array.
Gets
, definida em
bufbomb.c
, lê caracteres da entrada padrão e os armazena
no array recebido como parâmetro.
Execute ./bufbomb
escrevendo um texto qualquer quando o programa ficar a espera
de entrada, e veja o que acontece.
Nosso objetivo é interferir no funcionamento de bufbomb
, fornecendo
no arquivo de entrada uma sequência de valores "apropriados".
Para obtermos essa entrada sem precisar gerar diretamente um arquivo
binário, usaremos o programa auxiliar
hex2raw
.
Esse programa lê, de um arquivo texto, uma sequência de valores
em formato hexadecimal e gera um arquivo binário com esses valores.
Por exemplo, suponha um arquivo texto, chamado s1
, cujo conteúdo é
00 01 02 03
Se executarmos na linha de comando
./hex2raw < s1 > s1.raw
o programa hex2raw
vai ler o arquivo de entrada s1
e gerar o arquivo de saída s1.raw
.
Este arquivo de saída, ao invés dos caracteres '0','0'
, etc.,
conterá uma sequência de bytes com os valores 0, 1, 2 e 3.
Dessa forma, se executarmos depois
./bufbomb < s1.raw
o programa irá receber como entrada
a sequência de bytes equivalente ao conteúdo do arquivo.
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,
ou para forçar a execução de código inserido "maliciosamente" na pilha.
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 pelo array local 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
.
O código de getbuf
está em buf.c
.
Para descobrir o endereço de danger
vamos usar o utilitário
objdump
, que permite a inspeção de arquivos objeto e executáveis.
A opção -d deste utilitário produz como saída um
desassembly do código de máquina armazenado no arquivo.
Com o comando
objdump -d bufbomb > bufbomb.d
redirecionamos a saída do objdump
para um arquivo chamado bufbomb.d
.
Inspecione esse arquivo: o endereço de danger
estará ao lado
do label danger. 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
.
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 %rbp
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
....
86 04 80 00 00 00 00 00
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!
Utilize agora o programa 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 exibidas:
se você conseguiu desviar o controle para danger
,
a mensagem exibida por essa função deverá estar no terminal.
Mas para observar o efeito "danoso" da execução de danger
,
crie um arquivo chamado vitima
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.
Iremos mudar isso agora.
danger
, pois não há um endereço de retorno no lugar adequado na
pilha.
Corrija isto,
criando um novo arquivo stringinvasora2
. Nesse arquivo,
estenda a sequência de bytes
de maneira a preencher a pilha com um endereço de retorno
para 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.)
getbuf
"retornar" para a função
fizz
, passando como argumento um inteiro com
valor 0x01020304
.
Veja o código de fizz
; se você conseguir invocá-la com
esse argumento, ela imprimirá a linha: fizz! You called fizz....
Neste exercício, vamos precisar do endereço do array buf
.
Repare que a função getbuf
imprime este endereço antes
de ler sua entrada.
(Esse endereço deve ser sempre o mesmo, em todas as execuções. Se não for,
provavelmente você se esqueceu de desabilitar a randomização de endereços...)
Crie um arquivo codigo.s
com as instruções
movl $0x01020304, %edi
ret
e gere o arquivo objeto correspondente com o comando
gcc -c codigo.s
Se você executar, agora,
objdump -d codigo.o
você poderá obter o código de máquina correspondente a essas instruções
(repare que elas preparam um argumento em %edi
).
Prepare agora um arquivo stringinvasora3
que sobrescreva a
pilha da seguinte forma:
buf
devem ser
preenchidas com um código
"malicioso" (as instruções de codigo.s
);
getbuf
deve ser
sobrescrito com o endereço de buf
, para
forçar a execução desse código;
fizz
, para que a instrução
ret
do código inserido na pilha force
a invocação dessa função.