Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi

eelajalugu

Juhtus nii, et serverit ründas lunavaraviirus, mis "õnneliku õnnetuse" tõttu jättis osaliselt .ibd-failid (innodb-tabelite töötlemata andmefailid) puutumata, kuid samal ajal krüpteeris täielikult .fpm-failid ( struktuurifailid). Sel juhul võib .idb jagada järgmisteks osadeks:

  • Taastatakse standardsete tööriistade ja juhendite abil. Sellistel juhtudel on suurepärane muutuda;
  • osaliselt krüptitud tabelid. Enamasti on need suured tabelid, mille jaoks (nagu ma aru saan) ei olnud ründajatel täielikuks krüptimiseks piisavalt RAM-i;
  • Noh, täiesti krüptitud tabelid, mida ei saa taastada.

Tabelite hulka kuuluvaid valikuid oli võimalik kindlaks teha, avades selle lihtsalt mis tahes tekstiredaktoris soovitud kodeeringu all (minu puhul on see UTF8) ja lihtsalt vaadates faili tekstiväljade olemasolu kohta, näiteks:

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi

Samuti võib faili alguses jälgida suurt hulka 0 baiti ja plokkrüpteerimisalgoritmi kasutavad viirused (kõige levinumad) mõjutavad tavaliselt ka neid.
Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi

Minu puhul jätsid ründajad iga krüptitud faili lõppu 4-baidise stringi (1, 0, 0, 0), mis lihtsustas ülesannet. Nakatamata failide otsimiseks piisas 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)

Seega selgus, et leiti esimesse tüüpi kuuluvad failid. Teisega kaasneb palju käsitsitööd, kuid leitu oli juba piisav. Kõik oleks hästi, aga sa pead teadma täiesti täpne struktuur ja (muidugi) tekkis juhus, et pidin töötama tihti muutuva lauaga. Keegi ei mäletanud, kas välja tüüpi muudeti või lisati uus veerg.

Kahjuks ei saanud Wilds City sellise juhtumi puhul aidata, mistõttu seda artiklit kirjutataksegi.

Tulge asja juurde

Seal on 3 kuu taguse tabeli struktuur, mis ei kattu praegusega (võib-olla üks väli ja võib-olla ka rohkem). Tabeli struktuur:

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)
); 

sel juhul peate välja võtma:

  • id_point int(11);
  • id_user int(11);
  • date_start KUUPÄEV KELLAAEG;
  • date_finish KUUPÄEV KELLAAEG.

Taastamiseks kasutatakse .ibd-faili bait-baidi analüüsi, millele järgneb nende teisendamine loetavamaks vormiks. Kuna vajaliku leidmiseks peame analüüsima ainult andmetüüpe, nagu int ja datatime, kirjeldatakse artiklis ainult neid, kuid mõnikord viitame ka teistele andmetüüpidele, mis võivad aidata muude sarnaste juhtumite korral.

1 probleem: väljadel tüüpidega DATETIME ja TEXT olid NULL väärtused ja need jäetakse failis lihtsalt vahele, seetõttu ei olnud minu puhul võimalik taastada struktuuri. Uutes veergudes oli vaikeväärtus null ja osa tehingust võis kaotsi minna sätte innodb_flush_log_at_trx_commit = 0 tõttu, mistõttu tuleks struktuuri määramiseks kulutada lisaaega.

2 probleem: tuleb arvestada, et DELETE kaudu kustutatud read on kõik ibd failis, kuid ALTER TABLE abil nende struktuuri ei uuendata. Selle tulemusena võib andmestruktuur faili algusest lõpuni varieeruda. Kui kasutate sageli OPTIMISE TABLE'i, ei teki tõenäoliselt sellist probleemi.

Обратите внимание, mõjutab DBMS-i versioon andmete salvestamise viisi ja see näide ei pruugi teiste suuremate versioonide puhul töötada. Minu puhul kasutati mariadb 10.1.24 windowsi versiooni. Kuigi mariadb-s töötate InnoDB tabelitega, on need tegelikult nii XtraDB, mis välistab meetodi rakendatavuse InnoDB mysql-iga.

Failianalüüs

Pythonis andmetüüp baiti () kuvab Unicode'i andmed tavalise numbrikomplekti asemel. Kuigi saate faili sellel kujul vaadata, saate mugavuse huvides teisendada baidid numbriliseks, teisendades baitimassiivi tavaliseks massiiviks (list(example_byte_array)). Igal juhul sobivad analüüsiks mõlemad meetodid.

Pärast mitme ibd-faili läbivaatamist leiate järgmist.

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi

Veelgi enam, kui jagate faili nende märksõnadega, saate enamasti ühtlased andmeplokid. Jagajana kasutame infimumi.

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

Huvitav tähelepanek: väikese andmemahuga tabelite puhul on infimum ja supremum vahel osuti ploki ridade arvule.

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi — katsetabel 1. reaga

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi - 2-realine katsetabel

Rea massiivi tabeli[0] saab vahele jätta. Pärast selle läbivaatamist ei leidnud ma ikka veel tabeli töötlemata andmeid. Tõenäoliselt kasutatakse seda plokki indeksite ja võtmete salvestamiseks.
Alustades tabelist[1] ja tõlkides selle numbrimassiiviks, võite juba märgata mõningaid mustreid, nimelt:

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi

Need on stringi salvestatud int väärtused. Esimene bait näitab, kas arv on positiivne või negatiivne. Minu puhul on kõik numbrid positiivsed. Ülejäänud 3 baidist saate arvu määrata järgmise funktsiooni abil. 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

Näiteks 128, 0, 0, 1 = 1Või 128, 0, 75, 108 = 19308.
Tabelil oli primaarvõti koos automaatse suurendamisega ja selle leiate ka siit

Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi

Pärast testtabelite andmete võrdlemist selgus, et objekt DATETIME koosneb 5 baidist ja algas 153-ga (mis näitab tõenäoliselt aastaseid intervalle). Kuna DATTIME vahemik on '1000-01-01' kuni '9999-12-31', arvan, et baitide arv võib erineda, kuid minu puhul langevad andmed perioodi 2016-2019, seega eeldame et 5 baiti piisab.

Aja määramiseks 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 kohta ei olnud võimalik funktsionaalset funktsiooni kirjutada, seega pidin selle häkkima. 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 kui kulutate n palju aega, saab selle arusaamatuse parandada.
Järgmiseks funktsioon, mis tagastab stringist datetime objekti. 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)

Suutis tuvastada sageli korduvad väärtused alates int, int, datetime, datetime Andmete taastamine XtraDB tabelitest ilma struktuurifailita, kasutades ibd-faili bait-baidi analüüsi, tundub, et see on see, mida vajate. Pealegi ei korrata sellist järjestust kaks korda rea ​​kohta.

Regulaaravaldise abil 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 avaldise abil otsides ei ole võimalik vajalikel väljadel NULL-väärtusi määrata, kuid minu puhul pole see kriitiline. Seejärel vaatame leidu silmusena läbi. 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)

Tegelikult on see kõik, tulemuste massiivi andmed on andmed, mida me vajame. ###PS.###
Ma saan aru, et see meetod ei sobi kõigile, kuid artikli põhieesmärk on pigem ärgitada tegutsema, mitte lahendada kõiki teie probleeme. Arvan, et kõige õigem lahendus oleks hakata ise lähtekoodi uurima mariadb, kuid piiratud aja tõttu tundus praegune meetod kõige kiirem.

Mõnel juhul saate pärast faili analüüsimist määrata ligikaudse struktuuri ja taastada selle, kasutades ühte ülaltoodud linkide standardmeetoditest. See on palju õigem ja põhjustab vähem probleeme.

Allikas: www.habr.com

Lisa kommentaar