Die Implementierung von Hersteller "S"

Der einführende Beitrag wies bereits auf einige Ähnlichkeiten und Unterschiede in "unabhängigen" Implementierungen einer bestimmten Routine durch drei Hersteller hin. In diesem Artikel werde ich zwei der drei Implementierungen mit einander vergleichen. Und bei der Gelegenheit werde ich dann das Geheimnis um die Identität zumindest zweier Hersteller lüften.

Als Referenz dient mir die Implementierung von Microsoft im NTLDR Version 5.1.2600.2180 (XP SP2). Ich habe auch eine frühere Version (5.1.2600.1106) betrachtet, aber keine erwähnenswerten Unterschiede gefunden. Also werde ich mich fortan auf diese eine Version beschränken. Dies also ist Hersteller "M".

Hinter dem Hersteller "S" verbirgt sich Sandman, ein Open-Source Projekt von Nicolas Ruff und Matthieu Suiche. Die Autoren haben ihren Code unter Version 3 der GNU Public License erstmals am 26. Februar 2008 veröffentlicht.

Bei der fraglichen Routine handelt es sich um "XpressDecode". Sie wird für die Dekomprimierung der Hibernate-Datei benötigt. Aufgerufen wird sie dabei von "HbReadNextCompressedBlock". Sie bedient sich selbst schließlich eines im Original namenlosen Dekoders. Sandman nannte diese Funktion "Decode"; ich werde diese Bezeichnung übernehmen.

Nachfolgend ist noch einmal der Flußgraph aus dem letzten Beitrag abgebildet. Microsofts Code ist links zu finden. In der Mitte sehen Sie die Implementierung des Sandman-Projekts.

Implementierungen von Microsoft (links), dem Sandman-Projekt (mitte) und dem Hersteller X (rechts)

Der Graph deutet auf signifikante Unterschiede zwischen beiden Implementierungen hin. Ich entscheide mich deshalb, zuerst die originale Implementierung zu untersuchen und dann mit dem Code der Sandman-Programme zu vergleichen. Zusätzlich untersuche ich auch das Beispielprogramm hibinfo.exe aus dem Sandman-Paket in dieser Weise für eine spätere Untersuchung. Wenn nicht anders angegeben, dann beziehen sich Zeilennummern auf die Datei source/src/compression.c aus dem Sandman-Paket. Wenn Sie sich nicht für die trockenen und nur schwer verständlichen technischen Details interessieren, dann springen Sie bitte zur Zusammenfassung an das Ende dieses Beitrags.

Nun aber zu den Ergebnissen des Vergleichs:

Nr. 1: In der Datei source/include/compression.h deklariert das Sandman-Projekt die Struktur _DECODE_DATA. Sie ist 0x38 Bytes groß.

Microsofts Implementierung reserviert 0x3c Bytes auf dem Stack für lokale Variablen. Die als Differenz verbleibenden 4 Bytes werden jedoch nicht angesprochen. Ich folgere daraus, dass die Struktur eigentlich 0x3c Bytes groß ist. Das letzte DWORD ist dabei möglicherweise nur für die zukünftige Verwendung reserviert.

Nr. 2: Sandman initialisiert seine DecodeData Variable mit Null-Bytes,wie es gute Praxis ist (Zeile 72). Microsoft verzichtet hingegen auf diese Initialisierung.

Nr. 3: Sandman weist zunächst DecodeData.DataOffset einen Wert zu und führt dann Vergleiche und Verzweigungen aus. Bei Microsoft ist es genau anders herum: Zuerst werden Size1 und CompressedBlockSize getestet. Nur bei Ungleichheit der beiden Variablen erfolgt eine Zuweisung an DecodeData.DataOffset. Auf diese Weise lässt sich ab und zu die Zuweisung einsparen.

Nr. 4: Sandman enthält (in Zeile 79) einen kleinen Trick, der Size2 um 0x108 erhöht. Das ist gegenüber der Referenz allerdings etwas zu früh. Folglich gibt Zeile 83 einen falschen Wert an die aufrufende Funktion zurück.

Nr. 5: Im Programm folgt jetzt eine Reihe von Bedingungen. Zunächst ihre Implementierung im NTLDR in Pseudo-Code:

if (
      (Size1 < CompressedBlockSize) ||
      (CompressedBlockSize < 0) ||		
      (Size1 <= 8) ||					
      (CompressedBlockSize < 8)
   ) 
{
   return -1;
}

if ((Size1 > 0x10000) || (Size2 <= 0))
{ return Size2; }

Dieser Abschnitt wird in Sandman zu sehr vereinfacht. Bitte beachten Sie die unterschiedliche Reihenfolge der einzelnen Bedingungen.

if ((Size1 == CompressedBlockSize) || (Size1 > 0x10000) || (!Size2))
{
   return Size2;
}

if ( (Size1 < CompressedBlockSize) || (!CompressedBlockSize) || (Size1 < 8) || (CompressedBlockSize < 8) ) { return -1; }

Nr. 6: Abgesehen von der Reihenfolge wurden auch einige Verzweigungen durch das Sandman-Projekt falsch verstanden, zum Beispiel wurde

mov   esi, [ebp+Size2]
test  esi, esi   ; bitwise AND of esi with itself
jle   return_size2   ; jump if less or equal, Size2 <= 0

als (!Size2) interpretiert, was aber wie folgt hätte codiert werden müssen:

cmp   [ebp+Size2], 0
jnz   somewhere   ; jump if not zero, Size2 != 0
mov   eax, [ebp+Size2]   ; return Size2
jmp   done


Hiervon betroffen sind die folgenden Ausdrücke:

  1. (!Size2) anstatt (Size2 <= 0) in Zeile 81
  2. (!CompressedBlockSize) anstatt (CompressedBlockSize<0) in Zeile 86
  3. (Size1<8) anstatt (Size1<=8) in Zeile 86

Nr. 7: In einigen Ausdrücken der Sandman Implementierung fehlt der Term BlockEntry. Dies betrifft die Berechnungen von

  1. DecodeData.CompressedBlockSize1 in Zeile 33
  2. DecodeData.CompressedBlockSize3 in Zeile 34
  3. DecodeData.PageLimit in Zeile 38
  4. DecodeData.CompressedBlockSize2 in Zeile 39

Nr. 8: Schließlich unterscheidet sich noch einmal die Reihenfolge einiger Bedingungen. Zunächst Microsoft:

if (
      (DecodeData.Status == 0) ||
      (DecodeData.DecodedSize > DecodeData.Size2) ||
      (DecodeData.PageOffset > DecodeData.PageLimit)
   )
{
   return -1;
}

if (DecodeData.Size2 != DecodeData.BufferLimit) { return Size2; }

if (DecodeData.Status2 == 0) { return -1; }

return Size2;

Und jetzt Sandman:

if (
      (DecodeData.PageOffset > DecodeData.PageLimit) || 
      (DecodeData.Status == FALSE) ||
      (DecodeData.Status2 == FALSE) || 
      (DecodeData.DecodedSize > PtrToUlong(DecodeData.Size2))
   )
{
   return -1;
}

if (DecodeData.Size2 != DecodeData.BufferLimit) { return Size2; }

return Size2;

Die falsche Reihenfolge führt dazu, dass die Funktion in einem von 32 Fällen fälschlicherweise -1 anstatt Size2 zurückgibt. Ich weiß jedoch nicht, ob dieser Unterschied im Alltag von Bedeutung ist.

No. 9: Bitte beachten Sie auch, dass in der Implementierung von Sandman der letzte Vergleich überflüssig ist, da in jedem Fall Size2 zurückgegeben wird.

Zusammenfassung: Die untersuchte Implementierung von XpressDecode durch das Sandman-Projekt weicht an zahlreichen Stellen von Microsofts Code ab. Einige der Differenzen sind eher kosmetischer Natur, andere sind kapitale Fehler und werden höchstwahrscheinlich die korrekte Dekomprimierung des Datenstroms verhindern.

Was kann man daraus lernen? Die Qualität forensischer Software und Methoden sollte unbedingt durch unabhängige Überprüfungen gesichert werden. Zweitens scheinen sich Fehler und subtile Unterschiede in der oben genannten Form gut als "elektronischer Fingerabdruck" zu eignen. Wie groß ist die Wahrscheinlichkeit, dass sich genau diese genannten Unterschiede in einer von Sandman unabhängig entwickelten Software finden?

Archiv

Impressum

Dieses Blog ist ein Projekt von:
Andreas Schuster
Im Äuelchen 45
D-53177 Bonn
impressum@forensikblog.de

Copyright © 2005-2012 by
Andreas Schuster
Alle Rechte vorbehalten.
Powered by Movable Type 5.12