Transbordamento de dados
Este artigo não cita fontes confiáveis. (Abril de 2012) |
Em segurança computacional e programação, um transbordamento de dados ou estouro de buffer (do inglês buffer overflow ou buffer overrun) é uma anomalia onde um programa, ao escrever dados em um buffer, ultrapassa os limites do buffer e sobrescreve a memória adjacente. Esse é um caso especial de violação de segurança de memória.
Estouros de buffer podem ser disparados por entradas que são projetadas para executar código, ou alterar o modo como o programa funciona. Isso pode resultar em comportamento errado do programa, incluindo erros de acesso à memória, resultados incorretos, parada total do sistema, ou uma brecha num sistema de segurança. Portanto, eles são a base de muitas vulnerabilidade de software e pode ser explorados maliciosamente.
Linguagens de programação comumente associadas com transbordamentos de dados incluem C e C++, as quais não proveem proteção contra acesso ou sobrescrita de dados em qualquer parte da memória e não verificam automaticamente se dados escritos em um array (cadeia de elementos – o tipo de buffer dessas linguagens) estão nos limites do array. Verificação de limites pode prevenir transbordamentos de dados.
Descrição técnica
[editar | editar código-fonte]Um estouro de buffer ocorre quando dados escritos em um buffer, devido a verificação de limites insuficiente, corrompe valores de dados no endereço de memória adjacente ao buffer alocado. Isso ocorre mais comumente quando se copiam strings (cadeias de caracteres) de um buffer para outro.
Exemplo básico
[editar | editar código-fonte]No exemplo a seguir, um programa define dois itens de dados que são adjacentes na memória: um buffer de strings A de tamanho de 8 bytes, e um inteiro B de tamanho de 2 bytes. Inicialmente, A não contém nada além de bytes zero, e B contém o número 1 979. O tamanho dos caracteres é 1 byte.
variable name | A | B | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
value | [null string] | 1979 | ||||||||
hex value | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 07 | BB |
Agora, o programa tenta guardar a string terminada em nulo excessivo no buffer A. Ao falhar em checar o comprimento da string, ele sobrescreve o valor de B.
variable name | A | B | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
value | 'e' | 'x' | 'c' | 'e' | 's' | 's' | 'i' | 'v' | 111 | |
hex | 65 | 78 | 63 | 65 | 73 | 73 | 69 | 76 | 6f | 00 |
Embora o programador não tenha intenção alguma de mudar B, o valor de B foi substituído pelo número formado por parte de uma cadeia de caracteres. Neste exemplo, em um sistema little-endian que usa ASCII, "e" seguido por um byte zero se tornam o número 111. Se B fosse a única outra variável definida pelo programa, escrever uma string ainda maior que ultrapassasse B poderia causar um erro como uma falha de segmentação, terminando o processo.
Exploração
[editar | editar código-fonte]As técnicas para se explorar uma vulnerabilidade de estouro de buffer podem variar de acordo com a arquitetura, sistema operacional e região da memória. Por exemplo, explorar um heap (usado para alocar memória dinamicamente) é muito diferente de explorar uma pilha de chamadas.
Exploração baseada em pilha
[editar | editar código-fonte]Um usuário malicioso e com habilidades técnicas pode explorar estouro de buffer em pilhas para manipular o programa de uma das seguintes maneiras:
- Sobrescrever uma variável local que está próxima do buffer na memória da pilha para mudar o comportamento do programa de modo a beneficiar o atacante.
- Sobrescrever o endereço de retorno da pilha. Uma vez que a função retorna, a execução irá continuar no endereço de retorno especificado pelo atacante, geralmente um buffer preenchido por entrada do usuário.
- Sobrescrever um ponteiro de função, ou tratador de exceção, que é posteriormente executado.
Com um método chamado trampolining, se um endereço de um dado fornecido pelo usuário é desconhecido, mas a sua localização é guardada em um registrador, então o endereço de retorno pode ser sobrescrito com um opcode que fará a execução saltar para os dados fornecidos pelo usuário. Se a localização é guardada num registrador R, então um salto de execução para a localização contendo o opcode para um salto para R, chamada para R ou uma instrução similar, causará a execução de dados fornecidos pelo usuário. A localização de opcodes adequados, ou bytes na memória, podem ser encontradas em DLLs ou no próprio executável. Entretanto, o endereçamento de opcodes tipicamente não pode conter nenhum caractere nulo e as localizações desses opcodes podem mudar entre aplicações e versões do sistema operacional. O Projeto Metasploit é um banco de dados de opcodes, apesar de apenas os opcodes encontrados nos sistemas Windows estarem listados. Não se deve confundir estouro de buffer baseado em pilha com estouro de pilha. Essas vulnerabilidade são descobertas geralmente com uso de um fuzzer.
Exploração baseada em heap
[editar | editar código-fonte]Um estouro de buffer que ocorre na área de dados da heap é chamado de estouro de heap e é explorado de forma diferente do estouro de buffer baseado em pilha. A memória na heap é alocada dinamicamente pela aplicação em tempo de execução e contem tipicamente dados do programa. A exploração se dá corrompendo-se esses dados de modos específicos para fazer a aplicação sobrescrever estruturas internas como listas encadeadas e ponteiros. A técnica de estouro de heap canônica sobrescreve a lincagem de alocação de memória dinâmica (como o malloc) e usa a troca de ponteiro resultante para sobrescrever um ponteiro de função do programa. A vulnerabilidade do GDI+ da Microsoft em tratar JPEGs é um exemplo do perigo que um estouro de heap pode apresentar.
Barreiras à exploração
[editar | editar código-fonte]Manipulação do buffer, que ocorre antes que ele seja lido ou executado, pode levar à falha de uma tentativa de exploração. Essas manipulações podem mitigar a ameaça de exploração, mas não pode fazê-la se tornar impossível. Manipulação pode incluir conversão de maiúscula em minúscula, remoção de caracteres especiais e filtragem de strings não alfanuméricas. Entretanto, existem técnicas para burlar esses filtros e manipulações: códigos alfanuméricos, códigos polimórficos, códigos autodeslocáveis, e ataques return-to-libc. Os mesmo métodos podem ser usado para evitar detecção em sistemas de detecção de invasão. Em alguns casos, incluindo onde o código é convertido para Unicode, a ameaça da vulnerabilidade tem sido erroneamente confundida pelos divulgadores como apenas negação de serviço, quando de fato a execução remota de código arbitrário é possível.
Aspectos práticos da exploração
[editar | editar código-fonte]Em explorações do mundo real, existe uma variedade de desafios que precisam ser superados para as explorações operarem de fato. Esses fatores incluem bytes nulos em endereços, variação na localização do shellcode, diferenças entre ambientes e várias contramedidas em operação.
Medidas de proteção
[editar | editar código-fonte]Várias técnicas foram utilizadas para detectar ou prevenir estouros de buffer, com vantagens e desvantagens. O modo mais confiável de evitar ou prevenir estouros de buffer é usar proteção automática a nível de linguagem. Esse tipo de proteção, entretanto, não pode ser aplicado a códigos legados. As seguintes seções descrevem as opções e implementações disponíveis.
Escolha de linguagem de programação
[editar | editar código-fonte]A escolha da linguagem de programação pode ter um efeito profundo na ocorrência de estouros de buffer. Como em 2008, entre as linguagens mais populares estão C e sua derivativa C++, com uma ampla gama de softwares sendo escritos nessas linguagens. C e C++ não proveem proteção embutida contra acesso indevido ou sobrescrita de qualquer parte da memória. Mais especificadamente, elas não verificam se um dado escrito em um buffer está nos limites do buffer. Entretanto, as bibliotecas de C++ padrão proveem muitas maneiras de copiar dados para um buffer de forma segura, e técnicas para evitar estouros de buffer também existem em C.
Muitas outras linguagens de programação proveem verificação em tempo de execução e em alguns casos até verificações em tempo de compilação, que enviam um alerta ou lançam uma exceção quando C ou C++ iriam sobrescrever os dados e continuar a executar instruções até que resultados errados fossem obtidos, os quais poderiam ou não fazer o programa parar. Exemplos de tais linguagens incluem Ada, Eiffel, Lisp, Modula-2, Smalltalk, Objective Caml e derivadas de C como Cyclone e D. Os ambientes de bytecodes de Java e do .Net Framework também requerem verificação de limites em todos os arrays. Quase todas as linguagens interpretadas irão proteger contra estouros de buffer, sinalizando uma condição de erro bem definida.
Na maioria das vezes, quando uma linguagem proveem informação de tipos suficiente para se fazer verificação de limites de arrays, é dada a opção de habilitar ou desabilitar a verificação. Análise estática de código pode remover muitas verificações de limites e tipos dinâmicas, mas implementações pobres e casos estranhos podem diminuir significativamente o desempenho. Engenheiros de software devem considerar cuidadosamente os ganhos e perdas de segurança versus custos de desempenho ao decidirem qual linguagem e configuração de compilador utilizar.
Uso de bibliotecas seguras
[editar | editar código-fonte]O problema de estouro de buffer é comum em C e C++ porque elas expõem detalhes de baixo nível de implementação dos buffers como conteúdos e tipos de dados. Estouros de buffer devem ser evitados para se manter um alto nível de correção no código que executa o gerenciamento de buffer. Também é recomendado há muito tempo que se evitem as funções de bibliotecas padrão que não fazem verificação de limites, como gets, scanf e strcpy. O worm de Morris explorou uma chamada de gets no fingerd.
Bibliotecas de tipos de dados abstratos bem escritas e testadas que centralizam e verificam automaticamente o gerenciamento de buffer, incluindo checar os limites, podem reduzir a ocorrência e impacto de estouros de buffer. Os dois principais tipos de dados básicos nessas linguagens nos quais estouros de buffer geralmente ocorrem são strings e arrays. Então, bibliotecas que previnem estouro de buffer nesses tipos podem prover a grande maioria da segurança necessária. Ainda assim, falhas em se usar bibliotecas seguras podem resultar em estouros de buffer e outras vulnerabilidades, e naturalmente, qualquer erro na própria biblioteca é uma vulnerabilidade em potencial. Implementações "seguras" de bibliotecas incluem The Better String Library, Vstr e Erwin. A biblioteca C do sistema operacional OpenBSD provê as funções strlcpy e strlcat, mas elas são mais limitadas que a implementação completa da biblioteca segura.
História
[editar | editar código-fonte]Estouros de buffer foram entendido e parcialmente documentados publicamente em 1972, quando o Computer Security Technology Planning Study divulgou a técnica: "O código que executa essa função não verifica os endereços de origem e destino devidamente, permitido que porções do monitor sejam sobrepostas pelo usuário. Isto pode ser usado para injetar código no monitor que permitirá ao usuário tomar controle da máquina." (Página 61) Hoje, o monitor seria chamado de kernel.
A primeira exploração hostil documentada de um estouro de buffer foi em 1988. Era uma de várias explorações usadas pelo worm criado por Morris para se propagar pela internet. O programa explorado era um serviço do Unix chamado finger. Depois, em 1995, Thomas Lopatic redescobriu independentemente o estouro de buffer e publicou suas descobertas na lista de mensagens de segurança Bugtraq. Um ano depois, em 1996, Elias Levy (também conhecido como Aleph One) publicou na revista Phrack o artigo "Destruindo a Pilha por Diversão e Lucro", uma introdução passo a passo para explorar vulnerabilidades de estouro de buffer baseado em pilha.
Desde então, no mínimo dois grandes worms de internet exploraram estouro de buffer para comprometer um grande número de sistemas. Em 2001, o worm Code Red explorou um estouro de buffer no Internet Information Services (ISS) 5.0 da Microsoft e em 2003 o worm SQL Slammer comprometeu máquinas rodando o Microsoft SQL Server 2000. Em 2003, estouros de buffer presentes em jogos licenciados do Xbox foram explorados para permitir que softwares não licenciados, incluindo jogos caseiros, rodassem no console sem a necessidade de modificações de hardware, também conhecidas como modchips. O PS2 Indepentence Exploit também usou um estouro de buffer para fazer o mesmo com o Playstation 2. O hack Twilight conseguiu o mesmo no Wii usando um estouro de buffer no jogo The Legend of Zelda: Twilight Princess.