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. Ainda que com muitas simplificações, esse laboratório mostra como é importante escrever código seguro...
Ao longo do tempo, os sistemas foram ficando mais protegidos contra esse tipo
de ataque. Neste laboratório, 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.
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:
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, 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, 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 essa saída para um arquivo chamado bufbomb.c
.
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.
Não se preocupe, iremos mudar isso agora.
danger
, pois não há um endereço de retorno no lugar adequado na
pilha.
Corrija isto,
estendendo sua sequência de bytes em stringinvasora
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.
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 uma stringinvasora
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.