Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd

Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd

prasejarah

Kebetulan server diserang oleh virus ransomware, yang, secara kebetulan, membiarkan sebagian file .ibd (file data mentah tabel innodb) tidak tersentuh, tetapi pada saat yang sama mengenkripsi file .fpm sepenuhnya ( file struktur). Dalam hal ini, .idb dapat dibagi menjadi:

  • dapat direstorasi melalui alat dan panduan standar. Untuk kasus seperti itu, ada solusi terbaik menjadi;
  • tabel terenkripsi sebagian. Sebagian besar ini adalah tabel besar, yang (seperti yang saya pahami) penyerang tidak memiliki cukup RAM untuk enkripsi penuh;
  • Ya, tabel terenkripsi sepenuhnya yang tidak dapat dipulihkan.

Dimungkinkan untuk menentukan opsi mana yang dimiliki tabel hanya dengan membukanya di editor teks apa pun dengan pengkodean yang diinginkan (dalam kasus saya ini adalah UTF8) dan cukup melihat file untuk mengetahui keberadaan bidang teks, misalnya:

Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd

Selain itu, di awal file Anda dapat mengamati sejumlah besar 0 byte, dan virus yang menggunakan algoritma enkripsi blok (yang paling umum) biasanya juga memengaruhinya.
Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd

Dalam kasus saya, penyerang meninggalkan string 4-byte (1, 0, 0, 0) di akhir setiap file terenkripsi, yang menyederhanakan tugas. Untuk mencari file yang tidak terinfeksi, skripnya cukup:

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)

Jadi, ternyata menemukan file milik tipe pertama. Yang kedua melibatkan banyak pekerjaan manual, tetapi apa yang ditemukan sudah cukup. Semuanya akan baik-baik saja, tetapi Anda perlu mengetahuinya struktur yang benar-benar tepat dan (tentu saja) muncul kasus bahwa saya harus bekerja dengan meja yang sering berpindah-pindah. Tidak ada yang ingat apakah jenis bidang diubah atau kolom baru ditambahkan.

Sayangnya, Wilds City tidak dapat membantu mengatasi kasus seperti itu, itulah sebabnya artikel ini ditulis.

Langsung ke intinya

Ada struktur tabel dari 3 bulan lalu yang tidak sesuai dengan tabel saat ini (mungkin satu bidang, dan mungkin lebih). Struktur tabel:

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

dalam hal ini, Anda perlu mengekstrak:

  • id_point int(11);
  • id_user int(11);
  • date_start TANGGAL WAKTU;
  • date_finish TANGGAL WAKTU.

Untuk pemulihan, analisis file .ibd byte demi byte digunakan, diikuti dengan mengubahnya menjadi bentuk yang lebih mudah dibaca. Karena untuk menemukan apa yang kita butuhkan, kita hanya perlu menganalisis tipe data seperti int dan datatime, artikel ini hanya akan menjelaskannya saja, namun terkadang kita juga akan merujuk ke tipe data lain, yang dapat membantu dalam kejadian serupa lainnya.

Masalah 1: bidang dengan tipe DATETIME dan TEXT memiliki nilai NULL, dan keduanya dilewati begitu saja dalam file, karena itu, tidak mungkin menentukan struktur yang akan dipulihkan dalam kasus saya. Di kolom baru, nilai defaultnya adalah null, dan sebagian transaksi mungkin hilang karena pengaturan innodb_flush_log_at_trx_commit = 0, jadi waktu tambahan harus dihabiskan untuk menentukan strukturnya.

Masalah 2: perlu diingat bahwa baris yang dihapus melalui DELETE semuanya akan ada di file ibd, tetapi dengan ALTER TABLE strukturnya tidak akan diperbarui. Akibatnya, struktur data dapat bervariasi dari awal file hingga akhir. Jika Anda sering menggunakan OPTIMIZE TABLE, kemungkinan besar Anda tidak akan mengalami masalah seperti itu.

Catatan, versi DBMS mempengaruhi cara data disimpan, dan contoh ini mungkin tidak berfungsi untuk versi utama lainnya. Dalam kasus saya, mariadb versi windows 10.1.24 digunakan. Selain itu, meskipun di mariadb Anda bekerja dengan tabel InnoDB, kenyataannya memang demikian XtraDB, yang mengecualikan penerapan metode dengan InnoDB mysql.

Analisis berkas

Di python, tipe data byte() menampilkan data Unicode sebagai pengganti serangkaian angka biasa. Meskipun Anda dapat melihat file dalam bentuk ini, untuk kenyamanan Anda dapat mengonversi byte menjadi bentuk numerik dengan mengonversi array byte menjadi array biasa (daftar(example_byte_array)). Bagaimanapun, kedua metode ini cocok untuk analisis.

Setelah memeriksa beberapa file ibd, Anda dapat menemukan yang berikut:

Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd

Selain itu, jika Anda membagi file dengan kata kunci ini, Anda akan mendapatkan sebagian besar blok data yang rata. Kami akan menggunakan infimum sebagai pembagi.

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

Pengamatan yang menarik: untuk tabel dengan jumlah data yang sedikit, antara infimum dan supremum terdapat penunjuk jumlah baris dalam blok.

Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd — tabel uji dengan baris pertama

Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd - meja tes dengan 2 baris

Tabel array baris[0] dapat dilewati. Setelah memeriksanya, saya masih tidak dapat menemukan data tabel mentah. Kemungkinan besar, blok ini digunakan untuk menyimpan indeks dan kunci.
Dimulai dengan tabel[1] dan menerjemahkannya ke dalam array numerik, Anda sudah dapat melihat beberapa pola, yaitu:

Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd

Ini adalah nilai int yang disimpan dalam sebuah string. Byte pertama menunjukkan apakah bilangan tersebut positif atau negatif. Dalam kasus saya, semua angkanya positif. Dari sisa 3 byte, Anda dapat menentukan jumlahnya menggunakan fungsi berikut. Naskah:

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

Misalnya, 128, 0, 0, 1 = 1Atau 128, 0, 75, 108 = 19308.
Tabel tersebut memiliki kunci utama dengan kenaikan otomatis, dan juga dapat ditemukan di sini

Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd

Setelah membandingkan data dari tabel pengujian, terungkap bahwa objek DATETIME terdiri dari 5 byte dan dimulai dengan 153 (kemungkinan besar menunjukkan interval tahunan). Karena rentang DATTIME adalah '1000-01-01' hingga '9999-12-31', menurut saya jumlah byte dapat bervariasi, tetapi dalam kasus saya, datanya berada pada periode 2016 hingga 2019, jadi kita asumsikan itu 5 byte cukup.

Untuk menentukan waktu tanpa detik, ditulis fungsi berikut. Naskah:

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}

Tidak mungkin menulis fungsi fungsional untuk tahun dan bulan, jadi saya harus meretasnya. Naskah:

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

Saya yakin jika Anda menghabiskan waktu sebanyak n, kesalahpahaman ini dapat diperbaiki.
Selanjutnya, fungsi yang mengembalikan objek datetime dari sebuah string. Naskah:

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)

Berhasil mendeteksi nilai yang sering diulang dari int, int, datetime, datetime Memulihkan data dari tabel XtraDB tanpa file struktur menggunakan analisis byte demi byte dari file ibd, sepertinya inilah yang Anda butuhkan. Selain itu, urutan seperti itu tidak diulang dua kali per baris.

Menggunakan ekspresi reguler, kami menemukan data yang diperlukan:

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)

Harap dicatat bahwa ketika mencari menggunakan ekspresi ini, tidak mungkin untuk menentukan nilai NULL di bidang yang diperlukan, tetapi dalam kasus saya ini tidak penting. Lalu kita membahas apa yang kita temukan dalam satu lingkaran. Naskah:

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)

Sebenarnya itu saja, data dari array hasil adalah data yang kita butuhkan. ###PS.###
Saya memahami bahwa metode ini tidak cocok untuk semua orang, tetapi tujuan utama artikel ini adalah untuk segera mengambil tindakan daripada menyelesaikan semua masalah Anda. Saya pikir solusi paling tepat adalah mulai mempelajari kode sumbernya sendiri mariadb, namun karena keterbatasan waktu, metode saat ini sepertinya yang tercepat.

Dalam beberapa kasus, setelah menganalisis file, Anda akan dapat menentukan perkiraan struktur dan memulihkannya menggunakan salah satu metode standar dari tautan di atas. Ini akan jauh lebih tepat dan menimbulkan lebih sedikit masalah.

Sumber: www.habr.com

Tambah komentar