L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl ibd

L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl ibd

preistorja

Так произошло, что сервере был атакован вирусом шифровальщиком, который по "счастливой случайности", частично отставил не тронутыми файлы .ibd (файлы сырых данных innodb таблиц), но при этом полностью зашифровал файлы .fpm (файлы структур). При этом .idb можно было поделить на:

  • подлежащие восстановлению через стандартные средства и гайды. Для таких случаев, есть отличная issir;
  • частично зашифрованные таблицы. Преимущественно это большие таблицы, на которые (как я понял), злоумышленниками не хватило оперативной памяти на полное шифрование;
  • Ukoll, tabelli kompletament encrypted li ma jistgħux jiġu restawrati.

Определить к какому из варианта относятся таблицы удалось банальным открыв в любом текстовом редакторе под нужной кодировкой (в моём случае это UTF8) и просто просмотреть просмотреть файл на наличие текстовых полей, например:

L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl ibd

Также, в начале файла можно наблюдать большое количество 0-вых байт, а вирусы использующие алгоритм блочного шифрования (наиболее распространено), обычно и их затрагивают.
L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl 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)

Таким образом получилось найти файлы принадлежащие к первому тип. Второй подразумевает долгую мануальщину, но найденного уже было достаточно. Всё бы хорошо, но необходимо знать struttura assolutament preċiża и (разумеется) вышел такой случай, что пришлось работать с часто меняющейся таблицей. Никто и не помнил, то ли тип поля менялся, то ли добавлялся новый столбец.

Дебри сити к сожалению не смогли помочь с таким случаем, поэтому и пишется данная статья.

Niżżel il-punt

Есть структура таблицы 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)
); 

f'dan il-każ, għandek bżonn estratt:

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

Для восстановление используется побайтовый анализ .ibd файла, с последующим переводом их в более читаемый вид. Так как для поиска требуемого, нам достаточно проанализировать такие типы данных как int и datatime, в статье будут описаны только они, но иногда будуn ссылаться и на другие типы данных, что может помочь в иных подобных происшествиях.

Problema 1: oqsma bit-tipi DATETIME u TEXT kellhom valuri NULL, u huma sempliċement maqbuża fil-fajl, minħabba dan, ma kienx possibbli li tiġi determinata l-istruttura li tirrestawra fil-każ tiegħi. Fil-kolonni l-ġodda, il-valur default kien null, u parti mit-tranżazzjoni setgħet tintilef minħabba l-issettjar innodb_flush_log_at_trx_commit = 0, għalhekk ikollu jintefaq ħin addizzjonali biex tiġi ddeterminata l-istruttura.

Problema 2: следует учесть, что строки удалённые через DELETE, все ровно будут находится в ibd файле, но при ALTER TABLE их структура обновятся не будет. В итоге, структура данных может варьироватся от начала файла, к его концу. Если вы часто используете OPTIMIZE TABLE, то с подобной проблемой вряд ли столкнетесь.

Oqgħod attent, версия СУБД влияет на способ хранения данных, и данный пример может не сработать для других мажорных версий. В моём случае использовалась windows версия mariadb 10.1.24. Также, хоть и в mariadb вы работаете с InnoDB таблицами, но по факту они являются XtraDB, li jeskludi l-applikabilità tal-metodu b'InnoDB mysql.

Analiżi tal-fajl

F'python, tip ta' dejta bytes () отображает данные в юникоде в место обычного набора чисел. Хоть рассматривать файл можно и в таком виде, но для удобства можно перевести байты в числовой вид переведя массив байт в обычный массив (list(example_byte_array)). В любом случае, для анализа пригладятся оба способа.

Wara li tħares minn diversi fajls ibd, tista' ssib dan li ġej:

L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl ibd

При чём, если делить файл по этим ключевым словам, получатся преимущественно ровные блоки данных. Будем использовать infimum как делитель.

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

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

L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl ibd — tabella tat-test bl-ewwel ringiela

L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl ibd - tabella tat-test b'2 ringieli

Массив строк table[0] можно пропустить. Просмотрев его, мне так и не удалось обнаружить сырые данные таблиц. Скорей всего, данный блок служит для хранения индексов и ключей.
Начиная с table[1] и переведя её в числовой массив, уже можно заметить некоторые закономерности, а именно:

L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl 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

Per eżempju, 128, 0, 0, 1 = 1Jew 128, 0, 75, 108 = 19308.
В таблице имелся первичный ключ с автоинкрементом, и здесь его также можно обнаружить

L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl ibd

Wara li qabbel id-dejta mit-tabelli tat-test, ġie żvelat li l-oġġett DATETIME jikkonsisti f'5 bytes u beda b'153 (x'aktarx li jindika intervalli annwali). Peress li l-firxa DATTIME hija '1000-01-01' sa '9999-12-31', naħseb li n-numru ta 'bytes jista' jvarja, iżda fil-każ tiegħi, id-dejta taqa 'fil-perjodu mill-2016 sal-2019, għalhekk se nassumu li 5 bytes biżżejjed.

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

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 число времени, то и это недоразумение можно исправить.
Sussegwentement, funzjoni li tirritorna oġġett datetime minn string. 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)

Удалось обнаружить часто повторяющиеся значения из int, int, datetime, datetime L-irkupru tad-dejta minn tabelli XtraDB mingħajr fajl struttura bl-użu ta’ analiżi byte b’byte tal-fajl ibd, похоже это то что нужно. Причём, такая последовательность дважды за строку не повторяется.

Bl-użu ta' espressjoni regolari, insibu d-dejta meħtieġa:

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)

Собственно всё, данные из массива result, это и есть необходимые нам данные. ###PS.###
Я понимаю что такой способ подойдёт далеко не всем, но основная цель статьи скорей натолкнуть на действие, чем решить все ваши проблемы. Думаю наиболее правильное решение было бы начать изучать исходный код самой mariadb, но в связи с ограниченным временем, текущий способ показался наиболее быстрый.

В некоторых случаях, проанализировав файл, вы сможете определить примерную структуру и восстановить одним из стандартных способов из ссылок выше. Это будет гораздо правильней и вызовет меньше проблем.

Sors: www.habr.com

Żid kumment