Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd

Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd

thời tiền sử

Điều đó đã xảy ra khi máy chủ bị tấn công bởi một loại virus ransomware, do một "tai nạn may mắn", một phần đã khiến các tệp .ibd (tệp dữ liệu thô của bảng innodb) không bị ảnh hưởng, nhưng đồng thời mã hóa hoàn toàn các tệp .fpm ( tập tin cấu trúc). Trong trường hợp này, .idb có thể được chia thành:

  • có thể phục hồi thông qua các công cụ và hướng dẫn tiêu chuẩn. Đối với những trường hợp như vậy, có một giải pháp tuyệt vời trở nên;
  • bảng được mã hóa một phần. Hầu hết đây là những bảng lớn, mà (theo tôi hiểu) những kẻ tấn công không có đủ RAM để mã hóa toàn bộ;
  • Chà, các bảng được mã hóa hoàn toàn không thể khôi phục được.

Có thể xác định các bảng thuộc về tùy chọn nào bằng cách chỉ cần mở nó trong bất kỳ trình soạn thảo văn bản nào theo mã hóa mong muốn (trong trường hợp của tôi là UTF8) và chỉ cần xem tệp để biết sự hiện diện của các trường văn bản, ví dụ:

Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd

Ngoài ra, ở phần đầu của tệp, bạn có thể quan sát thấy một số lượng lớn 0 byte và vi-rút sử dụng thuật toán mã hóa khối (phổ biến nhất) cũng thường ảnh hưởng đến chúng.
Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd

Trong trường hợp của tôi, những kẻ tấn công đã để lại một chuỗi 4 byte (1, 0, 0, 0) ở cuối mỗi tệp được mã hóa, điều này đã đơn giản hóa tác vụ. Để tìm kiếm các tệp không bị nhiễm, tập lệnh là đủ:

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)

Vì vậy, hóa ra là tìm thấy các tập tin thuộc loại đầu tiên. Việc thứ hai liên quan đến rất nhiều công việc thủ công, nhưng những gì tìm được đã là đủ. Mọi chuyện sẽ ổn thôi, nhưng bạn cần biết cấu trúc hoàn toàn chính xác và (tất nhiên) có một trường hợp xảy ra là tôi phải làm việc với một cái bàn thường xuyên thay đổi. Không ai nhớ liệu loại trường đã được thay đổi hay cột mới đã được thêm vào.

Thật không may, Wilds City không thể giúp đỡ trong trường hợp như vậy, đó là lý do tại sao bài viết này được viết.

Gần hơn với điểm

Có cấu trúc của một bảng từ 3 tháng trước không trùng với cấu trúc của bảng hiện tại (có thể là một trường và có thể nhiều hơn). Cấu trúc bảng:

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

trong trường hợp này, bạn cần trích xuất:

  • id_point int(11);
  • id_user int(11);
  • date_start NGÀY GIỜ;
  • date_finish NGÀY GIỜ.

Để khôi phục, phân tích từng byte của tệp .ibd được sử dụng, sau đó chuyển đổi chúng thành dạng dễ đọc hơn. Vì để tìm được thứ mình cần nên chúng ta chỉ cần phân tích các kiểu dữ liệu như int và datatime nên bài viết sẽ chỉ mô tả chúng nhưng đôi khi chúng ta cũng sẽ đề cập đến các kiểu dữ liệu khác, có thể giúp ích trong các sự cố tương tự khác.

Vấn đề 1: các trường có loại DATETIME và TEXT có giá trị NULL và chúng chỉ bị bỏ qua trong tệp, vì điều này, không thể xác định cấu trúc để khôi phục trong trường hợp của tôi. Trong các cột mới, giá trị mặc định là null và một phần giao dịch có thể bị mất do cài đặt innodb_flush_log_at_trx_commit = 0, do đó sẽ phải dành thêm thời gian để xác định cấu trúc.

Vấn đề 2: cần lưu ý rằng các hàng bị xóa qua DELETE đều sẽ nằm trong tệp ibd, nhưng với ALTER TABLE cấu trúc của chúng sẽ không được cập nhật. Kết quả là cấu trúc dữ liệu có thể thay đổi từ đầu đến cuối tệp. Nếu bạn thường xuyên sử dụng BẢNG TỐI ƯU thì bạn khó có thể gặp phải sự cố như vậy.

Chú ý, phiên bản DBMS ảnh hưởng đến cách lưu trữ dữ liệu và ví dụ này có thể không hoạt động đối với các phiên bản chính khác. Trong trường hợp của tôi, phiên bản windows của mariadb 10.1.24 đã được sử dụng. Ngoài ra, mặc dù trong mariadb bạn làm việc với các bảng InnoDB, nhưng trên thực tế chúng là XtraDB, loại trừ khả năng áp dụng của phương thức với InnoDB mysql.

Phân tích tập tin

Trong python, kiểu dữ liệu byte () hiển thị dữ liệu Unicode thay cho một bộ số thông thường. Mặc dù bạn có thể xem tệp ở dạng này, nhưng để thuận tiện, bạn có thể chuyển đổi byte thành dạng số bằng cách chuyển đổi mảng byte thành mảng thông thường (danh sách(example_byte_array)). Trong mọi trường hợp, cả hai phương pháp đều phù hợp để phân tích.

Sau khi xem qua một số tệp ibd, bạn có thể tìm thấy những thông tin sau:

Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd

Hơn nữa, nếu bạn chia tệp theo các từ khóa này, bạn sẽ nhận được hầu hết các khối dữ liệu chẵn. Chúng ta sẽ sử dụng infimum như một số chia.

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

Một quan sát thú vị: đối với các bảng có lượng dữ liệu nhỏ, giữa infimum và supremum có một con trỏ tới số hàng trong khối.

Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd — bảng kiểm tra có hàng thứ nhất

Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd - bảng kiểm tra có 2 hàng

Bảng mảng hàng [0] có thể được bỏ qua. Sau khi xem qua, tôi vẫn không thể tìm thấy dữ liệu bảng thô. Rất có thể, khối này được sử dụng để lưu trữ các chỉ mục và khóa.
Bắt đầu với bảng[1] và dịch nó thành một mảng số, bạn có thể nhận thấy một số mẫu, cụ thể là:

Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd

Đây là các giá trị int được lưu trữ trong một chuỗi. Byte đầu tiên cho biết số đó là dương hay âm. Trong trường hợp của tôi, tất cả các số đều dương. Từ 3 byte còn lại, bạn có thể xác định số bằng hàm sau. Kịch bản:

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

Ví dụ, 128, 0, 0, 1 = 1Hoặc 128, 0, 75, 108 = 19308.
Bảng có khóa chính có khả năng tăng tự động và bạn cũng có thể tìm thấy khóa này tại đây

Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd

Sau khi so sánh dữ liệu từ các bảng thử nghiệm, người ta thấy rằng đối tượng DATETIME bao gồm 5 byte và bắt đầu bằng 153 (rất có thể biểu thị các khoảng thời gian hàng năm). Vì phạm vi DATTIME là '1000-01-01' đến '9999-12-31' nên tôi nghĩ số byte có thể thay đổi, nhưng trong trường hợp của tôi, dữ liệu rơi vào khoảng thời gian từ 2016 đến 2019, vì vậy chúng tôi sẽ giả định đủ 5 byte đó.

Để xác định thời gian không có giây, các hàm sau đã được viết. Kịch bản:

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}

Không viết được hàm chức năng cho năm, tháng nên phải hack. Kịch bản:

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

Tôi chắc chắn rằng nếu bạn dành n khoảng thời gian, sự hiểu lầm này có thể được sửa chữa.
Tiếp theo, một hàm trả về đối tượng datetime từ một chuỗi. Kịch bả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)

Được quản lý để phát hiện các giá trị lặp lại thường xuyên từ int, int, datetime, datetime Khôi phục dữ liệu từ các bảng XtraDB mà không có tệp cấu trúc bằng cách sử dụng phân tích từng byte của tệp ibd, có vẻ như đây chính là thứ bạn cần. Hơn nữa, trình tự như vậy không được lặp lại hai lần trên mỗi dòng.

Sử dụng biểu thức chính quy, chúng tôi tìm thấy dữ liệu cần thiết:

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)

Xin lưu ý rằng khi tìm kiếm bằng biểu thức này, sẽ không thể xác định giá trị NULL trong các trường bắt buộc, nhưng trong trường hợp của tôi, điều này không quan trọng. Sau đó, chúng tôi xem xét những gì chúng tôi tìm thấy trong một vòng lặp. Kịch bản:

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)

Thực ra chỉ vậy thôi, dữ liệu từ mảng kết quả chính là dữ liệu chúng ta cần. ###PS.###
Tôi hiểu rằng phương pháp này không phù hợp với tất cả mọi người, nhưng mục tiêu chính của bài viết là nhắc nhở hành động hơn là giải quyết mọi vấn đề của bạn. Tôi nghĩ giải pháp đúng đắn nhất là bắt đầu tự mình nghiên cứu mã nguồn mariadb, nhưng do thời gian có hạn nên phương pháp hiện tại có vẻ là nhanh nhất.

Trong một số trường hợp, sau khi phân tích tệp, bạn sẽ có thể xác định cấu trúc gần đúng và khôi phục nó bằng một trong các phương pháp tiêu chuẩn từ các liên kết ở trên. Điều này sẽ đúng hơn nhiều và gây ra ít vấn đề hơn.

Nguồn: www.habr.com

Thêm một lời nhận xét