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.