ประวัติศาสตร์
มันเกิดขึ้นที่เซิร์ฟเวอร์ถูกโจมตีโดยไวรัสแรนซัมแวร์ ซึ่งโดย "อุบัติเหตุที่โชคดี" ทำให้ไฟล์ .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)
ดังนั้นจึงพบว่าพบไฟล์ที่เป็นประเภทแรก ประการที่สองเกี่ยวข้องกับการทำงานด้วยตนเองจำนวนมาก แต่สิ่งที่พบก็เพียงพอแล้ว ทุกอย่างจะดี แต่คุณต้องรู้ โครงสร้างที่แม่นยำอย่างยิ่ง และ (แน่นอน) มีกรณีเกิดขึ้นว่าฉันต้องทำงานกับโต๊ะที่เปลี่ยนบ่อย ไม่มีใครจำได้ว่าประเภทฟิลด์มีการเปลี่ยนแปลงหรือมีการเพิ่มคอลัมน์ใหม่หรือไม่
น่าเสียดายที่ Wilds City ไม่สามารถช่วยในกรณีเช่นนี้ได้ ซึ่งเป็นเหตุผลว่าทำไมจึงเขียนบทความนี้
ใกล้ชิดกับประเด็น
มีโครงสร้างของตารางเมื่อ 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 และฟิลด์เหล่านั้นจะถูกข้ามไปในไฟล์ ด้วยเหตุนี้จึงไม่สามารถกำหนดโครงสร้างที่จะกู้คืนในกรณีของฉันได้ ในคอลัมน์ใหม่ ค่าเริ่มต้นเป็นค่าว่าง และส่วนหนึ่งของธุรกรรมอาจสูญหายได้เนื่องจากการตั้งค่า innodb_flush_log_at_trx_commit = 0 ดังนั้นจะต้องใช้เวลาเพิ่มเติมเพื่อกำหนดโครงสร้าง
ปัญหาที่ 2: ควรคำนึงว่าแถวที่ถูกลบผ่าน DELETE ทั้งหมดจะอยู่ในไฟล์ ibd แต่ด้วย ALTER TABLE โครงสร้างของพวกเขาจะไม่ได้รับการอัปเดต ด้วยเหตุนี้ โครงสร้างข้อมูลจึงอาจแตกต่างกันไปตั้งแต่ต้นไฟล์จนถึงจุดสิ้นสุด หากคุณใช้ OPTIMIZE TABLE บ่อยครั้ง คุณไม่น่าจะประสบปัญหาดังกล่าว
หมายเหตุเวอร์ชัน DBMS ส่งผลต่อวิธีการจัดเก็บข้อมูล และตัวอย่างนี้อาจใช้ไม่ได้กับเวอร์ชันหลักอื่นๆ ในกรณีของฉัน มีการใช้ mariadb 10.1.24 เวอร์ชัน windows นอกจากนี้ แม้ว่าใน mariadb คุณจะทำงานกับตาราง InnoDB แต่จริงๆ แล้วเป็นเช่นนั้น
การวิเคราะห์ไฟล์
ในหลามชนิดข้อมูล
หลังจากดูไฟล์ ibd หลายไฟล์แล้ว คุณจะพบสิ่งต่อไปนี้:
ยิ่งไปกว่านั้น หากคุณแบ่งไฟล์ด้วยคีย์เวิร์ดเหล่านี้ คุณจะได้รับบล็อกข้อมูลเป็นคู่เป็นส่วนใหญ่ เราจะใช้ infimum เป็นตัวหาร.
table = table.split("infimum".encode())
ข้อสังเกตที่น่าสนใจ: สำหรับตารางที่มีข้อมูลจำนวนน้อย จะมีตัวชี้ไปยังจำนวนแถวในบล็อกระหว่างค่าต่ำสุดและค่าสูงสุด
— โต๊ะทดสอบพร้อมแถวที่ 1
- โต๊ะทดสอบแบบ 2 แถว
ตารางอาร์เรย์แถว[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
ฉันมั่นใจว่าหากคุณใช้เวลาไปสักระยะ ความเข้าใจผิดนี้สามารถแก้ไขได้
ถัดไปคือฟังก์ชันที่ส่งคืนวัตถุ 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 ดูเหมือนว่านี่คือสิ่งที่คุณต้องการ นอกจากนี้ ลำดับดังกล่าวจะไม่ทำซ้ำสองครั้งต่อบรรทัด
เมื่อใช้นิพจน์ทั่วไป เราจะค้นหาข้อมูลที่จำเป็น:
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)
จริงๆ แล้ว นั่นคือทั้งหมด ข้อมูลจากอาร์เรย์ผลลัพธ์คือข้อมูลที่เราต้องการ ###ปล.###
ฉันเข้าใจว่าวิธีนี้ไม่เหมาะสำหรับทุกคน แต่เป้าหมายหลักของบทความนี้คือการดำเนินการอย่างรวดเร็วแทนที่จะแก้ไขปัญหาทั้งหมดของคุณ ฉันคิดว่าทางออกที่ถูกต้องที่สุดคือการเริ่มศึกษาซอร์สโค้ดด้วยตัวเอง
ในบางกรณี หลังจากวิเคราะห์ไฟล์แล้ว คุณจะสามารถกำหนดโครงสร้างโดยประมาณและกู้คืนไฟล์โดยใช้วิธีมาตรฐานวิธีใดวิธีหนึ่งจากลิงก์ด้านบน สิ่งนี้จะถูกต้องมากขึ้นและทำให้เกิดปัญหาน้อยลง
ที่มา: will.com