RAW-Recovery mit Python 3 & Linux

22.07.2020, 13:08 - Autor: Jasmin Horvath

Wie speichern Festplatten Daten?

Um Daten auf einer Festplatte zu organiesieren benutzt man sogenannte Dateisysteme wie zB Ext2/3/4, XFS, BrtFS, NTFS, HFS+, ... Diese erstellen auf der Festplatte einen Kalalog der den Zusammenhang zwischen der Blockadresse der Daten auf der Festplatte und der logischen Strukturierung in Ordner, Dateinamen, etc. herstellt. Was wir also als Dateinamen, Ordnerstruktur, Dateiattribute, etc. kennen sind nur Metadaten die in besagtem Katalog abgelegt werden. Festplatten kennen hingegen nur die LBA (Logical Block Addressing) was im Grunde nichts anderes ist als ein durchnummerieren der Sektoren der alten Zylinder-Kopf-Sektor-Adressierung.

Blöcke (auch Cluster genannt) sind eine Gruppe von Sektoren die als eine Einheit zusammengefasst werden. Daten werden in diesem Blöcken gespeichert und können sowohl einen als auch mehrere Blöcke belegen. Sind die ganzen Blöcke einer Datei nicht hintereinander auf der Festplatte gespeichert dann spricht man von "Fragmentierung".

Wird der Katalog überschrieben oder ist nicht mehr zugänglich und auch nicht mit speziellen Tools wiederherstellbar dann bleibt nur ein sogenanntes RAW-Recovery bei dem wir die ganze Festplatte nach brauchbaren Daten durchsuchen...

Auf der Suche nach JPGs

Dateien beginnen mit sogenannten Signaturen - siehe: https://www.file-recovery.com/signatures.htm. Im Falle von JPG-Dateien ist das relativ einfach - wir müssen auf der Platte nur Blöcke oder Sektoren finden die mit den Hex-Werten 0xFF 0xD8 (SOI - start of image) beginnen und dann das nächste Vorkommen von 0xFF 0xD9 (EOI - end of image) finden und diesen Bereich als JPG-Datei abspeichern. Dazu machen wir uns die Eigenschaft von Linux zu nutze, dass alles eine Datei ist und wir so eine Festplatte wie jede andere Binärdatei lesen können wenn wir über root-Rechte verfügen. (Windows-User könnten dazu das wmi-Modul verwenden)

#!/usr/bin/python
import sys

if len(sys.argv) != 4 or "-h" in sys.argv:
    print("\nUSAGE: \n./" + sys.argv[0] + " [FILE] [BLOCK-SIZE] [OUTPUT-FOLDER]\n")
    quit()

file      = sys.argv[1]
bs        = int(sys.argv[2])
output_to = sys.argv[3]

found = False
ctr   = 0

with open(file, 'rb') as f:
    while True:
        block = f.read(bs)

        # End of drive / image
        if block == b'':
            break
        
        # Check for SOI-marker
        # see: https://de.wikipedia.org/wiki/JPEG_File_Interchange_Format
        if block[0] == 0xFF and block[1] == 0xD8:
            jpg_file = open(output_to + "/" + str(ctr) +  ".jpg", 'wb')
            found = True
            print("B" + str(ctr) + " ", end="")

        if found:
            # Check for EOI-marker
            for i in range(bs):
                # End of file
                if block[i] == 0xFF and i < bs - 1 and block[(i + 1)] == 0xD9:
                    found = False
                    # Append part of the block till end-marker
                    jpg_file.write(block[0:(i + 1)])
                    # Close Image-file
                    jpg_file.close()
                    print(". FOUND JPG")
                    break

        if found:
            # Append hole block
            jpg_file.write(block)
            print(".", end="")

        ctr += 1

Nachdem wir das sys-Modul importiert haben prüfen wir mit if len(sys.argv) != 4 or "-h" in sys.argv ob dem Script die nötigen Parameter übergeben wurden oder ob einer davon -h ist und falls ja, geben wir eine kurze Anleitung aus und beenden das Script mit quit().

Danach weisen wir den Variblen file, bs und output_to die entsprechenden Kommandozeilenargumente zu und setzen die Variable found, die später dazu dient zu steuern ob eine Ausgabe der Daten in eine Datei erfolgt, auf False sowie den Block-Counter (ctr) auf 0.

Mit with open(file, 'rb') as f öffnen wir die Geräte- oder Imagedatei zum lesen im binären Modus (rb) und starten mit while True eine Enloßschleife in der wir dann die Datei Block für Block je Schleifendurchlauf mit block = f.read(bs) einlesen. Gleich danach prüfen wir ob wir Daten erhalten (if block == b'') und beenden die Schleife mit break falls wir keine Daten mehr erhalten. Danach prüfen wir für jeden Block mit if block[0] == 0xFF and block[1] == 0xD8 ob der Block mit der gesuchten Signatur beginnt. Falls ja, öffnen wir eine Datei im binären Modus zum schreiben (jpg_file = open(output_to + "/" + str(ctr) + ".jpg", 'wb')), setzen die Variabe found auf True und geben eine Statusmeldung aus.

Im nächsten Code-Block (1. if found) durchsuchen wir den Block zeichenweise mit for i in range(bs) und prüfen ob darin die EOI-Markirung zu finden ist (if block[i] == 0xFF and i < bs - 1 and block[(i + 1)] == 0xD9). Falls ja, wird das found-Flag wieder auf False gestellt und mit jpg_file.write(block[0:(i + 1)]) der Teilbereich bis zur EOI-Markierung des Blockes in die Datei geschrieben, die Datei geschlossen und eine Erfolgsmeldung ausgegeben.

Da wir mit break nur eine Schleife abbrechen können ist das erneute Prüfen mit dem 2. if found der einfachste Weg das Schreiben des gesamten Block in die Datei zu überspringen falls zuvor eine EOI-Martierung gefunden wurde. (Gesagtes gilt natürlich auch für die continue-Anweisung)

Schließlich müssen wir nur noch den Counter erhöhen und schon ist unser einfaches RAW-Recovery Tool fertig.

Fazit

Natürlich wird die Ausbeute mit einer derart primitiven Suchmethode geringer sein - professionelle Recovery-Tools arbeiten mit smarteren Methoden wie zB SmartCarving oder GuidedCarving. Außerdem versuchen diese Tools aus partiell noch vorhandenen Daten des Katalogs ebenfalls noch Informationen über Fragmentierung, etc. zu gewinnen. Darüber hinaus greifen Sie auf bessere Prüfmethoden als nur die Signatur zurück.

Unsere Suche liefert nur Dateien, die nicht fragmentiert sind denn bei fragmentierten Daten werden sonstige Binärdaten mit in die Datei geschrieben bis eine neue SOI-Martkierung oder eine EOI-Markierung gefunden wird. So erhalten wir dann leider beschädigte bzw. unbrauchbare Bilddateien. Dennoch ist dies eine gute Übung um mit Binärdaten zu arbeiten und auch gleichzeitig noch etwas über die Art und Weise zu lernen wie unsere Daten auf der Festplatte abgelegt werden.