Bastionado, seguridad en sistemas: Introducción a la creación de preprocesadores Snort (II) pre { background:#eeeeee; border:1px solid #A6B0BF; font-size:120%; line-height:100%; overflow:auto; padding:10px; color:#000000 } pre:hover { border:1px solid #efefef; } code { font-size:120%; text-align:left; margin:0;padding:0; color: #000000;} .clear { clear:both; overflow:hidden; }

Bienvenido al blog

Bienvenidos al blog de seguridad en sistemas

jueves, 18 de agosto de 2011

Introducción a la creación de preprocesadores Snort (II)

Para esta segunda entrado vamos a familiarizarnos con el funcionamiento de las dos principales estructuras de Snort requeridas durante la programación de un preprocesador dinámico:

DynamicPreprocessorData

Es el propio preprocesador dinámico, lo que sería "this" en un lenguaje OO. Lo usaremos para añadir las acciones que se van a realizar durante la fase de lectura o inicialización de la configuración del preprocesador, lectura de los ficheros de reglas, procesado de cada paquete durante la fase del módulo de preprocesado y en el módulo del motor de reglas.

Ésta estructura es declarada en el fichero principal del preprocesador (blog.c) después de los include y de forma global mediante la siguiente sentencia: "extern DynamicPreprocessorData _dpd;". Por tanto, a lo largo de la implementación del preprocesador trataremos con la variable "_dpd".

SFSnortPacket: 

Es la estructura del paquete de red que ha sido leído por el sniffer y desglosado por los normalizadores en un formato de estructura C. Ésta se encuentra definida en el fichero "/usr/src/snort-version/src/dynamic-preprocessors/include/sf_snort_packet.h".

Cuando trabajemos con SFSnortPacket es muy importante tener en cuenta los siguientes tres puntos:



1ª) No todos los campos de la estructura existen, solo se rellenan aquellos campos que han sido requeridos por el paquete leído. Si hemos leído un paquete UDP los campos del paquete TCP y ICMP serán nulos (NULL) y por tanto no se podrá acceder a ellos.

Si por ejemplo no tenemos en cuenta que Snort podría leer un paquete ARP e intentamos leer la IP destino de un paquete, el cual se encuentra en: "SFSnortPacket -> ip4_header -> destination", el preprocesador fallara al intentar acceder a un dato no inicializado puesto que ip4_header valdrá "null" dando lugar a un "segmentation fault" y la caída del proceso Snort. Por tanto siempre tenemos que comprobar que la estructura está inicializada, en este caso que "ip4_header != NULL" antes de intentar acceder al campo "destination" de la estructura "ip4_header".

2ª) Dependiendo de como hayamos compilado Snort y de la versión de este (2.8 o 2.9) tenemos que tener especial cuidado con que estructura usamos, ya que dependiendo de éste la información del paquete se encontrará en distintas estructuras dentro de la macro estructura SFSnortPacket.

Por ejemplo en unos entornos la información de la cabecera IP se encontrará en "SFSnortPacket -> ip4h" en otros estará en "SFSnortPacket -> ip4_header". Por lo que si queremos tener compatibilidad entre versiones y compilaciones deberemos usar una comprobación como la siguiente (hay mas cosas a tener en cuenta, pero esta es la principal):


    if(p->ip4h == NULL)
    {
        if(p->ip4_header == NULL)
        {
        //No es un paquete IP
                return;
        }
        else
        {
        //El paquete IP está en p->ip4_header
        Código
        }
    }
    else
    {
      //El paquete IP está en p->ip4h
      Código
    }


3º) Otro punto importante relacionado con SFSnortPacket, y en mi opinión, me parece una autentica barbaridad está relacionado justamente con esa diferencia entre la estructura "ipv4" y "ip4_header" puesto que el primero usa el formato propio de snort "sfip_t" y el segundo usa el formato arpa "in_addr":


typedef struct _IPV4Header
{
    u_int8_t version_headerlength;
    u_int8_t type_service;
    u_int16_t data_length;
    u_int16_t identifier;
    u_int16_t offset;
    u_int8_t time_to_live;
    u_int8_t proto;
    u_int16_t checksum;
    struct in_addr source;
    struct in_addr destination;
} IPV4Header;


typedef struct _IPv4Hdr
{
    u_int8_t ip_verhl;      /* version & header length */
    u_int8_t ip_tos;        /* type of service */
    u_int16_t ip_len;       /* datagram length */
    u_int16_t ip_id;        /* identification  */
    u_int16_t ip_off;       /* fragment offset */
    u_int8_t ip_ttl;        /* time to live field */
    u_int8_t ip_proto;      /* datagram protocol */
    u_int16_t ip_csum;      /* checksum */
    sfip_t ip_src;          /* source IP */
    sfip_t ip_dst;          /* dest IP */
} IP4Hdr;

Y diréis, ¿qué problema hay con esto? pues que la gente de Snort ha renombrado las funciones de Arpa sustituyéndolas por las del sfip_t. Es decir, imaginaros que queremos pasar una IP a Ascii y estamos usando la estructura "IPV4Header" pues si intentamos usar la función "inet_ntoa" (network to Ascii) para obtener la IP en formato texto, esta fallará porque la función "inet_ntoa" ha sido renombrada por "sfip_ntoa", y lógicamente ésta espera una estructura sfip_t y no una estructura in_addr puesto que estas no son compatibles entre si:


# grep inet_ntoa /usr/src/snort-version/src/dynamic-preprocessors/include/ipv6_port.h
#ifdef inet_ntoa
#undef inet_ntoa
#define inet_ntoa sfip_ntoa

Para poder saltarse esta restricción, debemos añadir como últimos include del fichero de implementación del preprocesador, en nuestro caso "blog.c", las siguientes líneas de código:

//Soporte inet_ntoa!!!
#undef inet_ntoa
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

Desconozco si hay una forma menos guarra de hacerlo pero viendo el soporte y la ayuda de los foros y listas de Snort es lo mejor que he podido hacer. Si tenéis alguna otra forma más elegante soy todo oídos (no vale declarar una función copia de "inet_ntoa" con otro nombre :P).

Una vez explicada estas dos estructuras, realizaremos un último cambio para personalizar más todavía nuestro ejemplo. Para ello editaremos el fichero "sf_preproc_info.h":


Modificamos esto:
...
define DYNAMIC_PREPROC_SETUP   ExampleSetup
extern void ExampleSetup();
...


Por esto:
...
define DYNAMIC_PREPROC_SETUP BlogSetup
extern void BlogSetup();
...

A continuación se deberá modificar el fichero "blog.c":


# sed -e "s/example/blog/g" blog.c >> blog2.c
# mv blog2.c blog.c
# sed -e "s/Example/Blog/g" blog.c >> blog2.c
# mv blog2.c blog.c

Para finalizar limpiaremos la última compilación mediante un "make clean" y comprobaremos que el cambio fue realizado con éxito:

# make clean
# make

Bueno con esto ya tenemos un poco las nociones básicas de las dos estructuras principales que usaremos en Snort, por lo que tal como he comentado en la proxima entrada ya empezaremos a meternos ya con las funciones de Snort. Espero que os esten gustando estas entradas, slds ;)


No hay comentarios:

Publicar un comentario