การกู้คืนข้อมูลจากตาราง 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 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 แต่จริงๆ แล้วเป็นเช่นนั้น เอ็กซ์ตร้าดีบีซึ่งไม่รวมการบังคับใช้วิธีการกับ InnoDB mysql

การวิเคราะห์ไฟล์

ในหลามชนิดข้อมูล ไบต์ () แสดงข้อมูล Unicode แทนที่ชุดตัวเลขปกติ แม้ว่าคุณจะสามารถดูไฟล์ในรูปแบบนี้ได้ แต่เพื่อความสะดวก คุณสามารถแปลงไบต์เป็นรูปแบบตัวเลขได้โดยการแปลงอาร์เรย์ไบต์เป็นอาร์เรย์ปกติ (list(example_byte_array)) ไม่ว่าในกรณีใดทั้งสองวิธีก็เหมาะสำหรับการวิเคราะห์

หลังจากดูไฟล์ ibd หลายไฟล์แล้ว คุณจะพบสิ่งต่อไปนี้:

การกู้คืนข้อมูลจากตาราง XtraDB โดยไม่มีไฟล์โครงสร้างโดยใช้การวิเคราะห์แบบไบต์ต่อไบต์ของไฟล์ ibd

ยิ่งไปกว่านั้น หากคุณแบ่งไฟล์ด้วยคีย์เวิร์ดเหล่านี้ คุณจะได้รับบล็อกข้อมูลเป็นคู่เป็นส่วนใหญ่ เราจะใช้ infimum เป็นตัวหาร.

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

ข้อสังเกตที่น่าสนใจ: สำหรับตารางที่มีข้อมูลจำนวนน้อย จะมีตัวชี้ไปยังจำนวนแถวในบล็อกระหว่างค่าต่ำสุดและค่าสูงสุด

การกู้คืนข้อมูลจากตาราง XtraDB โดยไม่มีไฟล์โครงสร้างโดยใช้การวิเคราะห์แบบไบต์ต่อไบต์ของไฟล์ ibd — โต๊ะทดสอบพร้อมแถวที่ 1

การกู้คืนข้อมูลจากตาราง XtraDB โดยไม่มีไฟล์โครงสร้างโดยใช้การวิเคราะห์แบบไบต์ต่อไบต์ของไฟล์ ibd - โต๊ะทดสอบแบบ 2 แถว

ตารางอาร์เรย์แถว[0] สามารถข้ามได้ หลังจากตรวจสอบแล้ว ฉันยังคงไม่พบข้อมูลตารางดิบ เป็นไปได้มากว่าบล็อกนี้ใช้เพื่อจัดเก็บดัชนีและคีย์
เริ่มต้นด้วย table[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

ฉันมั่นใจว่าหากคุณใช้เวลาไปสักระยะ ความเข้าใจผิดนี้สามารถแก้ไขได้
ถัดไปคือฟังก์ชันที่ส่งคืนวัตถุ 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)

จริงๆ แล้ว นั่นคือทั้งหมด ข้อมูลจากอาร์เรย์ผลลัพธ์คือข้อมูลที่เราต้องการ ###ปล.###
ฉันเข้าใจว่าวิธีนี้ไม่เหมาะสำหรับทุกคน แต่เป้าหมายหลักของบทความนี้คือการดำเนินการอย่างรวดเร็วแทนที่จะแก้ไขปัญหาทั้งหมดของคุณ ฉันคิดว่าทางออกที่ถูกต้องที่สุดคือการเริ่มศึกษาซอร์สโค้ดด้วยตัวเอง MariaDBแต่เนื่องจากระยะเวลาที่จำกัด วิธีการปัจจุบันจึงดูเหมือนจะเร็วที่สุด

ในบางกรณี หลังจากวิเคราะห์ไฟล์แล้ว คุณจะสามารถกำหนดโครงสร้างโดยประมาณและกู้คืนไฟล์โดยใช้วิธีมาตรฐานวิธีใดวิธีหนึ่งจากลิงก์ด้านบน สิ่งนี้จะถูกต้องมากขึ้นและทำให้เกิดปัญหาน้อยลง

ที่มา: will.com

เพิ่มความคิดเห็น