Rivendosja e të dhënave nga tabelat XtraDB pa skedarin e strukturës, duke përdorur analizën me byte të skedarit ibd

Rivendosja e të dhënave nga tabelat XtraDB pa skedarin e strukturës, duke përdorur analizën me byte të skedarit ibd

Pas historia

Kështu ndodhi që serveri u sulmua nga një virus kriptimi, i cili për "fat të mirë", la pjesërisht të paprekura skedarët .ibd (skedarët e të dhënave bruto të tabelave innodb), por në të njëjtën kohë e enkriptoi plotësisht skedarin .fpm (skedarët strukturor). Nga kjo, .idb mund të ndahen në:

  • tĂ« rikuperueshme pĂ«rmes mjeteve standarde dhe udhĂ«zimeve. PĂ«r kĂ«to raste, ekziston njĂ« rast;
  • tabela tĂ« pĂ«rfshira pjesĂ«risht nĂ« enkriptim. Kryesisht, kĂ«to janĂ« tabela tĂ« mĂ«dha, pĂ«r tĂ« cilat (siç kuptova), dĂ«shtakĂ«t nuk kishin mjaftueshĂ«m memorie pĂ«r tĂ« bĂ«rĂ« enkriptim tĂ« plotĂ«;
  • dhe tabela tĂ« plota tĂ« enkriptuara, tĂ« cilat nuk janĂ« tĂ« rikuperueshme.

Të përcaktojmë se në cilin variant i përkasin tabelat, mund të kuptohet në mënyrë banale duke i hapur në ndonjë redaktues teksti me kodimin e nevojshëm (në rastin tim, kjo është UTF8) dhe thjesht duke e kontrolluar skedarin për praninë e fushave tekstuale, për shembull:

Rivendosja e të dhënave nga tabelat XtraDB pa skedarin e strukturës, duke përdorur analizën me byte të skedarit ibd

Gjithashtu, në fillim të skedarit mund të vërehet një numër i madh bajtësh 0, dhe viruset që përdorin algoritmin e enkriptimit me bllok (më së shpeshti), zakonisht i prekin ata gjithashtu.
Rivendosja e të dhënave nga tabelat XtraDB pa skedarin e strukturës, duke përdorur analizën me byte të skedarit ibd

Në rastin tim, ata që kryenin sulmin linin në fund të çdo file të enkriptuar një varg prej 4 bajtësh (1, 0, 0, 0), gjë që e thjeshtoi detyrën. Për të kërkuar skedarët e pa prekur, mjaftonte vetëm një skript:

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)

KĂ«shtu arrita tĂ« gjej skedarĂ«t qĂ« i pĂ«rkisnin tipi tĂ« parĂ«. TĂ« dytin kĂ«rkon njĂ« punĂ« tĂ« gjatĂ« manuale, por ishte e mjaftueshme ajo qĂ« kisha gjetur. Çdo gjĂ« do tĂ« ishte mirĂ«, por Ă«shtĂ« e nevojshme tĂ« di strukturĂ«n absolutisht tĂ« saktĂ« dhe (sigurisht) ndodhi qĂ« duhej tĂ« punoja me njĂ« tabelĂ« qĂ« ndryshonte shpesh. Askush nuk e kishte tĂ« qartĂ«, nĂ«se lloji i fushĂ«s kishte ndryshuar apo ishte shtuar njĂ« kolonne e re.

Debri City fatkeqësisht nuk mundi të ndihmonte në një rast të tillë, andaj po shkruhet ky artikull.

Të kalojmë në thelb

Ekziston një strukturë tabelash nga tre muaj më parë që nuk përputhet me atë aktuale (ndoshta për një fushë, ndoshta edhe më shumë). Struktura e tabelës:

KRIJONI TABELËN `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)
); 

në të njëjtën kohë, nevojitet të nxirren:

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

Për restaurim përdoret analiza byte për byte e skedarit .ibd, me kalimin e tyre në një formë më të lexueshme. Duke qenë se për të gjetur të nevojshmen, mjafton të analizojmë tipe të dhënash si int dhe datetime, në artikull do të përmenden vetëm këto, megjithatë ndonjëherë do të referohem dhe në tipe të tjera të dhënash, që mund të ndihmojnë në raste të ngjashme.

Problemi 1: në fushat me tipe DATETIME dhe TEXT kishte vlera NULL, dhe në skedarin ato thjesht injorohen, për këtë arsye, të përcaktosh strukturën për restaurim në rastin tim nuk ishte e mundur. Në kolonat e reja vlera për default ishte null, dhe një pjesë e transaksionit mund të ishte humbur për shkak të konfigurimit innodb_flush_log_at_trx_commit = 0, prandaj për të përcaktuar strukturën do të duhej të harxhohej kohë shtesë.

Problemi 2: duhet të merret parasysh që rreshtat e fshirë përmes DELETE, megjithatë do të jenë të pranishëm në skedarin ibd, por gjatë ALTER TABLE struktura e tyre nuk do të përditësohet. Si rezultat, struktura e të dhënave mund të variojë nga fillimi i skedarit në fundin e tij. Nëse përdorni shpesh OPTIMIZE TABLE, është e vështirë të hasni një problem të tillë.

Kërkojmë vëmendje, versioni i DBMS ndikon në mënyrën e ruajtjes së të dhënave, dhe ky shembull mund të mos funksionojë për versionet e tjera kryesore. Në rastin tim, është përdorur versioni windows i mariadb 10.1.24. Po ashtu, ndonëse në mariadb punoni me tabela InnoDB, ato në fakt janë XtraDB, gjë që përjashton përdorimin e metodës me InnoDB mysql.

Analiza e skedarit

Në python, tipi i të dhënave bytes() tregon të dhënat në unicode në vend të një grupi të zakonshëm numrash. Megjithëse skedari mund të shqyrtohet edhe në këtë formë, për lehtësi mund të konvertohen bajtat në një pamje numerike duke konvertuar masivin e bajtave në një masiv të zakonshëm (list(example_byte_array)). Në çdo rast, për analizë do të vlejnë të dyja metodat.

Pasi të shikoni disa skedarë ibd, mund të takoni të mëposhtmet:

Rivendosja e të dhënave nga tabelat XtraDB pa skedarin e strukturës, duke përdorur analizën me byte të skedarit ibd

Dhe, nëse e ndan skedarin sipas këtyre fjalëve kyçe, do të krijohen kryesisht blloqe të barabarta të të dhënave. Do të përdorim infimum si ndarës.

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

Një vëzhgim interesant, për tabelat me pak të dhëna, midis infimum dhe supremum ka një tregues për numrin e rreshtave në bllok.

Rivendosja e tĂ« dhĂ«nave nga tabelat XtraDB pa skedarin e strukturĂ«s, duke pĂ«rdorur analizĂ«n me byte tĂ« skedarit ibd — tabela testuese me 1 rresht

Rivendosja e tĂ« dhĂ«nave nga tabelat XtraDB pa skedarin e strukturĂ«s, duke pĂ«rdorur analizĂ«n me byte tĂ« skedarit ibd — tabela testuese me 2 rreshta

Masa e rreshtave table[0] mund të anashkalohet. Pas shqyrtimit të saj, nuk arrita të zbuloja të dhëna të papërpunuara të tabelave. Me sa duket, ky bllok shërben për ruajtjen e indekseve dhe çelësave.
Duke filluar nga table[1] dhe duke e përkthyer atë në një masë numerike, tashmë mund të vërehen disa ligjësi, konkretisht:

Rivendosja e të dhënave nga tabelat XtraDB pa skedarin e strukturës, duke përdorur analizën me byte të skedarit ibd

Këto janë vlera int të ruajtura në rresht. Bytes i parë tregon nëse numri është pozitiv ose negativ. Në rastin tim, të gjitha numrat janë pozitivë. Nga 3 bytes të tjerë, mund të përcaktohet numri duke përdorur funksionin e mëposhtëm. Skripti:

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

Për shembull, 128, 0, 0, 1 = 1, ose 128, 0, 75, 108 = 19308.
Në tabelë kishte një çelës të parë me auto-increment, dhe këtu gjithashtu mund të zbulohet.

Rivendosja e të dhënave nga tabelat XtraDB pa skedarin e strukturës, duke përdorur analizën me byte të skedarit ibd

Duke krahasuar tĂ« dhĂ«nat nga tabelat testuese, u zbulua se objekti DATETIME pĂ«rbĂ«het nga 5 byta dhe fillon me 153 (ka shumĂ« mundĂ«si qĂ« tregon intervalet vjetore). MeqenĂ«se diapazoni DATETIME Ă«shtĂ« ‘1000-01-01’ deri nĂ« ‘9999-12-31’, mendoj se numri i byte-ve mund tĂ« ndryshojĂ«, por nĂ« rastin tim, tĂ« dhĂ«nat bien nĂ« intervalin nga viti 2016 deri nĂ« vitin 2019, prandaj do ta marrim si tĂ« mjaftueshme 5 byte.

Për të përcaktuar kohën pa sekonda, janë shkruar funksionet e mëposhtme. Skripti:

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}

Për vitin dhe muajin nuk arrita të shkruaj një funksion që funksionon siç duhet, prandaj më duhej të përdor një vlerë të ngurtë. Skripti:

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

Jam e sigurtë, nëse kaloni një kohë të caktuar, edhe kjo keqkuptim mund të rregullohet.
Më pas, funksi që kthen një objekt datetime nga një varg. Skripti:

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)

Arritëm të zbulojmë vlera që përsëriten shpesh nga int, int, datetime, datetime Rivendosja e të dhënave nga tabelat XtraDB pa skedarin e strukturës, duke përdorur analizën me byte të skedarit ibd, duket se është pikërisht ajo që na nevojitet. Më shumë, kjo sekuencë nuk përsëritet dy herë në varg.

Duke përdorur shprehjen e rregullt, gjejmë të dhënat e nevojshme:

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)

Vini re se gjatë kërkimit sipas kësaj shprehjeje, nuk do të mund të përcaktoni vlerat NULL në fushat e kërkuara, por në rastin tim, kjo nuk është kritike. Më pas, në cikël përshkruajmë atë që kemi gjetur. Skripti:

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)

Në fund, të dhënat nga vargu result janë pikërisht ato që na duhen. ###PS.###
E kuptoj që ky mënyrë nuk do t'i përshtatet shumëkënd, por qëllimi kryesor i artikullit është më shumë për të nxitur veprimin se sa për të zgjidhur të gjitha problemet tuaja. Mendoj se zgjidhja më e duhur do të ishte të fillonit të studionit kodin burimor të vetë mariadb, por për shkak të kohës së kufizuar, kjo mënyrë u duk si më e shpejta.

Në disa raste, duke analizuar skedarin, do të jeni në gjendje të përcaktoni strukturën e përafërt dhe të riktheni në një nga mënyrat standarde nga linket e mësipërme. Kjo do të ishte shumë më e saktë dhe do të shkaktonte më pak probleme.

Burimi: habr.com

Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster