por TageTora
(Proyecto HIS)
http://his.sourceforge.net
10 Mayo del 2003
Se da permiso para copiar, distribuir y/o modificar este documento bajo los términos de la Licencia Libre de Documentación GNU, versión 1.2 o cualquier versión posterior publicada por la Free Software Foundation (Fundación para el Software Libre); este documento no tiene secciones invariantes, textos de portada ni de contraportada. Se incluye una copia de dicha licencia en la sección titulada "Licencia de Documentación Libre GNU".
Este texto es una continuación (o ampliación, como quiera verse) del texto Instalación y configuración de Sebek 0.4, donde se trataba a través de un ejemplo práctico el proceso de instalación y configuración de la anterior versión de Sebek. Aunque ha habido cambios importantes en la nueva versión, que comentaremos más adelante, el proceso para poner en marcha Sebek no ha cambiado mucho, así que en vez de repetir lo dicho en el anterior texto nos centraremos en definir los cambios y aprovecharemos para explicar el funcionamiento interno de Sebek.
Así pues, recomendamos la lectura del texto sobre la versión 0.4 antes de seguir con este, ya que seguimos con el mismo entorno, las mismas máquinas y la misma configuración.
Aunque la forma de usar Sebek apenas ha cambiado, sus características internas han sufrido una completa remodelación, tan completas que es difícil saber por donde empezar. Para hacerlo más fácil, vamos a dividir los cambios en cuatro partes: cambios en el proceso de instalación, en la configuración, en el funcionamiento de Sebek2 en el equipo trampa y en el recolector (máquina de control).
Parte 1. Cambios en la instalación.La instalación en esta nueva versión se ha 'estandarizado'. Ahora tenemos dos programas diferentes, Sebek y el recolector, que se obtienen en paquetes separedos y cada uno con sus scripts de instalación. La instalación de los dos paqutes se realiza ahora a la forma más tradicional:
$ ./configure [opciones de conf.] $ make $ make install
Para ver que opciones de configuración tenemos podemos echarle un vistazo al fichero INSTALL o ejecutar ./configure --help.
Parte 2. Cambios en la configuración.El programa recolector (sebeksniff), no necesita configuración, simplemente cambian los parámetros utilizados al ejecutar el programa, pero esto lo veremos dentro de la parte dedicada al funcionamiento del recolector. La configuración principal de Sebek sigue haciéndose a través de modificaciones a sebek.sh, aunque las opciones que tenemos han cambiado:
Como podemos ver, algunas opciones han desaparecido, como el MAGIC_NO, el intervalo entre envio de paquetes y curiosamente también el cifrado. Podríamos decir que estas medidas de protección (o anti-detección) se han reemplazado por la característica del filtrado por MAC (FILTER_OUI).
Parte 3. Cambios en el funcionamiento de Sebek.El principal cambio de Sebek2 es que ha dejado de ser una modificación de Adore, del que sólo conserva el módulo cleaner. La forma de capturar los datos sigue siendo la misma, reemplazar la función read del sistema por una nueva que llame a la vieja y además envie los datos en paquetes UDP. Además, modifica la entrada para la familia AF_PACKET, y así poder realizar el filtrado por OUI.
Desde el punto de vista del funcionamiento, Sebek2 se compone de dos módulos para el núcleo. El primero es el propio Sebek (módulo sebek.o) y el segundo es el que se encargará de ocultarlo en el sistema (módulo cleaner.o). Esto supone una nueva ventaja, ya que si sólo cargamos el módulo de Sebek lo podemos eliminar con un rmmod, modificar la configuración y volver a cargarlo hasta que ajustemos la configuración a nuestras necesidades.
Parte 4. Cambios en el recolector.El programa recolector sebeksniff se ha visto simplificado debido a los cambios realizados en Sebek2. Ya no necesita tener en cuenta el MAGIC_NO ni claves para el cifrado Blowfish, ahora simplemente captura todos los paquetes que tengan un puerto destino determinado (el mismo valor que DESTINATION_PORT).
Por otra parte, se ha eliminado la posibilidad de capturar paquetes Syslog, aunque suponemos que eligiendo el puerto 514 como DESTINATION_PORT, se podrán capturar las dos cosas sin ningún problema.
La última parte que nos queda comentar del recolector es el programa sbdump.pl, que muestra la información recolectada. Para nuestra decepción, este script es casi exactamente el mismo que en la versión anterior, con los mismos defectos que comentamos en el texto de la versión 0.4.
Problemas.En este punto vamos a describir los problemas encontrados durante la instalación de Sebek2. Puede que sean específicos de los sistemas y/o configuraciones de los equipos utilizados, pero por si alguien tiene los mismos problemas y esto le puede servir de ayuda...
1.- En nuestra máquina trampa (una Debian Woody 3.0r0), tuvimos problemas al ejecutar ./configure, ya que no encontraba las librerias del núcleo. Esto se debe a que se instalan en /usr/src/kernel-headers-2.4.18/ y el programa no es capaz de encontrarlas, por lo que deberemos utilizar la opción ./configure --oldincludedir=/usr/src/kernel-headers-2.4.18/.
2.- Al ejecutar make, el programa de instalación comprime los módulos y el fichero de configuración, para que al hacer el make install los descomprima en el lugar adecuado para la instalación. El problema es que para comprimir utiliza por defecto el programa gtar. Si no tenemos esta aplicación, cambiándolo por tar en la linea 113 de /sebek-linux-2.0.1/Makefile.in funciona igual de bien.
En este punto vamos a describir brevemente los cambios realizados en el entorno utilizado para la versión 0.4, para adaptarnos a Sebek2 manteniendo más o menos las mismas direcciones.
Básicamente, en el planteamiento inicial lo que teníamos era que el SDM (Monitor de Dispositivo Sebek), enviaba los paquetes con direcciones de orignen falsas elegidas al azar de la red 192.168.222.0/24, y con destino al host 192.168.222.17:
DST_NET="192.168.222.17/32" SRC_NET="192.168.222.0/24"
Ahora en Sebek2 no tenemos la opción de la dirección de origen (porque no es necesaria), pero si la dirección destino, que dejaremos igual:
DESTINATION_IP="192.168.222.17"
Otra de las opciones importantes es el puerto de destino. Si recordamos, en la configuración para la versión 0.4 elegimos el puerto destino 161 (tráfico SNMP), porque en la red en que estamos no hay tráfico de este tipo, y es fácil detectar cuando sebek está mandando paquetes (hay un intruso). Como la configuración de la red respecto a esto no ha cambiado, seguimos con el mismo puerto destino:
DESTINATION_PORT="161"
El resto de opciones, como ya hemos comentado, no aparecen en Sebek2 (MAGIC_NO y PACKET_DELAY). Por otro lado, en el fichero de configuración de la nueva versión nos quedan tres opciones más que configurar. La interfaz simplemente la hemos asignado a eth0, que es la única de que dispone la máquina trampa. Para la dirección MAC de destino hemos elegido una dirección aleatoria aunque parecida a la que tenemos realmente. No tenemos necesidad de elegir una direccion MAC en concreto, ya que el filtrado de tráfico Sebek se realiza en la capa de red (iptables).
Por último nos queda un parámetro muy importante, el FILTER_UOI. Hemos elegido un OUI aleatorio que no aparezca en ningún equipo de la red. El hecho de elegirla de esta forma se debe a que el resto de máquinas trampa no utilizan Sebek, y por lo tanto obtenemos ninguna ventaja al elegir una dirección en concreto. Aquí hay que destacar la importancia de no elegir un OUI que pertenezca a una de las máquinas de la red, ya que desde nuestra máquina trampa se observarían patrones de tráfico muy extraño. Por ejemplo, si ponemos nuestra MAC, simplemente no veríamos el tráfico que generamos nosotros mismos, y si ponemos la MAC de otro equipo, podríamos ver conexiones a medias (sólo de una de las partes). De esta forma, los últimos parámetros quedan configurados de la siguiente forma:
INTERFACE="eth0" DESTINATION_MAC="00:05:1C:AA:B3:07" FILTER_OUI="00:02:69"
En esta parte vamos a ver cómo se las apaña Sebek para realizar las tareas que lleva a cabo en esta segunda versión. Principalmente nos centraremos en las tres funciones más importantes de Sebek2: el registro de pulsaciones de teclas (keystrokes), el filtrado por OUI y la forma en que se oculta. Llegamos a la parte más técnica del texto, aunque vamos a intentar explicar las operaciones que realiza Sebek de forma que sean comprensibles para la gente que no tiene experiencia en LKMs (Loadable Kernel Modules), pero tampoco queremos que se convierta en un tutorial sobre el funcionamiento/programación del LKMs.
Para registrar las pulsaciones de teclas, sebek implementa lo que se conoce como syscall proxy, es decir, se coloca entre el usuario y el núcleo para así poder interceptar las llamadas al sistema:
Concretamente, Sebek2 sólo intercepta las llamadas a la función read(). Simplemente, lo que hace es reemplazar la verdadera llamada al sistema por una función propia de Sebek2 llamada nrd() (de New Read). Veamos el código correspondiente a esta operación:
sebek.c:202:init_module()
// sct es la tabla de llamadas
// al sistema (sys_call_table)
if(sct){
// Guardamos puntero a read del sistema
(unsigned long *)ord = sct[__NR_read];
// Escribimos puntero a nuestra read
sct[__NR_read] = (unsigned long *)nrd;
}else{
// Algo no ha ido bien.
goto out_unlock;
}
A partir de este momento, cada vez que un programa en espacio de usuario llame a read(), realmente estará llamando a nuestra función. ¿Y qué hace Sebek2 en este momento? Pues muy fácil, simplemente llama a la vieja función para que todo funcione correctamente, pero además envia una copia de los datos a través de la función sebek_log(), que se encarga de enviar un paquete generado según nuestra configuración con esos datos:
sebek.c:425
inline int nrd (unsigned int fd, char *buf, size_t count) {
// llamamos a la funcion original
r = ord(fd, buf, count);
// Recogemos informacion de donde y cuando
// provienen los datos
do_gettimeofday(&tv);
get_tty_name (current->tty,tty_id,BUFLEN);
[...]
// Construimos el mensaje que enviar al recolector
snprintf (key,BUFLEN,"%d:%d:%d:%s:%d:%s:c:2:%c\n",
tv.tv_sec,current->pid,current->uid,current->comm,
fd,tty_id,buf[0]);
// Le pasamos el mensaje a sebek_log para
// que lo envie
sebek_log(key,strlen(key),0);
return r;
}
Cabe destacar que hemos omitido algunas líneas de código por claridad. En estas líneas, simplemente se comprobaban los datos recogidos para enviar un tipo de mensaje u otro.
Esto es básicamente el keylogger de Sebek2. Si quisiéramos registrar otro tipo de datos, tan sólo deberíamos reemplazar la entrada correspondiente en la tabla y tener una función que realizara algo muy parecido a lo que hace nrd(), llamar a la original y enviar una copia de los datos. Fácil ¿no?
Para realizar el filtrado, Sebek2 incorpora una copia modificada del fichero af_packet.c, que incluye una pequeña modificación en la función packet_recvmsg(). Esta función se encarga de pasar los paquetes de la cola de llegada al espacio de usuario. La modificación incluida, simplemente comprueba los paquetes antes de pasarlos a espacio de usuario, de forma que si la OUI de la dirección MAC de origen del paquete coincide con la que hemos configurado, descarta directamente el paquete:
af_packet.c:1072:packet_recvmsg()
// skb es el datagrama recibido. Comparamos los
// tres primeros bytes de la direccion MAC (OUI)
if(skb-gt;mac.ethernet->h_source[0] == eth_src[0] &&
skb->mac.ethernet->h_source[1] == eth_src[1] &&
skb->mac.ethernet->h_source[2] == eth_src[2]){
// Si coincide, nos cargamos el paquete
skb_free_datagram(sk, skb);
// Volvemos a por otro paquete
goto try_again;
}
El vector eth_src[], contiene los tres bytes especificados como parámetro, o si no estaba configurado, el valor por defecto 10:00:00, como podemos ver en el código:
sebek.c:47
MODULE_PARM("filter_oui",s);
sebek.c:142:parse_params()
if(filter_oui && strnlen(filter_oui,18) == 8){
memcpy(octet,filter_oui,2);
eth_src[0] = hotou(octet);
memcpy(octet,filter_oui+3,2);
eth_src[1] = hotou(octet);
memcpy(octet,filter_oui+6,2);
eth_src[2] = hotou(octet);
}
sebek.c:230:init_module()
// Si filter_oui no estaba configurado
// ponemos el valor por defecto.
if(!filter_oui){
eth_src[0] = 0x10;
eth_src[1] = 0x00;
eth_src[2] = 0x00;
}
// El resto de bytes de la MAC los rellenamos
// con los valores reales de la tarjeta de red.
eth_src[3] = output_dev->dev_addr[3];
eth_src[4] = output_dev->dev_addr[4];
eth_src[5] = output_dev->dev_addr[5];
En esta última parte del código podemos ver cómo los últimos tres bytes de la dirección generada de origen no són aleatorios, simplemente se copian de la dirección real del interfaz de salida de los paquetes ( output_dev).
Como hemos comentado al principio del texto, esta es la única parte que se conserva de Adore. Para ocultar el módulo sebek.o ante un lsmod, se utiliza otro módulo para el núcleo llamado cleaner.o. Este es un programa muy corto y simple:
cleaner.c:
// Funcion ejecutada al cargar cleaner.o
// (insmod cleaner.o)
int init_module()
{
#ifdef USE_MOD_LICENSE
MODULE_LICENSE("GPL");
#endif
if (__this_module.next)
__this_module.next = __this_module.next->next;
return 0;
}
// Funcion ejecutada al borrar cleaner.o
// (rmmod cleaner.o)
int cleanup_module()
{
return 0;
}
Como podemos ver, al cargarse el módulo, modifica el puntero al siguiente módulo, para saltarse el módulo que se cargó antes que él (en nuestro caso será sebek.o). De esta forma, el núcleo mantiene un puntero al primer módulo de la lista, que en este momento es cleaner.o, pero al borrarlo, pasa ese puntero al primero de la lista a cleaner_module.next, saltándose el módulo principal de Sebek2. Ahora, si hacemos un lsmod, el núcleo recorre la lista e imprime todos los módulos, pero recordemos que ahora sebek.o no está en la lista (no hay ningún nodo/módulo que tenga un puntero hacia él), por lo que no aparecerá en el listado. Quizá se vea más fácil desde el punto de vista del núcleo, veamos los pasos que realiza suponiendo que el último módulo cargado antes de Sebek fué bttv.o:
// tenemos lista_modulos = bttv (primero) // instalamos sebek.o sebek.next = lista_modulos; lista_modulos = sebek; // Ahora sebek.o es el primero de la lista // lista_modulos->sebek->bttv->... // instalamos cleaner.o cleaner.next = lista_modulos; lista_modulos = cleaner; // Ahora tenemos esta lista // lista_modulos->cleaner->sebek->bttv->... // cleaner cambia su puntero a // siguiente, tenemos // lista_modulos->cleaner->bttv->... // sebek ha quedado desconectado de la lsita // borramos cleaner lista_modulos = cleaner.next; // lista_modulos = bttv delete(cleaner); // La lista queda al final // lista_modulos->bttv->...
Recordar que aunque el módulo de Sebek no esté en la lista, sigue ejecutándose. Además, del mismo modo que no podemos ver el módulo al hacer un lsmod, el sistema no puede borrarlo al hacer un rmmod