A escassez de programas para detecção de sniffers em redes, principalmente gratuitos e de qualidade, além do interesse pela área de segurança computacional motivou a implementação de um aplicativo para detecção remota de sniffers.
O projeto, batizado de sniffdet, foi dividido em duas partes, a saber, uma biblioteca contendo os testes de detecção implementados, chamada libsniffdet, e uma aplicação para utilizar e testar esta biblioteca. A vantagem desta divisão está no fato de que uma vez definida a API da biblioteca, ambas as partes poderiam ser desenvolvidas simultaneamente até o estágio de depuração, otimizando o tempo usado para implementação.
O restante deste capítulo mostra os princípios, as qualidades, orientações e preocupações relevados durante o processo de desenvolvimento do projeto sniffdet.
O projeto começa com a definição da interface de seu componente mais importante, a biblioteca libsniffdet. Sabe-se, a princípio, que preocupações comuns em desenvolvimento de software tais como eficiência, eficácia, modularidade e manutenibilidade devem ser somadas a outros itens específicos de acordo com sua aplicação e utilização. Alguns destes itens apresentam-se ressaltados abaixo.
como se trata de um aplicativo que lida com softwares e dados potencialmente maliciosos, a segurança dentro do ambiente de execução é primordial, sendo levada em consideração durante todo o processo de desenvolvimento.
dada a dinamicidade da área de detecção de sniffers, é esperado que novos testes e variações dos já existentes sejam criados com o passar do tempo, devendo ser incorporados à biblioteca.
Sniffers podem alterar seu comportamento de modo a evitar sua detecção, principalmente se os testes de detecção possuírem algum tipo de comportamento que os denuncie, ou seja, algum tipo de ``assinatura'' que os caracterize. Pensando nisso, a utilização de valores constantes é evitada e são disponibilizados vários artifícios para se configurar os atributos dos testes de detecção.
Pouco vale um sistema para auxiliar administradores de rede se somente uns poucos forem capazes de utilizá-lo. O software é acompanhado de extenso material explicando seu funcionamento e sua utilização e é adotada a utilização de valores padrão para alguns dos parâmetros omitidos pelo usuário, tornando seu uso mais prático.
É importante fornecer um mecanismo de comunicação entre a biblioteca e a aplicação que funcione durante a execução dos testes, permitindo uma melhor interação entre os dois. Este mecanismo evita a impressão de ``congelamento'', principalmente naqueles testes que exigem bastante tempo para realizarem medições mais precisas.
A preocupação com a utilização do sistema fomentou a criação de vários documentos que descrevem a utilização da libsniffdet e sua aplicação, bem como seu funcionamento interno, convenções de suas APIs e preocupações na utilização. Além destes textos específicos, existem páginas de manual (manpages), compilações de perguntas mais freqüentes (faqs) e a página oficial do projeto, disponível em http://sniffdet.sourceforge.net. Como é um projeto concebido para ser utilizado e receber contribuições da comunidade mundial de desenvolvimento de software livre, existem versões da documentação em português e inglês.
Todo o código fonte e documentação deste projeto estão sob a licença GPL (General Public License) versão 2 da GNU [17]. A GPL é uma licença de software que garante a livre distribuição e alteração deste contanto que os direitos autorais sejam mantidos e a licença não revogada.
O projeto sniffdet está sendo distribuído pela Internet e conta com toda a infra-estrutura necessária para receber contribuições da comunidade do software livre como novas funcionalidades, indicações de erros, sugestões, correções e discussões em geral a respeito do projeto.
O material produzido pode ser obtido através da página do projeto ou da utilização de um cliente CVS. A página do projeto está localizada em http://sniffdet.sourceforge.net. Para se obter o material diretamente do repositório de desenvolvimento (códigos fonte e documentação) basta usar um cliente CVS com :pserver:anonymous@abrigo.dyndns.org:/sniffdetcomo raiz.
O projeto está dividido em três módulos no repositório, a saber:
Na página web são encontradas as últimas versões oficiais, correções para problemas sérios e pacotes binários em formato RPM.
Tanto a biblioteca quanto a aplicação foram implementados usando linguagem C a partir de uma plataforma operacional Linux. Dada a popularidade e disponibilidades das redes padrão Ethernet, este foi escolhido para a implementação e testes do projeto.
Foram utilizadas diversas ferramentas e bibliotecas auxiliares para o desenvolvimento do mesmo, sendo que as mais relevantes são:
Biblioteca POSIX de gerenciamento de threads.
Biblioteca GNU padrão C.
Compilador GNU C.
Utilitário para automação de configuração do sistema.
Biblioteca para criação de pacotes de rede.
Biblioteca para captura de pacotes de rede.
Sistema de gerenciamento para acesso concorrente a repositórios de código centralizados.
Utilitário de construção automática de projetos.
Wrapper da biblioteca padrão de alocação de memória. Útil para a fase de depuração, pois ajuda a encontrar vazamentos de memória e acessos a endereços inválidos.
Depurador de processos GNU.
Sniffer com recursos de visualização de pacotes capturados, inclusive descrição dos protocolos utilizados e verificação de payload. Essencial para verificar a adequação dos pacotes gerados pela libsniffdet e a captura correta dos mesmos nas máquinas testadas.
Sniffer bastante flexível que funciona em interface texto.
Todos os itens acima são gratuitos, de livre distribuição e disponibilizados em várias plataformas operacionais diferentes. Juntando este fato à preocupação com a documentação, a adoção do padrão ANSI para linguagem C e a utilização de várias bibliotecas padrão POSIX, espera-se que o porte deste projeto para outras plataformas torne-se uma tarefa bem simplificada. Atualmente, ele já compila e é executado num sistema FreeBSD, sendo necessárias apenas algumas adaptações no processo de compilação.
Bibliotecas são componentes de sistemas que agrupam funções e definições de propósito comum em objetos disponibilizados de maneira centralizada. Sua finalidade é agregar funcionalidades a programas em tempo de execução de acordo com a demanda, num processo chamado de ligação dinâmica. A vantagem está no fato de que o código existente na biblioteca não precisa ser replicado em cada processo com interesse em utilizá-lo, economizando memória e o tempo de processamento levado para carregá-la.
Outra facilidade na utilização de bibliotecas é que as mesmas podem ser modificadas de forma transparente aos usuários, permitindo sua expansão e correção sem exigir a recompilação dos aplicativos que as usam. Para que este procedimento possa ocorrer sem problemas, deve-se seguir algumas regras como, por exemplo, as citadas em [42] e mencionadas abaixo.
O procedimento de ligação dinâmica trata, basicamente, da exportação de símbolos e as restrições acima garantem a compatibilidade entre diferentes versões de uma biblioteca, ou melhor dizendo, a compatibilidade binária das mesmas.
Todos estas qualidades juntas da possibilidade de inserção de novos testes de detecção de sniffers num futuro próximo justificam a escolha de uma biblioteca como residência dos testes implementados.
A arquitetura geral da biblioteca está representada pela figura 4.1. Como pode ser visto, a biblioteca está dividida em módulos, um para cada teste de detecção implementado. Os testes de detecção existentes na versão atual são o ICMP, ARP, DNS e de Latência. Existe um módulo especial, o módulo helpers, que agrega várias funções de propósito geral tais como tradutores de nomes e geradores de números aleatórios, podendo ser utilizado tanto pelos outros módulos quanto pela aplicação. A comunicação da biblioteca com a aplicação é feita através da ativação com passagem de parâmetros e do uso de funções de callback. O fluxo comum de utilização de suas funções segue abaixo.
Pensando na praticidade de sua utilização, a biblioteca incorpora o conceito de classificação de argumentos, dividindo-os em 2 tipos: obrigatórios e opcionais. Os argumentos minimamente necessários para a correta execução de um teste são ditos obrigatórios e sua omissão gera um aviso em tempo de execução e a pronta parada do teste. A omissão dos argumentos opcionais faz com que a biblioteca substitua seus valores internamente por padrões que se mostrem adequados. Os valores escolhidos para esta substituição são discutidos logo a seguir.
Todos os testes de detecção até agora implementados funcionam com a utilização de múltiplas threads. Os testes são do tipo de detecção remota ativa, que injetam pacotes na rede e coletam pacotes ou impressões (no caso do teste de latência). Podemos generalizar seu modelo de acordo com a figura 4.2. São três as threads responsáveis pelos testes. Uma delas - main - é responsável pelo controle das outras duas threads, incluindo a criação, destruição, inicialização, desalocação de suas estruturas de controle e monitoração do tempo máximo de execução, representado pela barra de timeout. As outras duas threads são responsáveis, respectivamente, pelo envio de estímulos à rede (sender) e pela percepção de respostas que caracterizem a utilização de sniffers na rede testada (catcher), sendo estas as características inerentes aos testes de detecção ativos.
Alguns testes de detecção podem precisar de vários minutos de funcionamento para realizar uma verificação significativa do ambiente a ser testado. Durante este tempo, é importante que haja algum indício do que o programa está fazendo atualmente a fim de se evitar a impressão de ``congelamento'' por parte do usuário. Por outro lado, pode ocorrer a necessidade de se cancelar um teste em execução ao invés de esperar pelo seu término. Estas características são ainda mais necessárias em ambientes com interfaces gráficas.
A preocupação com a responsividade do sistema foi satisfeita através da implementação de um mecanismo de comunicação entre a biblioteca e a aplicação. A partir da implementação e freqüente ativação de uma função compatível com o protótipo abaixo, é possível obter notificações sobre o andamento da execução dos testes de detecção ao mesmo tempo em que a aplicação pode requisitar o cancelamento de um procedimento em andamento. Este tipo de função também é chamada de função de callback.
int (*user_callback)(struct test_status *status, int msg_type,
char *msg);
O primeiro argumento simplesmente mostra dados estatísticos, como a porcentagem de execução do teste ativado e a quantidade de bytes recebidos e enviados. O segundo argumento identifica o tipo da mensagem. O terceiro traz uma mensagem a respeito do evento que ativou a chamada desta callback quando necessária e o valor de retorno indica a requisição de cancelamento do teste em execução.
São seis os tipos de mensagens passadas à aplicação pela biblioteca:
Outro fator importante sobre a utilização de callbacks é que como sua implementação ocorre no espaço de código da aplicação, cada programa usuário pode desenvolvê-la de acordo com sua necessidade, podendo criar mecanismos diferenciados de registro de acordo com o tipo de mensagem recebida, assim como ativar sistemas externos a partir dela. Um exemplo disso seria o envio de e-mail ao administrador da rede testada quando da detecção de um sniffer na mesma.
Algumas aplicações podem implementar o conceito de bateria de testes. Portanto, para facilitar a classificação e posterior visualização dos resultados, foi criada uma estrutura comum de retorno dos testes que identifica o teste realizado através de seu código (enumeração), nome, descrição, tempo de início e fim de sua execução, além de uma indicação sobre sua validade (correta execução) e algumas informações estatísticas. Esta estrutura é mostrada abaixo:
struct test_info {
enum test_code code;
int valid;
char *test_name;
char *test_short_desc;
time_t time_start;
time_t time_fini;
unsigned int b_sent;
unsigned int b_recvd;
unsigned int pkts_sent;
unsigned int pkts_recvd;
union {
struct icmptest_result icmp;
struct arptest_result arp;
struct dnstest_result dns;
struct latencytest_result latency;
} test;
};
Cada teste possui uma estrutura de retorno específica de acordo com suas características. Cada uma delas será explicada nas subseções à seguir, junto das implicações práticas de utilização de cada teste.
O teste de detecção usando mensagens ICMP apresenta o seguinte protótipo.
int sndet_icmptest(char *host,
struct sndet_device *device,
unsigned int tmout,
unsigned int tries,
unsigned int send_interval,
user_callback callback,
struct test_info *result,
char *fakehwaddr
);
Os argumentos obrigatórios são a estrutura de controle da interface de rede (device) e o endereço da máquina a ser testada (host).
A flexibilidade e a descaracterização de assinaturas necessárias para se evitar a contra-atuação por parte de sniffers mais avançados são garantidas pela variação do valor dos argumentos de intervalo de envio de pacotes (send interval) e endereço MAC utilizado como destino (fakehwaddr). A omissão destes valores faz com que sejam internamente substituídos por um segundo de intervalo de envio de pacotes - valor comum para aplicativos de diagnóstico de rede, tais como o ping - e pelo endereço 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, conforme sugestão explicada em 3.3.1.
Seu funcionamento dentro do modelo genérico apresentado consiste na utilização de uma thread para enviar as requisições ICMP falsas e outra thread para capturar respostas enviadas pela máquina alvo.
Devido à natureza determinística deste teste, sua parte específica da estrutura de retorno contém somente uma argumento que diz se o alvo testado foi flagrado ou não com um sniffer em execução.
O teste ARP apresenta o seguinte protótipo e tem um comportamento similar ao do teste ICMP:
int sndet_arptest(char *host,
struct sndet_device *device,
unsigned int tmout,
unsigned int tries,
unsigned int send_interval,
user_callback callback,
struct test_info *result,
char *fakehwaddr
);
De acordo com sua semelhança com o teste ICMP, tem os mesmos argumentos obrigatórios, assim como valores padrão, funcionamento geral e estrutura de retorno.
O teste DNS apresenta o seguinte protótipo:
int sndet_dnstest(char *host,
struct sndet_device *device,
unsigned int tmout,
unsigned int tries,
unsigned int send_interval,
user_callback callback,
struct test_info *info,
char *fake_ipaddr,
char *fake_hwaddr,
ushort dport, ushort sport,
char *payload,
short int payload_len
);
Tem os mesmos argumentos obrigatórios dos testes anteriores, porém, a flexibilidade e descaracterização de assinaturas é mais elaborada. São usados os argumentos de tempo de envio de pacotes (send interval) e outros mais para simular um pacote de comunicação comum, através da parametrização dos endereços IP de destino e origem, das portas de destino e origem e de um payload opcional.
Os seguintes valores são usados quando da omissão dos argumentos opcionais:
Tanto o endereço MAC quando o IP são endereços inválidos na rede testada
O funcionamento da thread de leitura tem um cuidado especial no que diz respeito à leitura do pacote de consulta DNS. Como o próprio dado contido na requisição DNS orienta sobre a formação dos nomes representados no pacote, um sniffer pode construir um pacote mal-formado com o intuito de causar uma falha no teste de detecção através do acesso à memória indevido. Este problema é contornado respeitando sempre o tamanho total do pacote capturado.
A estrutura de resposta deste teste é semelhante a do ICMP e ARP, com somente uma indicação da localização ou não do sniffer na máquina testada.
O teste de latência apresenta o seguinte protótipo:
int sndet_latencytest_pktflood(char *host,
struct sndet_device *device,
unsigned int tmout,
unsigned int probe_interval,
user_callback callback,
struct test_info *info,
struct custom_info *bogus_pkt
);
Os argumentos obrigatórios são o nome da máquina alvo (host) e a estrutura de controle da interface de rede (device). Para a descaracterização de assinaturas, têm-se a parametrização do intervalo de verificação de latência e a estrutura de construção de pacote (bogus pkt), apresentada logo abaixo.
struct custom_info {
int values_set;
// ETH
u_char dmac[6];
u_char smac[6];
// IP
uint id;
uint timestamp;
u_char ttl;
ulong dest_ip;
ulong source_ip;
// TCP/UDP
short protocol; // udp/tcp/icmp
int flags; // header flags
uint seq;
uint ack;
ushort winsize;
short dport;
short sport;
u_char *payload;
short payload_len; // mandatory if payload is used
};
Com todos estes parâmetros, é possível criar vários tipos de pacotes diferentes para a inundação da rede. Como discutido no capítulo anterior, o importante é fazer com que o sniffer gaste o máximo de tempo com o processamento destes pacotes.
A omissão do argumento bogus_pkt faz com que a biblioteca o susbstitua internamente por um pacote telnet com o flag SYN ligado, simulando um início de conexão.
Devido à natureza não-determinística deste teste, a estrutura de retorno restringe-se a disponibilizar os dados colhidos durante o estágio de tráfego normal e de inundação da rede para que, através da comparação e de uma certa subjetividade, o usuário decida sobre a possibilidade de existência do sniffer. Os dados disponibilizados são o tempo médio de resposta na rede com tráfego normal e os tempos mínimo, médio e máximo e o número de pacotes enviados e perdidos na rede sobrecarregada.
Em futuras versões deste teste, pretende-se aprimorar o cálculo do tempo médio de resposta através do uso de desvio padrão ou outras técnicas estatísticas. A utilização de vários tipos de pacotes para inundação também é requerida, pois quanto mais se explorar a pilha da máquina alvo, maior será o tempo gasto por ela para processar os pacotes enviados, fornecendo uma medida melhor da latência incorrida em máquinas que rodam com sua interface em modo promíscuo.
O principal objetivo da aplicação é fornecer um modo simples e flexível para se testar a biblioteca. Além de cumprir esta tarefa, a aplicação propiciou uma melhor visão sobre a utilização dos testes de detecção. Durante sua codificação e testes, houve um amadurecimento da biblioteca, que teve uma melhor definição de que valores utilizar por padrão e como se comunicar com a aplicação através do uso da função de callback.
A arquitetura geral da aplicação pode ser vista na figura 4.3.
Nesta figura, percebe-se a segmentação dos procedimentos em três fases. Na primeira fase ocorre a carga dos dados do arquivo de configuração, o qual contém os parâmetros utilizados na execução de cada um dos testes. Na segunda fase ocorre a ativação dos testes pedidos usando os parâmetros lidos e a execução dos mesmos prossegue até seu término ou cancelamento através da callback. Na última fase, os resultados obtidos são passados aos plugins de saída, cujo funcionamento será melhor explicado na seção correspondente.A aplicação têm uma interface texto simples mas poderosa. Através de parâmetros passados na linha de comando, é possível escolher as funcionalidades presentes. Abaixo temos a tela de ajuda da aplicação, onde as opções existentes são listadas:
sniffdet 0.7
A Remote sniffer Detection Tool
Copyright (C) 2002
Ademar de Souza Reis Jr. <myself /at/ ademar.org>
Milton Soares Filho <eu_mil /at/ yahoo.com>
Usage: ./sniffdet [options] TARGET
Where:
TARGET is a canonical hostname or a dotted decimal IPv4 address
-i --iface=DEVICE Use network DEVICE interface for tests
-c --configfile=FILE Use FILE as configuration file
-l --log=FILE Use FILE for tests log
-f --targetsfile=FILE Use FILE for tests target
--pluginsdir=DIR Search for plugins in DIR
-p --plugin=FILE Use FILE plugin
-u --uid=UID Run program with UID (after dropping root)
-g --gid=GID Run program with GID (after dropping root)
-t --test=[testname] Perform specific test
Where [testname] is a list composed by:
dns DNS test
arp ARP response test
icmp ICMP ping response test
latency ICMP ping latency test
-v --verbose Run in verbose mode
-h, --help Show this help screen and exit
--version Show version info and exit
Defaults:
Interface: "eth0"
Log file: "sniffdet.log"
Config file: "/etc/sniffdet.conf"
Plugins Directory: "/usr/lib/sniffdet/plugins"
Plugin: "stdout.so"
You have to inform at least one test to perform
O apêndice A traz a página manual relativa à utilização da aplicação sniffdet.
Cada administrador tem uma predileção quanto à maneira de se armazenar ou visualizar os resultados obtidos pelos testes de detecção. Bases de dados, registros de sistema, arquivos XML ou simplesmente arquivos em formato texto são alguns exemplos de locais onde tais resultados podem ser guardados. Para facilitar o atendimento a todos estes diferentes gostos, a aplicação dá suporte ao conceito de plugins, que são objetos executáveis que contém uma interface padronizada de ativação. Cada plugin oferecido pode ser relacionado a um destes diferentes métodos de armazenamento, sendo que novos podem ser adicionados sem que haja a necessidade de recompilação de qualquer parte do sistema. Atualmente, um plugin de visualização em modo texto e outro com saída em XML são disponibilizados.
Um exemplo de visualização de resultado através de um plugin em modo texto pode ser visto abaixo.
------------------------------------------------------------
Sniffdet Report
Generated on: Mon Oct 14 19:54:35 2002
------------------------------------------------------------
Tests Results for target 192.168.1.1
------------------------------------------------------------
Test: ARP Test
Check if target replies a bogus ARP request (with wrong MAC)
Validation: OK
Started on: Mon Oct 14 19:54:34 2002
Finished on: Mon Oct 14 19:54:35 2002
Bytes Sent: 84
Bytes Received: 60
Packets Sent: 2
Packets Received: 1
------------------------------------------------------------
RESULT: POSITIVE
------------------------------------------------------------
------------------------------------------------------------
Number of tests with positive result: #1
------------------------------------------------------------
O conteúdo do arquivo gerado pelo plugin de saída em XML pode ser visto abaixo.
<?xml version="1.0"?>
<SNIFFDET-SESSION>
<info>
<name>Latency test</name>
<description>Ping response with custom packet flood</description>
<validation>VALID</validation>
<start-time>Wed Oct 30 01:16:37 2002</start-time>
<finish-time>Wed Oct 30 01:17:02 2002</finish-time>
<bytes-sent>337629680</bytes-sent>
<bytes-received>0</bytes-received>
<pkts-sent>3246439</pkts-sent>
<pkts-received>13</pkts-received>
<results unit="msecs">
<normal>0.6</normal>
<minimal>9.1</minimal>
<maximal>548.9</maximal>
<mean>168.4</mean>
</results>
</info>
</SNIFFDET-SESSION>
Uma das características mais importantes da biblioteca é sua flexibilidade. Pensando em aproveitar este poder e facilitar a utilização do sistema, a aplicação permite sua configuração através da utilização de um arquivo externo, no qual todos os parâmetros utilizados podem ser facilmente editados e adaptados de acordo com a necessidade do usuário. Além de toda a flexibilidade do arquivo de configuração, é possível ativar diversas opções através de parâmetros passados através da linha de comando.
Um exemplo de arquivo de configuração, assim como sua documentação estão disponíveis no apêndice A.
Quanto à segurança, é importante frisar que a aplicação, apesar de poder ser executada somente por um usuário com privilégios de administrador, abdica de tais privilégios após a inicialização da interface, diminuindo o impacto da exploração de potenciais vulnerabilidades existentes no código.
Foi grande o desafio de se implementar um projeto que, além de funcionar em um ambiente de rede de computadores, toca em tantos subsistemas do núcleo do sistema operacional (rede, sistema de arquivos, timers, threads e outros). É claro que tal desafio seria muito mais complicado se não houvessem tantas ferramentas livres para auxiliar no desenvolvimento, em especial, ferramentas de diagnóstico de rede e de depuração de código.
Apesar da dificuldade, o produto final consegue agrupar várias qualidades necessárias para, ao menos, interessar pela sua utilização e estudo. Dentre o público que pode ser tocado pela existência deste software, pode-se destacar os administradores de rede, profissionais da área de segurança e estudantes cursando disciplinas intermediárias sobre redes de computadores.
Até o momento, o sniffdet é a única ferramenta que agrupa as seguintes qualidades:
Um fator preocupante para a implementação é a possível heterogeneidade de sistemas e plataformas que trabalham numa rede. Estas diferentes configurações, tanto de software quanto de hardware, dificultam a generalização e adaptação dos parâmetros dos testes de detecção. Somente através de sucessivos testes utilizando várias configurações diferentes é possível chegar a um ajuste próximo do ideal. Por causa disso, a preparação do próximo capítulo levou nossa atenção de volta ao estágio de implementação, tornando-se parte do processo evolutivo do sistema.
Ademar de Souza Reis Jr. 2003-03-11