Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand

Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand

prehistorie

Het gebeurde zo dat de server werd aangevallen door een ransomware-virus, dat, door een ‘gelukkig ongeluk’, de .ibd-bestanden (onbewerkte gegevensbestanden van innodb-tabellen) gedeeltelijk onaangeroerd liet, maar tegelijkertijd de .fpm-bestanden volledig versleutelde ( structuurbestanden). In dit geval kan .idb worden onderverdeeld in:

  • onderhevig aan restauratie via standaardgereedschappen en handleidingen. Voor dergelijke gevallen is er een uitstekende worden;
  • gedeeltelijk gecodeerde tabellen. Meestal zijn dit grote tabellen, waarvoor (zoals ik begrijp) de aanvallers niet genoeg RAM hadden voor volledige codering;
  • Nou ja, volledig gecodeerde tabellen die niet kunnen worden hersteld.

Het was mogelijk om te bepalen tot welke optie de tabellen behoren door deze eenvoudigweg in een willekeurige teksteditor te openen onder de gewenste codering (in mijn geval is dit UTF8) en eenvoudigweg het bestand te bekijken op de aanwezigheid van tekstvelden, bijvoorbeeld:

Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand

Ook kun je aan het begin van het bestand een groot aantal 0-bytes waarnemen, en virussen die het blokversleutelingsalgoritme gebruiken (het meest voorkomende) hebben daar meestal ook invloed op.
Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand

In mijn geval lieten de aanvallers aan het einde van elk gecodeerd bestand een string van 4 bytes (1, 0, 0, 0) achter, wat de taak vereenvoudigde. Om naar niet-geïnfecteerde bestanden te zoeken, was het script voldoende:

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)

Het bleek dus bestanden te vinden die tot het eerste type behoorden. Bij het tweede gaat het om veel handwerk, maar wat er werd gevonden was al genoeg. Alles zou in orde zijn, maar je moet het weten absoluut nauwkeurige structuur en (uiteraard) deed zich het geval voor dat ik met een vaak verschoontafel moest werken. Niemand herinnerde zich of het veldtype was gewijzigd of dat er een nieuwe kolom was toegevoegd.

Wilds City kon helaas niet helpen met een dergelijk geval, daarom wordt dit artikel geschreven.

Dichter bij het punt

Er is een structuur van een tabel van 3 maanden geleden die niet samenvalt met de huidige (mogelijk één veld, en mogelijk meer). Tabelstructuur:

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 dit geval moet u het volgende extraheren:

  • id_point int(11);
  • id_user int(11);
  • date_start DATUM TIJD;
  • date_finish DATUM TIJD.

Voor herstel wordt een byte-voor-byte-analyse van het .ibd-bestand gebruikt, gevolgd door het converteren ervan naar een beter leesbare vorm. Omdat we om te vinden wat we nodig hebben alleen gegevenstypen zoals int en datatime hoeven te analyseren, zal het artikel alleen deze beschrijven, maar soms zullen we ook verwijzen naar andere gegevenstypen, die kunnen helpen bij andere soortgelijke incidenten.

Probleem 1: velden met de typen DATETIME en TEXT hadden NULL-waarden en deze worden eenvoudigweg overgeslagen in het bestand. Hierdoor was het in mijn geval niet mogelijk om te bepalen welke structuur moest worden hersteld. In de nieuwe kolommen was de standaardwaarde nul en kon een deel van de transactie verloren gaan vanwege de instelling innodb_flush_log_at_trx_commit = 0, waardoor er extra tijd zou moeten worden besteed om de structuur te bepalen.

Probleem 2: er moet rekening mee worden gehouden dat rijen die via DELETE zijn verwijderd allemaal in het ibd-bestand staan, maar met ALTER TABLE wordt hun structuur niet bijgewerkt. Als gevolg hiervan kan de datastructuur variëren vanaf het begin van het bestand tot het einde. Als u OPTIMIZE TABLE vaak gebruikt, is het onwaarschijnlijk dat u een dergelijk probleem tegenkomt.

Noot, heeft de DBMS-versie invloed op de manier waarop gegevens worden opgeslagen, en dit voorbeeld werkt mogelijk niet voor andere hoofdversies. In mijn geval werd de Windows-versie van mariadb 10.1.24 gebruikt. Hoewel je in mariadb met InnoDB-tabellen werkt, is dat in feite ook het geval XtraDB, wat de toepasbaarheid van de methode met InnoDB mysql uitsluit.

Bestandsanalyse

In Python: gegevenstype bytes() geeft Unicode-gegevens weer in plaats van een gewone reeks getallen. Hoewel u het bestand in deze vorm kunt bekijken, kunt u voor het gemak de bytes naar numerieke vorm converteren door de byte-array om te zetten in een gewone array (lijst (voorbeeld_byte_array)). In ieder geval zijn beide methoden geschikt voor analyse.

Nadat u verschillende ibd-bestanden heeft doorgenomen, kunt u het volgende vinden:

Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand

Als u het bestand bovendien opdeelt op basis van deze trefwoorden, krijgt u meestal gelijkmatige gegevensblokken. We gebruiken infimum als deler.

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

Een interessante observatie: voor tabellen met een kleine hoeveelheid gegevens is er tussen infimum en supremum een ​​verwijzing naar het aantal rijen in het blok.

Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand — testtafel met 1e rij

Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand - testtafel met 2 rijen

De rijarraytabel [0] kan worden overgeslagen. Nadat ik er doorheen had gekeken, kon ik de onbewerkte tabelgegevens nog steeds niet vinden. Hoogstwaarschijnlijk wordt dit blok gebruikt om indexen en sleutels op te slaan.
Beginnend met tabel[1] en deze vertalend naar een numerieke array, kun je al enkele patronen opmerken, namelijk:

Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand

Dit zijn int-waarden die in een string zijn opgeslagen. De eerste byte geeft aan of het getal positief of negatief is. In mijn geval zijn alle cijfers positief. Van de resterende 3 bytes kunt u het aantal bepalen met behulp van de volgende functie. Script:

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

Bijvoorbeeld 128, 0, 0, 1 = 1Of 128, 0, 75, 108 = 19308.
De tabel had een primaire sleutel met automatische ophoging, en deze is ook hier te vinden

Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand

Na vergelijking van de gegevens uit de testtabellen bleek dat het DATETIME-object uit 5 bytes bestaat en begon met 153 (hoogstwaarschijnlijk met vermelding van jaarlijkse intervallen). Omdat het DATTIME-bereik '1000-01-01' tot '9999-12-31' is, denk ik dat het aantal bytes kan variëren, maar in mijn geval vallen de gegevens in de periode van 2016 tot 2019, dus we gaan ervan uit dat 5 bytes genoeg.

Om de tijd zonder seconden te bepalen, zijn de volgende functies geschreven. Script:

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}

Het was niet mogelijk om een ​​functionele functie voor het jaar en de maand te schrijven, dus moest ik deze hacken. Script:

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

Ik ben er zeker van dat dit misverstand kan worden gecorrigeerd als u er een hoeveelheid tijd aan besteedt.
Vervolgens een functie die een datetime-object retourneert uit een string. Script:

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)

Beheerd om vaak herhaalde waarden van int, int, datetime, datetime te detecteren Gegevens uit XtraDB-tabellen herstellen zonder een structuurbestand met behulp van byte-voor-byte-analyse van het ibd-bestand, het lijkt erop dat dit is wat je nodig hebt. Bovendien wordt een dergelijke reeks niet tweemaal per regel herhaald.

Met behulp van een reguliere expressie vinden we de benodigde gegevens:

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)

Houd er rekening mee dat het bij het zoeken met deze expressie niet mogelijk is om NULL-waarden in de verplichte velden te bepalen, maar in mijn geval is dit niet cruciaal. Vervolgens doorlopen we wat we hebben gevonden in een lus. Script:

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)

Eigenlijk is dat alles, de gegevens uit de resultaatarray zijn de gegevens die we nodig hebben. ###PS.###
Ik begrijp dat deze methode niet voor iedereen geschikt is, maar het belangrijkste doel van het artikel is om actie te ondernemen in plaats van al uw problemen op te lossen. Ik denk dat de meest correcte oplossing zou zijn om zelf de broncode te gaan bestuderen MariaDB, maar vanwege de beperkte tijd leek de huidige methode de snelste.

In sommige gevallen kunt u na analyse van het bestand de structuur bij benadering bepalen en deze herstellen met behulp van een van de standaardmethoden uit de bovenstaande links. Dit zal veel correcter zijn en minder problemen veroorzaken.

Bron: www.habr.com

Voeg een reactie