Ir al contenido principal

Desarrollo de módulos para el kernel Linux

Una de las primeras cosas que tiene que saber un hacker del kernel es cómo crear un módulo para el kernel Linux. Para continuar leyendo este artículo es conveniente haber leído antes el anterior Entorno para desarrollo del kernel Linux.
Linux es un kernel del tipo monolítico, lo cual quiere decir, grosso modo, que todo el kernel es un gran archivo ejecutable que se carga y toma el control al iniciar el ordenador. El problema de este tipo de kernels es que al compilarlo hay que añadir todos los controladores para el hardware que queremos soportar. Esto es un problema si queremos hacer un kernel genérico que soporte una gran variedad de dispositivos, ya que su tamaño será considerable, e incluso inviable.
Linux soluciona este problema a través de los módulos, por lo que podemos decir que Linux es un kernel modular que carga en memoria aquellos módulos que necesita en cada momento. Las distribuciones genéricas, como por ejemplo, Ubuntu, compilan un núcleo muy pequeño con lo mínimo para poder arrancar y en tiempo de ejecución carga en memoria aquellos módulos necesarios para dar servicio a los dispositivos que tiene nuestro equipo.
Los módulos son, pues, la manera más sencilla y cómoda de añadir código y funcionalidad al espacio del kernel sin tener que andar recompilándolo.
En este artículo vamos a crear un módulo muy simple (de hecho el más simple posible) y lo vamos a cargar en la máquina virtual Qemu que preparamos en el artículo anteriormente citado.



Una vez creado el módulo lo copiaremos desde nuestra máquina host a la máquina virtual usando scp. Por lo tanto, tenemos que asegurarnos de que nuestra máquina virtual tiene conectividad vía red con la máquina host. En mi caso, la configuración del kernel que compilamos en el anterior artículo tiene activado el soporte de la tarjeta de red Intel e1000 (la que emula Qemu) como módulo, pero en este caso, para facilitarnos la tarea nos va a interesar que el soporte a esta tarjeta esté compilado como parte integrante del kernel. Así que nos vamos a ir al directorio donde está el código fuente del kernel y nos vamos a asegurar de que el soporte para la tarjeta ethernet Intel e1000 está activado en el archivo .config (debe aparecer como CONFIG_E1000=y) y volvemos a compilar.

cd ~/kerneldev/src/linux-$(uname -r)
sed -i s/CONFIG_E1000=m/CONFIG_E1000=y/ .config
make -j4

También tenemos que asegurarnos de que tenemos instalado el servidor ssh en nuestra máquina host para poder copiar los ficheros de una máquina a otra.

sudo apt-get install openssh-server

Hechos los preparativos, podemos entrar en harina y comenzar a desarrollar nuestro módulo. Un módulo no es más que un programa escrito en C (aunque nada nos impide hacerlo directamente en ensamblador si nos sentimos fuertes) que cumple algunas condiciones. Veamos el código de nuestro primer módulo que pondremos en un archivos llamado hello_module.c y que estará en el directorio ~kerneldev/src/modulo1.

#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Modulo Hola mundo");
MODULE_AUTHOR("Alberto Garcia");

static int __init hello_init(void)
{
    printk(KERN_INFO "Hola mundo.\n");
    return (0);
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Hasta otra.\n");
}

module_init(hello_init);
module_exit(hello_exit);

De entrada nos encontramos con dos funciones de lo más normales salvo por que antes del nombre incluyen __init o __exit. Un módulo ha de tener al menos dos funciones: una que se invoca cuando se carga el módulo en memoria y que es la que lleva __init; y otra que es llamada cuando se descarga el módulo del kernel, y es la que lleva __exit. Esta función se encargará de limpiar y liberar todos los recursos que estemos usando. Si tienes curiosidad por saber como se definen __init y __exit puedes verlo en linux/init.h.
Las dos últimas líneas indican qué función es la que ha de llamarse al cargar y descargar el módulo usando module_init() y module_exit() respectivamente.
Las tres primeras líneas tras el #include son bastante autodescriptivas: en ellas indicamos el tipo de licencia del módulo (en este caso GPL), la descripción y el autor.
Otra función que no te resultará familiar es printk(). Recuerda que este código se ejecuta en el espacio del kernel y por lo tanto no tenemos acceso a la librería de C libc, es decir, no tenemos printf() así que tenemos que usar la función printk() que pertenece al código del kernel.

Para compilar el módulo vamos a usar el siguiente makefile.

ifneq ($(KERNELRELEASE),)
obj-m := hello_module.o 
else
KERNEL_VERSION = linux-3.5.0
KERNELDIR := ../$(KERNEL_VERSION)
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
rm -rf *.[oas] .*.flags *.ko .*.cmd .*.d .*.tmp *.mod.c \
.tmp_versions Module.symvers

Es importante poner la variable KERNEL_VERSION a su valor correcto. En mi caso particular el directorio donde está el código fuente de Linux es linux-3.5.0. Para compilar nuestro módulo sólo hemos de ejecutar la utilidad make y, si todo va bien, obtendremos varios archivos entre los que se encuentra el que vamos a usar nosotros, que es hello_module.ko.

Para probarlo iniciamos la máquina virtual Qemu que ya habíamos preparado en el artículo anterior con nuestro recién compilado kernel y una vez dentro copiamos el módulo hello_module.ko desde dentro de la máquina virtual.

scp alberto@10.0.2.2:kerneldev/src/modulo1/hello_module.ko .

La dirección IP 10.0.2.2 es la que Qemu le da por defecto a la máquina host, así que sólo tienes que cambiar alberto por el nombre el usuario de tu máquina host.
Una vez copiado, podemos cargar el módulo en memoria usando insmod:

insmod hello_module.ko

Si todo va bien verás aparecer la frase hola mundo. Para ver los módulos cargados en memoria usamos el comando lsmod. Finalmente, para descargar el módulo de memoria usamos el comando rmmod, que nos mostrará una frase de despedida.

rmmod hello_module


En este artículo llamado Linux Kernel Modules - Load, Unload, Configure puedes encontrar más información sobre la gestión de los módulos.
Por ahora nuestro módulo no es demasiado útil, pero nos ha permitido comprender todo el proceso de creación, carga y descarga. En próximos artículos iremos ampliándolo y empezaremos a hacer cosas más interesantes dentro de nuestro módulo.

Comentarios

  1. Buenas noches. Muchas gracias por esta info tan valiosa!!!
    webnegociosargentina@gmail.com

    ResponderEliminar

Publicar un comentario

Entradas populares de este blog

Creando firmas de virus para ClamAV

ClamAv es un antivirus opensource y multiplataforma creado por Tomasz Kojm muy utilizado en los servidores de correo Linux. Este antivirus es desarrollado por la comunidad, y su utilidad práctica depende de que su base de datos de firmas sea lo suficientemente grande y actualizado. Para ello es necesario que voluntarios contribuyan activamente aportando firmas. El presente artículo pretende describir de manera sencilla cómo crear firmas de virus para ClamAV y contribuir con ellas a la comunidad.

Manejo de grafos con NetworkX en Python

El aprendizaje computacional es un área de investigación que en los últimos años ha tenido un auge importante, sobre todo gracias al aprendizaje profundo (Deep Learning). Pero no todo son redes neuronales. Paralelamente a estas técnicas, más bien basadas en el aprendizaje de patrones, también hay un auge de otras técnicas, digamos, más basadas en el aprendizaje simbólico. Si echamos la vista algunos años atrás, podemos considerar que quizá, la promesa de la web semántica como gran base de conocimiento ha fracasado, pero no es tan así. Ha ido transmutándose y evolucionando hacia bases de conocimiento basadas en ontologías a partir de las cuales es posible obtener nuevo conocimiento. Es lo que llamamos razonamiento automático y empresas como Google ya lo utilizan para ofrecerte información adicional sobre tus búsquedas. Ellos lo llaman Grafos de Conocimiento o Knowledge Graphs . Gracias a estos grafos de conocimiento, Google puede ofrecerte información adicional sobre tu búsqueda, ad

Scripts en NMAP

Cuando pensamos en NMAP, pensamos en el escaneo de puertos de un host objetivo al que estamos relizando una prueba de intrusión, pero gracias a las posibilidades que nos ofrecen su Scripting Engine , NMAP es mucho más que eso. Antes de continuar, un aviso: algunas de posibilidades que nos ofrecen los scripts de NMAP son bastante intrusivas, por lo que recomiendo hacerlas contra hosts propios, máquinas virtuales como las de Metasploitable, o contrato de pentesting mediante. Para este artículo voy a usar las máquinas de Metasploitable3 . No voy a entrar en los detalles sobre el uso básico de NMAP, ya que hay miles de tutoriales en Internet que hablan sobre ello. Lo cierto es que NMAP tiene algunas opciones que permiten obtener información extra, además de qué puertos están abiertos y cuales no. Por ejemplo, la opción -sV trata de obtener el servicio concreto, e incluso la versión del servicio que está corriendo en cada puerto. Otro ejemplo es la opción -O, que intenta averiguar el