Pedro, esse seu esforço em criar uma documentação para desenvolvedores é louvável. O HLBR é um projeto complexo e se não fosse sua documentação, eu por exemplo, demoraria anos para aprender tudo o que você escreveu nesses dois guias. Parabéns cara, sua atitude acaba sendo um convite para novos desenvolvedores.
Abraços --- Em [email protected], PEdroArthur_JEdi <pedro.fo...@...> escreveu > > Continuando um pouco com a criação de guias para desenvolvedores, está > ai mais um. É um guia prático de criação de decoders. Contém um > exemplo grosseiro de um decodificador NTP (Network Time Protocol) o > qual não deve ser usado em ambiente de produção!!! > > Estamos abertos a dúvidas! > > Espero que gostem! > > Feliz ano novo a todos! > > -- > PEdroArthur_JEdi > > Nunca acredite num sistema que você não conhece o código fonte! > Never trust a system you don't have sources for! > > "Só se dedicará a um assunto com toda a seriedade alguém que esteja > envolvido de modo imediato e que se ocupe dele com amor. É sempre de > tais pessoas, e não dos assalariados, que vêm as grandes descobertas." > > -- Arthur Schopenhauer > > O desenvolvimento de Decoders para o HLBR envolve um simples entendimento de como manipular a estrutura de dados DecoderRec, definida em engine/hlbr.c e transcrita abaixo: > > 231: typedef struct decoder_rec{ > 232: char Name[MAX_NAME_LEN]; > 233: int ID; > 234: unsigned char DependencyMask[MAX_RULES/8]; > 235: struct test_rec* Tests; > 236: struct module_rec* Modules; > 237: struct decoder_rec* Children; > 238: struct decoder_rec* Parent; > 239: struct decoder_rec* NextChild; > 241: void* (*DecodeFunc) (int PacketSlot); > 242: void (*Free) (void *pointer); > 243: int (*ConfigFunction) (FILE *fp); > 245: char Active; /*true if anything actually uses it*/ > 246: } DecoderRec; > > O campo char* Name representa o nome do decoder e como ele será usado para escrever regras; ID é um identificador único gerado pela função CreateDecoder(char *), definida em decoders/decode.c; unsigned char* DependencyMask é a máscara de depêndencia do decoder na hierarquia, sendo esse parâmetro ajustado automaticamente pela função DecoderAddDecoder(int, int), a qual é definida em decoders/decode.c; os campos Tests e Module são manipulados automaticamente pelas funções DecoderAddTest(int,int) (responsável por associar um teste a um decoder) e DecoderAddModule(int,int) (responsável por associar um módulo a um decoder), respectivamente, estando ambas declaradas no arquivo fonte decoders/decode.c. Children, Parent e NextChild são todos manipulados pela função DecoderAddDecoder(int, int), a qual gerencia a hierárquia de decoders do HLBR; O campo void* (*DecodeFunc)(int) é a principal função de um decoder, estando encarregada de realizar a decodificação em si; O campo void (*Free)(void*) é a função que liberará qualquer dado que tenha sido alocado pelo decoder; int (*ConfigFunction)(FILE *) deve ser usada quando um decoder necessita de parâmetros especiais passados pelo arquivo de configuração, tal qual o DecoderHTTP. > > Estando explicado os campos do DecoderRec, podemos passar para a função de inicialização do decoder. Consideremos um decoder chamado NOME, que tem como decoder pai Parent, função de decodificação DecodeNOME e gera seus dados através de um malloc simples, o que consequentemente torna o uso da função da biblioteca padrão void free (void*) viável e não possue parâmetros especiais. O esqueleto desse decoder pode ser visto a seguir: > > 1: int ParentDecoderID; > 2: > 3: int InitDecoderNOME(){ > 4: int DecoderID; > 5: > 6: DEBUGPATH; > 7: > 8: if ((DecoderID=CreateDecoder("NOME"))==DECODER_NONE){ > 9: #ifdef DEBUG > 10: printf("Couldn't Allocate NOME Decoder\n"); > 11: #endif > 12: return FALSE; > 13: } > 14: > 15: Globals.Decoders[DecoderID].DecodeFunc=DecodeNOME; > 16: Globals.Decoders[DecoderID].Free=free; > 17: if (!DecoderAddDecoder(GetDecoderByName("Parent"), DecoderID)){ > 18: printf("Failed to Bind TCP Decoder to Parent Decoder\n"); > 19: return FALSE; > 20: } > 21: > 22: ParentDecoderID=GetDecoderByName("Parent"); > 23: > 24: return TRUE; > 25: } > > Por convenção e para facilitar o entendimento, usa-se no HLBR como nome da função de inicialização de um decoder InitDecoderNOME, onde NOME é substituido pelo nome do decoder, ex: InitDecoderTCP, InitDecoderUDP. A essa função é encarregada a missão de alocar espaço para o decoder, configurar sua função de decodificação, função de liberação de dados e sua posição na hierárquia. > > A alocação é feita pela função int CreateDecoder(char *), definida em decoder/decode.c. Ela fará o trabalho de registrar o decoder globalmente, incuindo-o na próxima posição livre de Globals.Decoders e configurando seu nome no campo char* Name. O valor de retorno de CreateDecoder será o identificador único do decoder e deverá ser usado para manipulá-lo na hierarquia de decoders. > > Logo em seguida, deve-se associar o recém criado decoder ao seu pai, aqui referenciado por "Parent". DecoderAddDecoder(int,int) irá associar o decoder de ID representado pelo seu segundo parâmetro ao ID do decoder do primeiro parâmetro, onde o segundo será filho do primeiro, sendo os campos Parent e Children atualizados. Para auxiliar nessa tarefa, a função int GetDecoderByName(char *) retorna o ID do decoder de nome igual ao seu parâmetro, sendo desconsiderado a caixa, ex: TCP == tcp == Tcp == tCp == tcP, e por ai vai... Caso essa tarefa ocorra com sucesso, será retornado TRUE, caso contrário será retornado FALSE. No código acima tivemos o cuidado de colocar uma estrutura de controle para que o fluxo seja interrompido caso não seja possível associar os decoders, linhas 17 a 20. > > Outro ponto importante é associar as funções de decodificação e liberação. Simplesmente, deve-se colocar os devidos valores nos campos DecodeFunc e Free. Essa tarefa está representada nas linhas 15 e 16, respectivamente. > > Por ultimo, tem se mostrado interessante guardar o ID do decoder pai para que se possa utiliza-lo posteriormente. Por convenção (e simplificação), é usado uma variável global ao escopo do código nomeada seguindo o padrão NOMEDecoderID, onde NOME deve ser substituido pelo nome do decoder pai, ex: TCPDecoderID, UDPDecoderID. > > Para que tudo ocorra bem, devemos retornar TRUE :) > > Vamos prosegeuir agora com a criação de uma pequena função de decodificação. As funções de decodificação devem retornar um ponteiro do tipo void, onde valores diferentes de nulo representam que a decodificação ocorreu sem problemas e o HLBR deve prosegeuir com os testes desse protocolo. Caso contrário, nulo fará com que a função DecodePacket (int) passe para o próximo decoder. Como exemplo, vamos criar um decoder que identifique a possível presença de um payload NTP. A função se limitará apenas a checar se o primeiro byte se enquadra como um payload NTP. Seu código encontra-se abaixo. > > /* * * * * * * * * > * > * decode_ntp.h > * > * * * * * * * * */ > > 1: #ifndef _HLBR_DECODE_NTP_H_ > 2: #define #ifndef _HLBR_DECODE_NTP_H_ > 3: > 4: typedef struct ntpv { > 5: unsigned char li:2, > 6: ver:3, > 7: mode:3; > 8: } NTPVer; > 9: > 10: typedef struct ntpd { > 11: char *BeginData; > 12: NTPVer Header; > 13: } NTPData; > 14: > 15: int InitDecoderNTP (); > 16: > 17: #endif > > /* * * * * * * * * > * > * decode_ntp.c > * > * * * * * * * * */ > > 1: #include "decode_ntp.h" > 2: #include "../config.h" > 3: #include "../packets/packet.h" > 4: #include "decode_udp.h" > 5: > 6: #include <stdio.h> > 7: #include <stdlib.h> > 8: > 9: int UDPDecoderID; > 10: > 11: int DecodeNTP (int PacketSlot) { > 12: UDPData* udpData; > 13: PacketRec* p; > 14: NTPData* data; > 15: > 16: DEBUGPATH; > 17: > 18: if (!GetDataByID(PacketSlot, UDPDecoderID, (void**)&udpData)) { > 19: printf("Failed to get UDP data\n"); > 20: return NULL; > 21: } > 22: > 23: p = &Globals.Packets[PacketSlot]; > 24: > 25: if (((p->BeginData[0] & 0x38) >> 3) < 3 || ((p->BeginData[0] & 0x38) >> 3) > 4) { > 26: return NULL; > 27: } > 28: > 29: data = (NTPData *) malloc (sizeof(NTPData)); > 30: > 31: data->Header = (NTPVer) p->BeginData[0]; > 32: data->BeginData = &p->BeginData[1]; > 33: > 34: return data; > 35: } > 36: > 37: int InitDecoderNTP () { > 38: int DecoderID; > 39: > 40: DEBUGPATH; > 41: > 42: if ((DecoderID=CreateDecoder("NTP")) == DECODER_NONE) { > 43: #ifdef DEBUG > 44: printf ("Couldn't Allocate NTP Decoder\n"); > 45: #endif > 46: return FALSE; > 47: } > 48: > 49: Globals.Decoders[DecoderID].DecodeFunc=DecodeNTP; > 50: Globals.Decoders[DecoderID].Free=free; > 51: > 52: if (!DecoderAddDecoder(GetDecoderByName("UDP"), DecoderID)){ > 53: printf("Failed to Bind NTP Decoder to UDP Decoder\n"); > 54: return FALSE; > 55: } > 56: > 57: UDPDecoderID=GetDecoderByName("UDP"); > 58: > 59: return TRUE; > 60: } > > OBS: Esse código não foi testado, nem compilado. Não integrar de maneira alguma a um servidor em produção!!! > OBS: Porém, caso queira apenas aprender como escrever um decoder e integrar ao código do HLBR, faça-o! > > Os decoders sempre são compostos por dois arquivos, o primeiro contento as estruturas de dados, as funções e as variáveis que poderão ser usadas em outras partes do código, tipo testes e módulos, e o segundo a implementação do decoder. No primeiro arquivo, decode_ntp.h, deve-se incluir uma diretiva de processamento para evitar que seu conteúdo seja incluído mais de uma vez: > > 1: #ifndef _HLBR_DECODE_NTP_H > 2: #define #ifndef _HLBR_DECODE_NTP_H > > /* Code Code Code */ > > 3: #endif > > A convenção usada é _HLBR_DECODE_NOME_H_ , onde NOME deve ser substituído pelo nome do decoder. > > No caso do exemplo, declaramos a estrutura de dados NTPData, que servirá para guardar as informações decodificadas, NTPVer, que servirá para guardar informações sobre a versão do cabeçalho NTP, e a função int InitDecoderNTP(), a qual será usada para registrar o decoder NTP no sistema. > > A função DecodeNTP simplesmente verifica se a versão é válida, linhas 25 e 26, e caso seja, aloca uma estrutura NTPData, linha 29, guarda as informações relevantes, linhas 31 e 32, e retorna os dados. > > Para verificar o payload do pacote, primeiro precisamos "pedir" os dados ao decoder específico. Aqui no caso, o UDP. A função GetDataByID (int,int,void**) recebe três parâmetros, sendo o primeiro o número do pacote decodificado, o decoder do qual se deseja a saída e um ponteiro para ponteiro nulo, no qual será retornado os dados. O descrito anteriormente ocorre nas linhas 18 a 21. > > OBS: Para um exemplo onde se tem uma função de inicialização e uma função de liberação veja decoders/decode_http.c. > > Feito os códigos fontes, a próxima etapa consiste em editar os arquivos de Makefile para que o código seja compilado. Os arquivos a serem editados são o Makefile.in e o decoders/Makefile. Em Makefile deve ser adicionar o caminho do código em HLBR_OBJECTS: > > HLBR_OBJECTS= \ > engine/hlbr.o \ > engine/alert_limit.o \ > engine/bits.o \ > > /* Mais Alguns */ > > decoders/decode.o \ > decoders/decode_interface.o \ > decoders/decode_ethernet.o \ > decoders/decode_ip.o \ > decoders/decode_ip_defrag.o \ > decoders/decode_icmp.o \ > decoders/decode_udp.o \ > decoders/decode_tcp.o \ > decoders/decode_http.o \ > decoders/decode_dns.o \ > decoders/decode_arp.o \ > decoders/decode_ntp.o \ > > /* continua */ > > Já no decoders/Makefile, deve adicionar a ação all e criar as regras do objeto: > > all: decode.o \ > decode_interface.o \ > decode_ethernet.o \ > decode_ip.o \ > decode_ip_defrag.o \ > decode_icmp.o \ > decode_udp.o \ > decode_tcp.o \ > decode_http.o \ > decode_dns.o \ > decode_arp.o \ > decode_ntp.o > > /* algumas regras */ > > decode_ntp.o: decode_ntp.c decode_ntp.h ../config.h ../engine/hlbr.h > $(CC) -c -o decode_ntp.o decode_ntp.c $(DEBUG) > > Agora, o processo mais angustiante: > > $ ./configure > $ make > /* Algumas Linhas */ > > gcc -g -c -o decoders/decode.o decoders/decode.c > gcc -g -c -o decoders/decode_interface.o decoders/decode_interface.c > gcc -g -c -o decoders/decode_ethernet.o decoders/decode_ethernet.c > gcc -g -c -o decoders/decode_ip.o decoders/decode_ip.c > gcc -g -c -o decoders/decode_ip_defrag.o decoders/decode_ip_defrag.c > gcc -g -c -o decoders/decode_icmp.o decoders/decode_icmp.c > gcc -g -c -o decoders/decode_udp.o decoders/decode_udp.c > gcc -g -c -o decoders/decode_tcp.o decoders/decode_tcp.c > gcc -g -c -o decoders/decode_http.o decoders/decode_http.c > gcc -g -c -o decoders/decode_dns.o decoders/decode_dns.c > gcc -g -c -o decoders/decode_arp.o decoders/decode_arp.c > gcc -g -c -o decodets/decode_ntp.o decoders/decode_ntp.c > > /* mais linhas */ > > gcc -g -o hlbr -g -Wall engine/hlbr.o engine/alert_limit.o engine/bits.o engine/parse_config.o engine/parse_rules.o engine/main_loop.o engine/session.o engine/jtree.o engine/num_list.o engine/message.o engine/cache.o engine/regex.o engine/hlbrlib.o engine/url.o engine/logfile.o packets/packet.o packets/packet_linux_raw.o packets/packet_obsd_bpf.o packets/packet_osx_bpf.o packets/packet_tcpdump.o packets/packet_solaris_dlpi.o decoders/decode.o decoders/decode_interface.o decoders/decode_ethernet.o decoders/decode_ip.o decoders/decode_ip_defrag.o decoders/decode_icmp.o decoders/decode_udp.o decoders/decode_tcp.o decoders/decode_http.o decoders/decode_dns.o decoders/decode_arp.o decoders/decode_ntp.o tests/test.o tests/test_interface_name.o tests/test_ethernet_type.o tests/test_ethernet_src.o tests/test_ethernet_dst.o tests/test_ip_src.o tests/test_ip_dst.o tests/test_ip_proto.o tests/test_ip_ttl.o tests/test_ip_check.o tests/test_icmp_type.o tests/test_icmp_code.o tests/test_tcp_port.o tests/test_tcp_src.o tests/test_tcp_dst.o tests/test_tcp_content.o tests/test_tcp_nocase.o tests/test_tcp_listcontent.o tests/test_tcp_listnocase.o tests/test_tcp_flags.o tests/test_tcp_offset.o tests/test_tcp_regex.o tests/test_http_content.o tests/test_http_method.o tests/test_http_nocase.o tests/test_http_regex.o tests/test_udp_regex.o tests/test_udp_src.o tests/test_udp_dst.o tests/test_udp_content.o tests/test_udp_nocase.o actions/action.o actions/action_drop.o actions/action_alert_console.o actions/action_alert_file.o actions/action_dump_packet.o actions/action_route_sip.o actions/action_bns.o actions/action_alert_syslog.o actions/action_alert_email.o actions/action_alert_socket.o actions/action_alert_listensocket.o routes/route.o routes/route_macfilter.o routes/route_simple_bridge.o routes/route_dip.o routes/route_sip.o routes/route_broadcast.o routes/route_arp.o routes/route_interface.o routes/route_bns.o - lpthread -ldl -lpcre -rdynamic > # > # --------------------------------------- > # Execute "# make install" para instalar. > # Run "# make install" to install. > # --------------------------------------- > # > > ** Dicas ** > > Apesar de simples, a criação de um decoder demanda tempo e estudo. Devemos pensar primeiramente no custo benefício do mesmo. Primeiramente, o custo está relacionado a performance. Deve-se escolher algoritmos eficientes, evitar alocação dinâmica ao máximo, evitar memory leaks, controlar o acesso concorrente aos dados, em fim, tudo! >
