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

miércoles, 24 de agosto de 2011

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

Por fin llega el último post de creación de preprocesadores dinámicos en Snort ¿y que mejor forma que coincidiendo con que se ha vuelto a editar el manual de Snort en HTML (http://manual.snort.org/)?

La entrada de hoy es posiblemente la más práctica pero también más largas de las anteriores, ya que crearemos un ejemplo sencillo de un preprocesador dinámico donde se indicará, tanto en la fase de preprocesador como en la de motor de reglas, que si detecta tráfico de una cierta IP que hemos indicado, tanto de origen como destino, se genere una alerta de Snort.

Evidentemente esto ya lo hace Snort con una regla tonta, pero es un ejemplo sencillo que explica bastante bien como funciona Snort, luego ya se puede desarrollar el preprocesador que necesitéis donde el entorno actual de Snort no os permita crearlo mediante reglas. Por ejemplo de esto y que se ha visto ya en el blog fue el preprocesador de geolocalización que permitía generar alertas dependiendo del origen y destino de la alerta.

Para este procesador usaremos el del ejemplo pero editando el fichero blog.c desde cero. Podeis usar el que tenemos y vereis que la estructura es muy parecida simplemente se han cambiado un par de cosas adaptandolo al ejemplo. Recordar el diagrama de la entrada anterior, es importantísimo para esta entrada.

Empecemos revisando el fichero sf_preproc_info.h, el cual tiene que tener una estructura como la siguiente:



#ifndef SF_BLOG_INFO_H_
#define SF_BLOG_INFO_H_

#define MAJOR_VERSION   1
#define MINOR_VERSION   0
#define BUILD_VERSION   1
#define PREPROC_NAME    "Blog"

#define DYNAMIC_PREPROC_SETUP   BlogSetup
extern void BlogSetup();

#endif /* SF_BLOG_INFO_H_ */

Como vemos lo más importante es que hemos declarado el preprocesador dinámico Blog, cuya versión es la 1.0 y hemos indicado que la función de arranque del preprocesador es BlogSetup el cual se encuentra definido en "blog.c".

A partir de ahora nos centraremos en la implementación del preprocesador Blog que se encuentra en el fichero "blog.c". El código está compuesto por cinco bloques virtuales:

1º) Librerías (si ya se, bibliotecas) y declaración del preprocesador.
2º) Configuración de arranque del preprocesador.
3º) Acciones a realizar en el módulo de preoprocesado.
4º) Acciones a realizar cuando es llamado en el motor de reglas.
5º) Acciones a realizar cuando es rearrancado.


Vamos a desarrollar poco a poco el preprocesador empezando por los 5 módulos documentados:

1º) Librerías y declaración del preprocesador:

Empecemos con la primera parte del preprocesador, las librerias:

//Estandar C
#include <stdlib.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>

//Preprocesadores Snort
//Configuración
#include "config.h"
//Para la devolución del valor al hacer match en una regla
#include "sf_snort_plugin_api.h"
#include "preprocids.h"
//Paquete de red que lee el Snort
#include "sf_snort_packet.h"
#include "sf_dynamic_preprocessor.h"
//Politica de los preprocesadores
#include "sfPolicy.h"
#include "sfPolicyUserData.h"

//Soporte inet_ntoa - ÑAPA / PARCHE GUARRO DE SNORT!!!
#undef inet_ntoa
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//Trabajar con sfip_t
#include "sf_ip.h"
#include "sf_ip.c"

A continuación declaramos el ID del preprocesador. Éste es el valor que se usa cuando queramos limitar o eliminar ciertas alertas del preprocesador empleando por ejemplo el tag "suppress". Cada procesador dinámico tiene un identificador único para él, incluido el propio motor de reglas cuyo id es el 1. Para nuestro ejemplo pondremos el número "300" (como la peli):

//GID del preprocesador
#define GENERATOR_ID 300

En el siguiente paso se declaran los ID y textos que se mostraran en caso de que se quiera generar una alerta en la fase de preprocesado. En nuestro caso crearemos la regla 1 del preprocesador 300 con el texto de alerta "He detectado tráfico de la IP indicada", tal como se muestra a continuación:

//Alertas en la fase de preprocesado
#define ALERT_BLOG 1
#define ALERT_BLOG_STR "He detectado tráfico TCP de la IP indicada"

En este punto también declararemos las estructuras que sean necesarias para el preprocesador. En nuestro caso será un puntero a char donde almacenaremos la IP leida en el fichero de configuración:
//Donde almacenaremos la IP indicada en la configuración del Snort:
char *IPPROC;

Para finalizar inicializaremos las variables de Snort necesarias como es el propio preprocesador, las políticas (usar las del ejemplo) y declararemos las funciones que vamos a usar en todo el preprocesador:

//Politicas
tSfPolicyUserContextId ex_config = NULL;

//Si no es un reinicio
#ifdef SNORT_RELOAD
    tSfPolicyUserContextId ex_swap_config = NULL;
#endif

//Estructura de nuestro preprocesador (sería el this en OO)
extern DynamicPreprocessorData _dpd;

//Funciones empleadas en el preprocesador
//Inicialización del preprocesador
static void BlogInit(char *);
//Acciones a realizar en la fase de preprocesado
static void BlogProcess(void *, void *);
//Acciones a realizar cuando cargamos el motor de reglas (lectura de reglas)
static int BlogRuleInit(char *, char *, void **);
//Acciones a realizar al tratar un paquete que hace match con una regla
static int BlogRuleEval(void *, const uint8_t **, void *);
//Esta parte es la que hemos dicho que no tocaríamos del reload, corresponde al quinto punto
#ifdef SNORT_RELOAD
static void BlogReload(char *);
static int BlogReloadSwapPolicyFree(tSfPolicyUserContextId, tSfPolicyId, void *);
static void * BlogReloadSwap(void);
static void BlogReloadSwapFree(void *);
#endif


2º) Configuración de arranque del preprocesador:

Si nos fijamos pusimos una función en el fichero "sf_preproc_info" de nombre "BlogSetup". Esa función es la primera función que llama Snort cuando arranca, éste dependiendo de si es un inicio o un recarga de Snort registra el preprocesador con "registerPreroc".

Para registrar un preprocesador necesitamos principalmente dos argumentos:
  • El primero corresponde con el nombre declarado en la variable "PREPROC_NAME" del fichero "sf_preproc_info" que es el empleado en el fichero de configuración de Snort. En nuestro ejemplo "Blog".
  • El segundo argumento indica el nombre de la función de inicio principal del preprocesador encargada de registrar todas las acciones que ejecutará éste. En nuestro ejemplo "BlogInit".
Veamos la implementación a continuación:

//Registro del preprocesador y inicialización de este
void BlogSetup(void)
{
    #ifndef SNORT_RELOAD
    //Registramos el preprocesador Blog y llamamos a la funcion BlogInit
    _dpd.registerPreproc("Blog", BlogInit);
    #else
    _dpd.registerPreproc("Blog", BlogInit, BlogReload,
        BlogReloadSwap, BlogReloadSwapFree);
    #endif
}

La siguiente función, BlogInit es la encargada real de inicializar el preprocesador, donde se registrará las funciones a usar en la fase de preprocesado mediante "addPreproc" y en la fase del motor de reglas mediante "preprocOptRegister". Para el ejemplo solo usaremos una de cada pero se pueden emplear más de una.

La función BlogInit soporta un único argumento que es un puntero a char, el cual contiene un puntero al texto existente en el fichero de configuración de Snort, y por tanto, contendrá las variables de configuración de nuestro preprocesador. En nuestro ejemplo será donde tendremos IP que queremos que genere alerta en caso de detectarse tráfico hacia o desde esa IP:

/* Inicialización del preprocesador */
static void BlogInit(char *args)
{
    //Variable para argumentos
    char *arg;
    char *config;

    //Politicas de uso
    tSfPolicyId policy_id = _dpd.getParserPolicy();
    if (ex_config == NULL)
    {
        ex_config = sfPolicyConfigCreate();
        if (ex_config == NULL)
            _dpd.fatalMsg("\tFallo en la inicialización del preprocesador, no puede reservar la estructura.\n");
    }

    //Obtenemos las opciones del preprocesador indicadas en el fichero snort.conf
    arg = strtok(args, " \t\n\r,");
    if(arg && !strcasecmp("miip", arg))
    {
      arg = strtok(NULL, " \t\n\r,");
      //Copiamos la IP que queremos que genere alerta
      IPPROC = (char *)calloc(1, sizeof(arg));
      strncpy(IPPROC, arg, strlen(arg));
      _dpd.logMsg("    La IP que generará alerta es %s\n",IPPROC);
    }
    else
    {
      _dpd.fatalMsg("    Blog: Opcion invalida %s\n");
    }
   
    //Seteamos políticas, a muerto un gatito
    config = arg;
    sfPolicyUserPolicySet(ex_config, policy_id);
    sfPolicyUserDataSetCurrent(ex_config, config);


    //Mensaje del preprocesador de salida indicando que vamos a registrar ya las acciones
    _dpd.logMsg("Blog config:\n");
    _dpd.logMsg("    Joaquin Moreno, el Moxi Sexy - 2011\n");

    //Declaración de funciones a procesar en la fase de preprocesador
    //Funcion, Prioridad, ID de la función - es única entre todos los preprocesadores, protocolos tratados
    _dpd.addPreproc(BlogProcess, PRIORITY_TRANSPORT, 10000, PROTO_BIT__TCP | PROTO_BIT__UDP | PROTO_BIT__ICMP);

    //Declaracion de reglas y evaluación de las mismas en el motor de reglas
    //Nombretag, Función de inicialización, Función de Evaluación de la regla, NULL, NULL, ...
    _dpd.preprocOptRegister("miip", BlogRuleInit, BlogRuleEval, NULL, NULL, NULL, NULL, NULL);

    //Mensaje que se mostrará cuando cargue el preprocesador
    DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "Preprocessor: Blog OK. Hola mama salgo en Interné :P\n"););
}

Como vemos, para registrar la función que se ejecutara en la fase de preprocesador usamos la función "addPreproc" cuyas opciones son las siguientes:
  • BlogProcess: nombre de la función que se ejecutara en la fase de preprocesador por cada paquete de red leido.
  • PRIORITY_TRANSPORT: prioridad del preprocesador.
  • 10000: corresponde con el identificador único de registro de la función. Mucho cuidado porque es GLOBAL para todo Snort... es decir, si tengo un preprocesador 300 donde registro la función número 1000 y creo un preprocesador 301 donde registro la función número 1000 ¡fallará!.
  • PROTO_BIT__TCP | PROTO_BIT__UDP | PROTO_BIT__ICMP: para que protocolos debe saltar esta función, en nuestro caso para TCP o UDP o ICMP.
A su vez para registrar la función que se ejecutará en la fase del motor de reglas usamos "preprocOptRegister" cuyas opciones son las siguientes:
  • "miip": es el tag identificativo de la regla. Un tag se construye en la regla como "tag:valor;", por ejemplo en content:"Hola"; el tag es "content". Por tanto si queremos usar nuestro preprocesador en el motor de reglas añadiríamos a la regla: miip:"127.0.0.1"; (siendo 127.0.0.1 una ip de ejemplo).
  • BlogRuleInit: es la función que se ejecuta por cada regla que lee Snort durante su arranque que contiene el tag indicado en la primera opción. Es decir, cuando arrancamos Snort leemos los ficheros de reglas, pues por cada regla que contenga el tag "miip" se ejecutará esta función. El objetivo de ésta es leer los parametros que hay indicados en la regla y configurarlos para ser procesados posteriormente por cada paquete que llegue al motor de reglas mediante la función "BlogRuleEval". 
  • BlogRuleEval: es la función que se ejecutara por cada paquete de red que llega al motor de reglas y ha cumplido la parte de la izquierda de la regla (esta parte es donde se indica el protocolo, puertos y ips destino / origen). Es la encargada de indicar si el tag se cumple o no. Una regla se activa si todos los tags son ciertos. 
  • "El resto": las restantes 5 opciones ponerlas a Null, no son requeridas para un ejemplo básico.

3º) Acciones a realizar en el módulo de preoprocesado:

Tal como se indico en el punto 2, la función BlogProcess que ha sido registrada mediante "addPreproc" será la que se ejecutara por cada paquete de red leido en la fase de preprocesado. Está función soporta dos opciones aunque únicamente nos interesa la primera. "void *pkt". la cual es un puntero a una estructura SFSnortPacket donde se encuentra normalizado el paquete leido:

void BlogProcess(void *pkt, void *context)
{
    //Paquete leído
    SFSnortPacket *p = (SFSnortPacket *)pkt;

    //Política de prioridades
    sfPolicyUserPolicySet(ex_config, _dpd.getRuntimePolicy());

    //Se debe comprobar que realmente el paquete es un paquete IP
    if(p->ip4h == NULL)
    {
        //No es un paquete IP
        if(p->ip4_header == NULL)
        {
                return;
        }
        else
        {
             //Arpa never die!, estructura in_addr
           //Si la IP origen o destino es la indicada en el fichero de configuración de snort generamos un alerta
  if((strcmp(IPPROC, inet_ntoa((p->ip4_header->destination))) == 0) || (strcmp(IPPROC, inet_ntoa((p->ip4_header->source))) == 0) )
           {
               _dpd.logMsg("Acertado: %s\n", ALERT_BLOG_STR);
               _dpd.alertAdd(GENERATOR_ID, ALERT_BLOG, 1, 0, 1, ALERT_BLOG_STR, 0);
               return;
            }
        } //fin Else de la condición (
p->ip4_header == NULL)
    } //Incio Else de la condición p->ip4h == NULL
    else
    {
        //Snort Never Die!, estructura snort
        //Si la IP origen o destino es la indicada en el fichero de configuración de snort generamos un alerta
  if((strcmp(IPPROC, sfip_to_str(&((p->ip4h->ip_dst)))) == 0) || (strcmp(IPPROC, sfip_to_str(&((p->ip4h->ip_src)))) == 0) )
        {
            //Generamos alerta de preprocesador (ID_GEN, ID_RULE, VER_RULE, REV_RULE, Priority, MSG, X)
            _dpd.alertAdd(GENERATOR_ID, ALERT_BLOG, 1, 0, 1, ALERT_BLOG_STR, 0);
         }
      return;
    }
}

Lo que hace la función anterior es buscar si la IP origen o destino del paquete es la misma que hemos indicado en el fichero snort.conf, entonces ejecutamos la función "alertAdd" encargada de generar un alerta a nivel de preprocesador. La explicación de los argumentos de la función son los siguientes:
  • GENERATOR_ID: es la variable que contiene el ID del preprocesador (300).
  • ALERT_BLOG: id de la regla en preprocesado (1).  
  • 1: versión de la alerta.
  • 0: revisión de la alerta.
  • 1: prioridad, dejar tal cual.
  • ALERT_BLOG_STR: cadena de texto que mostrará Snort como alerta. 
  • 0: nada, no nos interesa.
4º) Acciones a realizar cuando es llamado en el motor de reglas:

Primero se trabaja con la función de inicialización de las reglas de nuestro preprocesador, la cual se ejecuta cuando las reglas son leidas durante el arranque de Snort. El nombre de esta función es BlogRuleInit, la cual fue registrada mediante "preprocOptRegister". BlogRuleInit soporta los siguientes argumentos:
  • name: nombre de la regla. No se suele usar.
  • params: argumento de la opcion de la regla, lo que ha escrito a la derecha del tag en la regla, es decir si hemos puesto "miip:127.0.0.1;" entonces args contendría "127.0.0.1".
  • data: variable donde guardamos los datos que nos interesa de la regla, para pasarselo posteriormente a la función que se ejecutará en el motor de reglas por cada paquete leido.
La implementación se mostrará a continuación:

static int BlogRuleInit(char *name, char *args, void **data)
{
    //Guardaremos la IP como un simple char, aunque normalmente se usan estructuras complejas
    char *miip = NULL;

    if(args == NULL)
    {
      //Mensaje de fallo
      _dpd.fatalMsg("Ande vas pallo malvado!!!\n");
    }
    else
    {
      miip = (char *)calloc(1, sizeof(args));
      strncpy(miip, args, strlen(args));
      _dpd.logMsg("La IP leida es %s\n", miip);
    }

    //Almaceno la IP indicada en la regla
    *data = (void *)miip;

  return 1;
}

El código de la función anterior es sencillo, leemos el String "args" que contiene la configuración del preprocesador, por ejemplo: "miip 127.0.0.1" y es almacenada en el doble puntero data.

El siguiente función será llamada por el motor de reglas si por cada paquete que llegue a éste la parte de la izquierda de una regla se cumpla y en la parte de la derecha éste el tag que hemos usado para registrar la regla. En este caso el tag es "miip". Esta función la hemos llamado "BlogRuleEval" la cual fue registrada empleando "preprocOptRegister". Esta función soporta los siguientes argumentos:
  • pkt: contiene el paquete que estamos analizando en la regla.
  • cursor: ni caso.
  • data: contiene los valores que guardamos en el tercer argumento de la función "BlogRuleInit", es decir, los valores de la regla.
Veamos la implementación:

static int BlogRuleEval(void *pkt, const uint8_t **cursor, void *data)
{
    SFSnortPacket *p = (SFSnortPacket *)pkt;
    char * miip = (char *) data;
    if(p->ip4h == NULL)
    {
        if(p->ip4_header == NULL)
        {
                return RULE_NOMATCH;
        }
        else
        {
                //Soporte Arpa
  if((strcmp(miip, inet_ntoa((p->ip4_header->destination))) == 0) || (strcmp(miip, inet_ntoa((p->ip4_header->source))) == 0) )
                {
                      _dpd.logMsg("Acertado!");
                     return RULE_MATCH;
                 }
        }
    }
    else
    {
           //Soporte Snort
  if((strcmp(miip, sfip_to_str(&((p->ip4h->ip_dst)))) == 0))) || (strcmp(miip, sfip_to_str(&((p->ip4h->ip_src)))) == 0) )
           {
                _dpd.logMsg("Acertado!");
                 return RULE_MATCH;
            }
    }
    //Salimos si no cumple
    return RULE_NOMATCH; 
}

Como vemos es muy parecida a la regla de evaluación en la fase de preprocesado, pero en este caso la configuración está guardada en la variable "data". Así mismo para indicar que este tag se cumple se devuelve la constante "RULE_MATCH" o en caso contrario se devuelve "RULE_NOMATCH". Para que una regla se cumpla todos los tags que está tenga declarado debe cumplirse.


5º) Acciones a realizar cuando es rearrancado:

En ésta parte dejaremos tal cual está la del ejemplo puesto que su explicación requeriría más de una entrada solo para este punto. Digamos que nuestro preprocesador no funcionará si hacemos un reload, por tanto será necesario parar y rearrancar Snort. Si vemos ejemplos veremos que es prácticamente implementar lo mismo que hemos visto pero teniendo en cuenta un par de cosas. Veamos la implementación:

#ifdef SNORT_RELOAD
static void BlogReload(char *args)
{
    char *config = NULL;
    tSfPolicyId policy_id = _dpd.getParserPolicy();

    _dpd.logMsg("blog dynamic preprocessor configuration\n");

    if (ex_swap_config == NULL)
    {
        ex_swap_config = sfPolicyConfigCreate();
        if (ex_swap_config == NULL)
            _dpd.fatalMsg("Could not allocate configuration struct.\n");
    }

    sfPolicyUserPolicySet(ex_swap_config, policy_id);
    sfPolicyUserDataSetCurrent(ex_swap_config, config);

    DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "Preprocessor: blog is initialized\n"););
}

static int BlogReloadSwapPolicyFree(tSfPolicyUserContextId config, tSfPolicyId policyId, void *data)
{
    char *policy_config = (char *)data;

    sfPolicyUserDataClear(config, policyId);
    free(policy_config);
    return 0;
}

static void * BlogReloadSwap(void)
{
    tSfPolicyUserContextId old_config = ex_config;

    if (ex_swap_config == NULL)
        return NULL;

    ex_config = ex_swap_config;
    ex_swap_config = NULL;

    return (void *)old_config;
}

static void BlogReloadSwapFree(void *data)
{
    tSfPolicyUserContextId config = (tSfPolicyUserContextId)data;

    if (data == NULL)
        return;

    sfPolicyUserDataIterate(config, BlogReloadSwapPolicyFree);
    sfPolicyConfigDelete(config);
}
#endif

Una vez escrito nuestro preprocesador lo compilamos y lo instalamos:

# make
# make install

Vamos a declarar el preprocesador indicando que notifique en la fase de preprocesado si detecta tráfico de la IP 8.8.8.8. Para ello editamos el fichero de configuración de Snort (/etc/snort/snort.conf) y añadimos como último preprocesador (el orden de posicionamiento del preprocesador SI que importa) la siguiente línea:

preprocessor Blog: miip 8.8.8.8

Para crear una regla de ejemplo que generará una alerta si el paquete es TCP, el puerto es 80 y la ip de origen o destino es la 8.8.8.8:
alert tcp any any -> any 80 (msg:"Hola gente!"; miip:8.8.8.8; sid: 1; rev: 1;)

Una vez configurado inicializaremos Snort:

# /usr/local/bin/snort -u snort -g snort -c /etc/snort/snort.conf -i eth0 -l /var/log/snort

Running in IDS mode

        --== Initializing Snort ==--
...
  Loading dynamic preprocessor library /usr/local/lib/snort_dynamicpreprocessor/lib_sfdynamic_preprocessor_blog.so... done
...
Blog config:
    Joaquin Moreno, el Moxi Sexy - 2011
La IP que generará alerta es 8.8.8.8
...
      --== Initialization Complete ==--
...
Preprocessor Object: Blog  Version 1.0  <Build 1>
...
(Snort se queda a la escucha)

Posteriormente para probar el funcionamiento del preprocesador en la fase de preprocesado se he realizado un simple ping donde se muestra dos alertas a nivel de preprocesado, por un lado la ida y por otro la vuelta correspondiente al tráfico ICMP 8 y 0 del ping:

# ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_req=1 ttl=54 time=39.0 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 39.075/39.075/39.075/0.000 ms

Mostrando por la consola de Snort las siguientes dos notificaciones:


Acertado: He detectado tráfico TCP al puerto indicado
Acertado: He detectado tráfico TCP al puerto indicado

Y si accedemos al log de Snort veremos que se han generado dos alertas a nivel de preprocesador, donde se nos indica que ha sido el GID 300, regla 1, revision 1 ([300:1:1]):

[**] [300:1:1] He detectado tráfico TCP al puerto indicado [**]
[Priority: 1]
08/23-21:56:02.628184 10.0.0.1 -> 8.8.8.8
ICMP TTL:64 TOS:0x0 ID:0 IpLen:20 DgmLen:84 DF
Type:8  Code:0  ID:29225   Seq:1  ECHO

[**] [300:1:1] He detectado tráfico TCP al puerto indicado [**]
[Priority: 1]
08/23-21:56:02.668564 8.8.8.8 -> 10.0.0.1
ICMP TTL:54 TOS:0x0 ID:9947 IpLen:20 DgmLen:84
Type:0  Code:0  ID:29225  Seq:1  ECHO REPLY

Para probar la regla he realizado lo siguiente:
# printf "GET / HTTP/1.0\n\n" | nc 8.8.8.8 80 > /dev/null
(PD: Tened en cuenta que con esta acción también se cumple los requisitos del preprocesador, para no liar solo se muestra las alertas desde el motor de reglas)

Al no recibir respuesta desde el puerto 80 solo se detecta el paquete de la ída, teniendo como resultado en la pantalla de Snort la siguientes entrada:

Acertado!

Y como alerta de Snort:

[**] [1:1:1] Hola gente! [**]
[Priority: 0]
08/23-22:01:43.613062 10.0.0.1:49149 -> 8.8.8.8:80
TCP TTL:64 TOS:0x0 ID:23450 IpLen:20 DgmLen:60 DF
******S* Seq: 0x155736A2  Ack: 0x0  Win: 0x16D0  TcpLen: 40
TCP Options (5) => MSS: 1460 SackOK TS: 2701226025 0 NOP WS: 6

Bueno ahora ya sí, con esto hemos termino la introducción a la implementación de preprocesadores dinámicos. Realmene lo importante ahora es tener buenas ideas y cumplir requisitos que son necesarios en nuestra infraestructura, pero que Snort por defecto no nos lo ofrece. En mi caso, y gracias a tener a unos cracks en el curro, tenemos uns preprocesadores que son la bomba, parece que no, pero estos entornos permiten hacer muchísimas cosas y detectar cosas que por defecto un Snort no puede.

Hasta la proxima, que espero no sea dentro de mucho aunque teniendo en cuenta que empiezo el Inglés y que tengo el examen  del 542 de SANS me parece a mí que hasta finales de Septiembre la cosa estará difícil ;)



Continuar...

sábado, 20 de agosto de 2011

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

Hola de nuevo, esta es la tercera entrada de introducción a la implementación de un preprocesador dinámico, donde veremos las funciones principales de un preprocesador dinámico, su función y el orden de llamadas de éstas.

Como hemos visto a lo largo de las entradas pasadas éste tiene como mínimo dos ficheros:

  • sf_preproc_info.h: este nombre no se debe cambiar. Es donde se declara el preprocesador, su nombre, su número de registro y donde indica la función de inicialización para empezar la carga del preprocesador. Esta función se encuentra implementada en el fichero "nombrePreprocesador.c".
  • nombrePreprocesador.c: es el fichero donde se encuentra la implementación del preprocesador, en nuestro caso se llama "blog.c".

El funcionamiento básico de ambos ficheros se explica en el siguiente diagrama resumido:

Fichero sf_preproc_info.h:
  ...
  extern void BlogSetup();
  ...

Fichero blog.c:
  -> extern DynamicPreprocessorData _dpd;

  -> void BlogSetup(){ ...  _dpd.registerPreproc("Blog", BlogInit); ...}

      -> BlogInit(char * args)
      { ...
      "DeclaracionEstructuas" ,
      "Declaracion de politicas" , 
      "Lectura del parametro 'args' que contiene la configuración de snort.conf",
      "Declaramos la funcion que se ejecuta para cada paquete en el lado del preprocesado:
          _dpd.addPreproc(hacerEnPreproc,opc2,...);",
      "Para cada paquete leido que hacemos en el motor de reglas
          _dpd.preprocOptRegister("tag regla", leerReglaFichero,hacerEnRegla,...);"
       
        ... }

             -> void hacerEnPreproc(el_paquete, ...) { ... acciones ... }

             -> static int leerReglaFichero(nombreRegla,valoresRegla, GuardamosDatos)           
            -> static int hacerEnRegla(paquete, ..., LoGuardado) { ... acciones ... }

Veamos paso a paso que es exactamente lo que está ocurriendo:


1º) En el fichero "sf_preproc_info.h" se indica la función del fichero "nombrePreprocesador.c" que inicializa el preprocesador. En nuestro caso la función es "BlogSetup" la cual fue declarado en el fichero "sf_preproc_info.h" como "extern void BlogSetup();".

Por tanto una vez se carga la función "BlogSetup" todo el resto de la implementación que veremos a continuación estará en el fichero "nombrePreprocesador.c", es decir "blog.c".

2º) La función "BlogSetup()" es la encargada de registrar el preprocesador mediante "registerPreproc" de la estructura "DynamicPreprocessorData",  indicando como segundo argumento el nombre de la función que se encargara de empezar la carga del preprocesador; a esta función la llamaremos "BlogInit" y se declararía de la siguiente forma: "_dpd.registerPreproc("Blog", BlogInit)".

Dentro de esta función, dependiendo de si se hace un "Start" o un "Reload" del servicio Snort se cargara una u otra función de inicialización. Para que se entiende forma sencilla, si yo ejecuto Snort en modo start se carga la función "_dpd.registerPreproc("Blog", BlogInit);", si en cambio es un reload de un Snort ya en ejecución haremos  un "_dpd.registerPreproc("Blog", BlogInit, BlogReload, BlogReloadSwap, BlogReloadSwapFree);", tal como se muestra a continuación:

void BlogSetup(void)
{
    #ifndef SNORT_RELOAD
        _dpd.registerPreproc("Blog", BlogInit);
    #else
        _dpd.registerPreproc("Blog", BlogInit, BlogReload, BlogReloadSwap, BlogReloadSwapFree);
    #endif
}

Para estas entradas solo trataremos el caso de inicialización pero es importante entender que las funciones reload son obligatorias para el arranque del Snort por tanto se dejaran tal cual están las del ejemplo que proporciona Snort.

3º) La función "BlogInit" carga los datos necesarios para el preprocesador durante el arranque del Snort, por ejemplo, la creación de estructuras necesarias, lectura de ficheros, lectura de los parametros de configuración del preprocesador indicados en el fichero de configuración de Snort (/etc/snort/snort.conf) y declarar las políticas del preprocesador. También se registran las funciones que se van a ejecutaran en el módulo del preprocesado y en módulo del motor de reglas al leer un paquete de red. El primero se crea mediante la función "addPreproc" y el segundo mediante la función "preprocOptRegister", ambas pertenecientes a la estructura "DynamicPreprocessorData".

Para asegurar que se ha entendido el funcionamiento de cada una de las funciones vamos a repasarlo mediante un pequeño diagrama hecho con DIA (siendo esta forma más visual), ya que, entender el funcionamiento del core de un preprocesador dinámico es la base de la implementación de éstos:


     
Con esto ya disponemos de las nociones básicas del funcionamiento de un preprocesador.

Para finalizar vamos a mostrar dos pequeñas funciones de la estructura "DynamicPreprocessorData" (entre muchas comillas por que ya os habréis dado cuenta que no es realmente una estructura) que permite mostrar texto en la implementación de forma que podamos seguir la traza del preprocesador. Éstas son "logMsg" y "fatalMsg". Ambas muestran por pantalla texto y el valor de las variables, pero "fatalMsg" adicionalmente finaliza la ejecución de Snort, por ejemplo, al detectar un fallo:

Si no hay error:
    _dpd.logMsg("La IP mostrada es %s\n",IPPROC);
sino
      _dpd.fatalMsg("Fallo en la aplicación, no puedo continuar\n");

Estas funciones son importantes porque las usaremos mucho durante la implementación de Snort.

Con esto ya hemos explicado las funciones principales de un preprocesador y como son llamadas unas a otras para realizar una cierta tarea. En la siguiente entrada, y última, se expondrá el código de un procesador "chorra" que sirva de ejemplo de como se implementa. Nos leemos.



Continuar...

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 ;)



Continuar...

viernes, 12 de agosto de 2011

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

Después de 20 días sin escribir en el blog por motivos tan importantes como el calor, el trabajo y los estudios me he decidido a retomarlo que lo tenía olvidado. ¿Y que mejor forma que creando el dominio “www.bastionado.com” y un post de Snort?

Snort es una herramienta de prevención y detección de intrusos a nivel de red. Internamente se subdivide en bloques encargados de tratar la información y pasar el resultado al siguiente bloque:

  • El primero bloque es el Sniffer encargado de leer el tráfico.
  • El segundo bloque son los normalizadores encargados de tratar los datos leídos por el Sniffer transformándolos en una estructura de C bien formada.
  • El tercer bloque lo constituyen los preprocesadores, encargados de generar alertas, normalizar los payloads y aportar nuevas funcionalidades al motor de reglas.
  • El cuarto bloque es el motor de reglas, el cual a partir de un listado de reglas es capaz de detectar posibles ataques, notificando de este hecho a los postprocesadores con la directiva indicada en la regla.
  • El quinto bloque son los postprocesadores cuyo objetivo es aplicar la directiva estipulada en la regla, por ejemplo bloquear el tráfico, registrar la alerta en fichero, registrar en una base de datos, syslog, etc.

En ésta entrada veremos como crear un preprocesador básico de Snort de forma sencilla y introductoria. Para ello será necesario tener instalado un Snort en nuestro entorno (mirar manuales en la web de Snort para ello). Para empezar será necesario disponer de las fuentes de Snort de la versión empleada (2.8 o superior) para crearnos el preprocesador al que llamaremos “Blog”, comencemos:
 

# cd /usr/src
(Descargamos las fuentes del Snort que tenemos instalado)
# tar xvfz snort-version.tar.gz
# mkdir /usr/src/snort-version/src/dynamic-preprocessors/blog
# cd /usr/src/snort-version/src/dynamic-examples/dynamic-preprocessor/
# cp spp_example.c sf_preproc_info.h Makefile Makefile.am Makefile.in
/usr/src/snort-version/src/dynamic-preprocessors/blog/
# cd /usr/src/snort-version/src/dynamic-preprocessors/blog/

Modificamos el makefile:

# sed -e "s/dynamic-preprocessors/blog/g" Makefile >> Makefile2
# mv Makefile2 Makefile
# sed -e "s/dynamic-examples/dynamic-preprocessors/g" Makefile >> Makefile2
# mv Makefile2 Makefile
# sed -e "s/spp_example/blog/g" Makefile >> Makefile2
# mv Makefile2 Makefile
# sed -e "s/example/blog/g" Makefile >> Makefile2
# mv Makefile2 Makefile
# mv spp_example.c blog.c

Editamos el fichero "sf_preproc_info.h" con las siguientes modificaciones:

Modificamos esto:
#ifndef SF_PREPROC_INFO_H_
#define SF_PREPROC_INFO_H_
...
define PREPROC_NAME "SF_Dynamic_Example_Preprocessor"
...
#endif /* SF_PREPROC_INFO_H_ */

Por esto:
#ifndef SF_BLOG_INFO_H_
#define SF_BLOG_INFO_H_
...
define PREPROC_NAME "Blog"
...
#endif /* SF_BLOG_INFO_H_ */

Ahora ejecutaremos la primera compilación de nuestro preprocesador "blog" y veremos que no da ningún error:

# make
# make install

Si nos fijamos, al finalizar el "make install" se nos indica el directorio donde ha sido instalado el preprocesador:

Libraries have been installed in:
         /usr/local/lib/snort_dynamicpreprocessor

Veamos que ha creado el "make" en ese directorio "/usr/local/lib/snort_dynamicpreprocessor":

# ls -l /usr/local/lib/snort_dynamicpreprocessor | grep blog
-rw-r--r-- 1 root staff 95638 ago 8 16:32 lib_sfdynamic_preprocessor_blog.a
-rwxr-xr-x 1 root staff 1303 ago 8 16:32 lib_sfdynamic_preprocessor_blog.la
lrwxrwxrwx 1 root staff 40 ago 8 16:32 lib_sfdynamic_preprocessor_blog.so -> lib_sfdynamic_preprocessor_blog.so.0.0.0
lrwxrwxrwx 1 root staff 40 ago 8 16:32 lib_sfdynamic_preprocessor_blog.so.0 -> lib_sfdynamic_preprocessor_blog.so.0.0.0
-rwxr-xr-x 1 root staff 61529 ago 8 16:32 lib_sfdynamic_preprocessor_blog.so.0.0.0

Para confirmar que nuestro Snort espera encontrar los preprocesadores dinámicos en "/usr/local/lib/snort_dynamicpreprocessor" es necesario revisar el tag "dynamicpreprocessor" del fichero de configuración principal de snort, por defecto "/etc/snort/snort.conf":


# grep dynamicpreprocessor /etc/snort/snort.conf
dynamicpreprocessor directory /usr/local/lib/snort_dynamicpreprocessor/

Como vemos el directorio es el mismo puesto que estoy empleando una instalación estándar. En caso contrario, sería necesario mover el preprocesador al directorio que usemos o modificar el makefile para que copie los ficheros al directorio indicado.

En la siguiente entrada veremos como modificar el código del preprocesador que acabamos de crear para añadir funcionalidades en el módulo de preprocesadores y en el motor de reglas. 



Continuar...