使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據

使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據

恰巧伺服器遭到勒索病毒攻擊,「僥倖」地部分保留了 .ibd 檔案(innodb 表的原始資料檔案),但同時完全加密了 .fpm 檔案(結構檔案)。 在這種情況下,.idb 可以分為:

  • 可透過標準工具和指南進行修復。 對於這種情況,有一個很好的 變得;
  • 部分加密的表。 大多數情況下,這些都是大表,(據我所知)攻擊者沒有足夠的 RAM 來進行完全加密;
  • 好吧,完全加密的表無法恢復。

只需在任何文字編輯器中以所需編碼(在我的情況下為 UTF8)打開表並簡單地查看文件中是否存在文字字段,即可確定表屬於哪個選項,例如:

使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據

另外,在檔案的開頭,您可以觀察到大量的 0 字節,使用區塊加密演算法(最常見)的病毒通常也會影響它們。
使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據

在我的例子中,攻擊者在每個加密檔案的末尾留下了一個 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)

因此,結果發現屬於第一種類型的文件。 第二個涉及大量的手動工作,但找到的內容已經足夠了。 一切都會好起來的,但你需要知道 絕對精確的結構 (當然)出現了一個情況,我必須使用經常更換的桌子。 沒有人記得字段類型是否已更改或是否已添加了新列。

不幸的是,荒野城無法幫助解決這種情況,這就是撰寫本文的原因。

進入正題

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 整數(11);
  • id_user 整數(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 版本會影響資料的儲存方式,且此範例可能不適用於其他主要版本。 就我而言,使用的是 Windows 版本的 mariadb 10.1.24。 另外,雖然在 mariadb 中您使用 InnoDB 表,但實際上它們是 數據庫,這排除了該方法對 InnoDB mysql 的適用性。

文件分析

在Python中,資料類型 位元組() 顯示 Unicode 資料來取代一組常規數字。 雖然您可以以這種形式查看文件,但為了方便起見,您可以透過將位元組數組轉換為常規數組 (list(example_byte_array)) 將位元組轉換為數字形式。 無論如何,這兩種方法都適合分析。

翻閱了幾個ibd文件,可以發現以下內容:

使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據

此外,如果您按這些關鍵字劃分文件,您將獲得大部分偶數資料區塊。 我們將使用下確界作為除數。

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

一個有趣的觀察:對於資料量較小的表,在下確界和上界之間有一個指向區塊中行數的指標。

使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據 — 第一行的測試表

使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據 - 2行測試表

可以跳過行數組table[0]。 查了一下,還是找不到原始表資料。 該區塊很可能用於儲存索引和鍵。
從 table[1] 開始並將其轉換為數值數組,您已經可以注意到一些模式,即:

使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據

這些是儲存在字串中的 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 = 1128, 0, 75, 108 = 19308.
表格有一個自增主鍵,也可以在這裡找到

使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據

比較測試表中的資料後發現,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 中偵測頻繁重複的值 使用 ibd 檔案的逐字節分析從沒有結構檔案的 XtraDB 表中恢復數據,看起來這就是您所需要的。 此外,這樣的序列不會每行重複兩次。

使用正規表示式,我們找到必要的資料:

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.###
我知道這種方法並不適合所有人,但本文的主要目標是促使採取行動,而不是解決您的所有問題。 我認為最正確的解決方案是自己開始研究原始碼 MariaDB的但由於時間有限,目前的方法似乎是最快的。

在某些情況下,分析文件後,您將能夠確定大致結構並使用上面連結中的標準方法之一來恢復它。 這將更加正確並且導致更少的問題。

來源: www.habr.com

添加評論