Um exploit não é nada mais que um código capaz de explorar uma falha num segmento de código ou software. Do inglês, significa literalmente em português “explorar”.
No mundo da segurança informática, denomina-se exploit um método capaz de tirar proveito de um bug (falha) de um software provocando comportamentos não pretendidos do software, frequentemente para conseguir escalar privilégios, obter controlo do sistema ou negar serviços (DoS). Geralmente utilizados em milhares de sistemas diariamente, os exploits são a fonte de grande parte dos ataques ocorridos localmente e remotamente nos sistemas existentes. Estes podem ainda tomar formas e poderes bastantes variados. Pode ser um programa executável, uma mensagem num determinado protocolo de rede ou até mesmo uma mensagem escondida num email.
Neste artigo vamo-nos focar no tipo de falha buffer overflow e utilizaremos a linguagem C para demonstrar.
Como funcionam os exploits?
Os exploits quase sempre fazem proveito de uma falha conhecida como buffer overflow (sobrecarga da memória buffer).
O buffer overflow acontece quando um programa grava dados numa determinada variável passando, porém, uma quantidade maior de dados do que estava previsto pelo programa. Essa situação pode possibilitar a execução de um código arbitrário, necessitando apenas que este seja devidamente posicionado na área de memória do processo.
Abaixo temos um exemplo de um programa vulnerável a um ataque de buffer overflow. O problema está na segunda linha da função ProcessarParam
, que não limita o tamanho do argumento recebido (arg
).
void ProcessarParam(char *arg); void main(int argc, char *argv[]) { if (argc > 1){ printf("Param: %s\n",argv[1]); ProcessarParam(argv[1]); } void ProcessarParam(char *arg) { char buffer[10]; strcpy(buffer, arg); /* BUG: se a string contida em arg tiver mais que 10 carateres existirá um Buffer Overflow */ printf(buffer); }
O buffer overflow, quando ocorre de forma aleatória, normalmente causa um erro fatal/crash na aplicação. No Windows XP esta situação gera uma janela de erro, e no Linux gera a conhecida segmentation fault com core dump (dá-se um core dump quando o sistema consegue guardar o estado do programa antes de surgir a falha, sendo o core o ficheiro guardado). Porém, quando correctamente induzido pelo atacante, o buffer overflow pode permitir que se execute código malicioso que terá os mesmos privilégios de execução da aplicação a ser atacada, que geralmente são privilégios de administrador.
Para entender completamente como o buffer overflow é explorado para se obter acessos indevidos ao sistema, seria necessário compreender como é que os processos são organizados na memória, no qual cada arquitectura de hardware, sistema operativo ou compilador pode organizar de forma diferente.
Buffer overflow é apenas um dos muitos tipos de vulnerabilidades possíveis num software. Sendo alguns deles: heap overflow, integer overflow, return-to-libc attack, format string attack, race condition, code injection, SQL injection, cross-site scripting e cross-site request forgery. Geralmente um exploit apenas toma vantagem de uma única vulnerabilidade de software, o que torna por vezes normal serem utilizados vários exploits em simultâneo: primeiro para ganhar acesso de nível reduzido, depois para escalar privilégios repetidamente até obter privilégios máximos, nomeadamente Administrador/root.
Normalmente um único exploit pode apenas ser utilizado para tomar vantagem numa única vulnerabilidade de software. Habitualmente, quando um exploit é publicado, a vulnerabilidade é corrigida através de uma correcção (patch) e o exploit torna-se obsoleto para novas versões do software. Esta é a razão pelo qual alguns hackers não publicam os seus exploits, mantendo-os privados. Tais exploits são geralmente referenciados como exploits 0 day e obter tais exploits é o desejo principal dos atacantes inexperientes, geralmente chamados script kiddies.
Projecto Metasploit
O projecto Metasploit é uma plataforma online open-source, disponível para download, que permite controlar e utilizar exploits publicados online.
Escrito em Perl, com componentes em C, Assembly e Python, o Metasploit tornou-se uma ferramenta famosa por facilitar o uso de exploits a todos. Criticado pelos experts da área como uma ferramenta que vem facilitar o trabalho aos script kiddies e assim aumentar o número de sistemas sob ataques.
Com esta ferramenta, a fase de procura e desenvolvimento dos exploits é praticamente eliminada visto que se torna bastante fácil de descarregar novos exploits para utilizar na plataforma.
Programar em Segurança
Existem muitos tipos de vulnerabilidades, cada uma delas ocorre devido a um erro do programador. Geralmente causadas pela má concepção, falta de conhecimento, entendimento do funcionamento das funções/bibliotecas utilizadas de outros programadores ou até mesmo falhas aritméticas inesperadas. Com isto pode-se facilmente concluir que estas vulnerabilidades apesar de serem bastante conhecidas, não são geralmente entendidas, e isto explicaria o porquê de continuarem a aparecerem enormes quantidades destas falhas nas aplicações de software.
Apesar disto, temos de entender que todos os tipos de vulnerabilidades, nomeadamente as faladas anteriormente são previsíveis. Talvez num tempo próximo no futuro, as condições que permitem estas falhas existirem, venham a ser “corrigidas” e eliminadas, ficam aqui alguns métodos que podem ajudar qualquer programador a prevenir estas:
- Utilizar diferentes linguagens. Linguagens de programação que fornecem automaticamente verificação de limites como Perl, Python, Java, Ruby, etc. É verdade que estas existem, porém isto por vezes torna-se impossível quando se considera que praticamente todos os sistemas operativos modernos são escritos em C. A mudança de linguagem torna-se particularmente crítica quando é necessário acesso de baixo-nível ao hardware. A boa notícia é que as linguagens estão a evoluir, e a segurança tornou-se um assunto sério. Por exemplo, a Microsoft com a sua iniciativa .NET, rescreveu por inteiro o Visual Basic e Visual C++ com a segurança em mente. Adicionalmente, a linguagem Visual C# que foi desenhada por completo com a segurança em mente.
- Eliminar o uso de funções de bibliotecas com vulnerabilidades. Linguagens de programação, são tão vulneráveis como o programador permite que sejam. No exemplo dado, no início, utilizámos uma função vulnerável da Standard C Library (
strcpy
). Esta é uma, de várias, funções existentes na biblioteca que falham em verificar o comprimento/limite dos seus argumentos. Por exemplo, poderíamos ter corrigido a nossa aplicação alterando unicamente uma linha de código:// substituindo: strcpy(buffer, arg); // por: strncpy(buffer, arg, 10);
Esta simples alteração, informa o
strcpy()
que o buffer de destino só tem um tamanho de 10 bytes, e que deve descartar quaisquer dados após este comprimento. - Implementar e construir segurança dentro do código. Pode demorar mais tempo, e consome mais esforço, mas o software pode ser construído com a segurança em mente. Se no exemplo anterior, tivéssemos adicionado um passo extra, atingiríamos ainda um melhor nível de segurança:
strncpy(buffer, arg, sizeof(buffer));
Novamente, isto pode remeter a verdadeira questão, de como os programadores são educados. Será a segurança ensinada, ou encorajada? Será dado o tempo extra necessário para implementar a segurança adequada? Tipicamente, e infelizmente, a resposta é não.
- Utilizar módulos de bibliotecas seguras. Bibliotecas de segurança em strings estão disponíveis em linguagens como C++. Por exemplo, a C++ Standard Template Library (STL) oferece a classe
string
. Esta classe oferece funções internas que são seguras no tratamento das strings, e deve ser preferida em relação às funções usuais. - Utilizar bibliotecas disponíveis (middleware). Existem várias bibliotecas de “segurança” disponíveis para utilização. Por exemplo, a Bell Labs desenvolveu a libsafe que protege a utilização de funções inseguras. A libsafe funciona na estrutura da stack, e permite assegurar que quando uma função é executada, o endereço de retorno não é alterado. No entanto, como muitas outras bibliotecas, esta não é imune a falhas e deve ser sempre utilizada a última versão.
- Utilizar ferramentas de análise do código. Foram feitas várias tentativas de criar uma aplicação que executasse uma análise no código fonte e tentasse encontrar potenciais falhas, inclusive buffer overflows. Uma aplicação exemplar chama-se PurifyPlus criada pela Rational que executa análises a código escrito em Java, C ou C++, e detecta várias vulnerabilidades.
- Utilizar ferramentas de optimização do compilador. Praticamente um conceito novo, várias extensões foram recentemente feitas disponíveis para funcionar directamente com o compilador que permitem monitorizar o comportamento do RET (endereço de retorno duma determinada função) e salvaguardar este valor de potenciais alterações. Stack Shield (http://www.angelfire.com/sk/stackshield) e SSP (http://www.research.ibm.com/trl/projects/security/ssp).
- Actualizar o sistema operativo e aplicação. Talvez a melhor defesa é manter-se ofensivo e informado. Novas vulnerabilidades são descobertas e reportadas todos os dias. Aplicar as devidas alterações e actualizações às aplicações é fundamental. Por exemplo, recentemente foi descoberto uma falha na API
MessageBoxA
—para quem desenvolve aplicações em Windows esta função é bastante familiar, pois é utilizada para mostrar mensagens de erro/aviso/informação na plataforma. Este exploit é agora conhecido como o primeiro exploit do Windows Vista (abrange o Windows XP/2003/Vista). Um programador que utilize esta API para criar mensagens em que de alguma forma é permitido ao utilizador fornecer o texto a ser colocado na mensagem, têm uma potencial falha na aplicação que permite um atacante bloquear o sistema operativo. Mais informações em http://www.securiteam.com/windowsntfocus/6D00R0AHPK.html.
Finalizando
Educação é a chave no percurso de tornar uma aplicação segura. Grande parte dos programadores sabem da necessidade de verificar dados introduzidos pelo utilizador, verificar os limites nas operações de dados, entre outros, mas poucos têm a noção das consequências da falta destas atenções. É necessário conhecer estas consequências para podermos adoptar as devidas práticas. Por vezes não se trata apenas de uma potencial falha ou crash na aplicação, mas sim dos riscos de segurança que podem causar aos seus utilizadores.
Devem existir métodos e ciclos no desenvolvimento do software, em que o testar do software tem um papel importante. Uma maior atenção deve ser dada a todos os dados obtidos do utilizador, quer seja do teclado, ficheiro, socket, pipe, etc.