Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével

Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével

őstörténet

Történt ugyanis, hogy a szervert egy ransomware vírus támadta meg, amely egy „szerencsés véletlen” folytán részben érintetlenül hagyta az .ibd fájlokat (innodb táblák nyers adatállománya), ugyanakkor teljesen titkosította az .fpm fájlokat ( szerkezeti fájlok). Ebben az esetben az .idb a következőkre osztható:

  • szabványos szerszámok és útmutatók segítségével restaurálható. Az ilyen esetekre van egy kiváló válik;
  • részben titkosított táblák. Többnyire nagy táblákról van szó, amelyekhez (ha jól értem) a támadóknak nem volt elég RAM-juk a teljes titkosításhoz;
  • Nos, teljesen titkosított táblák, amelyeket nem lehet visszaállítani.

Meg lehetett határozni, hogy a táblák melyik opcióhoz tartoznak, egyszerűen megnyitva bármely szövegszerkesztőben a kívánt kódolás alatt (esetemben UTF8), és egyszerűen meg kell nézni a fájlt a szövegmezők jelenlétére, például:

Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével

Ezenkívül a fájl elején nagyszámú 0 bájt figyelhető meg, és a blokk titkosítási algoritmust használó vírusok (a leggyakoribb) általában rájuk is hatnak.
Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével

Az én esetemben a támadók minden titkosított fájl végén egy 4 bájtos karakterláncot (1, 0, 0, 0) hagytak, ami leegyszerűsítette a feladatot. A nem fertőzött fájlok kereséséhez elég volt a szkript:

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)

Így kiderült, hogy az első típushoz tartozó fájlokat találtak. A második sok kézi munkával jár, de amit találtak, az már elég volt. Minden rendben lenne, de tudnod kell teljesen pontos szerkezet és (természetesen) felmerült egy eset, hogy gyakran változó asztallal kellett dolgoznom. Senki sem emlékezett arra, hogy megváltozott-e a mező típusa, vagy új oszlop került-e hozzáadásra.

A Wilds City sajnos nem tudott segíteni egy ilyen eseten, ezért is készül ez a cikk.

Térjen a tárgyra

Van egy 3 hónappal ezelőtti táblázat szerkezete, ami nem esik egybe a jelenlegivel (esetleg egy mező, esetleg több). A táblázat felépítése:

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

ebben az esetben ki kell bontania:

  • id_point int(11);
  • id_user int(11);
  • date_start DÁTUM IDŐ;
  • date_finish DÁTUM IDŐ.

A helyreállításhoz az .ibd fájl bájtonkénti elemzését alkalmazzák, majd a fájlokat olvashatóbb formátumba konvertálják. Mivel ahhoz, hogy megtaláljuk, amire szükségünk van, csak olyan adattípusokat kell elemeznünk, mint az int és a datatime, ezért a cikk csak ezeket ismerteti, de néha hivatkozunk más adattípusokra is, amelyek segíthetnek más hasonló incidensekben.

1. probléma: a DATETIME és TEXT típusú mezők NULL értékkel bírtak, és egyszerűen kimaradnak a fájlban, emiatt esetemben nem lehetett meghatározni a visszaállítandó szerkezetet. Az új oszlopokban az alapértelmezett érték null volt, és a tranzakció egy része elveszhetett az innodb_flush_log_at_trx_commit = 0 beállítás miatt, így további időt kell fordítani a struktúra meghatározására.

2. probléma: figyelembe kell venni, hogy a DELETE-en keresztül törölt sorok mind az ibd fájlban lesznek, de az ALTER TABLE-nél a szerkezetük nem frissül. Ennek eredményeként az adatstruktúra a fájl elejétől a végéig változhat. Ha gyakran használja az OPTIMALIZÁLÁSI TÁBLÁZAT, akkor nem valószínű, hogy ilyen problémával találkozik.

Figyeljen oda, a DBMS-verzió befolyásolja az adatok tárolásának módját, és előfordulhat, hogy ez a példa nem működik más nagyobb verziók esetén. Az én esetemben a mariadb 10.1.24 windowsos verzióját használtam. Továbbá, bár a mariadb-ben InnoDB táblákkal dolgozol, valójában azok XtraDB, amely kizárja a módszer alkalmazhatóságát InnoDB mysql-lel.

Fájlelemzés

Pythonban adattípus bájt() Unicode-adatokat jelenít meg a szokásos számkészlet helyett. Bár megtekintheti a fájlt ebben a formában, a kényelem kedvéért a bájtokat numerikus formává alakíthatja, ha a bájttömböt normál tömbbé alakítja (list(example_byte_array)). Mindenesetre mindkét módszer alkalmas elemzésre.

Több ibd fájl átnézése után a következőket találhatja:

Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével

Sőt, ha felosztja a fájlt ezekkel a kulcsszavakkal, akkor többnyire egyenletes adatblokkokat kap. Osztóként az infimuumot fogjuk használni.

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

Érdekes megfigyelés: a kevés adatot tartalmazó tábláknál az infimum és a supremum között van egy mutató a blokkban lévő sorok számára.

Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével — próbatábla 1. sorral

Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével - teszttábla 2 sorral

A sortömb táblázat[0] kihagyható. Miután átnéztem, továbbra sem találtam a nyers táblázat adatait. Valószínűleg ez a blokk indexek és kulcsok tárolására szolgál.
A táblázatból [1] kezdve és numerikus tömbbe fordítva már észrevehet néhány mintát, nevezetesen:

Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével

Ezek egy karakterláncban tárolt int értékek. Az első bájt jelzi, hogy a szám pozitív vagy negatív. Az én esetemben minden szám pozitív. A fennmaradó 3 bájtból a következő függvény segítségével határozhatja meg a számot. Forgatókönyv:

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éldául 128, 0, 0, 1 = 1Vagy 128, 0, 75, 108 = 19308.
A táblázatnak volt egy elsődleges kulcsa automatikus növeléssel, és itt is megtalálható

Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével

A teszttáblázatok adatainak összehasonlítása után kiderült, hogy a DATETIME objektum 5 bájtból áll, és 153-mal kezdődik (valószínűleg éves intervallumokat jelez). Mivel a DATTIME tartomány '1000-01-01' és '9999-12-31' között van, úgy gondolom, hogy a bájtok száma változhat, de az én esetemben az adatok a 2016 és 2019 közötti időszakra esnek, ezért feltételezzük. hogy 5 bájt elég.

Az idő másodpercek nélküli meghatározásához a következő függvényeket írtuk. Forgatókönyv:

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}

Évre és hónapra nem lehetett funkcionális függvényt írni, ezért fel kellett hackelni. Forgatókönyv:

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

Biztos vagyok benne, hogy ha n mennyiségű időt töltesz, ez a félreértés javítható.
Ezután egy függvény, amely egy karakterláncból egy datetime objektumot ad vissza. Forgatókönyv:

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)

Sikerült észlelni a gyakran ismétlődő értékeket int, int, datetime, datetime Adatok helyreállítása XtraDB táblákból struktúrafájl nélkül az ibd fájl byte-byte elemzésével, úgy tűnik, erre van szüksége. Ezenkívül egy ilyen sorozat nem ismétlődik meg kétszer soronként.

Egy reguláris kifejezés segítségével megtaláljuk a szükséges adatokat:

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)

Kérjük, vegye figyelembe, hogy ha ezzel a kifejezéssel keres, nem lehet NULL értékeket meghatározni a kötelező mezőkben, de az én esetemben ez nem kritikus. Aztán körbejárjuk, amit találtunk. Forgatókönyv:

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)

Valójában ez minden, az eredménytömbből származó adatok azok az adatok, amelyekre szükségünk van. ###PS.###
Megértem, hogy ez a módszer nem mindenki számára megfelelő, de a cikk fő célja a cselekvés ösztönzése, nem pedig az összes probléma megoldása. Szerintem a leghelyesebb megoldás az lenne, ha magad kezded el tanulmányozni a forráskódot MariaDB, de a korlátozott idő miatt a jelenlegi módszer tűnt a leggyorsabbnak.

Egyes esetekben a fájl elemzése után meg tudja határozni a hozzávetőleges szerkezetet, és visszaállíthatja azt a fenti hivatkozások egyik szabványos módszerével. Ez sokkal korrektebb lesz, és kevesebb problémát okoz.

Forrás: will.com

Hozzászólás