[traducción del artículo original que se encuentra en ésta página]

Todos lo hemos hecho antes: accidentalmente tecleas el argumento incorrecto al hacer rm o seleccionas el fichero incorrecto para su borrado, al pulsar enter te das cuenta de tu error y tu estómago da un vuelco. Y cuando vas a buscar la copia de seguridad del sistema no hay ninguna.

Hay muchas herramientas de desborrado para sistemas FAT y NTFS, pero hay muy pocas para EXT3, que es actualmente el sistema de fichero por defecto de muchas distribuciones Linux. Esto es debido al modo en que los ficheros EXT3 son eliminados: información crucial que almacena donde está localizado el contenido del fichero es eliminada durante el proceso de borrado.

En este artículo, echaremos un vistazo a bajo nivel de porqué la recuperación es dificil y a algunas aproximaciones que son efectivas algunas veces. Usaremos herramientas de código abierto para la recuperación, pero las técnicas no son completamente automáticas.

¿Qué es un fichero?

Antes de poder ver como recuperar ficheros, necesitamos ver como se almacenan. Típicamente, los sistemas de ficheros están localizados dentro de particiones de disco, que a su vez está organizada en sectores (usualmente de 512 bytes). Cuando la partición está formateada usando EXT3, los sectores consecutivos son agrupados en bloques, cuyo rango puede variar entre 1024 y 4096 bytes. Los bloques son agrupados juntos en grupos de bloques, cuyo tamaño será de decenas de miles de bloques. Cada ficheros tiene sus datos almacenados en tres ubicaciones principales: bloques, inodos y entradas de directorio. El contenido del fichero se almacena en bloques, que son dispuestos para el uso exclusivo de ese fichero, expandiéndose por tantos bloques como sea necesario. Idealmente, el fichero debería utilizar bloques consecutivos, pero esto no es siempre posible.

Los metadatos del fichero son almacenados en una estructura de inodo, que se encuentra en una tabla de inodos colocada en el inicio de un grupo de bloques. Hay un número finito de inodos y cada uno es asignado a un grupo de bloques. Los metadatos de un fichero incluyen los datos temporales tales como la última modificación, el último acceso, el último cambio y la fecha de borrado. También incluye el tamaño del fichero, el identificador de usuario y de grupo, los permisos, y las direcciones de los bloques donde se puede encontrar el contenido del fichero.

Las direcciones de los primeros 12 bloques son guardadas en el inodo y las direcciones adicionales son almacenadas externamente en bloques, llamados bloques indirectos. Si el fichero requiere muchos bloques y no todas las direcciones caben en un bloque indirecto, un doble bloque indirecto es usado cuya dirección se incluye en el inodo. El doble bloque indirecto contiene direcciones de bloques indirectos simples, que contienen direcciones de bloques con datos de fichero. Hay incluso direcciones indirectas triples en el inodo que añaden una capa más de punteros.

Por último, el nombre del fichero está almacenado en una estructura de entrada de directorio ubicada en un bloque asignado al directorio padre del fichero. Un directorio EXT3 es parecido a un fichero y sus bloques contienen una lista de estructuras de entradas de directorio, cada una conteniendo el nombre de un fichero y las dirección del inodo en el que se almacenan los metadatos del fichero. Con el comando ls -i puedes ver la dirección del inodo que corresponde a cada nombre de fichero. Se puede ver la relación entre una entrada de directorio, el inodo y los bloques en la Figura 1.

Cuando un nuevo fichero es creado, el sistema operativo (SO) debe elegir que bloques e inodo asignará al fichero. Linux intentará asignar los bloques y el inodo en el mismo grupo de bloques que su directorio padre. Esto provoca que los ficheros en un mismo directorio estén mucho más cercanos. Posteriormente usaremos este hecho para restringir donde buscar datos eliminados.

El sistema de ficheros EXT3 tiene un journal (nota del traductor: me permito aquí mantener el término en inglés por comodidad en lugar de usar bitácora o diario de registro, ya que cualquiera de los dos queda algo raro) que registra las actualizaciones de los metadatos del sistea ANTES de que la actualización ocurra. En caso de una caída del sistema, el SO lee el journal y podrá reprocesar o deshacer las transacciones registradas para que la recuperación sea mucho más rápida que examinar cada estructura de metadatos, que era el antiguo y lentísimo mecanismo. Las estructuras de metadatos de ejemplo incluyen las entradas de directorio que almacenan nombres de fichero e inodos que almacenan los metadatos del fichero. El journal contiene el bloque entero que está siendo actualizado, no solo el valor que está cambiando. Cuando un nuevo fichero es creato, el journa debería contener la versión actualizada de los bloques que contienen la entrada del directorio y el inodo.

El proceso de borrado

Varias cosas ocurren cuando un fichero es borrado de un sistema EXT3 en Linux. Hay que tener en cuenta que el SO debe decidir exactamente que ocurre cuando un fichero es eliminado y éste artículo asume un sistema Linux general.

Como mínimo, el SO debe marcar cada uno de los bloques, el inodo y la entrada de directorio como no-asignados para que ficheros posteriores puedan usarlos. Esta mínima aproximación es lo que ocurría hace años con los sistemas EXT2. En ese caso, el proceso de recuperación era relativamente simple ya que el inodo aún contenía las direcciones de los bloques del contenido del fichero y herramientas tales como debugfs y e2undel podían re-crear el fichero fácilmente. Esto funcionaba mientras que los bloques no habían sido asignados a un nuevo fichero y el contenido original no había sido sobreescrito.

Con EXT3, hay un paso adicional que hace la recuperación mucho más difícil: cuando los bloques son desasignados, el tamaño del fichero y las direcciones de los bloques en el inodo son limpiados; por tanto ya no podemos determinar donde estaba el contenido del fichero. Podemos ver la relación entre la entrada de directorio, el inodo y los bloques de un fichero desasignado en la Figura 2.

Aproximaciones de recuperación

Ahora que conocemos los componente involucrados con los ficheros y cuales son limpiados durante el borrado, podemos examinar dos aproximaciones a la recuperación de ficheros (aparte de usar un backup). La primera aproximación usa el tipo de aplicación del fichero eliminado y la segunda aproximación usa los datos en el journal. Independientemente de la aproximación, debería dejar de usar el sistema de ficheros porque podría crear un fichero que sobreescriba los datos que está tratando de recuperar, puede detener su sistema y poner el disco en otro ordenador con un sistema Linux como un disco esclavo (slave) o arrancar desde un Linux LiveCD (knoppix o necromantux son buenas distribuciones para este tipo de tareas).

El primer paso para ambas técnicas es determinar la dirección del inodo del fichero eliminado. Esto puede ser determinado usando debugfs o The Sleuth Kit (TSK). Mostraré aquí el método usando debugfs ya que esta herramiento se incluye en muchas distribuciones Linux y es un depurador del sistema de ficheros. Para empezar con debugfs, debe saber el nombre del dispositivo de la partición que contiene el fichero eliminado. En mi ejemplo, he iniciado desde un LiveCD y el fichero está ubicado en /dev/hda5:

 # debugfs /dev/hda5

 debugfs 1.37 (21-Mar-2005)
 debugfs: 

Podemos usar el comando cd para cambiar al directorio del fichero eliminado:

 debugfs:  cd /home/carrier/ 

El comando ls -d listará los ficheros asignados y eliminados del directorio. Recuerde que la estructura de entrada de directorio almacena el nombre e inodo de los ficheros y este listado nos dará ambos valores ya que ninguno es eliminado durante el proceso de borrado. Los ficheros eliminados tendrán su dirección de inodo delimitado por «<» y «>»:

 debugfs: ls -d

 415848  (12) .    376097  (12) ..    415864  (16) .bashrc 
[...]

 <415926> (28 ) oops.dat 
(Nota: esta ultima linea deberia poner un 28 entre parentesis sin espacios dentro, pero aqui hay un espacio detras del 8 para evitar caritas sonrientes)

El fichero que estamos intentando recuperar es /home/carrier/oops.dat y podemos verlo previamente asociado al inodo 415,926. El «(28)» nos indica la longitud de la estructura de entrada de directorio, pero eso no nos interesa.

Recuperación por «esculpido» de ficheros (file carving)

La primera técnica de recuperación, llamada file carving (nota del traductor: mantendré el nombre en inglés), utiliza las firmas del fichero eliminado. Muchos tipos de ficheros tienen valores estándar en los primeros bytes de la cabecera del fichero, y ésta técnica de recuperación busca los valores de cabecera del fichero eliminado para determinar donde puede que empezara el fichero. Por ejemplo, los ficheros JPEG empiezan con 0xFFD8 y terminan con 0xFFD9. Para recuperar un fichero JPEG eliminado, deberíamos buscar en los dos primeros bytes de cada bloque hasta encontrarnos con la marca OxFFD8. Cuando encontrásemos ese bloque, deberíamos buscar un bloque con los bytes 0xFFD9 en él. Los datos intermedios se asume que era el fichero. Desafortunadamente, no todos los tipos de ficheros tienen una firma final, por tanto determinar el final es dificil. Un ejemplo de una herramiento de código abierto que hace file carving es foremost, también existen varias herramientas comerciales.

Podemos ejecutar una herramienta como foremost en un sistema de ficheros completo, pero acabaríamos probablemente con demasiados ficheros, incluyendo algunos ya asignados. Será preferible lanzarlo sobre cuantos menos datos sea posible. La primera manera en que podemos restringir el tamaño de los datos es examinar sólo los grupos de bloques donde el fichero estaba ubicado. Recuerde que los inodos y los bloques de un fichero están asociados a un mismo grupo de bloques, si hay espacio. En nuestro caso, sabemos que inodo usaba el fichero y por tanto podemos examinar sólo los bloques del mismo grupo. El comando imap en debugfs nos indicará a que grupo de bloques pertenece un inodo:

 debugfs:  imap <415926> Inode 415926 is part of block group 25

 located at block 819426, offset 0x0a80 

La salida del comando fsstat en TSK nos dirá también lo siguiente:

 # fsstat /dev/hda5 [...] Group: 25: Inode Range: 408801 - 425152 Block Range: 819200 - 851967

A continuación, necesitamos determinar los bloques que están en el grupo de bloques del fichero eliminado. Podemos verlos en la salida del comando fsstat mostrado anteriormente, pero si estuvieramos usando debugfs, necesitamos calcular el rango. El comando stats nos da el número de bloques en cada grupo:

 debugfs: stats [...] Blocks per group: 32768 [...]

Puesto que estamos buscando en el grupo de bloques 25, el rango de bloques va desde 819,200 ( 25 * 32,768 ) hasta el 851,967 ( 26 * 32,768 – 1 ). Si nos centramos sólo en estos bloques, estaremos mirando en 128Mb en lugar de en el sistema de ficheros completo. Aún así, si no podemos encontrar el fichero en estos bloques, necesitaremos mirar en todo el sistema de ficheros.

El siguiente paso para reducir los datos a analizar es extraer los bloques no asignados del sistema de ficheros ya que ahí es donde nuestro fichero eliminado estará. debugfs no nos permite actualmente extraer el espacio no asignado de sólo un grupo de bloques específico, por tanto deberemos usar la herramienta dls de TSK.

 # dls /dev/hda5 819200-851867 > /mnt/unalloc.dat 

El comando anterior nos guardará los bloques no asignados en el grupo de bloqes 25 en un fichero llamado /mnt/unalloc.dat . Asegurese que este fichero esté en un sistema de ficheros diferente ya que de otro modo podría acabar sobreescribiendo su fichero eliminado.

Ahora podemos ejecutar la herramienta foremost sobre los datos no asignados. foremost puede recuperar sólo tipos de ficheros para los que se haya configurado. Si foremost no tiene la firma de cabecera para el tipo del fichero eliminado, deberá examinar algunos ficheros similar y personalizar el fichero de configuración. Podemos ejecutarlo del siguiente modo:

 # foremost -d -i /mnt/unalloc.dat -o /mnt/output/ 

La opción -d intentará detectar que bloques son indirectos y no los incluirá en el fichero final. El directorio /mnt/output contendrá los ficheros que hayan podido ser recuperados. Si su fichero no está ahí, puede expandir su búsqueda a todos los bloques no asignados en el sistema de ficheros en lugar de sólo a los bloques del grupo.

Recuperación basada en journal

El segundo método para tratar de recuperar ficheros es usar el journal. Ya hemos visto que las actualizaciones de inodos se guardan primero en el journal, pero el concepto importante es que el bloque entero en el que el inodo está ubicado es guardado en el journal. Por tanto, cuando un inodo es actualizado, el journal contendrá copias de otros inodos almacenados en el mismo bloque. Versiones anteriores del inodo de nuestro fichero elimiando pueden existir en el journal porque otro fichero fuera actualizado antes del borrado.

La maner más fácil de buscar versiones anteriores del inodo es usando el comando logdump -i en debugfs:

 debugfs:  logdump -i <415926>
 Inode 415926 is at group 25, block 819426, offset 2688
 Journal starts at block 1, transaction 104588
 FS block 819426 logged at sequence 104940, journal block 2687
 (inode block for inode 415926):
 Inode: 415926   Type: regular        Mode:  0664   Flags: 0x0
 User:   500   Group:   500   Size: 2048000
 [...]
 Blocks:  (0+12): 843274 (IND): 843286
 [...] 

En este caso, podemos encontrar una copia previa del inodo y los bloques del contenido del fichero están listados en la última línea. La última línea muestra que el primer bloque del fichero es 843,274 y los siguientes 12 bloques en el sistema de ficheros son los siguientes 12 bloques del fichero. El fichero es grande y necesita un bloque indirecto, que está localizado en el bloque 843,286. Hasta aquí, todos los bloques son consecutivos y no había fragmentación. El bloque 843,286 contiene el resto de direcciones de bloques, así que podríamos buscar en una versión anterior para descubrir donde está ubicado el resto del fichero. Podemos ver si hay una copia en el journal usando logdump -b:

 debugfs:  logdump -b 843286 -c 

Desafortunadamente, no encontramos una copia del bloque que contiene la lista original de punteros de bloque así que, si queremos recuperar el fichero deberemos asumir que el resto del contenido del fichero está guardado en el bloque 843,287 y siguientes. Una aproximación más avanzada podría también considerar qué bloques están actualmente asignados y saltarnoslos. Los datos pueden ser extraidos con herramientas tales como dd o Linux Disk Editor. El journal puede ser consultado también usando las herramientas jls y jcat de TSK.

Conclusión

La recuperación de ficheros con EXT3 no es una tema trivial, lo que refuerza el concepto de la importancia de hacer copias de seguridad de los ficheros. Si el fichero no estaba fragmentado, la búsqueda de su firma de cabecera puede ser útil, pero la herramienta necesita saber ignorar los bloques indirectos y donde parar de copiar (no todos los ficheros tienen una marca de final de fichero). Restringiendo la búsqueda al grupo de bloques local puede ahorrar mucho tiempo. El journal puede ser útil si los ficheros cerca del fichero eliminado han sido reciéntemente actualizados y por tanto existe una versión previa del inodo, pero esto no está siempre garantizado y el bloque indirecto del fichero puede no existir.