بازیابی داده ها از جداول 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 متاسفانه نتوانست به چنین موردی کمک کند و به همین دلیل این مقاله نوشته می شود.

برو سر اصل مطلب

ساختار جدول 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 داشتند و به سادگی در فایل حذف می شوند، به همین دلیل، تعیین ساختار برای بازیابی در مورد من امکان پذیر نبود. در ستون‌های جدید، مقدار پیش‌فرض null بود و بخشی از تراکنش ممکن است به دلیل تنظیم innodb_flush_log_at_trx_commit = 0 از بین برود، بنابراین زمان بیشتری برای تعیین ساختار باید صرف شود.

مشکل 2: باید در نظر داشت که ردیف هایی که از طریق DELETE حذف می شوند، همگی در فایل ibd قرار می گیرند، اما با ALTER TABLE ساختار آنها به روز نمی شود. در نتیجه، ساختار داده می تواند از ابتدای فایل تا انتهای آن متفاوت باشد. اگر اغلب از OPTIMIZE TABLE استفاده می کنید، بعید است که با چنین مشکلی مواجه شوید.

یادداشت، نسخه DBMS بر نحوه ذخیره داده ها تأثیر می گذارد و این مثال ممکن است برای سایر نسخه های اصلی کارایی نداشته باشد. در مورد من از نسخه ویندوز mariadb 10.1.24 استفاده شد. همچنین، اگرچه در mariadb شما با جداول InnoDB کار می کنید، در واقع آنها هستند XtraDB، که کاربرد روش را با InnoDB mysql مستثنی می کند.

تجزیه و تحلیل فایل

در پایتون، نوع داده بایت () داده های یونیکد را به جای مجموعه ای منظم از اعداد نمایش می دهد. اگرچه می توانید فایل را در این فرم مشاهده کنید، اما برای راحتی کار می توانید با تبدیل آرایه بایت به یک آرایه معمولی (list(example_byte_array)) بایت ها را به شکل عددی تبدیل کنید. در هر صورت هر دو روش برای تحلیل مناسب هستند.

پس از بررسی چندین فایل 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 مقدار زمان صرف کنید، این سوء تفاهم قابل اصلاح است.
در مرحله بعد، تابعی که یک شی 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 بازیابی داده ها از جداول 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)

در واقع، این همه چیز است، داده های آرایه نتیجه، داده هایی است که ما نیاز داریم. ###PS.###
من می دانم که این روش برای همه مناسب نیست، اما هدف اصلی مقاله به جای حل همه مشکلات شما، سریع عمل کردن است. من فکر می کنم درست ترین راه حل این است که خودتان شروع به مطالعه کد منبع کنید ماریادب، اما به دلیل زمان محدود، روش فعلی سریعترین به نظر می رسید.

در برخی موارد، پس از تجزیه و تحلیل فایل، می توانید ساختار تقریبی آن را تعیین کرده و با استفاده از یکی از روش های استاندارد از لینک های بالا، آن را بازیابی کنید. این بسیار صحیح تر خواهد بود و مشکلات کمتری ایجاد می کند.

منبع: www.habr.com

اضافه کردن نظر