Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte

Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte

pravěk

Stalo se, že server byl napaden virem ransomwaru, který „šťastnou náhodou“ částečně ponechal soubory .ibd (soubory nezpracovaných dat tabulek innodb) nedotčené, ale zároveň soubory .fpm zcela zašifroval ( strukturní soubory). V tomto případě lze .idb rozdělit na:

  • podléhají restaurování pomocí standardních nástrojů a vodítek. Pro takové případy existuje vynikající stát se;
  • částečně šifrované tabulky. Většinou se jedná o velké tabulky, u kterých (jak jsem pochopil) útočníci neměli dostatek RAM pro plné šifrování;
  • No, plně zašifrované tabulky, které nelze obnovit.

Bylo možné určit, ke které možnosti tabulky patří, pouhým otevřením v libovolném textovém editoru pod požadovaným kódováním (v mém případě je to UTF8) a jednoduchým zobrazením souboru pro přítomnost textových polí, například:

Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte

Na začátku souboru můžete také pozorovat velké množství 0 bajtů a viry, které používají algoritmus blokového šifrování (nejběžnější), je obvykle také ovlivňují.
Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte

V mém případě útočníci nechali na konci každého zašifrovaného souboru 4bajtový řetězec (1, 0, 0, 0), což zjednodušilo úlohu. K vyhledání neinfikovaných souborů 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 se tedy, že byly nalezeny soubory patřící do prvního typu. Druhá obnáší hodně ruční práce, ale toho, co se našlo, už bylo dost. Všechno by bylo v pořádku, ale musíte to vědět naprosto precizní konstrukce a (samozřejmě) vznikl případ, že jsem musel pracovat s často přebalovacím pultem. Nikdo si nepamatoval, zda byl změněn typ pole nebo byl přidán nový sloupec.

Wilds City bohužel s takovým případem nedokázalo pomoci, a proto vzniká tento článek.

Dostaňte se k věci

Existuje struktura tabulky před 3 měsíci, která se neshoduje se současnou (možná jedno pole a možná více). Struktura tabulky:

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 případě musíte extrahovat:

  • id_point int(11);
  • id_user int(11);
  • date_start ČAS SCHŮZKY;
  • date_finish ČAS SCHŮZKY.

Pro obnovu se používá byte byte analýza souboru .ibd s následným převedením do čitelnější podoby. Protože k nalezení toho, co potřebujeme, potřebujeme pouze analyzovat datové typy jako int a datatime, článek popíše pouze je, ale někdy se odkážeme i na jiné datové typy, které mohou pomoci při dalších podobných incidentech.

Problém 1: pole s typy DATETIME a TEXT měla hodnoty NULL a v souboru jsou jednoduše přeskočena, z tohoto důvodu nebylo možné v mém případě určit strukturu k obnovení. V nových sloupcích byla výchozí hodnota null a část transakce mohla být ztracena kvůli nastavení innodb_flush_log_at_trx_commit = 0, takže určení struktury by bylo nutné věnovat více času.

Problém 2: je třeba vzít v úvahu, že řádky smazané pomocí DELETE budou všechny v souboru ibd, ale s ALTER TABLE nebude jejich struktura aktualizována. V důsledku toho se datová struktura může lišit od začátku souboru do jeho konce. Pokud často používáte OPTIMIZE TABLE, pak je nepravděpodobné, že se s takovým problémem setkáte.

Poznámka, verze DBMS ovlivňuje způsob ukládání dat a tento příklad nemusí fungovat pro jiné hlavní verze. V mém případě byla použita windows verze mariadb 10.1.24. Také, ačkoli v mariadb pracujete s tabulkami InnoDB, ve skutečnosti jsou XtraDB, což vylučuje použitelnost metody s InnoDB mysql.

Analýza souborů

V pythonu datový typ bytes() zobrazí data Unicode místo běžné sady čísel. I když můžete soubor zobrazit v této podobě, pro usnadnění můžete převést bajty do číselné podoby převedením bajtového pole na běžné pole (seznam(example_byte_array)). V každém případě jsou pro analýzu vhodné obě metody.

Po prohlédnutí několika souborů ibd můžete najít následující:

Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte

Navíc, pokud soubor rozdělíte těmito klíčovými slovy, získáte většinou sudé bloky dat. Jako dělitel použijeme infimum.

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

Zajímavý postřeh: u tabulek s malým množstvím dat je mezi infimum a supremum ukazatel na počet řádků v bloku.

Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte — zkušební tabulka s 1. řádkem

Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte - testovací stůl se 2 řádky

Tabulku pole řádků[0] lze přeskočit. Po prozkoumání jsem stále nemohl najít nezpracovaná data tabulky. S největší pravděpodobností se tento blok používá k ukládání indexů a klíčů.
Počínaje tabulkou[1] a převést ji do numerického pole si již můžete všimnout některých vzorů, konkrétně:

Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte

Toto jsou hodnoty int uložené v řetězci. První bajt označuje, zda je číslo kladné nebo záporné. V mém případě jsou všechna čísla kladná. Ze zbývajících 3 bajtů můžete určit číslo pomocí následující funkce. 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

Například, 128, 0, 0, 1 = 1Nebo 128, 0, 75, 108 = 19308.
Tabulka měla primární klíč s automatickým přírůstkem a lze jej také nalézt zde

Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte

Po porovnání dat z testovacích tabulek bylo zjištěno, že objekt DATETIME se skládá z 5 bajtů a začíná 153 (s největší pravděpodobností označující roční intervaly). Vzhledem k tomu, že rozsah DATTIME je '1000-01-01' až '9999-12-31', myslím, že počet bajtů se může lišit, ale v mém případě data spadají do období 2016 až 2019, takže budeme předpokládat že 5 bajtů stačí.

Pro určení času bez sekund byly napsány následující funkce. 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}

Nebylo možné napsat funkční funkci na rok a měsíc, tak jsem to musel hacknout. 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

Jsem si jist, že pokud strávíte n množství času, lze toto nedorozumění napravit.
Dále funkce, která vrací objekt datetime z řetězce. 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)

Podařilo se detekovat často se opakující hodnoty z int, int, datetime, datetime Obnova dat z tabulek XtraDB bez souboru struktury pomocí analýzy souboru ibd byte po byte, vypadá to, že to je to, co potřebujete. Navíc se taková sekvence neopakuje dvakrát na řádek.

Pomocí regulárního výrazu najdeme potřebná 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)

Upozorňujeme, že při vyhledávání pomocí tohoto výrazu nebude možné určit hodnoty NULL v požadovaných polích, ale v mém případě to není kritické. Pak procházíme to, co jsme našli, ve smyčce. 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)

Ve skutečnosti to je vše, data z pole výsledků jsou data, která potřebujeme. ###PS.###
Chápu, že tato metoda není vhodná pro každého, ale hlavním cílem článku je spíše vyvolat akci, než vyřešit všechny vaše problémy. Myslím, že nejsprávnějším řešením by bylo začít studovat zdrojový kód sám mariadb, ale vzhledem k omezenému času se současná metoda zdála být nejrychlejší.

V některých případech budete po analýze souboru schopni určit přibližnou strukturu a obnovit ji pomocí jedné ze standardních metod z výše uvedených odkazů. Bude to mnohem správnější a způsobí to méně problémů.

Zdroj: www.habr.com

Přidat komentář