Freitag, 2. Februar 2018

Restaurierung von kaputten TIFF-Dateien

(English version below)

Kaputtes TIFF, erste Analyse


Ein Kollege schickte uns dieser Tage eine TIFF-Datei, die sich nicht öffnen liess. ImageMagick meldete:

display-im6.q16: Can not read TIFF directory count. `TIFFFetchDirectory' @ error/tiff.c/TIFFErrors/564.
display-im6.q16: Failed to read directory at offset 27934990. `TIFFReadDirectory' @ error/tiff.c/TIFFErrors/564.

Das Tool tiffinfo gab diese Fehlermeldung zurück:

TIFFFetchDirectory: Can not read TIFF directory count.
TIFFReadDirectory: Failed to read directory at offset 27934990.

Ein Blick mit dem Hexeditor Okteta und aktiviertem TIFF-Profil (welches im Übrigen unter https://github.com/art1pirat/okteta_tiff zu finden ist) zeigt, dass das der Offset-Zeiger, der auf das erste ImageFileDirectory (IFD) verweisen sollte, eine Adresse außerhalb der Datei enthält:

Screenshot Okteta, TIFF mit defektem Verweis auf erstes IFD
Faktisch ist das TIFF damit kaputt. Doch bestimmte Eigenschaften dieses Dateiformates erlauben es, eine Restaurierung zu versuchen.

Nebeneinschub

Für eine gut lesbare Einführung in den Aufbau von TIFF-Dateien sei auf den Blogeintrag "baseline TIFF" verwiesen. In "baseline TIFF - Versuch einer Rekonstruktion" wird auf einige manuelle Plausibilitätsprüfungen eingegangen.

Einen kurzen Überblick liefert auch "nestor Thema: Das Dateiformat TIFF" (zu finden auf http://www.langzeitarchivierung.de/Subsites/nestor/DE/Publikationen/Thema/thema.html)

Finden von IFDs


TIFF bringt ein paar Eigenschaften mit, die den Versuch einer Restaurierung erleichtern. So müssen laut Spezifikation Offsets immer auf gerade Adressen verweisen. Damit halbiert sich schon einmal der Suchraum.

Desweiteren können wir annehmen, dass ein IFD mindestens 4 Tags (oft deutlich mehr) enthält, in der Regel Subfiletype (0x00fe), ImageWidth (0x0100), ImageLength (0x0101) und BitsPerSample (0x0102).

Da ein IFD nach den Tags als letzten Eintrag ein NextIFD Feld enthält, welches entweder auf 0 gesetzt ist oder auf ein weiteres IFD verweist, haben wir bereits einiges an wertvollen Hinweisen zusammen.

Auch die Tageinträge innerhalb des IFD selber folgen einer Struktur. Jeder Eintrag besteht aus 2 Bytes TagId, 2 Bytes FieldType, sowie 4 Bytes Count und 4 Bytes ValueOrOffset (sh. Tag-Aufbau, Artikel "baseline TIFF" auf http://art1pirat.blogspot.de).

In der TIFF-Spezifikation sind für FieldType 12 mögliche Werte definiert, die libtiff kennt 18 Werte. Wir können also für jedes angenommene Tag prüfen, ob die Werte im Bereich 1-18 liegen.

Neben diesen harten Kriterien könnten wir, falls die Notwendigkeit besteht, noch weitere hinzuziehen, zum Beispiel:

  • Prüfe, ob bestimmte Pflicht-Tags vorhanden sind
  • Prüfe, ob alle Tags, wie von der Spezifikation gefordert, aufsteigend sortiert sind und keine Dubletten enthalten
  • Prüfe, ob ValueOrOffset ein Offset sein könnte und damit auf eine gerade Adresse verweist

Sicherlich ließen sich noch weitere Kriterien finden, doch in der Praxis zeigt sich, dass die og. harten Kriterien in der Regel schon ausreichen.

Um die Suche nach diesen nicht händisch vornehmen zu müssen, besitzt das Tool fixit_tiff seit kurzem das Programm "find_potential_IFD_offsets".

Wenn man es mit:

$> ./find_potential_IFD_offsets test.tiff test.out.txt

aufruft, spuckt es in der Datei "test.out.txt" eine Liste von Adressen aus, die potentiell ein IFD sein könnten. Für unsere Datei lieferte es den Wert "0x0008", sprich: das IFD müsste an Adresse 8 anfangen.

Mit Okteta die Datei geladen und geändert, voila!, es sieht gut aus:

Screenshot Okteta, TIFF mit repariertem Verweis auf erstes IFD





Auch tiffinfo ist jetzt etwas glücklicher:


TIFFReadDirectory: Warning, Bogus "StripByteCounts" field, ignoring and calculating from imagelength.
TIFF Directory at offset 0x8 (8)
  Subfile Type: (0 = 0x0)
  Image Width: 4506 Image Length: 6101
  Resolution: 300, 300 pixels/inch
  Bits/Sample: 8
  Compression Scheme: None
  Photometric Interpretation: min-is-black
  FillOrder: msb-to-lsb
  Orientation: row 0 top, col 0 lhs
  Samples/Pixel: 1
  Rows/Strip: 6101
  Planar Configuration: single image plane
  Color Map: (present)
  Software: Quantum Process V 1.04.73


Und ImageMagick zeigt sich nun gnädiger:

Ansicht des TIFFs mit repariertem Offset auf IFD





Wie man sieht, ist noch nicht alles repariert, schliesslich meldet auch ImageMagick noch Probleme:

display-im6.q16: Bogus "StripByteCounts" field, ignoring and calculating from imagelength. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/912.
display-im6.q16: Read error on strip 4075; got 2706 bytes, expected 4506. `TIFFFillStrip' @ error/tiff.c/TIFFErrors/564.

Doch sollte vorliegend gezeigt werden, dass eine Restaurierung von kaputten TIFF-Dateien durchaus möglich ist.

---------------------------------------------------------------------

Broken TIFF, a first analysis


A colleague recently sent us a TIFF file that he couldn't open. ImageMagick reported:

display-im6.q16: Can not read TIFF directory count. `TIFFFetchDirectory' @ error/tiff.c/TIFFErrors/564.
display-im6.q16: Failed to read directory at offset 27934990. `TIFFReadDirectory' @ error/tiff.c/TIFFErrors/564.

The tool tiffinfo returned the following error:

TIFFFetchDirectory: Can not read TIFF directory count.
TIFFReadDirectory: Failed to read directory at offset 27934990.

A quick investigation in the Hex editor Okteta with the TIFF profile activated (to be found at https://github.com/art1pirat/okteta_tiff) revealed that the offset pointer, which should be pointing to the first ImageFileDirectory (IFD), points to an address that is beyond the end of the file:

screenshot Okteta, TIFF with defective pointer to the 1st IFD
Given that, the TIFF is de facto broken. However, we can leverage certain properties of this file format to try a restoration.

Side note

For a well-readable introduction into the structure of TIFF files, pleases refer to the blog post "baseline TIFF". The article "baseline TIFF - Versuch einer Rekonstruktion" describes some manual plausibility checks.

Another short overview is provided by "nestor Thema: Das Dateiformat TIFF" (to be found at http://www.langzeitarchivierung.de/Subsites/nestor/DE/Publikationen/Thema/thema.html)

Finding IFDs


TIFF comes with a few properties that facilitate restoration attempts. According to the specification, offsets must point to even addresses, which already cuts the search space in half.

Also, we can assume that an IFD contains at least four tags (often significantly more), usually Subfiletype (0x00fe), ImageWidth (0x0100), ImageLength (0x0101) and BitsPerSample (0x0102).

As an IFD's last entry after all the tags is a pointer to the NextIFD, which is either set to 0 or points to another IFD, we already have some useful hints to work with.

The tag entries  inside of the IFD follow a strict structure as well. Each entry consists of 2 Bytes TagId, 2 Bytes FieldType, 4 Bytes Count and 4 Bytes ValueOrOffset (also see Tag-Aufbau, Artikel "baseline TIFF" auf http://art1pirat.blogspot.de).

The TIFF specification defines 12 possible values for the FieldType, libtiff knows 18 values. Following that, we can check for each chunk of Bytes that might be a tag if the value is between 1 and 18.

Additionally, we could add some soft criteria to these hard criteria that we already have:

  • check if certain mandatory tags can be found
  • check if all tags are sorted in an ascending order and don't contain any duplicates as required by the specification
  • check is ValueOrOffset can be an actual offset by checking if it points to an even offset

We could think up even more criteria, but practical experience shows that the hard criteria are already sufficient for most of the cases.

In order to avoid having to search for potential IFDs in the files manually, the tool fixit_tiff now comes with the program "find_potential_IFD_offsets".

If it is invoked like:

$> ./find_potential_IFD_offsets test.tiff test.out.txt

it will spew out a list of addresses to the file "test.out.txt" that might potentially mark the beginning of an IFD. For the file from our colleague, it gave us only one value, which was "0x0008". In other words, the IFD should start at address 8.

Now load up the file in Okteta change the pointer to the first IFD right after the TIFF header to the correct address, et voila!, it looks good:

screenshot Okteta, TIFF with repaired pointer to 1st IFD





tiffinfo is now a little happier as well:


TIFFReadDirectory: Warning, Bogus "StripByteCounts" field, ignoring and calculating from imagelength.
TIFF Directory at offset 0x8 (8)
  Subfile Type: (0 = 0x0)
  Image Width: 4506 Image Length: 6101
  Resolution: 300, 300 pixels/inch
  Bits/Sample: 8
  Compression Scheme: None
  Photometric Interpretation: min-is-black
  FillOrder: msb-to-lsb
  Orientation: row 0 top, col 0 lhs
  Samples/Pixel: 1
  Rows/Strip: 6101
  Planar Configuration: single image plane
  Color Map: (present)
  Software: Quantum Process V 1.04.73


And even ImageMagick is now a little more gracious:

Ansicht des TIFFs mit repariertem Offset auf IFD





As you can see, not everything has been repaired yet, and ImageMagick is still reporting some problems:

display-im6.q16: Bogus "StripByteCounts" field, ignoring and calculating from imagelength. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/912.
display-im6.q16: Read error on strip 4075; got 2706 bytes, expected 4506. `TIFFFillStrip' @ error/tiff.c/TIFFErrors/564.

However, we were able to show that a restoration of broken TIFFs is indeed feasible, and even though some of the data is lost, we still can see a part of what has been a magazine scan.