ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası

ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası

Prehistorya

Elə oldu ki, server ransomware virusunun hücumuna məruz qaldı, bu virus “xoşbəxt bir qəza” ilə .ibd fayllarına (innodb cədvəllərinin xam məlumat faylları) qismən toxunulmaz, lakin eyni zamanda .fpm fayllarını tamamilə şifrələdi ( struktur faylları). Bu halda, .idb aşağıdakılara bölünə bilər:

  • standart alətlər və təlimatlar vasitəsilə bərpa edilməlidir. Belə hallar üçün əla var olmaq;
  • qismən şifrələnmiş cədvəllər. Əsasən bunlar böyük cədvəllərdir, onlar üçün (başa düşdüyüm kimi) təcavüzkarların tam şifrələmə üçün kifayət qədər operativ yaddaşı yox idi;
  • Yaxşı, bərpa edilə bilməyən tamamilə şifrələnmiş cədvəllər.

Cədvəllərin hansı seçimə aid olduğunu sadəcə olaraq istənilən kodlaşdırma altında istənilən mətn redaktorunda açmaqla (mənim vəziyyətimdə bu UTF8-dir) və sadəcə mətn sahələrinin olması üçün fayla baxmaqla müəyyən etmək mümkün oldu, məsələn:

ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası

Həmçinin, faylın əvvəlində çoxlu sayda 0 bayt müşahidə edə bilərsiniz və blok şifrələmə alqoritmini (ən çox yayılmış) istifadə edən viruslar adətən onlara da təsir göstərir.
ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası

Mənim vəziyyətimdə, təcavüzkarlar hər bir şifrələnmiş faylın sonunda 4 baytlıq bir sətir (1, 0, 0, 0) qoydular ki, bu da tapşırığı asanlaşdırdı. Yoluxmamış faylları axtarmaq üçün skript kifayət idi:

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)

Beləliklə, birinci növə aid faylların tapıldığı ortaya çıxdı. İkincisi çoxlu əl işlərini əhatə edir, lakin tapılanlar artıq kifayət idi. Hər şey yaxşı olardı, amma bilmək lazımdır tamamilə dəqiq struktur və (əlbəttə) tez-tez dəyişən masa ilə işləməli olduğum bir vəziyyət yarandı. Sahənin növünün dəyişdirildiyini və ya yeni sütunun əlavə edildiyini heç kim xatırlamadı.

Wilds City, təəssüf ki, belə bir işdə kömək edə bilmədi, buna görə də bu məqalə yazılır.

Nöqtəyə gəlin

3 ay əvvəlki cədvəlin indiki ilə üst-üstə düşməyən strukturu var (bəlkə bir sahə, bəlkə də daha çox). Cədvəlin quruluşu:

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

bu halda çıxarmaq lazımdır:

  • id_point int(11);
  • id_user int(11);
  • date_start TARİX VAXT;
  • date_finish TARİX VAXT.

Bərpa üçün, .ibd faylının bayt-bayt təhlili istifadə olunur, sonra onları daha oxunaqlı bir formaya çevirmək lazımdır. Bizə lazım olanı tapmaq üçün yalnız int və datatime kimi məlumat növlərini təhlil etməliyik, məqalədə yalnız onları təsvir edəcəyik, lakin bəzən digər oxşar hadisələrdə kömək edə biləcək digər məlumat növlərinə də istinad edəcəyik.

Problem 1: DATETIME və TEXT tipli sahələr NULL dəyərlərə malik idi və onlar sadəcə olaraq faylda atlanır, buna görə mənim vəziyyətimdə bərpa ediləcək strukturu müəyyən etmək mümkün olmadı. Yeni sütunlarda standart dəyər sıfır idi və innodb_flush_log_at_trx_commit = 0 parametrinə görə əməliyyatın bir hissəsi itirilə bilər, ona görə də strukturu müəyyən etmək üçün əlavə vaxt sərf edilməlidir.

Problem 2: nəzərə almaq lazımdır ki, DELETE vasitəsilə silinən sətirlər hamısı ibd faylında olacaq, lakin ALTER TABLE ilə onların strukturu yenilənməyəcək. Nəticədə məlumat strukturu faylın əvvəlindən sonuna qədər dəyişə bilər. Əgər siz tez-tez OPTIMIZE TABLE-dən istifadə edirsinizsə, o zaman belə bir problemlə qarşılaşma ehtimalınız azdır.

Xahiş edirik, DBMS versiyası məlumatların saxlanma üsuluna təsir göstərir və bu nümunə digər əsas versiyalar üçün işləməyə bilər. Mənim vəziyyətimdə, mariadb 10.1.24-ün Windows versiyası istifadə edilmişdir. Həmçinin, mariadb-də InnoDB cədvəlləri ilə işləsəniz də, əslində onlar var XtraDB, bu metodun InnoDB mysql ilə tətbiqini istisna edir.

Fayl təhlili

Pythonda məlumat növü bayt() adi nömrələr dəsti yerinə Unicode məlumatlarını göstərir. Fayla bu formada baxa bilsəniz də, rahatlıq üçün bayt massivini adi massiləyə çevirərək baytları ədədi formaya çevirə bilərsiniz (siyahı(misal_bayt_array)). Hər halda, hər iki üsul analiz üçün uyğundur.

Bir neçə ibd faylına baxdıqdan sonra aşağıdakıları tapa bilərsiniz:

ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası

Üstəlik, faylı bu açar sözlərə bölsəniz, əsasən hətta məlumat blokları əldə edəcəksiniz. Bölən kimi infimumdan istifadə edəcəyik.

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

Maraqlı bir müşahidə: az miqdarda məlumat olan cədvəllər üçün infimum və supremum arasında blokdakı sətirlərin sayına işarə var.

ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası — 1-ci sıra ilə sınaq masası

ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası - 2 sıra ilə sınaq masası

Sıra massivi cədvəli[0] atlana bilər. Baxdıqdan sonra mən hələ də xam cədvəl məlumatlarını tapa bilmədim. Çox güman ki, bu blok indeksləri və açarları saxlamaq üçün istifadə olunur.
Cədvəldən [1] başlayaraq onu rəqəmsal massivə çevirməklə, artıq bəzi nümunələri görə bilərsiniz, yəni:

ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası

Bunlar sətirdə saxlanılan int dəyərlərdir. Birinci bayt rəqəmin müsbət və ya mənfi olduğunu göstərir. Mənim vəziyyətimdə bütün rəqəmlər müsbətdir. Qalan 3 baytdan aşağıdakı funksiyadan istifadə edərək nömrəni təyin edə bilərsiniz. Ssenari:

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

Məsələn, 128, 0, 0, 1 = 1Və ya 128, 0, 75, 108 = 19308.
Cədvəlin avtomatik artımı olan əsas açarı var idi və onu da burada tapmaq olar

ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası

Test cədvəllərindən alınan məlumatları müqayisə etdikdən sonra məlum oldu ki, DATETIME obyekti 5 baytdan ibarətdir və 153 ilə başlayır (çox güman ki, illik intervalları göstərir). DATTIME diapazonu '1000-01-01' ilə '9999-12-31' arasında olduğundan, düşünürəm ki, baytların sayı dəyişə bilər, lakin mənim vəziyyətimdə məlumatlar 2016-2019-cu illərə düşür, ona görə də biz fərz edəcəyik 5 bayt kifayətdir.

Saniyəsiz vaxtı müəyyən etmək üçün aşağıdakı funksiyalar yazılmışdır. Ssenari:

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}

İl və ay üçün funksional funksiya yazmaq mümkün olmadığından onu sındırmalı oldum. Ssenari:

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

Əminəm ki, n çox vaxt sərf etsəniz, bu anlaşılmazlıq düzəldilə bilər.
Sonra, sətirdən datetime obyektini qaytaran funksiya. Ssenari:

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)

Biz int, int, datetime, datetime-dan tez-tez təkrarlanan dəyərləri aşkarlaya bildik ibd faylının bayt-bayt analizindən istifadə edərək struktur faylı olmadan XtraDB cədvəllərindən məlumatların bərpası, deyəsən bu sizə lazım olan şeydir. Üstəlik, belə bir ardıcıllıq hər sətirdə iki dəfə təkrarlanmır.

Normal bir ifadədən istifadə edərək lazımi məlumatları tapırıq:

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)

Nəzərə alın ki, bu ifadədən istifadə edərək axtarış apararkən tələb olunan sahələrdə NULL dəyərləri müəyyən etmək mümkün olmayacaq, amma mənim vəziyyətimdə bu kritik deyil. Sonra bir döngədə tapdığımızı keçirik. 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)

Əslində, hamısı budur, nəticə massivindəki məlumatlar bizə lazım olan məlumatlardır. ###PS.###
Mən başa düşürəm ki, bu üsul hər kəs üçün uyğun deyil, lakin məqalənin əsas məqsədi bütün problemlərinizi həll etməkdənsə, dərhal hərəkətə keçməkdir. Məncə, ən düzgün həll mənbə kodunu özünüz öyrənməyə başlamaq olardı mariadb, lakin məhdud vaxta görə indiki üsul ən sürətli kimi görünürdü.

Bəzi hallarda faylı təhlil etdikdən sonra yuxarıdakı keçidlərdən standart üsullardan birini istifadə edərək təxmini strukturu müəyyən edə və onu bərpa edə biləcəksiniz. Bu, daha düzgün olacaq və daha az problem yaradacaq.

Mənbə: www.habr.com

Добавить комментарий