Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei

Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei

Vorgeschichte

So kam es, dass der Server von einem Ransomware-Virus angegriffen wurde, der durch einen „glücklichen Zufall“ die .ibd-Dateien (Rohdatendateien der Innodb-Tabellen) teilweise unangetastet ließ, gleichzeitig aber die .fpm-Dateien vollständig verschlüsselte ( Strukturdateien). In diesem Fall könnte .idb unterteilt werden in:

  • Vorbehaltlich der Wiederherstellung mit Standardwerkzeugen und Anleitungen. Für solche Fälle gibt es ein hervorragendes werden;
  • teilweise verschlüsselte Tabellen. Meistens handelt es sich dabei um große Tabellen, für die die Angreifer (soweit ich weiß) nicht genügend RAM für eine vollständige Verschlüsselung hatten;
  • Nun, vollständig verschlüsselte Tabellen, die nicht wiederhergestellt werden können.

Es war möglich, zu bestimmen, zu welcher Option die Tabellen gehören, indem man sie einfach in einem beliebigen Texteditor mit der gewünschten Kodierung (in meinem Fall ist es UTF8) öffnete und die Datei einfach auf das Vorhandensein von Textfeldern überprüfte, zum Beispiel:

Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei

Außerdem können Sie am Anfang der Datei eine große Anzahl von 0 Bytes beobachten, und Viren, die den Blockverschlüsselungsalgorithmus verwenden (der am häufigsten vorkommende), befallen normalerweise auch diese.
Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei

In meinem Fall hinterließen die Angreifer am Ende jeder verschlüsselten Datei eine 4-Byte-Zeichenfolge (1, 0, 0, 0), was die Aufgabe vereinfachte. Um nach nicht infizierten Dateien zu suchen, reichte das Skript aus:

def opened(path):
    files = os.listdir(path)
    for f in files:
        if os.path.isfile(path + f):
            yield path + f

for full_path in opened("C:somepath"):
    file = open(full_path, "rb")
    last_string = ""
    for line in file:
        last_string = line
        file.close()
    if (last_string[len(last_string) -4:len(last_string)]) != (1, 0, 0, 0):
        print(full_path)

Es stellte sich also heraus, dass Dateien des ersten Typs gefunden wurden. Bei der zweiten ist viel Handarbeit nötig, aber was gefunden wurde, reichte bereits aus. Alles wäre gut, aber Sie müssen es wissen absolut präzise Struktur und (natürlich) kam es vor, dass ich mit einem häufig wechselnden Tisch arbeiten musste. Niemand erinnerte sich daran, ob der Feldtyp geändert oder eine neue Spalte hinzugefügt wurde.

Wilds City konnte in einem solchen Fall leider nicht helfen, weshalb dieser Artikel geschrieben wird.

Komm zum Punkt

Es gibt eine Struktur einer Tabelle von vor drei Monaten, die nicht mit der aktuellen übereinstimmt (möglicherweise ein Feld und möglicherweise mehrere). Tabellenstruktur:

CREATE TABLE `table_1` (
    `id` INT (11),
    `date` DATETIME ,
    `description` TEXT ,
    `id_point` INT (11),
    `id_user` INT (11),
    `date_start` DATETIME ,
    `date_finish` DATETIME ,
    `photo` INT (1),
    `id_client` INT (11),
    `status` INT (1),
    `lead__time` TIME ,
    `sendstatus` TINYINT (4)
); 

In diesem Fall müssen Sie Folgendes extrahieren:

  • id_point int(11);
  • id_user int(11);
  • date_start TERMINZEIT;
  • date_finish TERMINZEIT.

Zur Wiederherstellung wird eine Byte-für-Byte-Analyse der .ibd-Datei verwendet, gefolgt von deren Konvertierung in eine besser lesbare Form. Da wir, um zu finden, was wir brauchen, nur Datentypen wie int und datatime analysieren müssen, werden im Artikel nur diese beschrieben, aber manchmal werden wir auch auf andere Datentypen verweisen, die bei anderen ähnlichen Vorfällen hilfreich sein können.

Problem 1: Felder mit den Typen DATETIME und TEXT hatten NULL-Werte und werden in der Datei einfach übersprungen. Aus diesem Grund war es in meinem Fall nicht möglich, die wiederherzustellende Struktur zu bestimmen. In den neuen Spalten war der Standardwert Null und ein Teil der Transaktion könnte aufgrund der Einstellung innodb_flush_log_at_trx_commit = 0 verloren gehen, sodass zusätzliche Zeit für die Bestimmung der Struktur aufgewendet werden müsste.

Problem 2: Es ist zu berücksichtigen, dass über DELETE gelöschte Zeilen alle in der ibd-Datei vorhanden sind, ihre Struktur jedoch mit ALTER TABLE nicht aktualisiert wird. Dadurch kann die Datenstruktur vom Anfang bis zum Ende der Datei variieren. Wenn Sie OPTIMIZE TABLE häufig verwenden, ist es unwahrscheinlich, dass ein solches Problem auftritt.

Beachten, wirkt sich die DBMS-Version auf die Art und Weise aus, wie Daten gespeichert werden, und dieses Beispiel funktioniert möglicherweise nicht für andere Hauptversionen. In meinem Fall wurde die Windows-Version von Mariadb 10.1.24 verwendet. Auch wenn Sie in Mariadb mit InnoDB-Tabellen arbeiten, sind sie es tatsächlich XtraDB, was die Anwendbarkeit der Methode mit InnoDB mysql ausschließt.

Datenanalyse

In Python Datentyp Bytes() Zeigt Unicode-Daten anstelle eines regulären Zahlensatzes an. Obwohl Sie die Datei in dieser Form anzeigen können, können Sie der Einfachheit halber die Bytes in eine numerische Form umwandeln, indem Sie das Byte-Array in ein reguläres Array (list(example_byte_array)) umwandeln. Für die Analyse eignen sich in jedem Fall beide Methoden.

Nachdem Sie mehrere ibd-Dateien durchgesehen haben, können Sie Folgendes finden:

Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei

Wenn Sie die Datei außerdem nach diesen Schlüsselwörtern unterteilen, erhalten Sie größtenteils gleichmäßige Datenblöcke. Als Teiler verwenden wir das Infimum.

table = table.split("infimum".encode())

Eine interessante Beobachtung: Bei Tabellen mit einer kleinen Datenmenge gibt es zwischen Infimum und Supremum einen Zeiger auf die Anzahl der Zeilen im Block.

Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei — Testtisch mit 1. Reihe

Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei - Testtabelle mit 2 Reihen

Das Zeilenarray table[0] kann übersprungen werden. Nachdem ich es durchgesehen hatte, konnte ich die Rohdaten der Tabelle immer noch nicht finden. Höchstwahrscheinlich wird dieser Block zum Speichern von Indizes und Schlüsseln verwendet.
Wenn Sie mit Tabelle[1] beginnen und diese in ein numerisches Array übersetzen, können Sie bereits einige Muster erkennen, nämlich:

Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei

Dies sind int-Werte, die in einem String gespeichert sind. Das erste Byte gibt an, ob die Zahl positiv oder negativ ist. In meinem Fall sind alle Zahlen positiv. Aus den verbleibenden 3 Bytes können Sie die Anzahl mit der folgenden Funktion ermitteln. Skript:

def find_int(val: str):  # example '128, 1, 2, 3'
    val = [int(v) for v in  val.split(", ")]
    result_int = val[1]*256**2 + val[2]*256*1 + val[3]
    return result_int

Zum Beispiel kann die 128, 0, 0, 1 = 1Oder 128, 0, 75, 108 = 19308.
Die Tabelle hatte einen Primärschlüssel mit automatischer Inkrementierung und ist auch hier zu finden

Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei

Beim Vergleich der Daten aus den Testtabellen stellte sich heraus, dass das DATETIME-Objekt aus 5 Bytes besteht und mit 153 begann (was höchstwahrscheinlich auf jährliche Intervalle hinweist). Da der DATTIME-Bereich von „1000-01-01“ bis „9999-12-31“ reicht, denke ich, dass die Anzahl der Bytes variieren kann, aber in meinem Fall fallen die Daten in den Zeitraum von 2016 bis 2019, also gehen wir davon aus dass 5 Bytes genug sind.

Um die Zeit ohne Sekunden zu ermitteln, wurden die folgenden Funktionen geschrieben. Skript:

day_ = lambda x: x % 64 // 2  # {x,x,X,x,x }

def hour_(x1, x2):  # {x,x,X1,X2,x}
    if x1 % 2 == 0:
        return x2 // 16
    elif x1 % 2 == 1:
        return x2 // 16 + 16
    else:
        raise ValueError

min_ = lambda x1, x2: (x1 % 16) * 4 + (x2 // 64)  # {x,x,x,X1,X2}

Es war nicht möglich, eine funktionale Funktion für Jahr und Monat zu schreiben, also musste ich sie hacken. Skript:

ym_list = {'2016, 1': '153, 152, 64', '2016, 2': '153, 152, 128', 
           '2016, 3': '153, 152, 192', '2016, 4': '153, 153, 0',
           '2016, 5': '153, 153, 64', '2016, 6': '153, 153, 128', 
           '2016, 7': '153, 153, 192', '2016, 8': '153, 154, 0', 
           '2016, 9': '153, 154, 64', '2016, 10': '153, 154, 128', 
           '2016, 11': '153, 154, 192', '2016, 12': '153, 155, 0',
           '2017, 1': '153, 155, 128', '2017, 2': '153, 155, 192', 
           '2017, 3': '153, 156, 0', '2017, 4': '153, 156, 64',
           '2017, 5': '153, 156, 128', '2017, 6': '153, 156, 192',
           '2017, 7': '153, 157, 0', '2017, 8': '153, 157, 64',
           '2017, 9': '153, 157, 128', '2017, 10': '153, 157, 192', 
           '2017, 11': '153, 158, 0', '2017, 12': '153, 158, 64', 
           '2018, 1': '153, 158, 192', '2018, 2': '153, 159, 0',
           '2018, 3': '153, 159, 64', '2018, 4': '153, 159, 128', 
           '2018, 5': '153, 159, 192', '2018, 6': '153, 160, 0',
           '2018, 7': '153, 160, 64', '2018, 8': '153, 160, 128',
           '2018, 9': '153, 160, 192', '2018, 10': '153, 161, 0', 
           '2018, 11': '153, 161, 64', '2018, 12': '153, 161, 128',
           '2019, 1': '153, 162, 0', '2019, 2': '153, 162, 64', 
           '2019, 3': '153, 162, 128', '2019, 4': '153, 162, 192', 
           '2019, 5': '153, 163, 0', '2019, 6': '153, 163, 64',
           '2019, 7': '153, 163, 128', '2019, 8': '153, 163, 192',
           '2019, 9': '153, 164, 0', '2019, 10': '153, 164, 64', 
           '2019, 11': '153, 164, 128', '2019, 12': '153, 164, 192',
           '2020, 1': '153, 165, 64', '2020, 2': '153, 165, 128',
           '2020, 3': '153, 165, 192','2020, 4': '153, 166, 0', 
           '2020, 5': '153, 166, 64', '2020, 6': '153, 1, 128',
           '2020, 7': '153, 166, 192', '2020, 8': '153, 167, 0', 
           '2020, 9': '153, 167, 64','2020, 10': '153, 167, 128',
           '2020, 11': '153, 167, 192', '2020, 12': '153, 168, 0'}

def year_month(x1, x2):  # {x,X,X,x,x }

    for key, value in ym_list.items():
        key = [int(k) for k in key.replace("'", "").split(", ")]
        value = [int(v) for v in value.split(", ")]
        if x1 == value[1] and x2 // 64 == value[2] // 64:
            return key
    return 0, 0

Ich bin sicher, dass dieses Missverständnis korrigiert werden kann, wenn Sie sich viel Zeit nehmen.
Als nächstes eine Funktion, die ein Datetime-Objekt aus einer Zeichenfolge zurückgibt. Skript:

def find_data_time(val:str):
    val = [int(v) for v in val.split(", ")]
    day = day_(val[2])
    hour = hour_(val[2], val[3])
    minutes = min_(val[3], val[4])
    year, month = year_month(val[1], val[2])
    return datetime(year, month, day, hour, minutes)

Es ist gelungen, häufig wiederholte Werte aus int, int, datetime, datetime zu erkennen Wiederherstellen von Daten aus XtraDB-Tabellen ohne Strukturdatei mithilfe einer byteweisen Analyse der ibd-Datei, es sieht so aus, als wäre es das, was Sie brauchen. Darüber hinaus wird eine solche Sequenz nicht zweimal pro Zeile wiederholt.

Mit einem regulären Ausdruck finden wir die notwendigen Daten:

fined = re.findall(r'128, d*, d*, d*, 128, d*, d*, d*, 153, 1[6,5,4,3]d, d*, d*, d*, 153, 1[6,5,4,3]d, d*, d*, d*', int_array)

Bitte beachten Sie, dass bei der Suche mit diesem Ausdruck keine NULL-Werte in den Pflichtfeldern ermittelt werden können, dies ist in meinem Fall jedoch nicht kritisch. Dann gehen wir in einer Schleife durch, was wir gefunden haben. Skript:

result = []
for val in fined:
    pre_result = []
    bd_int  = re.findall(r"128, d*, d*, d*", val)
    bd_date= re.findall(r"(153, 1[6,5,4,3]d, d*, d*, d*)", val)
    for it in bd_int:
        pre_result.append(find_int(bd_int[it]))
    for bd in bd_date:
        pre_result.append(find_data_time(bd))
    result.append(pre_result)

Eigentlich ist das alles, die Daten aus dem Ergebnisarray sind die Daten, die wir brauchen. ###PS.###
Ich verstehe, dass diese Methode nicht für jeden geeignet ist, aber das Hauptziel des Artikels besteht darin, zum Handeln anzuregen und nicht alle Ihre Probleme zu lösen. Ich denke, die richtigste Lösung wäre, selbst mit dem Studium des Quellcodes zu beginnen Mariadb, aber aufgrund der begrenzten Zeit schien die aktuelle Methode die schnellste zu sein.

In einigen Fällen können Sie nach der Analyse der Datei die ungefähre Struktur ermitteln und sie mit einer der Standardmethoden über die obigen Links wiederherstellen. Dies wird viel korrekter sein und weniger Probleme verursachen.

Source: habr.com

Kommentar hinzufügen