Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла

Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла

праистория

Случи се така, че сървърът беше атакуван от ransomware вирус, който по „щастлива случайност“ частично остави .ibd файловете (файлове с необработени данни на innodb таблици) недокоснати, но в същото време напълно криптира .fpm файловете ( структурни файлове). В този случай .idb може да бъде разделен на:

  • подлежи на възстановяване чрез стандартни инструменти и водачи. За такива случаи има отличен да стане;
  • частично криптирани таблици. Предимно това са големи таблици, за които (доколкото разбирам) нападателите не са имали достатъчно RAM за пълно криптиране;
  • Е, напълно криптирани таблици, които не могат да бъдат възстановени.

Възможно е да се определи към коя опция принадлежат таблиците, като просто го отворите във всеки текстов редактор под желаното кодиране (в моя случай това е UTF8) и просто прегледате файла за наличие на текстови полета, например:

Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла

Освен това в началото на файла можете да видите голям брой 0 байта и вирусите, които използват алгоритъма за блоково криптиране (най-често срещаният), обикновено също ги засягат.
Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла

В моя случай нападателите оставиха 4-байтов низ (1, 0, 0, 0) в края на всеки шифрован файл, което опрости задачата. За търсене на незаразени файлове беше достатъчен скриптът:

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)

Така се оказа, че се намират файлове, принадлежащи към първия тип. Втората включва много ръчна работа, но това, което се намери, вече беше достатъчно. Всичко би било наред, но трябва да знаете абсолютно точна структура и (разбира се) възникна случай, че трябваше да работя с маса за често сменяне. Никой не запомни дали типът на полето е променен или е добавена нова колона.

Wilds City, за съжаление, не можа да помогне с такъв случай, поради което се пише тази статия.

По-близо до точката

Има структура на таблица от преди 3 месеца, която не съвпада с текущата (може едно поле, а може и повече). Структура на таблицата:

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

в този случай трябва да извлечете:

  • id_point int(11);
  • id_user int(11);
  • date_start ВРЕМЕ ЗА СРЕЩА;
  • date_finish ВРЕМЕ ЗА СРЕЩА.

За възстановяване се използва анализ байт по байт на .ibd файла, последван от конвертирането им в по-четлива форма. Тъй като, за да намерим това, от което се нуждаем, трябва да анализираме само типове данни като int и datatime, статията ще опише само тях, но понякога ще се позоваваме и на други типове данни, които могат да помогнат при други подобни инциденти.

Проблем 1: полета с типове DATETIME и TEXT имаха NULL стойности и те просто се пропускат във файла, поради това не беше възможно да се определи структурата за възстановяване в моя случай. В новите колони стойността по подразбиране беше null и част от транзакцията може да бъде загубена поради настройката innodb_flush_log_at_trx_commit = 0, така че ще трябва да се отдели допълнително време за определяне на структурата.

Проблем 2: трябва да се има предвид, че всички редове, изтрити чрез DELETE, ще бъдат в ibd файла, но с ALTER TABLE тяхната структура няма да бъде актуализирана. В резултат на това структурата на данните може да варира от началото на файла до неговия край. Ако често използвате OPTIMIZE TABLE, тогава е малко вероятно да срещнете такъв проблем.

Моля, обърнете внимание, версията на СУБД засяга начина, по който се съхраняват данните и този пример може да не работи за други основни версии. В моя случай беше използвана Windows версията на mariadb 10.1.24. Освен това, въпреки че в mariadb работите с InnoDB таблици, всъщност те са XtraDB, което изключва приложимостта на метода с InnoDB mysql.

Анализ на файла

В python, тип данни байтове() показва Unicode данни вместо обикновен набор от числа. Въпреки че можете да видите файла в тази форма, за удобство можете да конвертирате байтовете в числова форма, като преобразувате масива от байтове в обикновен масив (списък(example_byte_array)). Във всеки случай и двата метода са подходящи за анализ.

След като прегледате няколко ibd файла, можете да намерите следното:

Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла

Освен това, ако разделите файла по тези ключови думи, ще получите предимно равни блокове от данни. Ще използваме инфимум като делител.

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

Интересно наблюдение: за таблици с малко количество данни, между infimum и supremum има указател към броя на редовете в блока.

Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла — тестова маса с 1-ви ред

Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла - тестова таблица с 2 реда

Редовият масив table[0] може да бъде пропуснат. След като го прегледах, все още не можах да намеря необработените данни от таблицата. Най-вероятно този блок се използва за съхраняване на индекси и ключове.
Започвайки с table[1] и превеждайки я в числов масив, вече можете да забележите някои модели, а именно:

Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла

Това са int стойности, съхранени в низ. Първият байт показва дали числото е положително или отрицателно. В моя случай всички числа са положителни. От останалите 3 байта можете да определите числото, като използвате следната функция. Скрипт:

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

Например, 128, 0, 0, 1 = 1Или 128, 0, 75, 108 = 19308.
Таблицата имаше първичен ключ с автоматично нарастване и може да се намери и тук

Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла

След сравняване на данните от тестовите таблици беше разкрито, че обектът DATETIME се състои от 5 байта и започва с 153 (най-вероятно показващи годишни интервали). Тъй като диапазонът DATTIME е от „1000-01-01“ до „9999-12-31“, мисля, че броят на байтовете може да варира, но в моя случай данните попадат в периода от 2016 до 2019 г., така че ще приемем, че 5 байта са достатъчни.

За да се определи времето без секунди, бяха написани следните функции. Скрипт:

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}

Не беше възможно да напиша функционална функция за годината и месеца, така че трябваше да я хакна. Скрипт:

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

Сигурен съм, че ако отделите n време, това недоразумение може да бъде коригирано.
След това функция, която връща обект за дата и час от низ. Скрипт:

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)

Успява да открие често повтарящи се стойности от int, int, datetime, datetime Възстановяване на данни от XtraDB таблици без структурен файл с помощта на анализ байт по байт на ibd файла, изглежда, че това е, от което се нуждаете. Освен това такава последователност не се повтаря два пъти на ред.

Използвайки регулярен израз, намираме необходимите данни:

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)

Моля, имайте предвид, че при търсене с помощта на този израз няма да е възможно да се определят NULL стойности в задължителните полета, но в моя случай това не е критично. След това преминаваме през това, което намерихме в цикъл. Скрипт:

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)

Всъщност това е всичко, данните от масива с резултати са данните, от които се нуждаем. ###PS.###
Разбирам, че този метод не е подходящ за всички, но основната цел на статията е да подтикне към действие, а не да реши всичките ви проблеми. Мисля, че най-правилното решение би било да започнете сами да изучавате изходния код mariadb, но поради ограниченото време сегашният метод изглеждаше най-бързият.

В някои случаи, след като анализирате файла, ще можете да определите приблизителната структура и да го възстановите, като използвате един от стандартните методи от връзките по-горе. Това ще бъде много по-правилно и ще причини по-малко проблеми.

Източник: www.habr.com

Добавяне на нов коментар