Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch

Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch

pravek

Stalo sa, že server bol napadnutý vírusom ransomware, ktorý „šťastnou náhodou“ čiastočne ponechal súbory .ibd (súbory s nespracovanými údajmi tabuliek innodb) nedotknuté, no zároveň súbory .fpm úplne zašifroval ( štruktúrne súbory). V tomto prípade možno .idb rozdeliť na:

  • predmetom obnovy pomocou štandardných nástrojov a návodov. Pre takéto prípady je tu výborná stať sa;
  • čiastočne šifrované tabuľky. Väčšinou ide o veľké tabuľky, pre ktoré (ako som pochopil) útočníci nemali dostatok RAM na úplné šifrovanie;
  • No, plne zašifrované tabuľky, ktoré sa nedajú obnoviť.

Bolo možné určiť, do ktorej možnosti tabuľky patria, jednoduchým otvorením v ľubovoľnom textovom editore pod požadovaným kódovaním (v mojom prípade je to UTF8) a jednoduchým zobrazením súboru na prítomnosť textových polí, napríklad:

Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch

Na začiatku súboru môžete tiež pozorovať veľké množstvo 0 bajtov a vírusy, ktoré používajú algoritmus blokového šifrovania (najbežnejší), ich zvyčajne ovplyvňujú.
Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch

V mojom prípade útočníci nechali na konci každého zašifrovaného súboru 4-bajtový reťazec (1, 0, 0, 0), čo zjednodušilo úlohu. Na vyhľadávanie neinfikovaných súborov stačil 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)

Ukázalo sa teda, že sa našli súbory patriace do prvého typu. Druhá zahŕňa veľa ručnej práce, ale toho, čo sa našlo, už bolo dosť. Všetko by bolo v poriadku, ale musíte to vedieť absolútne presná štruktúra a (samozrejme) nastal prípad, že som musel pracovať s často prebaľovacím pultom. Nikto si nepamätal, či sa zmenil typ poľa alebo bol pridaný nový stĺpec.

Wilds City, žiaľ, v takomto prípade nevedelo pomôcť, a preto vzniká tento článok.

Choďte k veci

Existuje štruktúra tabuľky spred 3 mesiacov, ktorá sa nezhoduje s aktuálnou (možno jedno pole a možno viac). Štruktúra tabuľky:

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

v tomto prípade musíte extrahovať:

  • id_point int(11);
  • id_user int(11);
  • date_start DÁTUM ČAS;
  • date_finish DÁTUM ČAS.

Na obnovu sa používa bajt po byte analýza súboru .ibd, po ktorej nasleduje ich konverzia do čitateľnejšej podoby. Keďže na to, aby sme našli to, čo potrebujeme, potrebujeme analyzovať iba dátové typy ako int a datatime, článok popíše len ich, no niekedy sa odvoláme aj na iné dátové typy, ktoré môžu pomôcť pri iných podobných incidentoch.

Problém 1: polia s typmi DATETIME a TEXT mali hodnoty NULL a v súbore sú jednoducho preskočené, z tohto dôvodu nebolo možné v mojom prípade určiť štruktúru na obnovenie. V nových stĺpcoch bola predvolená hodnota null a časť transakcie sa mohla stratiť v dôsledku nastavenia innodb_flush_log_at_trx_commit = 0, takže na určenie štruktúry by sa musel stráviť ďalší čas.

Problém 2: treba vziať do úvahy, že riadky vymazané pomocou DELETE budú všetky v súbore ibd, ale s ALTER TABLE sa ich štruktúra neaktualizuje. V dôsledku toho sa štruktúra údajov môže líšiť od začiatku súboru až po jeho koniec. Ak často používate OPTIMIZE TABLE, je nepravdepodobné, že by ste sa s takýmto problémom stretli.

Venujte pozornosť, verzia DBMS ovplyvňuje spôsob ukladania údajov a tento príklad nemusí fungovať pre iné hlavné verzie. V mojom prípade bola použitá windows verzia mariadb 10.1.24. Aj keď v mariadb pracujete s tabuľkami InnoDB, v skutočnosti sú XtraDB, čo vylučuje použiteľnosť metódy s InnoDB mysql.

Analýza súborov

V pythone typ údajov bytes() zobrazuje údaje Unicode namiesto bežnej sady čísel. Hoci súbor môžete zobraziť v tejto forme, pre pohodlie môžete previesť bajty do číselnej formy tak, že skonvertujete bajtové pole na bežné pole (zoznam(vzorové_bajtové_pole)). V každom prípade sú obe metódy vhodné na analýzu.

Po prezretí niekoľkých súborov ibd môžete nájsť nasledovné:

Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch

Navyše, ak súbor rozdelíte týmito kľúčovými slovami, získate väčšinou rovnomerné bloky údajov. Ako deliteľ použijeme infimum.

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

Zaujímavý postreh: pri tabuľkách s malým množstvom údajov je medzi infimum a supremum ukazovateľ na počet riadkov v bloku.

Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch — skúšobná tabuľka s 1. riadkom

Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch - testovacia tabuľka s 2 riadkami

Tabuľku riadkov [0] je možné preskočiť. Po prezretí sa mi stále nepodarilo nájsť nespracované údaje tabuľky. S najväčšou pravdepodobnosťou sa tento blok používa na ukladanie indexov a kľúčov.
Počnúc tabuľkou[1] a jej prevodom do číselného poľa si už môžete všimnúť niektoré vzory, konkrétne:

Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch

Toto sú hodnoty typu int uložené v reťazci. Prvý bajt označuje, či je číslo kladné alebo záporné. V mojom prípade sú všetky čísla kladné. Zo zostávajúcich 3 bajtov môžete určiť číslo pomocou nasledujúcej funkcie. 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

Napríklad, 128, 0, 0, 1 = 1Alebo 128, 0, 75, 108 = 19308.
Tabuľka mala primárny kľúč s automatickým prírastkom a nájdete ho aj tu

Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch

Po porovnaní údajov z testovacích tabuliek sa zistilo, že objekt DATETIME pozostáva z 5 bajtov a začínal na 153 (s najväčšou pravdepodobnosťou označujúce ročné intervaly). Keďže rozsah DATTIME je '1000-01-01' až '9999-12-31', myslím si, že počet bajtov sa môže líšiť, ale v mojom prípade údaje spadajú do obdobia 2016 až 2019, takže budeme predpokladať že 5 bajtov stačí.

Na určenie času bez sekúnd boli napísané nasledujúce funkcie. 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}

Na rok a mesiac sa nedala napísať funkčná funkcia, tak som ju musel hacknúť. 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

Som si istý, že ak strávite n množstvo času, toto nedorozumenie sa dá napraviť.
Ďalej funkcia, ktorá vracia objekt dátumu a času z reťazca. 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)

Podarilo sa zistiť často sa opakujúce hodnoty z int, int, datetime, datetime Obnova údajov z tabuliek XtraDB bez súboru štruktúry pomocou analýzy súboru ibd po bajtoch, zdá sa, že toto je to, čo potrebujete. Okrem toho sa takáto sekvencia neopakuje dvakrát na riadok.

Pomocou regulárneho výrazu nájdeme potrebné údaje:

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)

Upozorňujeme, že pri vyhľadávaní pomocou tohto výrazu nebude možné určiť hodnoty NULL v požadovaných poliach, ale v mojom prípade to nie je kritické. Potom v slučke prechádzame tým, čo sme našli. 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)

V skutočnosti je to všetko, údaje z poľa výsledkov sú údaje, ktoré potrebujeme. ###PS.###
Chápem, že táto metóda nie je vhodná pre každého, ale hlavným cieľom článku je skôr podnietiť k akcii než vyriešiť všetky vaše problémy. Myslím, že najsprávnejším riešením by bolo začať študovať zdrojový kód sám mariadb, no z dôvodu obmedzeného času sa súčasný spôsob zdal byť najrýchlejší.

V niektorých prípadoch po analýze súboru budete môcť určiť približnú štruktúru a obnoviť ju pomocou jednej zo štandardných metód z vyššie uvedených odkazov. Bude to oveľa správnejšie a spôsobí to menej problémov.

Zdroj: hab.com

Pridať komentár