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!

Responder a