Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке

Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке

praistorija

Десило се да је сервер нападнут вирусом рансомваре-а, који је „срећним случајем“ делимично оставио .ибд датотеке (датотеке сирових података иннодб табела) нетакнутим, али је истовремено потпуно шифровао .фпм датотеке ( структурне датотеке). У овом случају, .идб се може поделити на:

  • подлежащие восстановлению через стандартные средства и гайды. Для таких случаев, есть отличная постати;
  • делимично шифроване табеле. Углавном су то велике табеле, за које (како разумем) нападачи нису имали довољно РАМ-а за потпуну енкрипцију;
  • Па, потпуно шифроване табеле које се не могу вратити.

Било је могуће утврдити којој опцији припадају табеле једноставним отварањем у било ком уређивачу текста под жељеним кодирањем (у мом случају је то УТФ8) и једноставним прегледом фајла за присуство текстуалних поља, на пример:

Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке

Такође, на почетку датотеке можете уочити велики број 0 бајтова, а вируси који користе алгоритам блок шифровања (најчешћи) обично утичу и на њих.
Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке

У мом случају, нападачи су оставили стринг од 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)

Тако се испоставило да су пронађене датотеке које припадају првом типу. Други подразумева доста ручног рада, али оно што је пронађено већ је било довољно. Све би било у реду, али морате знати апсолутно прецизна структура и (наравно) појавио се случај да сам морао да радим са столом који се често мења. Нико се није сетио да ли је промењен тип поља или је додата нова колона.

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

Доћи до тачке

Постоји структура табеле од пре 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 инт(11);
  • id_user инт(11);
  • date_start ДАТУМ ВРЕМЕ;
  • date_finish ДАТУМ ВРЕМЕ.

За опоравак се користи анализа бајта по бајт .ибд датотеке, након чега се конвертује у читљивији облик. Пошто да бисмо пронашли оно што нам је потребно, потребно је само да анализирамо типове података као што су инт и дататиме, чланак ће описати само њих, али понекад ћемо се позвати и на друге типове података, који могу помоћи у другим сличним инцидентима.

КСНУМКС проблем: поља са типовима ДАТЕТИМЕ и ТЕКСТ су имала НУЛЛ вредности и једноставно се прескачу у датотеци, због тога није било могуће одредити структуру за враћање у мом случају. У новим колонама, подразумевана вредност је била нулта, а део трансакције би могао бити изгубљен због подешавања иннодб_флусх_лог_ат_трк_цоммит = 0, тако да би се додатно време морало потрошити да би се утврдила структура.

КСНУМКС проблем: треба узети у обзир да ће сви редови избрисани преко ДЕЛЕТЕ бити у ибд датотеци, али са АЛТЕР ТАБЛЕ њихова структура неће бити ажурирана. Као резултат тога, структура података може варирати од почетка датотеке до њеног краја. Ако често користите ОПТИМИЗЕ ТАБЛЕ, мало је вероватно да ћете наићи на такав проблем.

Обратите внимание, верзија ДБМС-а утиче на начин на који се подаци чувају, а овај пример можда неће радити за друге главне верзије. У мом случају је коришћена виндовс верзија мариадб 10.1.24. Такође, иако у мариадб-у радите са ИнноДБ табелама, у ствари јесу КстраДБ, што искључује применљивост методе са ИнноДБ мискл.

Анализа датотека

У Питхон-у, тип података бајтови() приказује Уницоде податке уместо регуларног скупа бројева. Иако можете да видите датотеку у овом облику, због погодности можете да конвертујете бајтове у нумерички облик тако што ћете конвертовати низ бајтова у обичан низ (лист(пример_бајт_низ)). У сваком случају, обе методе су погодне за анализу.

Просмотрев несколько ibd файлов, можно встретить следующие:

Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке

Штавише, ако поделите датотеку према овим кључним речима, добићете углавном чак и блокове података. Користићемо инфимум као делилац.

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

Занимљиво запажање: за табеле са малом количином података, између инфимума и супремума постоји показивач на број редова у блоку.

Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке — тест табела са 1. редом

Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке - тест табела са 2 реда

Табела низа редова[0] може се прескочити. Након што сам га прегледао, још увек нисам могао да пронађем необрађене податке табеле. Највероватније, овај блок се користи за складиштење индекса и кључева.
Начиная с table[1] и переведя её в числовой массив, уже можно заметить некоторые закономерности, а именно:

Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке

Ово су инт вредности ускладиштене у низу. Први бајт показује да ли је број позитиван или негативан. У мом случају, сви бројеви су позитивни. Од преостала 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.
В таблице имелся первичный ключ с автоинкрементом, и здесь его также можно обнаружить

Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке

Упоређивањем података из тестних табела, откривено је да се објекат ДАТЕТИМЕ састоји од 5 бајтова и почиње са 153 (што највероватније указује на годишње интервале). Пошто је ДАТТИМЕ опсег од '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 число времени, то и это недоразумение можно исправить.
Далее, функция возвращающая объект datetime из строки. Скрипт:

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 Опоравак података из КстраДБ табела без датотеке структуре коришћењем бајт по бајт анализе ибд датотеке, похоже это то что нужно. Причём, такая последовательность дважды за строку не повторяется.

Користећи регуларни израз, налазимо потребне податке:

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)

Имајте на уму да када претражујете користећи овај израз, неће бити могуће одредити НУЛЛ вредности у обавезним пољима, али у мом случају то није критично. Затим пролазимо кроз оно што смо пронашли у петљи. Скрипта:

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.###
Разумем да овај метод није погодан за све, али главни циљ чланка је да подстакне акцију, а не да реши све ваше проблеме. Мислим да би најисправније решење било да сами почнете да проучавате изворни код мариадб, но в связи с ограниченным временем, текущий способ показался наиболее быстрый.

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

Извор: ввв.хабр.цом

Додај коментар