Récupération de photos numériques

De Page Personnelle de Cédric Blancher.

English version


Comment j'ai récupéré 1,2Go de photos à 98% de réussite après un effacement malencontreux sur une partition EXT3...


Sommaire

[modifier] Avertissement

Cet article explicite la récupération de données (en l'occurence des photos numériques) effacées sur une partition Linux avec un système de fichiers de type EXT3 et les impacts particuliers que cela entraîne. D'autres systèmes de fichiers, comme FAT32 ou NTFS des systèmes Windows ne présentent pas ces particularités, ce qui rend la récupération nettement plus simple sur ces supports. Il existe de nombreux outils, commerciaux ou non, pour y parvenir. Je vous conseille en particulier photorec, dont il sera discuté plus loin dans cet article.

[modifier] La genèse : la grosse boulette

En vue d'une gravure de DVD imminente, j'ai stocké 1,2Go de photos de vacances dans /tmp. Idée saugrenue me direz-vous, mais il s'avère que c'était le seul endroit où il me restait assez de place, /var/tmp étant lui même pas mal occupé. Et puis pris par autre chose entre temps, j'ai éteint le PC, puis redémarré le lendemain... Et là, c'est le drame !

/tmp est nettoyé au démarrage, donc plus rien. Groumpf.

[modifier] L'état des lieux

  • Première constatation : il ne reste plus rien dans /tmp. Le répertoire burn contenant les photos n'est plus là...
  • Première action : faire une image complète de la partition pour arrêter le massacre et pouvoir travailler au calme.
~# dd if=/dev/hda8 of=tmp.img bs=4096
  • Première idée : me tourner vers les techniques et outils classiques de récupération sur EXT2.

[modifier] Premiers pas avec debugfs

C'est parti.

~# debugfs tmp.img
debugfs 1.38 (30-Jun-2005)
debugfs:  lsdel
 Inode  Owner  Mode    Size    Blocks   Time deleted
0 deleted inodes found.

Fichtre, ça commence mal.

debugfs:  ls -d
[...]
 140833  (3716) sva6l.tmp   <172129> (40) .exchange-sid
<219073> (16) v858323   <140833> (32) burn   <78244> (20) ati_blank
<172130> (20) orbit-root   <62596> (3604) gconfd-root
[...]

C'est déjà mieux. On voit le répertoire burn, dont l'inode est effacée. Allons jeter un oeil de plus près.

debugfs:  imap <140833>
Inode 140833 is part of block group 9
        located at block 294916, offset 0x0000
debugfs:  cd <140833>
debugfs:  ls
 140833  (12) .    2  (4084) ..
debugfs:  lsdel
 Inode  Owner  Mode    Size    Blocks   Time deleted
0 deleted inodes found.
debugfs:  ls -d
 140833  (12) .    2  (4084) ..

C'est l'impasse... Après ce répertoire, plus rien. Aucun lien, aucune inode marquée comme effacée...

[modifier] EXT3 vs. EXT2

Après quelques recherche, j'apprends que bien que très similaire, EXT3 n'est pas juste un EXT2 avec un journal. Parmi les différences se trouve en particulier le mécanisme d'effacement qui nous intéresse ici. En effet, là où EXT2 se contente de marquer les structures du fichiers (entrée de répertoire, inode et blocs) comme non allouées, EXT3 ajoute une passe durant laquelle il efface de l'inode la taille du fichier et les adresses des blocs précédemment alloués. Il n'est donc plus possible de retrouver ses petits à partir de l'inode...

Ce qui tend à expliquer que j'ai été capable de retrouver l'inode correspondant au répertoire burn, mais pas le reste, puisque les blocs alloués à cette inode qui contiendront les références vers ses sous-répertoires et leurs fichiers sont détruits. Les outils de récupération de données sur EXT2 comme e2undel ou recover par exemple ne fonctionneront tout simplement pas.

Un excellent article sur la question, d'où sont extraites les illustrations ci-dessus, est disponible sur Linux World Magazine.

[modifier] Début des opérations

Dans la mesure où les données que je veux récupérer sont à 99,9% des images au format JPEG et quelques vidéos MPEG, je me tourne vers une récupération par recherche de motifs. En essayant de repérer les marqueurs de début et de fin des fichiers, je devrais être capable de les reconstituer.

[modifier] Hypothèses de travail

La partition qui supporte /tmp n'est quasiment jamais utilisée au delà des 200Mo. Ceci me rend assez confiant dans le fait que les données (ou en tout cas un grosse majorité) a été écrite séquentiellement et pourra être trouvée en parcourant les blocs.

[modifier] Les images JPEG

Une image JPEG a un marqueur de début (SOI, Start Of Image) dont la valeur est 0xFFD8, et un marqueur de fin (EOI, End of image) dont la valeur est 0xFFD9. On pourra trouver de plus amples renseignements sur le net, ici par exemple.

Une recherche sur l'image de la partition avec hexedit permet de trouver assez rapidement des images qu'on peut facilement extraire.

~# hexedit tmp.img
[...]
023DAFF8   00 00 00 00  00 00 00 00  FF D8 FF E1  18 F9 45 78  69 66 00 00  49 49 2A 00  ..............Exif..II*.
023DB010   08 00 00 00  0B 00 0E 01  02 00 20 00  00 00 92 00  00 00 0F 01  02 00 05 00  .......... .............
023DB028   00 00 B2 00  00 00 10 01  02 00 08 00  00 00 B8 00  00 00 12 01  03 00 01 00  ........................
023DB040   00 00 01 00  00 00 1A 01  05 00 01 00  00 00 C0 00  00 00 1B 01  05 00 01 00  ........................
023DB058   00 00 C8 00  00 00 28 01  03 00 01 00  00 00 02 00  00 00 32 01  02 00 14 00  ......(...........2.....
023DB070   00 00 D0 00  00 00 13 02  03 00 01 00  00 00 02 00  00 00 69 87  04 00 01 00  ..................i.....
023DB088   00 00 00 01  00 00 A5 C4  07 00 1C 00  00 00 E4 00  00 00 A0 08  00 00 20 20  ......................
023DB0A0   20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20
023DB0B8   20 20 20 20  20 00 53 4F  4E 59 00 00  44 53 43 2D  50 31 30 00  48 00 00 00       .SONY..DSC-P10.H...
023DB0D0   01 00 00 00  48 00 00 00  01 00 00 00  32 30 30 35  3A 30 38 3A  30 35 20 30  ....H.......2005:08:05 0
023DB0E8   39 3A 33 32  3A 30 32 00  50 72 69 6E  74 49 4D 00  30 32 35 30  00 00 02 00  9:32:02.PrintIM.0250....
[...]
023DC870   53 7C 96 24  00 33 DB D2  A9 BB C5 21  5D 8D D9 9E  B8 A4 6C 05  38 E7 E9 4A  S|.$.3.....!].....l.8..J
023DC888   DD 09 EB 72  26 20 A8 F6  A6 ED 00 F4  19 A1 AD 4A  52 7C B6 1E  B2 3C 5D 14  ...r& .........JR|...<].
023DC8A0   B1 63 80 3D  69 8F 3B CB  F2 B8 2B B4  F2 B8 E4 1A  8F 66 AF CC  63 C9 1F 69  .c.=i.;...+......f..c..i
023DC8B8   7E A4 13 06  13 C5 B9 71  D4 F3 53 79  AC 54 A9 C6  3D 71 5A 5F  B8 DC 2E D9  ~......q..Sy.T..=qZ_....
023DC8D0   56 DD 8A BB  B7 5E C6 A5  79 B8 18 07  8A 52 8D DD  CB A7 34 A1  66 45 CB 0F  V....^..y....R....4.fE..
023DC8E8   98 0F CA 9E  93 CD 1F DD  72 47 A3 73  4E 51 4F 46  62 BB 9F FF  D9 FF DB 00  ........rG.sNQOFb.......
023DC900   84 00 01 01  01 01 01 01  01 01 01 01  01 01 01 01  01 02 02 01  01 01 01 03  ........................
023DC918   02 02 02 02  03 03 04 04  03 03 03 03  04 04 06 05  04 04 05 04  03 03 05 07  ........................

Évidemment, je pourrais extraire les images une à une en utilisant dd. Il suffit de repérer les index de début (0x023DAFF8) et de fin (0x023DC8E8) pour en extraire le décalage en nombre de blocs et la taille, toujours en nombre de blocs.

dd if=tmp.img of=toto.jpg bs=4096 skip=<decalage> count=<nombre_blocs>

Mais vu qu'il y a 1,2Go de données à récupérer, ça va prendre trop de temps. En outre, mes premiers essais sur des images relativement grosses (environ 2Mo) me rendent des fichiers corrompus. Je vais donc chercher des outils capables de faire tout cela pour moi.

[modifier] Les outils

Quelques recherches sur Google m'ont permis de trouver quelques outils que j'ai pu essayer :

Après de nombreux essais sur mon image, deux besoins se font sentir :

  • pouvoir sélectionner les types de fichiers à chercher, ce qui évite de parcourir le disque pour rien à chercher des .zip, .png, .doc et autres extensions qui ne m'intéressent pas
  • pouvoir conserver les fichiers corrompus de manière à les étudier et pouvoir travailler dessus

Parmi ces outils, photorec offre ces fonctionnalités et donne en outre de très bons résultats. Il me retrouve quelques 750 objets de type JPEG ou MPEG. Les vidéos sont nickel, c'est toujours ça de pris. Par contre, l'immense majorité des images sont corrompus : la plupart sont tronquées près du début, d'autres ressemblent à des puzzles, certaines ont une palette de couleur ridicule à faire pâlir un caméléon, etc. Manifestement, il y a un soucis.


[modifier] Plus loin

[modifier] Analyse des fichiers

Une analyse visuelle rapide des fichiers extraits me montre que des corruptions apparaissent dès que l'image dépasse les 50Ko. Si on en regarde le contenu avec un éditeur hexadécimal, on constate un bloc de données bizarre situé entre 0xC000 et 0xCFFF, soit 4096 octets de garbage, c'est à dire la taille d'un bloc. Or :

0xC000 = 49152
       = 12 * 4096

C'est le 13e bloc, c'est à dire le premier bloc d'indirection, qui vient s'intercaller entre les blocs de données et pourrir la photo.

Les outils que j'utilise semblent tous avoir été développés pour retrouver des images sur des systèmes de fichiers de type FAT qui ne présentent pas ce type de particularité. Ils ne tiennent donc pas compte du bloc d'indirection, l'incluant dans les données. La taille de la photo est donc supérieure à celle annoncée dans l'entête et l'interpréteur JPEG se retrouve face à des marqueurs inconnus.

[modifier] Suppression des blocs d'indirection

La suppression du bloc peut être faite à la main. Ça marche parfaitement, mais c'est long et fastidieux. Je donc décidé d'automatiser la tâche en utilisant dd. Basiquement, on va copier les 12 premiers blocs du fichier dans un autre, puis lui ajouter les autres blocs à partir du 14e.

for i in *.jpg; do
      dd if=$i of=${i/.jpg/}_new.jpg bs=4096 count=12;
      dd if=$i bs=4096 skip=13 >> ${i/.jpg/}_new.jpg;
done

[modifier] Résultats

Le résultat final est extrêmement satisfaisant. Sur les 750 objets récupérés, j'ai 9 vidéos qui ne sont en fait que des triplets de 3 vidéos. Sur les 740 photos qui restent, environ 690 sont impécables, dont certaines n'ont pas de rapport avec ma recherche, les autres présentent encore des défauts, qu'on peut diviser en trois catégories :

  • photos auxquelles il manque des données (conséquence d'une fragmentation)
  • photos composées de morceaux de plusieurs autres photos (conséquence d'une fragmentation)
  • des défauts similaires à ceux rencontrés en premier lieu

J'ai récupéré les images de la première catégorie qui n'avait que très peu de données manquantes en utilisant ImageMagick pour corriger l'entête JPEG, puis The Gimp pour supprimer les zones vides. Ceci m'a permis de récupérer une dizaine d'images.

Les images de la seconde catégorie semblent irrécupérables. Je n'ai pas trouver de moyen d'associer les blocs de données à d'autres images, en particulier celle de la première catégorie.

L'étude de cette troisième catégorie montre de nouveaux blocs d'indirection. Ce sont en effet les plus gros fichiers qui présentent ces défauts. Il y aurait donc un nouveau bloc à supprimer. À la main, ça ne donne pas grand chose.

[modifier] Améliorations de photorec

Ayant contacté Christophe Grenier l'auteur de photorec (qui fait partie de testdisk), je lui ai fait part de l'avancement de mes recherches et des problèmes que j'avais rencontré. Il a alors ajouté le support des systèmes de fichiers EXT2/EXT3 à son outil (merci beaucuop Christophe), ce qui me permet aujourd'hui de récupérer environ 700 images et 3 vidéos impeccables.

La version beta 5.9-WIP de photorec inclue cette fonctionnalité et offre donc les meilleurs résultats.

[modifier] Tri des photos

On obtient finalement une liste de fichiers numérotés par photorec dans l'ordre dans lequel il les a trouvés. Or on préfèrerait largement avoir un tri chronologique. Pour se faire, on va utiliser les méta-données EXIF du format JPEG pour récupérer la date à laquelle la photo a été prise. Pour y parvenir, j'utilise l'outil exiv2 qui va me permettre, entre autres, d'afficher les données.

~# exiv2 -p s 1.jpg
Filename        : 1.jpg
Filesize        : 1285238 Bytes
Camera make     : FUJIFILM
Camera model    : FinePix F601Z
Image timestamp : 2005:08:07 14:31:22
Image number    :
Exposure time   : 1/70 s
Aperture        : F3.5
Exposure bias   : 0
Flash           : No, auto
Flash bias      :
Focal length    : 6.1 mm
Subject distance:
ISO speed       : 200
Exposure mode   : Auto
Metering mode   : Matrix
Macro mode      : Off
Image quality   : NORMAL
Exif Resolution : 2736 x 1824
White balance   : Auto
Thumbnail       : JPEG, 9612 Bytes
Copyright       :
Exif comment    :

On en extrait la date (champ "Image timestamp") et on renomme le fichier en fonction de celle-ci.

[modifier] Solution simplissime pour cas simplissime

Si on suppose qu'on se trouve dans le répertoire contenant les images récupérées et qu'on va prendre le parti de les copier dans un autre répertoire (../final/ en l'occurence), on utilisera la commande suivante :

for i in ./*.jpg; do cp $i ../final/`exiv2 -p s $i | grep timestamp | awk '{ print $4 "-" $5 }'`.jpg; done

On obtient alors la liste des images dans l'ordre dans lequel elles ont été prises.

Dans le cas présent, mon archive contenait les photos de plusieurs personnes. Pour différencier les auteurs, je me suis basé sur le modèle de l'appareil photo (champ "Camera model"). Je lance donc, dans le répertoire contenant les photos classées au moyen de la commande précédente la commande :

for i in *.jpg; do mv $i `exiv2 -p s $i | grep model | awk '{ print $4 }'`-$i; done

Et le tour est joué. Chaque fichier a un schéma de nommage de type APPAREIL-DATE-HEURE.jpg et il ne me reste plus qu'à déplacer les photos dans un répertoire approprié en fonction du nom du fichier. En fait, il sera sûrement plus profitable de faire le tri par appareil en premier au cas où devrait faire face à deux photos provenant de deux appareils différents, mais possédant le même timestamp...

[modifier] Solution avancée pour cas plus compliqués

Jean-Cédric Chappelier, ayant lui aussi dû procéder à une récupération massive de photos effacées avec Photorec, m'a transmis un script améliorer permettant de gérer des conflits assez rares, mais cependant possibles comme des photos en rafale qui se retrouveraient toutes prises dans la même seconde...

trash=trash$$
unknown=unknown$$
mkdir $trash
mkdir $unknown
for file in f*.jpg; do
  newname=`exiv2 -p s $file | grep -a timestamp | awk '{ print $4 "-" $5 }' | sed 's/:/-/g'`
  if [ $? -eq 0 -a "x$newname" != "x" ]; then
    if [ ! -f ${newname}.jpg ]; then
      mv $file ${newname}.jpg
    else
      cmp $file ${newname}.jpg
      if [ $? -eq 0 ]; then
        echo "WARN: $file is a duplicate of ${newname}.jpg: moved to $trash"
        mv $file $trash
      else
        echo "ERR: ${newname}.jpg already exists but differs. $file unchanged"
      fi
    fi
  else
      echo "ERR: unable to get info from ${file}: moved to $unknown"
      mv $file $unknown
  fi
done

Je vous invite chaudement à consulter le récit de ses aventure. C'est en effet un exemple parfait de l'aide que peut apporter le logiciel libre dans des cas difficiles.

[modifier] Conclusion

La première leçon de cette mésaventure, c'est que contrairement à ce qu'on peut lire à droite et à gauche, récupérer des données sur EXT3 n'est pas chose simple. EXT3 n'efface pas ses fichiers comme EXT2 et les outils développés pour ce dernier système de fichier ne fonctionnent pas.

La seconde leçon, c'est que la recherche de marqueurs s'est révélée très efficace parce que j'ai eu de la chance. D'abord, je n'avais que deux type de fichiers très bien défini et caractéristiques à chercher (JPEG et MPEG). Ensuite, les données ont été écrites avec très peu de fragmentation, comme le montre le taux de récupération qui avoisine les 98% à chercher, dans des blocs contigüs consécutifs.

La dernière leçon, c'est le rappel que EXT2/EXT3 présentent ces blocs d'indirections, et qu'il faut en tenir en compte pour la reconstruction des fichiers.

Navigation
autres
Locations of visitors to this page

No software patents !

Valid XHTML 1.0 Transitional

Valid CSS 2.1