Buscar

Buffer Overflow

Prévia do material em texto

Buffer Overflow
 Conseqüência: Softwares podem ser derrubados ou forçados a executar outras funções (código arbitrário).
 Abrangência: Atinge todos os tipos de software, sistemas operacionais, Serviços (ex: Web Servers), aplicativos (scripts CGI).
 Como os hackers exploram esta vulnerabilidade: Hackers técnicos desenvolvem programas para explorar um buffer overflow (exploits) em um determinado software/versão. Estes programas são extremamente sofisticados, exigindo que se "mescle" em tempo de execução dois códigos de máquina. Uma vez desenvolvido estes exploits e divulgados na internet, qualquer hacker pode se utilizar dos mesmos para fazer um ataque contra um servidor que utilize o software com problema de buffer overflow.
Como funciona o buffer Overflow
Este bug ocorre quando o programador esquece de validar se os dados de entrada recebidos por uma variável estão dentro dos limites máximos de tamanho reservado para aquela variável. Um buffer overflow ocorre quando  uma área destinada a uma variável transborda. Ou seja, foi recebida uma quantidade de bytes para ser armazenado maior do que o espaço reservado para aquela variável. Normalmente as variáveis pertencem a funções, e por terem escopo local foram criadas na pilha. Deve-se lembrar que a pilha contém o endereço de retorno de uma função, e valores de registradores que serem restaurados quando a função encerra. Portanto, se o atacante for capaz de trocar estes valores, será capaz de mudar o comportamento de um programa. 
Um hacker pode utilizar uma falha de buffer overflow para paralisar um programa, forçando-o a executar instruções ilegais, ou então danificando a sua área de dados a tal ponto que o estado do programa fique tão inconsistente a ponto de causar um auto cancelamento. Desta forma, o buffer overflow pode ser utilizado para causar denial of service (Técnica onde o atacante tenta indisponibilizar o uso de serviço(s), em uma ou mais máquinas em uma rede), indisponibilizando o serviço prestado por um programa que foi paralisado.
O buffer overflow também pode ser utilizado para fazer com que um programa passe a executar uma seqüência de código determinada pelo hacker. Neste caso, os dados que serão enviados contém uma seqüência de instruções de máquina que irão substituir o código de máquina original. Isto permite ao buffer overflow criar uma porta de entrada, abrindo, por exemplo, um shell para entrada de comandos.
Exemplo de buffer Overflow
Vamos ilustrar como funciona um buffer overflow através de um exemplo que mostra um overflow de áreas de dados (uma variável preenchendo ilegalmente dados de outra variável). Observe o programa abaixo:
Trata-se de um programa extremamente simples, que lê um string de caracteres (s1), e em seguida exibe um outro string de caracteres (s2), o qual não recebeu nenhum valor. O resultado esperado deste programa é exibir s2 como um string vazio (nenhum caracter). Contudo, em nenhum instante este programa valida se a quantidade de caracteres lido em s1 ultrapassa o tamanho da variável. Isto abre condições para um buffer overflow. 
Abaixo podemos ver o mesmo programa com as suas instruções em código de máquina:
Pode-se verificar que a variável s1 está num endereço mais alto do que a variável s2. Logo, se s1 transbordar, s2 receberá um conteúdo.
Inicialmente ambas as variáveis estão preenchidas com zero binário, conforme mostrado a seguir:
Vamos imaginar que na entrada de dados foram digitados mais do que 9 caracteres, como mostra a tela abaixo:
 
Gerando a seguinte configuração de memória:
Deve-se observar que o buffer overflow já ocorreu. A área reservada para s1 era capaz de armazenar apenas 9 caracteres (último para \0). Como foi lido uma seqüência de 40 caracteres, estes caracteres adicionais foram armazenados nas posições consecutivas de memória, invadindo o espaço de memória da variável s2.
Portanto, prosseguindo o programa teremos o seguinte resultado:
De forma "inesperada", s2 apareceu com uma seqüência de caracteres. Isto é o resultado do buffer overflow.
Este é um buffer overflow em que o transbordamento ocorreu na área de dados. Foi apenas um exemplo simplificado para que o conceito fosse esclarecido.
Em casos reais, este transbordamento ocorre na pilha, que contém variáveis locais de sub-rotinas, endereços de retorno, e valores de registradores. Quando o hacker consegue substituir esta área de memória, consegue modificar o fluxo de execução, fazendo com que o computador atacado passe a executar um novo código (por exemplo, uma chamada ao shell para receber comandos). É desta forma que os hackers conseguem "controlar" softwares que apresentam problemas de buffer overflow.
Existem três tipos básicos de ataques a vulnerabilidades por buffer overflow
Buffer overflow baseado em pilha: a técnica de exploração mais simples e comum, atua pela alteração do estado da pilha durante a execução do programa para direcionar a execução para o código malicioso contido no buffer estourado:
  
Buffer overflow baseado em heap: bem mais difícil de explorar, por causa da disciplina de acesso à heap (blocos não contíguos, fragmentação interna). Deve-se estourar o buffer armazenado na área da heap em direção ao endereço de retorno na pilha, para direcionar a execução para o código malicioso que se encontra no buffer estourado;  
Buffer overflow de retorno à libc: alteram o fluxo de execução pelo estouro de algum buffer na pilha ou heap, para algum trecho de código armazenado no segmento de texto do programa. Tipicamente este trecho de código é alguma chamada de função comumente utilizada da biblioteca padrão libc, como as chamadas de execução arbitrária de comandos (funções da família exec(3)). Este tipo de ataque tem sido bastante utilizado após a inclusão de patches nos sistemas operacionais que impedem a execução de código na pilha, heap ou região de dados.
  
Técnicas para evitar a vulnerabilidade
A solução tradicional é utilizar funções de biblioteca que não apresentem problemas relacionados a buffer overflow. A solução na biblioteca padrão é utilizar as funções strncpy(3) e strncat(3) que recebem como argumento o número máximo de caracteres copiados entre as strings. Deve haver controle no argumento fornecido para que ele não exceda o tamanho da string de destino, ou teremos novamente código vulnerável. A função sprintf(3) também pode ser utilizada, desde que se forneça na string de formato o número máximo de caracteres a serem impressos na string de destino, e que este número seja compatível com a sua capacidade. 
Os sistemas BSD fornecem as funções strlcpy(3) e strlcat(3) para cópia e concatenação de strings. Estas funções recebem como argumento o tamanho total do buffer de destino. 
Existem soluções em bibliotecas distintas da padrão, como a Libmib que implementa realocação dinâmica das strings quando seu tamanho é ultrapassado, e a Libsafe que contém versões modificadas das funções suscetíveis a buffer overflow, funcionando como um wrapper para a libc padrão. 
Um dos problemas do servidor implementado é a falta de checagem de tamanho do buffer nas chamadas sucessivas à função read(2). As alternativas nesse caso são a inclusão de código de checagem de limite do buffer ou a utilização de funções como recv(2) que recebem como argumento o tamanho máximo da string recebida. 
Outras recomendações passam pela utilização de compiladores com checagem de limite, aplicação de patches ao sistema operacional que impossibilitem a execução de código na pilha ou heap (ainda restam os ataques utilizando a região de texto, entretanto), preferência por alocação dinâmica dos buffers na área de heap, atenção redobrada na codificação dos laços de interação que preenchem os buffers e exame cuidadoso das possíveis entradas do usuário. 
Existe um patch para o kernel do Linux que torna o segmento da pilha não-executável,apesar deste não se encontrar ainda embutido no kernel padrão do Linux. 
O sistema OpenBSD recebeu no dia 30 de Janeiro deste ano uma atualização que impede a execução de código contido na pilha do processo. Esta atualização está no ramo de código corrente e, após estabilizado, deverá ser replicada para outros sistemas operacionais, particularmente os BSDs.  
	
Conclusão
A exploração de código vulnerável a buffer overflow exige alguma habilidade. Entretanto, o conhecimento necessário para tal tarefa pode ser facilmente adquirido pelo material difundido na rede e experimentação exaustiva. 
A tarefa de codificar software seguro é difícil, mas deve ser tomada com máxima seriedade. Principalmente quando se está desenvolvendo software de segurança ou projetado para ser executado com privilégios de super-usuário ou usuário especial do sistema. Chega a impressionar o número de vulnerabilidades a buffer overflow encontradas em software de utilização ampla, dada a simplicidade das técnicas em evitá-las. É claro que na maioria das vezes aproveitar-se da falha não é fácil como apresentado aqui, mas ainda possível com alguma dedicação.
#include <stdio.h>
char s1[10];
char s2[10];
int main()
{
printf ("\ns2 antes = %s", s2);
printf ("\nDigite s1: ");
gets (s1);
printf ("\ns2 depois = %s", s2);
}

Outros materiais

Materiais relacionados

Perguntas relacionadas

Perguntas Recentes