Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd

Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd

Ιστορικό

Συνέβη ο διακομιστής να δεχτεί επίθεση από έναν ιό ransomware, ο οποίος, από ένα «τυχερό ατύχημα», άφησε εν μέρει τα αρχεία .ibd (αρχεία ακατέργαστων δεδομένων των πινάκων innodb) ανέγγιχτα, αλλά ταυτόχρονα κρυπτογραφούσε πλήρως τα αρχεία .fpm ( αρχεία δομής). Σε αυτήν την περίπτωση, το .idb θα μπορούσε να χωριστεί σε:

  • υπόκεινται σε αποκατάσταση μέσω τυπικών εργαλείων και οδηγών. Για τέτοιες περιπτώσεις, υπάρχει ένα εξαιρετικό γίνομαι;
  • μερικώς κρυπτογραφημένους πίνακες. Κυρίως πρόκειται για μεγάλα τραπέζια, για τα οποία (όπως καταλαβαίνω) οι εισβολείς δεν είχαν αρκετή μνήμη RAM για πλήρη κρυπτογράφηση.
  • Λοιπόν, πλήρως κρυπτογραφημένοι πίνακες που δεν μπορούν να αποκατασταθούν.

Ήταν δυνατό να προσδιοριστεί σε ποια επιλογή ανήκουν οι πίνακες ανοίγοντάς την απλά σε οποιοδήποτε πρόγραμμα επεξεργασίας κειμένου κάτω από την επιθυμητή κωδικοποίηση (στην περίπτωσή μου είναι UTF8) και απλά προβάλλοντας το αρχείο για την παρουσία πεδίων κειμένου, για παράδειγμα:

Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd

Επίσης, στην αρχή του αρχείου μπορείτε να παρατηρήσετε έναν μεγάλο αριθμό 0 byte και οι ιοί που χρησιμοποιούν τον αλγόριθμο κρυπτογράφησης μπλοκ (ο πιο συνηθισμένος) συνήθως τους επηρεάζουν επίσης.
Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd

Στην περίπτωσή μου, οι εισβολείς άφησαν μια συμβολοσειρά 4 byte (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 int(11);
  • id_user int(11);
  • date_start ΗΜΕΡΟΜΗΝΙΑ ΩΡΑ;
  • date_finish ΗΜΕΡΟΜΗΝΙΑ ΩΡΑ.

Για την ανάκτηση, χρησιμοποιείται μια ανάλυση byte-byte του αρχείου .ibd, ακολουθούμενη από τη μετατροπή τους σε μια πιο ευανάγνωστη μορφή. Επειδή για να βρούμε αυτό που χρειαζόμαστε, χρειάζεται μόνο να αναλύσουμε τύπους δεδομένων όπως int και datatime, το άρθρο θα περιγράψει μόνο αυτούς, αλλά μερικές φορές θα αναφερθούμε και σε άλλους τύπους δεδομένων, που μπορούν να βοηθήσουν σε άλλα παρόμοια περιστατικά.

Πρόβλημα 1: πεδία με τύπους DATETIME και TEXT είχαν NULL τιμές και απλώς παραλείπονται στο αρχείο, εξαιτίας αυτού, δεν ήταν δυνατός ο προσδιορισμός της δομής προς επαναφορά στην περίπτωσή μου. Στις νέες στήλες, η προεπιλεγμένη τιμή ήταν μηδενική και μέρος της συναλλαγής θα μπορούσε να χαθεί λόγω της ρύθμισης innodb_flush_log_at_trx_commit = 0, επομένως θα πρέπει να δαπανηθεί επιπλέον χρόνος για τον προσδιορισμό της δομής.

Πρόβλημα 2: θα πρέπει να ληφθεί υπόψη ότι οι σειρές που διαγράφονται μέσω DELETE θα βρίσκονται όλες στο αρχείο ibd, αλλά με το ALTER TABLE η δομή τους δεν θα ενημερωθεί. Ως αποτέλεσμα, η δομή δεδομένων μπορεί να διαφέρει από την αρχή του αρχείου έως το τέλος του. Εάν χρησιμοποιείτε συχνά το OPTIMIZE TABLE, τότε είναι απίθανο να αντιμετωπίσετε τέτοιο πρόβλημα.

Обратите внимание, η έκδοση του DBMS επηρεάζει τον τρόπο αποθήκευσης των δεδομένων και αυτό το παράδειγμα ενδέχεται να μην λειτουργεί για άλλες κύριες εκδόσεις. Στην περίπτωσή μου χρησιμοποιήθηκε η έκδοση windows του mariadb 10.1.24. Επίσης, αν και στο mariadb δουλεύεις με πίνακες InnoDB, στην πραγματικότητα είναι XtraDB, το οποίο αποκλείει τη δυνατότητα εφαρμογής της μεθόδου με το InnoDB mysql.

Ανάλυση αρχείων

Στην python, τύπος δεδομένων bytes () εμφανίζει δεδομένα Unicode στη θέση ενός κανονικού συνόλου αριθμών. Αν και μπορείτε να προβάλετε το αρχείο σε αυτή τη φόρμα, για ευκολία μπορείτε να μετατρέψετε τα byte σε αριθμητική μορφή μετατρέποντας τον πίνακα byte σε έναν κανονικό πίνακα (list(example_byte_array)). Σε κάθε περίπτωση, και οι δύο μέθοδοι είναι κατάλληλες για ανάλυση.

Αφού διαβάσετε πολλά αρχεία ibd, μπορείτε να βρείτε τα εξής:

Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd

Επιπλέον, εάν διαιρέσετε το αρχείο με αυτές τις λέξεις-κλειδιά, θα λάβετε ως επί το πλείστον ακόμη και μπλοκ δεδομένων. Θα χρησιμοποιήσουμε το infimum ως διαιρέτη.

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

Μια ενδιαφέρουσα παρατήρηση: για πίνακες με μικρό όγκο δεδομένων, μεταξύ infimum και supremum υπάρχει ένας δείκτης στον αριθμό των σειρών στο μπλοκ.

Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd — πίνακας δοκιμών με 1η σειρά

Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd - πίνακας δοκιμών με 2 σειρές

Ο πίνακας του πίνακα σειρών[0] μπορεί να παραλειφθεί. Αφού το έψαξα, ακόμα δεν μπορούσα να βρω τα ακατέργαστα δεδομένα του πίνακα. Πιθανότατα, αυτό το μπλοκ χρησιμοποιείται για την αποθήκευση ευρετηρίων και κλειδιών.
Ξεκινώντας με τον πίνακα[1] και μεταφράζοντάς τον σε έναν αριθμητικό πίνακα, μπορείτε ήδη να παρατηρήσετε ορισμένα μοτίβα, και συγκεκριμένα:

Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd

Αυτές είναι τιμές int που είναι αποθηκευμένες σε μια συμβολοσειρά. Το πρώτο byte υποδεικνύει εάν ο αριθμός είναι θετικός ή αρνητικός. Στην περίπτωσή μου, όλα τα νούμερα είναι θετικά. Από τα υπόλοιπα 3 byte, μπορείτε να προσδιορίσετε τον αριθμό χρησιμοποιώντας την παρακάτω λειτουργία. Γραφή:

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 χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου ibd

Έχοντας συγκρίνει τα δεδομένα από τους πίνακες δοκιμής, αποκαλύφθηκε ότι το αντικείμενο DATETIME αποτελείται από 5 byte και ξεκίνησε με 153 (πιθανότατα υποδεικνύει ετήσια διαστήματα). Δεδομένου ότι το εύρος DATTIME είναι «1000-01-01» έως «9999-12-31», νομίζω ότι ο αριθμός των byte μπορεί να διαφέρει, αλλά στην περίπτωσή μου, τα δεδομένα εμπίπτουν στην περίοδο από το 2016 έως το 2019, επομένως θα υποθέσουμε ότι 5 byte είναι αρκετά.

Για τον προσδιορισμό του χρόνου χωρίς δευτερόλεπτα, γράφτηκαν οι ακόλουθες συναρτήσεις. Γραφή:

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 Ανάκτηση δεδομένων από πίνακες XtraDB χωρίς αρχείο δομής χρησιμοποιώντας ανάλυση byte-byte του αρχείου 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, αλλά λόγω περιορισμένου χρόνου, η τρέχουσα μέθοδος φαινόταν να είναι η ταχύτερη.

Σε ορισμένες περιπτώσεις, μετά την ανάλυση του αρχείου, θα μπορείτε να προσδιορίσετε την κατά προσέγγιση δομή και να την επαναφέρετε χρησιμοποιώντας μία από τις τυπικές μεθόδους από τους παραπάνω συνδέσμους. Αυτό θα είναι πολύ πιο σωστό και θα προκαλέσει λιγότερα προβλήματα.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο