Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file

Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file

prehistory

Nagkataon na ang server ay inatake ng isang ransomware virus, na, sa pamamagitan ng isang "masuwerteng aksidente," bahagyang iniwan ang mga .ibd file (raw data file ng mga innodb table) na hindi nagalaw, ngunit sa parehong oras ay ganap na na-encrypt ang mga .fpm file ( mga file ng istraktura). Sa kasong ito, maaaring hatiin ang .idb sa:

  • napapailalim sa pagpapanumbalik sa pamamagitan ng karaniwang mga tool at gabay. Para sa mga ganitong kaso, mayroong isang mahusay maging;
  • bahagyang naka-encrypt na mga talahanayan. Kadalasan ang mga ito ay malalaking talahanayan, kung saan (tulad ng naiintindihan ko) ang mga umaatake ay walang sapat na RAM para sa buong pag-encrypt;
  • Well, ganap na naka-encrypt na mga talahanayan na hindi maibabalik.

Posibleng matukoy kung aling opsyon ang nabibilang sa mga talahanayan sa pamamagitan lamang ng pagbubukas nito sa anumang text editor sa ilalim ng nais na pag-encode (sa aking kaso ito ay UTF8) at simpleng pagtingin sa file para sa pagkakaroon ng mga patlang ng teksto, halimbawa:

Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file

Gayundin, sa simula ng file maaari mong obserbahan ang isang malaking bilang ng 0 byte, at ang mga virus na gumagamit ng block encryption algorithm (ang pinakakaraniwan) ay kadalasang nakakaapekto rin sa kanila.
Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file

Sa aking kaso, ang mga umaatake ay nag-iwan ng 4-byte na string (1, 0, 0, 0) sa dulo ng bawat naka-encrypt na file, na nagpasimple sa gawain. Upang maghanap ng mga hindi na-infect na file, sapat na ang script:

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)

Kaya, nakahanap ito ng mga file na kabilang sa unang uri. Ang pangalawa ay nagsasangkot ng maraming manu-manong gawain, ngunit ang natagpuan ay sapat na. Magiging maayos ang lahat, ngunit kailangan mong malaman ganap na tumpak na istraktura at (siyempre) lumitaw ang isang kaso na kailangan kong magtrabaho sa isang madalas na pagbabago ng talahanayan. Walang nakaalala kung binago ang uri ng field o nagdagdag ng bagong column.

Ang Wilds City, sa kasamaang-palad, ay hindi makakatulong sa ganitong kaso, kaya naman isinusulat ang artikulong ito.

Mas malapit sa punto

Mayroong istraktura ng isang talahanayan mula 3 buwan na ang nakakaraan na hindi tumutugma sa kasalukuyang isa (maaaring isang field, at posibleng higit pa). Istraktura ng talahanayan:

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

sa kasong ito, kailangan mong kunin:

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

Para sa pagbawi, ginagamit ang isang byte-by-byte na pagsusuri ng .ibd file, na sinusundan ng pag-convert sa mga ito sa isang mas nababasang form. Dahil upang mahanap kung ano ang kailangan namin, kailangan lang naming pag-aralan ang mga uri ng data tulad ng int at datatime, ang artikulo ay ilalarawan lamang ang mga ito, ngunit kung minsan ay sasangguni din kami sa iba pang mga uri ng data, na maaaring makatulong sa iba pang katulad na mga insidente.

Problema 1: ang mga patlang na may mga uri ng DATETIME at TEXT ay may NULL na mga halaga, at nilaktawan lamang ang mga ito sa file, dahil dito, hindi posible na matukoy ang istraktura na ibabalik sa aking kaso. Sa mga bagong column, null ang default na value, at maaaring mawala ang bahagi ng transaksyon dahil sa setting na innodb_flush_log_at_trx_commit = 0, kaya kailangang gumastos ng karagdagang oras upang matukoy ang istraktura.

Problema 2: dapat isaalang-alang na ang mga row na natanggal sa pamamagitan ng DELETE ay lahat ay nasa ibd file, ngunit sa ALTER TABLE ang kanilang istraktura ay hindi maa-update. Bilang resulta, maaaring mag-iba ang istraktura ng data mula sa simula ng file hanggang sa katapusan nito. Kung madalas kang gumagamit ng OPTIMIZE TABLE, malamang na hindi ka makatagpo ng ganoong problema.

Magbayad ng pansin, ang bersyon ng DBMS ay nakakaapekto sa paraan ng pag-imbak ng data, at ang halimbawang ito ay maaaring hindi gumana para sa iba pang mga pangunahing bersyon. Sa aking kaso, ginamit ang bersyon ng windows ng mariadb 10.1.24. Gayundin, kahit na sa mariadb nagtatrabaho ka sa mga talahanayan ng InnoDB, sa katunayan sila ay XtraDB, na hindi kasama ang pagiging angkop ng pamamaraan sa InnoDB mysql.

Pagsusuri ng file

Sa python, uri ng data bytes() nagpapakita ng Unicode data sa halip ng isang regular na hanay ng mga numero. Bagama't maaari mong tingnan ang file sa form na ito, para sa kaginhawahan maaari mong i-convert ang mga byte sa numeric na form sa pamamagitan ng pag-convert ng byte array sa isang regular na array (list(example_byte_array)). Sa anumang kaso, ang parehong mga pamamaraan ay angkop para sa pagsusuri.

Pagkatapos tingnan ang ilang ibd file, makikita mo ang sumusunod:

Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file

Bukod dito, kung hahatiin mo ang file sa mga keyword na ito, makakakuha ka ng halos kahit na mga bloke ng data. Gagamitin natin ang infimum bilang divisor.

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

Isang kawili-wiling obserbasyon: para sa mga talahanayan na may maliit na halaga ng data, sa pagitan ng infimum at supremum mayroong isang pointer sa bilang ng mga row sa block.

Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file β€” test table na may 1st row

Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file - test table na may 2 row

Maaaring laktawan ang row array table[0]. Matapos tingnan ito, hindi ko pa rin mahanap ang hilaw na data ng talahanayan. Malamang, ang block na ito ay ginagamit upang mag-imbak ng mga index at key.
Simula sa table[1] at pagsasalin nito sa isang numeric array, mapapansin mo na ang ilang pattern, katulad ng:

Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file

Ito ay mga int value na nakaimbak sa isang string. Ang unang byte ay nagpapahiwatig kung ang numero ay positibo o negatibo. Sa aking kaso, lahat ng mga numero ay positibo. Mula sa natitirang 3 byte, matutukoy mo ang numero gamit ang sumusunod na function. Script:

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

Halimbawa, 128, 0, 0, 1 = 1O 128, 0, 75, 108 = 19308.
Ang talahanayan ay may pangunahing key na may auto-increment, at maaari rin itong matagpuan dito

Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file

Ang pagkakaroon ng paghahambing ng data mula sa mga talahanayan ng pagsubok, ito ay nagsiwalat na ang DATETIME object ay binubuo ng 5 bytes at nagsimula sa 153 (malamang na nagpapahiwatig ng taunang mga agwat). Dahil ang hanay ng DATTIME ay '1000-01-01' hanggang '9999-12-31', sa tingin ko ang bilang ng mga byte ay maaaring mag-iba, ngunit sa aking kaso, ang data ay bumaba sa panahon mula 2016 hanggang 2019, kaya ipagpalagay namin sapat na ang 5 bytes na iyon.

Upang matukoy ang oras nang walang mga segundo, isinulat ang mga sumusunod na function. Script:

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}

Hindi posibleng magsulat ng functional function para sa taon at buwan, kaya kinailangan kong i-hack ito. Script:

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

Sigurado ako na kung gumugugol ka ng maraming oras, ang hindi pagkakaunawaan na ito ay maaaring itama.
Susunod, isang function na nagbabalik ng datetime object mula sa isang string. Script:

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)

Pinamamahalaang upang makita ang madalas na paulit-ulit na mga halaga mula sa int, int, datetime, datetime Pagbawi ng data mula sa mga talahanayan ng XtraDB na walang structure file gamit ang byte-by-byte analysis ng ibd file, mukhang ito ang kailangan mo. Bukod dito, ang ganitong pagkakasunod-sunod ay hindi inuulit nang dalawang beses bawat linya.

Gamit ang isang regular na expression, nakita namin ang kinakailangang 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)

Mangyaring tandaan na kapag naghahanap gamit ang expression na ito, hindi posible na matukoy ang mga halaga ng NULL sa mga kinakailangang field, ngunit sa aking kaso hindi ito kritikal. Pagkatapos ay pumunta kami sa kung ano ang nakita namin sa isang loop. Script:

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)

Sa totoo lang, iyon lang, ang data mula sa hanay ng resulta ay ang data na kailangan namin. ###PS.###
Naiintindihan ko na ang pamamaraang ito ay hindi angkop para sa lahat, ngunit ang pangunahing layunin ng artikulo ay upang mag-udyok ng aksyon sa halip na malutas ang lahat ng iyong mga problema. Sa tingin ko ang pinakatamang solusyon ay ang simulang pag-aralan ang source code sa iyong sarili mariadb, ngunit dahil sa limitadong oras, ang kasalukuyang pamamaraan ay tila ang pinakamabilis.

Sa ilang mga kaso, pagkatapos suriin ang file, matutukoy mo ang tinatayang istraktura at ibalik ito gamit ang isa sa mga karaniwang pamamaraan mula sa mga link sa itaas. Ito ay magiging mas tama at magdulot ng mas kaunting mga problema.

Pinagmulan: www.habr.com

Magdagdag ng komento