Враќање податоци од табелите XtraDB без структура на датотека со користење бајт-по-бајт анализа на датотеката ibd

Враќање податоци од табелите XtraDB без структура на датотека со користење бајт-по-бајт анализа на датотеката ibd

праисторијата

Се случи серверот да биде нападнат од вирус за откуп, кој, по „среќна несреќа“, делумно ги остави недопрени датотеките .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 вредности и тие едноставно се прескокнуваат во датотеката, поради тоа, не беше можно да се одреди структурата што треба да се врати во мојот случај. Во новите колони, стандардната вредност беше нула, а дел од трансакцијата може да се изгуби поради поставката innodb_flush_log_at_trx_commit = 0, така што ќе треба да се потроши дополнително време за да се одреди структурата.

Проблем 2: треба да се земе предвид дека редовите избришани преку DELETE ќе бидат сите во датотеката ibd, но со ALTER TABLE нивната структура нема да се ажурира. Како резултат на тоа, структурата на податоците може да варира од почетокот на датотеката до нејзиниот крај. Ако често користите OPTIMIZE TABLE, тогаш веројатно нема да наидете на таков проблем.

Обрни внимание, верзијата на DBMS влијае на начинот на зачувување на податоците и овој пример може да не работи за други поголеми верзии. Во мојот случај, користена е верзијата на windows на mariadb 10.1.24. Исто така, иако во mariadb работите со InnoDB табели, всушност тие се XtraDB, што ја исклучува применливоста на методот со InnoDB mysql.

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

Во python, тип на податоци бајти () прикажува податоци за Уникод наместо обичен сет на броеви. Иако можете да ја видите датотеката во оваа форма, за погодност можете да ги конвертирате бајтите во нумеричка форма со конвертирање на низата бајти во редовна низа (листа(пример_бајт_низа)). Во секој случај, двата методи се погодни за анализа.

Откако ќе погледнете низ неколку датотеки ibd, можете да го најдете следново:

Враќање податоци од табелите XtraDB без структура на датотека со користење бајт-по-бајт анализа на датотеката ibd

Покрај тоа, ако ја поделите датотеката со овие клучни зборови, ќе добиете главно дури и блокови на податоци. Ќе користиме infimum како делител.

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

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

Враќање податоци од табелите XtraDB без структура на датотека со користење бајт-по-бајт анализа на датотеката ibd — тест табела со 1 ред

Враќање податоци од табелите XtraDB без структура на датотека со користење бајт-по-бајт анализа на датотеката ibd - тест табела со 2 реда

Табелата со низа со редови[0] може да се прескокне. Откако ја разгледав, сè уште не можев да ги најдам необработените податоци од табелата. Најверојатно, овој блок се користи за складирање на индекси и клучеви.
Почнувајќи од табелата[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)

Всушност, тоа е сè, податоците од низата со резултати се податоците што ни се потребни. ###ПС.###
Разбирам дека овој метод не е погоден за секого, но главната цел на статијата е да поттикне акција наместо да ги реши сите ваши проблеми. Мислам дека најправилно решение би било самите да започнете да го проучувате изворниот код mariadb, но поради ограниченото време, сегашниот метод се чинеше дека е најбрз.

Во некои случаи, по анализата на датотеката, ќе можете да ја одредите приближната структура и да ја вратите користејќи еден од стандардните методи од горните врски. Ова ќе биде многу поправилно и ќе предизвика помалку проблеми.

Извор: www.habr.com

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