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!