底
恰巧伺服器遭到勒索病毒攻擊,「僥倖」地部分保留了 .ibd 檔案(innodb 表的原始資料檔案),但同時完全加密了 .fpm 檔案(結構檔案)。 在這種情況下,.idb 可以分為:
- 可透過標準工具和指南進行修復。 對於這種情況,有一個很好的
變得 ; - 部分加密的表。 大多數情況下,這些都是大表,(據我所知)攻擊者沒有足夠的 RAM 來進行完全加密;
- 好吧,完全加密的表無法恢復。
只需在任何文字編輯器中以所需編碼(在我的情況下為 UTF8)打開表並簡單地查看文件中是否存在文字字段,即可確定表屬於哪個選項,例如:
另外,在檔案的開頭,您可以觀察到大量的 0 字節,使用區塊加密演算法(最常見)的病毒通常也會影響它們。
在我的例子中,攻擊者在每個加密檔案的末尾留下了一個 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 表,但實際上它們是
文件分析
在Python中,資料類型
翻閱了幾個ibd文件,可以發現以下內容:
此外,如果您按這些關鍵字劃分文件,您將獲得大部分偶數資料區塊。 我們將使用下確界作為除數。
table = table.split("infimum".encode())
一個有趣的觀察:對於資料量較小的表,在下確界和上界之間有一個指向區塊中行數的指標。
— 第一行的測試表
- 2行測試表
可以跳過行數組table[0]。 查了一下,還是找不到原始表資料。 該區塊很可能用於儲存索引和鍵。
從 table[1] 開始並將其轉換為數值數組,您已經可以注意到一些模式,即:
這些是儲存在字串中的 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.
表格有一個自增主鍵,也可以在這裡找到
比較測試表中的資料後發現,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 中偵測頻繁重複的值 ,看起來這就是您所需要的。 此外,這樣的序列不會每行重複兩次。
使用正規表示式,我們找到必要的資料:
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