Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd

Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd

parahistorinë

Kështu ndodhi që serveri u sulmua nga një virus ransomware, i cili, nga një "aksident me fat", i la pjesërisht skedarët .ibd (skedarët e të dhënave të papërpunuara të tabelave innodb) të paprekur, por në të njëjtën kohë i kodoi plotësisht skedarët .fpm ( skedarët e strukturës). Në këtë rast, .idb mund të ndahet në:

  • subjekt i restaurimit nëpërmjet mjeteve dhe udhëzuesve standardë. Për raste të tilla, ka një të shkëlqyer bëhet;
  • tabela pjesërisht të koduara. Kryesisht këto janë tabela të mëdha, për të cilat (siç e kuptoj) sulmuesit nuk kishin RAM të mjaftueshëm për kriptim të plotë;
  • Epo, tabela plotësisht të koduara që nuk mund të restaurohen.

Ishte e mundur të përcaktohet se cilit opsion i përkasin tabelat thjesht duke e hapur atë në çdo redaktues teksti nën kodimin e dëshiruar (në rastin tim është UTF8) dhe thjesht duke parë skedarin për praninë e fushave të tekstit, për shembull:

Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd

Gjithashtu, në fillim të skedarit mund të vëzhgoni një numër të madh prej 0 bajtesh dhe viruset që përdorin algoritmin e enkriptimit të bllokut (më i zakonshmi) zakonisht prekin edhe ata.
Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd

Në rastin tim, sulmuesit lanë një varg 4-bajtësh (1, 0, 0, 0) në fund të çdo skedari të koduar, gjë që thjeshtoi detyrën. Për të kërkuar skedarë të pa infektuar, skripti ishte i mjaftueshëm:

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, doli të gjente skedarë që i përkasin llojit të parë. E dyta përfshin shumë punë manuale, por ajo që u gjet tashmë ishte e mjaftueshme. Gjithçka do të ishte mirë, por ju duhet ta dini strukturë absolutisht e saktë dhe (sigurisht) lindi një rast që më duhej të punoja me një tavolinë që ndryshonte shpesh. Askush nuk u kujtua nëse lloji i fushës ishte ndryshuar apo ishte shtuar një kolonë e re.

Wilds City, për fat të keq, nuk mund të ndihmonte në një rast të tillë, prandaj edhe po shkruhet ky artikull.

Merrni në pikë

Ekziston një strukturë e një tabele të 3 muajve më parë që nuk përkon me atë aktuale (mundësisht një fushë, dhe ndoshta më shumë). Struktura e tabelës:

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

në këtë rast, ju duhet të nxirrni:

  • id_point int(11);
  • id_user int(11);
  • date_start DATETIME;
  • date_finish DATA KOHA.

Për rikuperim, përdoret një analizë byte-pas-byte e skedarit .ibd, e ndjekur nga konvertimi i tyre në një formë më të lexueshme. Meqenëse për të gjetur atë që na nevojitet, na duhet vetëm të analizojmë llojet e të dhënave si int dhe datatime, artikulli do të përshkruajë vetëm ato, por ndonjëherë do t'u referohemi edhe llojeve të tjera të të dhënave, të cilat mund të ndihmojnë në incidente të tjera të ngjashme.

Problemi 1: fushat me llojet DATETIME dhe TEXT kishin vlera NULL, dhe ato thjesht anashkalohen në skedar, për shkak të kësaj, nuk ishte e mundur të përcaktohet struktura për t'u rivendosur në rastin tim. Në kolonat e reja, vlera e paracaktuar ishte e pavlefshme dhe një pjesë e transaksionit mund të humbet për shkak të cilësimit innodb_flush_log_at_trx_commit = 0, kështu që do të duhej të shpenzohej kohë shtesë për të përcaktuar strukturën.

Problemi 2: duhet pasur parasysh që rreshtat e fshirë nëpërmjet DELETE do të jenë të gjitha në skedarin ibd, por me ALTER TABLE struktura e tyre nuk do të përditësohet. Si rezultat, struktura e të dhënave mund të ndryshojë nga fillimi i skedarit deri në fund të tij. Nëse përdorni shpesh OPTIMIZE TABLE, atëherë nuk ka gjasa të hasni një problem të tillë.

Обратите внимание, versioni DBMS ndikon në mënyrën se si ruhen të dhënat 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. Gjithashtu, edhe pse në mariadb ju punoni me tabela InnoDB, në fakt ato janë XtraDB, e cila përjashton zbatueshmërinë e metodës me InnoDB mysql.

Analiza e skedarit

Në python, lloji i të dhënave bytes () shfaq të dhënat e Unicode në vend të një grupi të rregullt numrash. Edhe pse mund ta shikoni skedarin në këtë formë, për lehtësi mund t'i konvertoni bajtet në formë numerike duke e kthyer grupin e bajtit në një grup të rregullt (lista(shembull_byte_array)). Në çdo rast, të dyja metodat janë të përshtatshme për analizë.

Pasi të shikoni disa skedarë ibd, mund të gjeni sa vijon:

Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd

Për më tepër, nëse e ndani skedarin me këto fjalë kyçe, do të merrni kryesisht blloqe të dhënash. Ne do të përdorim infimum si pjesëtues.

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

Një vëzhgim interesant: për tabelat me një sasi të vogël të dhënash, midis infimum dhe supremum ka një tregues për numrin e rreshtave në bllok.

Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd - tabela e testimit me rreshtin e parë

Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd - tabela e testimit me 2 rreshta

Tabela e grupit të rreshtave[0] mund të anashkalohet. Pasi e shikova, nuk isha ende në gjendje të gjeja të dhënat e papërpunuara të tabelës. Me shumë mundësi, ky bllok përdoret për të ruajtur indekset dhe çelësat.
Duke filluar me tabelën[1] dhe duke e përkthyer atë në një grup numerik, tashmë mund të vëreni disa modele, përkatësisht:

Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd

Këto janë vlera int të ruajtura në një varg. Byti i parë tregon nëse numri është pozitiv apo negativ. Në rastin tim, të gjithë numrat janë pozitivë. Nga 3 bajtët e mbetur, mund të përcaktoni numrin duke përdorur funksionin e mëposhtëm. 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

Për shembull, 128, 0, 0, 1 = 1Ose 128, 0, 75, 108 = 19308.
Tabela kishte një çelës primar me rritje automatike dhe mund të gjendet edhe këtu

Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd

Pas krahasimit të të dhënave nga tabelat e testimit, u zbulua se objekti DATETIME përbëhet nga 5 bajt dhe fillon me 153 (me shumë gjasa që tregon intervalet vjetore). Meqenëse diapazoni DATTIME është '1000-01-01' deri në '9999-12-31', mendoj se numri i bajteve mund të ndryshojë, por në rastin tim, të dhënat bien në periudhën nga 2016 deri në 2019, kështu që ne do të supozojmë mjaftojnë 5 bajt.

Për të përcaktuar kohën pa sekonda, janë shkruar funksionet e mëposhtme. 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}

Nuk ishte e mundur të shkruaja një funksion funksional për vitin dhe muajin, kështu që më duhej ta hakoja. 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

Jam i sigurt se nëse shpenzoni n kohë, ky keqkuptim mund të korrigjohet.
Më pas, një funksion që kthen një objekt datatime nga një varg. 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)

Menaxhuar për të zbuluar vlerat e përsëritura shpesh nga int, int, datatime, datatime Rikuperimi i të dhënave nga tabelat XtraDB pa një skedar strukture duke përdorur analizën byte-nga-byte të skedarit ibd, duket se kjo është ajo që ju nevojitet. Për më tepër, një sekuencë e tillë nuk përsëritet dy herë për rresht.

Duke përdorur një shprehje të 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)

Ju lutemi vini re se kur kërkoni duke përdorur këtë shprehje, nuk do të jetë e mundur të përcaktohen vlerat NULL në fushat e kërkuara, por në rastin tim kjo nuk është kritike. Pastaj kalojmë atë që gjetëm në një lak. 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)

Në fakt, kjo është e gjitha, të dhënat nga grupi i rezultateve janë të dhënat që na duhen. ###PS.###
Unë e kuptoj që kjo metodë nuk është e përshtatshme për të gjithë, por qëllimi kryesor i artikullit është të nxisë veprimin në vend që të zgjidhë të gjitha problemet tuaja. Unë mendoj se zgjidhja më e saktë do të ishte të filloni të studioni vetë kodin burimor mariadb, por për shkak të kohës së kufizuar, metoda aktuale dukej të ishte më e shpejta.

Në disa raste, pasi të keni analizuar skedarin, do të jeni në gjendje të përcaktoni strukturën e përafërt dhe ta rivendosni atë duke përdorur një nga metodat standarde nga lidhjet e mësipërme. Kjo do të jetë shumë më e saktë dhe do të shkaktojë më pak probleme.

Burimi: www.habr.com

Shto një koment