Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd

Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd

Pristoria

Hè accadutu chì u servitore hè statu attaccatu da un virus ransomware, chì, per un "accidente furtunatu", hà lasciatu parzialmente i fugliali .ibd (fichi di dati crudi di tavule innodb) senza toccu, ma à u stessu tempu hà criptatu cumplettamente i schedari .fpm ( file di struttura). In questu casu, .idb puderia esse divisu in:

  • sughjetti à a risturazione attraversu arnesi è guide standard. Per tali casi, ci hè un eccellente diventa;
  • tabelle parzialmente criptate. A maiò parte di questi sò grandi tavule, per quale (cum'è aghju capitu) l'attaccanti ùn anu micca abbastanza RAM per a criptografia completa;
  • Ebbè, tabelle cumplettamente criptate chì ùn ponu micca esse restaurate.

Hè statu pussibule di determinà quale opzione appartenenu i tavulini simpliciamente aprendu in qualsiasi editore di testu sottu a codificazione desiderata (in u mo casu hè UTF8) è simpricimenti vede u schedariu per a presenza di campi di testu, per esempiu:

Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd

Inoltre, à l'iniziu di u schedariu pudete osservà un gran numaru di 0 byte, è i virus chì utilizanu l'algoritmu di criptografia di bloccu (u più cumuni) sò generalmente affettanu ancu.
Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd

In u mo casu, l'attaccanti abbandunonu una stringa di 4 byte (1, 0, 0, 0) à a fine di ogni schedariu criptatu, chì simplificà u compitu. Per circà i fugliali micca infettati, u script era abbastanza:

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)

Cusì, si girò fora à truvà schedari chì appartene à u primu tipu. U sicondu implica assai travagliu manuale, ma ciò chì hè statu trovu era digià abbastanza. Tuttu saria bè, ma avete bisognu di sapè struttura assolutamente precisa è (di sicuru) hè un casu chì aghju avutu à travaglià cù una tavola chì cambiava spessu. Nimu hà ricurdatu se u tipu di campu hè statu cambiatu o una nova colonna hè stata aghjunta.

Wilds City, sfurtunatamenti, ùn pudia aiutà cun un tali casu, chì hè per quessa stu articulu hè scrittu.

Andate à u puntu

Ci hè una struttura di una tavula da 3 mesi fà chì ùn coincide micca cù l'attuale (possibilmente un campu, è possibbilmente più). Struttura di a tavola:

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)
); 

in questu casu, avete bisognu di estrazione:

  • id_point int(11);
  • id_user int(11);
  • date_start DATETIME;
  • date_finish DATETIME.

Per a ricuperazione, un analisi byte-byte di u schedariu .ibd hè utilizatu, seguitu da cunvertisce in una forma più leghjite. Siccomu per truvà ciò chì avemu bisognu, avemu solu bisognu di analizà i tipi di dati cum'è int è datatime, l'articulu discrive solu elli, ma qualchì volta avemu ancu riferite à altri tipi di dati, chì ponu aiutà in altri incidenti simili.

Prublemu 1: i campi cù tipi DATETIME è TEXT avianu valori NULL, è sò simpliciamente saltati in u schedariu, per quessa, ùn era micca pussibule di determinà a struttura per restaurà in u mo casu. In i novi culonni, u valore predeterminatu era nulu, è una parte di a transazzione puderia esse persa per via di l'impostazione innodb_flush_log_at_trx_commit = 0, perchè u tempu supplementu avissi da esse passatu per determinà a struttura.

Prublemu 2: deve esse cunsideratu chì e fila sguassate via DELETE seranu tutte in u schedariu ibd, ma cù ALTER TABLE a so struttura ùn serà micca aghjurnata. In u risultatu, a struttura di dati pò varià da u principiu di u schedariu à a so fine. Sè vo spessu aduprà OPTIMIZE TABLE, allura vi sò improbabile à scuntrà un tali prublema.

Attenti, a versione di DBMS afecta a manera chì i dati sò almacenati, è questu esempiu ùn pò micca travaglià per altre versioni maiò. In u mo casu, a versione Windows di mariadb 10.1.24 hè stata utilizata. Inoltre, ancu s'è in mariadb travagliate cù tavule InnoDB, in fatti sò XtraDB, chì esclude l'applicabilità di u metudu cù InnoDB mysql.

Analisi di u schedariu

In python, tipu di dati bytes () mostra dati Unicode invece di un inseme regulare di numeri. Ancu se pudete vede u schedariu in questa forma, per comodità pudete cunvertisce i byte in forma numerica cunvertisce l'array di byte in un array regular (list(example_byte_array)). In ogni casu, i dui metudi sò adattati per l'analisi.

Dopu avè cercatu parechji schedari ibd, pudete truvà i seguenti:

Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd

Inoltre, se dividite u schedariu per queste parole chjave, uttene per suprattuttu ancu blocchi di dati. Avemu aduprà infimum cum'è un divisore.

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

Una osservazione interessante: per e tavule cù una piccula quantità di dati, trà infimum è supremum ci hè un punteru à u numeru di fila in u bloccu.

Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd - tavula di prova cù a 1ª fila

Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd - Tavola di prova cù 2 fila

A tavola di fila array [0] pò esse saltata. Dopu avè cercatu, ùn era ancu incapace di truvà e dati di a tavola cruda. Probabilmente, stu bloccu hè utilizatu per almacenà indici è chjave.
Partendu cù a tavola [1] è traduzzione in un array numericu, pudete digià nutà parechji mudelli, à dì:

Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd

Quessi sò valori int almacenati in una stringa. U primu byte indica se u numeru hè pusitivu o negativu. In u mo casu, tutti i numeri sò pusitivi. Da i restanti 3 bytes, pudete determinà u numeru utilizendu a seguente funzione. Script:

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

Per esempiu, 128, 0, 0, 1 = 1o 128, 0, 75, 108 = 19308.
A tavula avia una chjave primaria cù auto-incrementu, è pò ancu esse truvata quì

Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd

Dopu avè paragunatu i dati da e tavule di teste, hè statu revelatu chì l'ughjettu DATETIME hè custituitu da 5 bytes è principia cù 153 (probabilmente indicà intervalli annuali). Siccomu a gamma DATTIME hè '1000-01-01' à '9999-12-31', pensu chì u numeru di bytes pò varià, ma in u mo casu, i dati cadenu in u periodu da 2016 à 2019, cusì assumeremu chì 5 byte abbastanza.

Per determinà u tempu senza seconde, e seguenti funzioni sò state scritte. Script:

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}

Ùn era micca pussibule di scrive una funzione funziunale per l'annu è u mese, perchè aghju avutu a pirate. Script:

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

Sò sicuru chì si passanu n quantità di tempu, stu malintesi pò esse currettu.
In seguitu, una funzione chì torna un ughjettu datetime da una stringa. Script:

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)

Riesciutu à detectà i valori ripetuti spessu da int, int, datetime, datetime Recuperazione di dati da e tavule XtraDB senza un schedariu di struttura cù l'analisi byte per byte di u schedariu ibd, pare chì questu hè ciò chì avete bisognu. Inoltre, una tale sequenza ùn hè micca ripetuta duie volte per linea.

Utilizendu una espressione regulare, truvamu i dati necessarii:

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)

Per piacè nutate chì quandu cercate cù sta espressione, ùn serà micca pussibule di determinà i valori NULL in i campi richiesti, ma in u mo casu ùn hè micca criticu. Allora andemu per ciò chì avemu trovu in un ciclu. Script:

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)

In verità, questu hè tuttu, i dati da a matrice di risultati sò i dati chì avemu bisognu. ###PS.###
Capiscu chì stu metudu ùn hè micca adattatu per tutti, ma u scopu principale di l'articulu hè di avvià l'azzione invece di risolve tutti i vostri prublemi. Pensu chì a suluzione più curretta seria di cumincià à studià u codice fonte mariadb, ma per via di u tempu limitatu, u metudu attuale pareva esse u più veloce.

In certi casi, dopu l'analisi di u schedariu, puderà determinà a struttura apprussimata è restaurà cù unu di i metudi standard da i ligami sopra. Questu serà assai più currettu è causanu menu prublemi.

Source: www.habr.com

Add a comment