Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer

Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer

voorgeskiedenis

Dit het so gebeur dat die bediener deur 'n ransomware-virus aangeval is, wat deur 'n "gelukkige ongeluk" die .ibd-lêers (rou datalêers van innodb-tabelle) gedeeltelik onaangeraak gelaat het, maar terselfdertyd die .fpm-lêers heeltemal geënkripteer het ( struktuur lêers). In hierdie geval kan .idb verdeel word in:

  • onderhewig aan herstel deur standaard gereedskap en gidse. Vir sulke gevalle is daar 'n uitstekende word;
  • gedeeltelik geënkripteerde tabelle. Meestal is dit groot tabelle, waarvoor (soos ek verstaan) die aanvallers nie genoeg RAM gehad het vir volle enkripsie nie;
  • Wel, volledig geënkripteerde tabelle wat nie herstel kan word nie.

Dit was moontlik om te bepaal aan watter opsie die tabelle behoort deur dit bloot in enige teksredigeerder onder die verlangde enkodering oop te maak (in my geval is dit UTF8) en bloot die lêer te bekyk vir die teenwoordigheid van teksvelde, byvoorbeeld:

Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer

Ook, aan die begin van die lêer kan jy 'n groot aantal 0 grepe waarneem, en virusse wat die blokenkripsie-algoritme gebruik (die mees algemene) beïnvloed hulle gewoonlik ook.
Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer

In my geval het die aanvallers 'n string van 4 grepe (1, 0, 0, 0) aan die einde van elke geïnkripteer lêer gelaat, wat die taak vereenvoudig het. Om vir onbesmette lêers te soek, was die skrif genoeg:

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)

Dit het dus geblyk om lêers te vind wat aan die eerste tipe behoort. Die tweede behels baie handewerk, maar wat gevind is, was reeds genoeg. Alles sal goed wees, maar jy moet weet absoluut presiese struktuur en (natuurlik) het 'n geval ontstaan ​​dat ek met 'n gereeld wisselende tafel moes werk. Niemand het onthou of die veldtipe verander is of 'n nuwe kolom bygevoeg is nie.

Wilds City kon ongelukkig nie help met so 'n geval nie, en daarom word hierdie artikel geskryf.

Kom by die punt uit

Daar is 'n struktuur van 'n tabel van 3 maande gelede wat nie saamval met die huidige een nie (moontlik een veld, en moontlik meer). Tabelstruktuur:

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 hierdie geval moet jy onttrek:

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

Vir herstel word 'n greep-vir-greep-analise van die .ibd-lêer gebruik, gevolg deur dit om te skakel in 'n meer leesbare vorm. Aangesien ons net datatipes soos int en datatime hoef te ontleed om te vind wat ons nodig het, sal die artikel slegs hulle beskryf, maar soms sal ons ook na ander datatipes verwys, wat in ander soortgelyke voorvalle kan help.

Probleem 1: velde met tipes DATETIME en TEXT het NULL-waardes gehad, en hulle word eenvoudig in die lêer oorgeslaan, as gevolg hiervan was dit nie moontlik om die struktuur te bepaal om te herstel in my geval nie. In die nuwe kolomme was die verstekwaarde nul, en 'n deel van die transaksie kan verlore gaan as gevolg van die instelling innodb_flush_log_at_trx_commit = 0, dus sal ekstra tyd bestee moet word om die struktuur te bepaal.

Probleem 2: dit moet in ag geneem word dat rye wat via DELETE uitgevee is, almal in die ibd-lêer sal wees, maar met ALTER TABLE sal hul struktuur nie opgedateer word nie. As gevolg hiervan kan die datastruktuur van die begin van die lêer tot die einde daarvan verskil. As jy gereeld OPTIMIZE TABLE gebruik, is dit onwaarskynlik dat jy so 'n probleem sal teëkom.

Let wel, beïnvloed die DBMS-weergawe die manier waarop data gestoor word, en hierdie voorbeeld sal dalk nie vir ander hoofweergawes werk nie. In my geval is die Windows-weergawe van mariadb 10.1.24 gebruik. Ook, alhoewel jy in mariadb met InnoDB-tabelle werk, is dit in werklikheid XtraDB, wat die toepaslikheid van die metode met InnoDB mysql uitsluit.

Lêer analise

In python, data tipe grepe() vertoon Unicode-data in die plek van 'n gewone stel nommers. Alhoewel jy die lêer in hierdie vorm kan sien, kan jy gerieflikheidshalwe die grepe in numeriese vorm omskep deur die grepe-skikking om te skakel na 'n gewone skikking (lys(voorbeeld_greep_skikking)). Beide metodes is in elk geval geskik vir ontleding.

Nadat u deur verskeie ibd-lêers gekyk het, kan u die volgende vind:

Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer

Verder, as jy die lêer deur hierdie sleutelwoorde verdeel, sal jy meestal ewe blokke data kry. Ons sal infimum as 'n deler gebruik.

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

'n Interessante waarneming: vir tabelle met 'n klein hoeveelheid data, tussen infimum en supremum is daar 'n wyser na die aantal rye in die blok.

Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer — toetstabel met 1ste ry

Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer - toetstabel met 2 rye

Die ryskikkingstabel[0] kan oorgeslaan word. Nadat ek daardeur gekyk het, kon ek steeds nie die rou tabeldata vind nie. Heel waarskynlik word hierdie blok gebruik om indekse en sleutels te stoor.
Om met tabel[1] te begin en dit in 'n numeriese skikking te vertaal, kan jy reeds 'n paar patrone opmerk, naamlik:

Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer

Dit is int-waardes wat in 'n string gestoor word. Die eerste greep dui aan of die getal positief of negatief is. In my geval is alle getalle positief. Uit die oorblywende 3 grepe kan jy die getal bepaal deur die volgende funksie te gebruik. Skrip:

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

Byvoorbeeld, 128, 0, 0, 1 = 1Of 128, 0, 75, 108 = 19308.
Die tabel het 'n primêre sleutel met outo-inkrement gehad, en dit kan ook hier gevind word

Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer

Nadat die data van die toetstabelle vergelyk is, is dit aan die lig gebring dat die DATETIME-voorwerp uit 5 grepe bestaan ​​en met 153 begin het (wat heel waarskynlik jaarlikse intervalle aandui). Aangesien die DATTIME-reeks '1000-01-01' tot '9999-12-31' is, dink ek die aantal grepe kan verskil, maar in my geval val die data in die tydperk van 2016 tot 2019, so ons sal aanneem daardie 5 grepe genoeg.

Om die tyd sonder sekondes te bepaal, is die volgende funksies geskryf. Skrip:

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}

Dit was nie moontlik om 'n funksionele funksie vir die jaar en maand te skryf nie, so ek moes dit hack. Skrip:

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

Ek is seker dat as jy 'n hoeveelheid tyd spandeer, hierdie misverstand reggestel kan word.
Volgende, 'n funksie wat 'n datum-tyd-voorwerp van 'n string terugstuur. Skrip:

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)

Bestuur om gereeld herhaalde waardes van int, int, datetime, datetime op te spoor Herwinning van data van XtraDB-tabelle sonder 'n struktuurlêer met greep-vir-greep-analise van die ibd-lêer, dit lyk of dit is wat jy nodig het. Boonop word so 'n reeks nie twee keer per reël herhaal nie.

Deur 'n gereelde uitdrukking te gebruik, vind ons die nodige data:

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)

Let asseblief daarop dat wanneer u met hierdie uitdrukking soek, dit nie moontlik sal wees om NULL-waardes in die vereiste velde te bepaal nie, maar in my geval is dit nie krities nie. Dan gaan ons deur wat ons gevind het in 'n lus. Skrip:

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)

Eintlik is dit al, die data van die resultaatskikking is die data wat ons benodig. ###PS.###
Ek verstaan ​​dat hierdie metode nie geskik is vir almal nie, maar die hoofdoel van die artikel is om aksie te vra eerder as om al jou probleme op te los. Ek dink die mees korrekte oplossing sal wees om self die bronkode te begin bestudeer mariadb, maar as gevolg van beperkte tyd, het die huidige metode gelyk of dit die vinnigste was.

In sommige gevalle, nadat u die lêer ontleed het, sal u die benaderde struktuur kan bepaal en dit kan herstel met behulp van een van die standaardmetodes vanaf die skakels hierbo. Dit sal baie meer korrek wees en minder probleme veroorsaak.

Bron: will.com

Voeg 'n opmerking