2021/01/05

Selección de los archivos rescatados de un disco de Apple

Resumen, una persona borró un disco con un montón de backups de la forma:

Backup-AAA-MM-DD

Antes le corrí photorec y recuperé más de dos millones de archivos, iba a borrar algunos y con suerte reducir a medio millón, que igual es mucho, me he apiadado de la persona y le voy a meter más cerebro y trabajo.

 

Debido al conflicto entre hacer un proceso generico y lo particular de la información de la persona, lo que voy a dejar acá registrado no sirve como procedimiento, sirve para que te inspire. Además, no sabés cuántas veces tuve que reeescribir todo esto debido a los cambios producidos por cada nuevo descubrimiento.

En el caso en particular de la Apple, hay una cantidad considerable de archivos de importancia forense pero irrelevantes para mi objetivo.

Cuando estás explorando, como no sabés muy bien a dónde vás, el orden no importa mucho, pero hay que tener en cuenta algunas optimizaciones:

Si vamos a buscar archivos duplicados, hay que calcular md5 de cada archivo, entonces es mejor descartar antes de calcular por que el cálculo de md5 para muchos archivos consume mucho tiempo. 

 

Acciones

 

Hay algunas acciones que hay que repetir tras ejecutar otra, por ejemplo la lista de archivos hay que rehacerla tras renombrar las carpetas con la fecha tentativa.

 

Obtener lista de archivos

 

find . -type f > 00_archivos.txt

 

Obtener e identificar extensiones


cat 00_archivos.txt | rev | \
  cut -d "." -f 1 | rev | sort | \
  uniq -c | sort -nr > extensions_full.txt 

Puede haber basura, en mi caso todos los archivos que no tienen extensión, se pude filtrar o más fácil editar extensions_full.txt

Armé tres blacklists de extensiones:

[system]

plist$
DS_Store$

[photorec]

[0-9]\+.jpg$

[local]

.*txt$
.java$
.h$
.c$
etc...


Podría hacer blacklists más precisas, pero no vale la pena, mil archivos más o menos cuando estás con medio millón no lo amerita, al menos al comienzo. 

 

Descartar extensiones

 

Para el análisis trabajamos sobre 00_archivos con las blacklists:


wc -l 00_archivos.txt
2327955

grep -v -f local_extension_blacklist.txt \
   -f photorec_extension_blacklist.txt \
   -f system_extension_blacklist.txt 00_archivos.txt  | wc -l

890914

 

Archivos y extensiones eliminables


plist

Sirven para guardar información de apliciones, son 400 mil, chau.

 

.DS_Store


Contiene metadata de las carpeta como iconitos y posiciones, 30 mil, chau

 

txt

 

La persona no usa archivo de texto plano y además hay un número desproporcionado, 700 mil. Miré unos veinte al azar y parecen fragmentos xml, chau.

Si la persona hubiera usado archivos txt, debería haber creado alguna regla, ya sea con grep o con yara [1][2] para selecionar.

 

t*.jpg 


Los archivos con nombre t* son thumbnails que photorec extrajo de otros archivos, lo cual está ok para el propósito original de la herramienta, otros 40 mil menos


Analizar la cantidad


Este fué el análisis que me hizo comprender que en realidad no habían carpetas originales, sólo agrupamientos de 500 archivos.


for DIR in *; do
  echo -n "$DIR : "
  ls -1 "$DIR" \
  | grep -v -f photorec_extension_blacklist.txt \
  |  wc -l
done

 

Detectar y eliminar repeticiones

 

Obtener los hashes de los archivos cuyas extensiones no están en las blacklists.

grep -v -f local_extension_blacklist.txt \
   -f photorec_extension_blacklist.txt \
   -f system_extension_blacklist.txt 00_archivos.txt \
  > 01_archivos.txt

cat 01_archivos.txt | while read FILE; do
   md5sum "$FILE";
done > 01_hashes.txt

900 mil hashes, cinco horas...

 

Lo que te queda es:

hash..hash  ./recup_dir.xxx/fxxxxxxx.ext

Esto lo ordena:

sort 01_hashes.txt > 01_hashes.sort.txt

Esto extrae los hashes, elimina y cuenta los repetidos y ordena las frecuencias obtenidas:

cut -b -33 01_hashes.sort.txt | uniq -c | sort -nr \
> 02_only.unique.hashes.txt

Si contamos la líneas, sabemos cuantos archivos quedarán al final:

wc -l 02_only.unique.hashes.txt
243172

Ya casi estamos, salvo que no sé si me alcanza el lugar, veamos cuál es la situación:

  • El disco a rescatar tiene casi 900 GB libres
  • El disco de rescate tiene 120 GB libres
  • El rescate mide 730 GB.

 

Escenarios


Hay varios caminos a tomar:

 

Proyección ingenua

 

Tenía más de dos millones de archivos, queda un décimo de archivos, el rescate en bruto mide 730 GB, con unos 80 GB me arreglo para el rescate neto.

Mi instinto me dice que los archivos más grandes no estan tan repetidos.

 

El camino sin retorno

 

Hago el rescate neto sobre el disco a rescatar.

Lo bueno es que ya queda el disco tal como lo voy a devolver.

Lo malo es que si quisiera repetir el proceso no puedo pues ya pisé una parte.

 

El camino del cobarde con plata

 

Como tengo otro disco más, me puedo dar el lujo de copiar a otro lado.

 

La apuesta ingeniosa

 

Si en lugar de copiar, elimino los repetidos, no hay problema de espacio. Pero si me equivoco o quisiera recuperar algo perdido, tendría que repetir el proceso desde cero.

 

La solución ingeniosa


Si en lugar de borrar o copiar, simplemente muevo las cosas, no hay problemas de espacio, no toco el disco a rescatar, no pierdo los duplicados, que de todos modos no los necesito.


Lo cual me lleva inexorablemente a la respuesta correcta: borrar los repetidos.

La pregunta ahora es por qué hice todos estos rodeos. Muy sencillo. Por un lado no está mal pensar un poco más allá de lo evidente. Y fundamentalmente, por que me quedé enviciado con la idea de que los repetidos me iban a servir para identificar las carpetas, cosa ya descartada, así que a borrar.

¿Cuál sería la regla para borrar?

  • Leer cada linea de 01_hashes_sort, que es (hash,ruta)
    • Si es la primera de una serie de repeticiones, saltear
    • Si no es la primera de una serie, buscar y eliminar la ruta.

Más cerca de la implementación:

  • Leer la primera línea
  • Tomar nota del hash
  • Leer cada línea
    • Si el nuevo hash coincide, eliminar ruta 
    • Si no, tomar nota del nuevo hash

Más cerca de la implemetanción en awk:

  • Decidir que el hash tiene un valor arbitrario imposible
  • Leer cada línea
    • Si el nuevo hash coincide, eliminar ruta
    • Si no, tomar nota del nuevo hash

En awk:

BEGIN { hash="" }

/.*/ {
  if ( $1 == hash ) {
    print "rm " $2 " # " $1;
  } else {
    hash = $1;
    print "# keep " $2 " " $1;
  }
}


Cuando tengo miedo de embarrarla, acostumbro genera la impresión de los comandos en lugar de la ejecución concreta.


awk -f run.awk < 01_hashes.sort.txt > job.sh

Y compruebo que parezca correcto:

grep "# keep " job.sh | wc -l
243172

Y coincide, vamos para adelante.

sh job.sh

No termina nunca, es increiblemente ineficiente, si lo tuviera que hacer otra vez, vería la manera de que cada rm reciba varios archivos a la vez. 


Si estás en el medio del borrado y te asalta la impaciencia, esto te dice el porcentaje de lo realizado, te dejo de ejercicio entenderlo:


echo $(( $(grep -n  -m 1 $(ps ax | grep "rm " | grep -ve grep | grep -o "rm .*" | cut -b 4-) job.sh | cut -d":" -f 1) * 100 / $( grep "rm " job.sh | wc -l  )   ))

 

Si soy muy sagaz quizás ya te diste cuenta del error que cometí, lo noté mientras esperaba que termine el borrado, el primer borrado... ¿ya te diste cuenta?

El análisis de los repetidos fue sobre los archivos que no están en las blacklists de extensiones. Falta borrar esos tambien. Esta vez cada rm recibe 100 archivos

echo -n "rm "
count=0;
grep -f local_extension_blacklist.txt \
     -f photorec_extension_blacklist.txt \
     -f system_extension_blacklist.txt \
     00_archivos.txt \
| while read FILE;  do
   count=$(( $count + 1 ))
   echo -n " $FILE "
   if [ $count -eq 100 ] ; then
     echo
     echo -n "rm "
     count=0;
   fi;
done > job2.sh


wc -l job2.sh
14370

Ese número por 100 más 647764 de los borrados duplicados da 2084764 y más 243172 nos queda 2327936, que se parece bastante al número total de archivos recuperados, 2327955, tiene 19 de diferencia que debe ser el número de archivos de la última línea de job2.sh


 

El final


Y el empujoncito final, agrupar.

Para que la persona pueda hacer algo con todo esto que quedó, generé esta estructura:

  • rescatado
    • imagenes
      • jpg
      • png
      • otras
    • documentos
      • pdf
      • doc
      • docx
      • otros 
    • otros

 

y asi..

El problema es que de algunos tipos de archivos hay hasta más de 10 mil ejemplares y no es buena idea trabajar con carpetas tan grandes, de hecho photorec las hace de 500. Hay que hacer un script tal que tome cada archivo y lo ponga en una carpeta según la jerarquía anterior y además los amontone en carpetas según un límite.


Lo primero es recuperar nuestra frecuencia de extensiones, igual que antes pero con distintos nombre:


find . -type f > 10_archivos.txt

cat 10_archivos.txt | rev | \
  cut -d "." -f 1 | rev | sort | \
  uniq -c | sort -nr > extensions_full.txt

Estos son algunos de mis números:

 

  52000 png
  21000 jpg
  19000 html
  12000 gz
  11000 xml
   8500 pdf
   3000 docx
   1700 gif
   1500 sqlite
   1200 zip
    700 mov
    200 wav
    100 mp3
 

Pensaba particionar pero me cansé, hice un listado para cada tipo (audio, video, comprimidos), con nombre "mover.txt" y luego generé un archivo de patrones:


cat mover.txt| while read EXT; do
   echo ".*\.$EXT$";
done > mover.pattern 

Esto da algo así como:

.*\.mov$
.*\.avi$
.*\.mp4$
.*\.mpg$
.*\.swf$
.*\.webm$

Luego ejecuté:

grep -f mover.pattern 10_archivos.txt | while read FILE; do
  mv "$FILE" rescate/video;
done

Y así para cada grupo.

Elegí algunas extensiones eliminarlas, por ejemplo sqlite. Usé algo parecido a lo anterior, pero

grep -f borrar.pattern 10_archivos.txt | xargs rm

 

Si se le hace inusable a la persona, me dá el disco de regreso y haré el particionamiento.

Para copiar al disco original que tiene hfs plus como filesystem, tuve que pasarme a otra máquina basada en ubuntu para poder instalar hfsprogs, kali no lo tiene a la vista.
 
sudo mount -t hfsplus -o remount,force,rw /media/xxx

Luego, el disco está con root como owner y  hay un usuario 99.99, así que tuve que copiar como root y luego:
 
chown -R 99.99

 

Lo que no fué

 

Esto lo menciono pues me gusta el razonamiento, pero al hacer el análisis de cantidades de archivos por carpeta con lo que pensaba que me ayudaría a descubrir cuáles eran de datos y cuáles del sistema, me encontré con que las carpetas de rescate sólo cumplen la función de no hacer una sola carpeta con millones de archivos, no hay rastro de las estructura, son sólo archivos sueltos.

 

Esperaba que hubieran carpetas que representaran a la misma carpeta pero siendo la misma a travéz del tiempo. De ser así:

  • Hay carpetas del sistema que no nos interesan.
  • Hay carpetas de datos que no nos interesan.

Si en esta instancia eliminara los repetidos, tendría esta situación:

  • carpeta 01 (yo no lo sé pero es la última versión)
    • archivo 01
    • archivo 02
    • archivo 03
  • carpeta 02 
    • archivo 01
    • archivo 02
    • archivo 03

Me resulta más útil poder asignar una fecha a cada carpeta utilizando la fecha más vieja que encuentre adentro pero menor a la fecha de rescate y luego usar las repeticiones para poder relacionar cuales carpetas son la misma.

 

for DIR in  recup_dir.*; do
  date=$( ls "$DIR" --full-time -l \
         | grep -ve 2021-01-01 \
         | head -2 | tail -1 \
         | sed 's/ \+/ /g' | cut -d" " -f 6 )
  if [ $date ] ; then
    echo mv "$DIR" "$DIR.$date"
    mv "$DIR" "$DIR.$date"
  else
    echo "NO DATE FOR $DIR"
  fi
done


Este es el script para revertir:

ls -d recup_dir* | grep "-" | while read DIR;  do
  DST=$(  echo "$DIR" | cut -d "." -f -2 )
  mv $DIR $DST
done


[1] https://seguridad-agile.blogspot.com/2019/11/identificando-versiones-de-programas.html

[2] https://seguridad-agile.blogspot.com/2019/03/jugando-con-los-rayos-1-obteniendo-el.html

 


No hay comentarios:

Publicar un comentario