LiNUX KERNEL MODULES: LKMs

Sniffando paquetes:

Para esnifar paquetes nos hacen falta tres(3) cosas:

Una estructura packet_type que usaremos para registar e instalar nuestro filtro.

Registrar e instalar la estructura de arriba con dev_add_pack()

Una rutina que se encargue de recibir los paquetes y procesarlos, cuya dirección tendremos que poner en el campo func de la estructura arriba mencionada.

La rutina que se encarga de procesar los paquetes toma tres argumentos:

Un puntero a una estructura sk_buff, otro a una estructura device y otro a una est. packet_type.

El que nos interesa a nosotros es el primero. Todos los paquetes son colocados en estructuras sk_buffs, para luego ser procesados por los handlers registrados.

Campos que nos interesan de la estructura sk_buff:

skb->nh : nh significa network header, y contiene la cabecera de la capa de red. En este caso es una cabecera IP. Para referenciar dicha cabecera se usa skb->nh.iph. (en 2.0.x es skb->h.iph)

skb->h : es una unión de punteros que se usan para referenciar a la cabecera de la capa de transporte (tcp). Para apuntarlo se usa skb->h.raw y para referenciarlo skb->h.nh. (en 2.0.x solo existe el campo con la cabecera IP).

skb->data : Lo usaremos para apuntar a los datos del paquete.

De estos campos el único que esta apuntando correctamente es el de la cabecera IP. Los demás tendremos que ajustarlos nosotros.

Con lo que tenemos ya podemos hacer un sniffer exactamente igual que en un programa normal, pero hay un problema: no sabemos como "loguear" las conexiones. Pues exactamente igual que en un sniffer en Ring3: con las syscalls open y write. Pero si hacemos esto nos encontramos con otro problema; si recordáis, las syscalls esperan en los parámetros punteros que apuntan en el espacio de direcciones de la aplicacion que se esta ejecutando en ese momento, y nosotros queremos pasarle punteros del kernel, que evidentemente no funcionarían. Entonces necesitamos reservar memoria en el espacio del usuario, ¿como?, como lo hace malloc(): cambiando el valor del final del segmento de datos (brk), utilizando la syscall brk. Al principio parece complicado pero ya veréis como no lo es. Para escribir o leer datos desde un puntero que apunte a una direccion de usuario se usan las funciones:

__generic_copy_from_user(dst, src, count);

__generic_copy_to_user(dst, src, count);

Pero todavía tenemos otro problema (joder que cantidad de problemas!): hemos dicho que vamos a usar la tarea actual para conseguir memoria de usuario, pero el kernel no tiene siempre un tarea ejecutándose. ¿Como sabemos que una tarea se esta ejecutando? 

Cuando se produce una syscall. Lo que vamos a hacer es conseguir memoria de usuario cuando se produzca una syscall. Mirad el código fuente para que os quede mas claro.

Anterior

(c) 2000 KIKO81