Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi.

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi.

Eelalugu

Juhtus nii, et server koges krüptoviiruse rünnakut, mis "õnneliku juhuse" tõttu jättis osaliselt puutumata .ibd failid (InnoDB tabelite toorandmed), kuid samal ajal krüptis täielikult .fpm failid (struktuurifailid). Samuti võib .idb jagada järgmistesse kategooriatesse:

  • taastatavad standardsete vahendite ja juhendite abil. Selliste olukordade jaoks on olemas suurepärane artikkel;
  • osaliselt krüptitud tabelid. Peamiselt on need suured tabelid, mille täitmiseks (kuidas ma aru sain) ei olnud kurjategijatel piisavalt mälu täielikuks krüptimiseks;
  • ja täielikult krüptitud tabelid, mida ei ole võimalik taastada.

Tabelite kuuluvuse määramine õnnestus lihtsalt, avades faili mis tahes tekstiredaktoris sobivas kodeeringus (minu puhul oli see UTF8) ja lihtsalt vaadates faili tekstiväljade olemasolu, näiteks:

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi.

Samuti on faili alguses nähtav suur hulk 0-baiti, ja viirused, mis kasutavad plokksalvestuse algoritmi (kõige levinum) tavaliselt neid samuti mõjutavad.
Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi.

Minu puhul jätsid kurjategijad iga krüpteeritud faili lõppu 4-byte'i rea (1, 0, 0, 0), mis lihtsustas olukorda. Terveid faile leida oli piisav ka skriptist:

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)

Nii suudeti leida esimesse tüüpi kuuluvad failid. Teine nõuab pikka käsitööd, kuid leitud oli juba piisavalt. Kõik oleks hästi, kuid on oluline teada absoluutselt täpset struktuuri ja (muidugi) juhtus selline olukord, et tuli töötada sageli muutuva tabeliga. Keegi ei mäletanud, kas välja tüüp muutus või tuli uus veerg.

Debri city ei suutnud kahjuks sellises olukorras aidata, seega on kirjutatud see artikkel.

Liigume asja juurde

Kolme kuu tagune tabeli struktuur ei ühildu praegusega (võib-olla mõne välja, võib-olla ka rohkem). Tabeli struktuur:

LOO KED UPON 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)
); 

samuti on vaja välja võtta:

  • id_point INT(11);
  • id_user INT(11);
  • date_start DATETIME;
  • date_finish DATETIME.

Taastamiseks kasutatakse byte-level analüüsi .ibd failist, millele järgneb nende tõlkimine loetavamaks versiooniks. Kuna otsimiseks on piisav analüüsida selliseid andmetüüpe nagu int ja datetime, kirjeldatakse artiklis ainult neid, kuid mõnikord viidan ka teistele andmetüüpidele, mis võivad aidata sarnastes olukordades.

Probleem 1: DATETIME ja TEXT tüüpide valdkondades olid NULL väärtused ning need jäeti failis lihtsalt vahele, mistõttu ei õnnestunud taastamise struktuuri määrata. Uutes veergudes oli vaikeväärtus null ning osa tehingust võis olla kadunud innodb_flush_log_at_trx_commit = 0 seadistuse tõttu, seega struktuuri määrateks kuluks veel aega.

Probleem 2: tuleks arvesse võtta, et DELETE kaudu kustutatud read jäävad ikkagi ibd faili, kuid ALTER TABLE korral nende struktuur ei uuene. Tulemuseks võib andmestruktuur varieeruda faili algusest kuni selle lõpuni. Kui kasutate sageli OPTIMIZE TABLE, siis tõenäoliselt ei puutu te sellise probleemiga kokku.

Pange tähele, võivad andmete salvestamise viis sõltuda DBMS-i versioonist ja see näide ei pruugi töötada teiste peamise versiooni puhul. Minu puhul kasutati Windowsi versiooni mariadb 10.1.24. Kuigi mariadb-s töötate InnoDB tabelitega, on need tegelikult XtraDB, mis välistab meetodi rakendamise InnoDB MySQL-i puhul.

Faili analüüs

Pythonis, andmetüübid bytes() kuvavad andmeid unicode's tavapärase numbrikomplekti asemel. Kuigi faili võib vaadata ka sellisel kujul, on mugavuse huvides võimalik konverteerida bait numbrilistele väärtustele, muutes baitide massiivi tavaliseks massiiviks (list(example_byte_array)). Igal juhul on analüüsiks mõlemad viisid kasulikud.

Vaadates mitut ibd faili, võib leida järgmist:

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi.

Kuid kui jagada faili nende märksõnade kaupa, saadakse valdavalt ühtlased andmeplokid. Kasutame infimum'it jagajana.

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

Huvitav tähelepanek: väikeste andmehulkade puhul on infimumi ja supremumi vahel joonte arvu näitaja plokis.

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi. — testtabel ühe real

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi. — testtabel kahe reaga

Stringi massiivi table[0] võib vahele jätta. Selle läbivaatamisel ei suutnud ma leida tooreid tabeli andmeid. Ilmselt teenib see plokk indeksite ja võtmete salvestamise eesmärki.
Alates table[1] ja selle muundamise numbrimassiiviks, on juba märgata mõningaid mustreid, nimelt:

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi.

Need on int väärtused, mis on salvestatud reas. Esimene bait näitab, kas number on positiivne või negatiivne. Minu puhul on kõik numbrid positiivsed. Ülejäänud 3 baiti saab arvu määramiseks kasutada järgmisi funktsioone. Skript:

def find_int(val: str):  # näide '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

Näiteks, 128, 0, 0, 1 = 1, või 128, 0, 75, 108 = 19308.
Tabelis oli primaarvõti automaatse kasvuga ja ka siin on see olemas.

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi.

Testitabelite andmete võrdlemisel selgus, et DATETIME objekt koosneb 5 baitist, mis algab numbriga 153 (tõenäoliselt tähistab see aastavahemike algust). Kuna DATETIME vahemik on '1000-01-01' kuni '9999-12-31', siis arvan, et baitide arv võib varieeruda, kuid minu puhul langeb andmete vahemik aastatesse 2016 kuni 2019, seega võime lugeda, et 5 baiti on piisavalt.

Kuna oli vaja määrata aega ilma sekunditeta, kirjutati järgmised funktsioonid. 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}

Aasta ja kuu jaoks ei õnnestunud mul kirjutada tõhusat funktsiooni, seega pidin need kõvasti koodima. 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

Olen kindel, et kulutades n aega, on ka see arusaamatus parandatav.
Järgmiseks funktsioon, mis tagastab datetime objekti stringist. 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)

Õnnestus tuvastada tihti korduvad väärtused int, int, datetime, datetime Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili byte-by-byte analüüsi., näib, et see on vajalik. Pealegi, selline järjestus ei kordu stringis kaks korda.

Kasutades regulaarset väljendit, leiame vajalikud andmed:

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)

Pange tähele, et selle väljendi järgi otsides ei suudeta määrata NULL väärtusi nõutud väljadest, kuid minu puhul pole see kriitiline. Pärast seda iteratsioonis töötleme leitud tulemusi. 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)

Olen justkui kõik, andmed massiivist result on need, mis meile vajalikud. ###PS.###
Ma mõistan, et selline lähenemine ei pruugi kõigile sobida, kuid artikli põhieesmärk on pigem inspiratsiooni pakkuda kui kõiki teie probleeme lahendada. Arvan, et kõige õigem lahendus oleks alustada algkoodi uurimisest. mariadb, kuid piiratud aja tõttu tundus praegune meetod kõige kiirem.

Mõnedes olukordades, analüüsides faili, suudate määrata ligikaudse struktuuri ja taastada ühe ülaltoodud standardmeetodite abil. See oleks palju õigem ja tekitaks vähem probleeme.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster