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
-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.
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:
/.*/ {
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:
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
- 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
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