Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd

Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd

prehistòria

Va passar que el servidor va ser atacat per un virus de ransomware, que, per un "accident afortunat", va deixar parcialment intacs els fitxers .ibd (fitxers de dades en brut de taules innodb), però al mateix temps va xifrar completament els fitxers .fpm ( fitxers d'estructura). En aquest cas, .idb es podria dividir en:

  • subjectes a restauració mitjançant eines i guies estàndard. Per a aquests casos, hi ha un excel·lent convertir-se en;
  • taules parcialment xifrades. Majoritàriament es tracta de taules grans, per a les quals (segons tinc entès) els atacants no tenien prou RAM per a un xifratge complet;
  • Bé, taules totalment xifrades que no es poden restaurar.

Va ser possible determinar a quina opció pertanyen les taules simplement obrint-la en qualsevol editor de text sota la codificació desitjada (en el meu cas és UTF8) i simplement visualitzant el fitxer per la presència de camps de text, per exemple:

Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd

A més, a l'inici del fitxer es pot observar un gran nombre de 0 bytes, i els virus que utilitzen l'algoritme de xifratge de blocs (el més comú) també solen afectar-los.
Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd

En el meu cas, els atacants van deixar una cadena de 4 bytes (1, 0, 0, 0) al final de cada fitxer xifrat, cosa que va simplificar la tasca. Per cercar fitxers no infectats, l'script era suficient:

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)

Així, va resultar trobar fitxers pertanyents al primer tipus. La segona implica molta feina manual, però amb el que es va trobar ja n'hi havia prou. Tot aniria bé, però cal saber-ho estructura absolutament precisa i (per descomptat) va sorgir un cas que havia de treballar amb una taula que canviava sovint. Ningú no recordava si s'havia canviat el tipus de camp o si s'havia afegit una columna nova.

Wilds City, malauradament, no va poder ajudar amb un cas així, per això s'està escrivint aquest article.

Arriba al punt

Hi ha una estructura d'una taula de fa 3 mesos que no coincideix amb l'actual (possiblement un camp, i possiblement més). Estructura de la taula:

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

en aquest cas, cal extreure:

  • id_point int(11);
  • id_user int(11);
  • date_start DATA I HORA;
  • date_finish DATA I HORA.

Per a la recuperació, s'utilitza una anàlisi byte a byte del fitxer .ibd, seguida de la conversió en una forma més llegible. Com que per trobar el que necessitem, només hem d'analitzar tipus de dades com int i datatime, l'article només els descriurà, però de vegades també ens referirem a altres tipus de dades, que poden ajudar en altres incidents similars.

Problema 1: els camps amb els tipus DATETIME i TEXT tenien valors NULL, i simplement s'ometen al fitxer, per això, no va ser possible determinar l'estructura a restaurar en el meu cas. A les columnes noves, el valor predeterminat era nul, i part de la transacció es podia perdre a causa de la configuració innodb_flush_log_at_trx_commit = 0, de manera que caldria gastar més temps per determinar l'estructura.

Problema 2: cal tenir en compte que les files esborrades mitjançant DELETE estaran totes al fitxer ibd, però amb ALTER TABLE la seva estructura no s'actualitzarà. Com a resultat, l'estructura de dades pot variar des de l'inici del fitxer fins al seu final. Si feu servir sovint OPTIMIZE TABLE, és poc probable que trobeu aquest problema.

Preste atenció, la versió del SGBD afecta la manera com s'emmagatzemen les dades, i aquest exemple pot no funcionar amb altres versions principals. En el meu cas, es va utilitzar la versió de Windows de mariadb 10.1.24. A més, encara que a mariadb treballes amb taules InnoDB, de fet ho són XtraDB, que exclou l'aplicabilitat del mètode amb InnoDB mysql.

Anàlisi de fitxers

A Python, tipus de dades bytes () mostra dades Unicode en lloc d'un conjunt normal de números. Tot i que podeu veure el fitxer en aquest formulari, per comoditat podeu convertir els bytes en forma numèrica convertint la matriu de bytes en una matriu normal (list(example_byte_array)). En qualsevol cas, tots dos mètodes són adequats per a l'anàlisi.

Després de mirar diversos fitxers ibd, podeu trobar el següent:

Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd

A més, si dividiu el fitxer per aquestes paraules clau, obtindreu majoritàriament fins i tot blocs de dades. Utilitzarem infimum com a divisor.

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

Una observació interessant: per a taules amb poca quantitat de dades, entre infimum i supremum hi ha un punter al nombre de files del bloc.

Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd — taula de proves amb 1a fila

Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd - taula de proves amb 2 files

La taula de matriu de files[0] es pot ometre. Després de mirar-lo, encara no he pogut trobar les dades de la taula en brut. Molt probablement, aquest bloc s'utilitza per emmagatzemar índexs i claus.
Començant per la taula[1] i traduint-la a una matriu numèrica, ja podeu notar alguns patrons, a saber:

Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd

Aquests són valors int emmagatzemats en una cadena. El primer byte indica si el nombre és positiu o negatiu. En el meu cas, tots els números són positius. A partir dels 3 bytes restants, podeu determinar el nombre mitjançant la funció següent. Guió:

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 exemple, 128, 0, 0, 1 = 1O 128, 0, 75, 108 = 19308.
La taula tenia una clau primària amb increment automàtic, i també es pot trobar aquí

Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd

Després de comparar les dades de les taules de prova, es va revelar que l'objecte DATETIME consta de 5 bytes i començava amb 153 (el més probable és que indiqui intervals anuals). Atès que l'interval DATTIME és '1000-01-01' a '9999-12-31', crec que el nombre de bytes pot variar, però en el meu cas, les dades cauen en el període del 2016 al 2019, així que assumirem amb 5 bytes suficients.

Per determinar el temps sense segons, es van escriure les funcions següents. Guió:

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}

No era possible escriure una funció funcional per a l'any i el mes, així que la vaig haver de piratejar. Guió:

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

Estic segur que si dediqueu n quantitat de temps, aquest malentès es pot corregir.
A continuació, una funció que retorna un objecte de data i hora d'una cadena. Guió:

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)

Gestionat per detectar valors que es repeteixen amb freqüència des de int, int, datetime, datetime Recuperació de dades de taules XtraDB sense un fitxer d'estructura mitjançant l'anàlisi byte a byte del fitxer ibd, sembla que això és el que necessites. A més, aquesta seqüència no es repeteix dues vegades per línia.

Utilitzant una expressió regular, trobem les dades necessàries:

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)

Tingueu en compte que quan cerqueu amb aquesta expressió, no serà possible determinar valors NULL als camps obligatoris, però en el meu cas això no és crític. Després repassem el que hem trobat en un bucle. Guió:

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)

De fet, això és tot, les dades de la matriu de resultats són les dades que necessitem. ###PS.###
Entenc que aquest mètode no és adequat per a tothom, però l'objectiu principal de l'article és impulsar l'acció en lloc de resoldre tots els vostres problemes. Crec que la solució més correcta seria començar a estudiar el codi font tu mateix mariadb, però a causa del temps limitat, el mètode actual semblava ser el més ràpid.

En alguns casos, després d'analitzar el fitxer, podreu determinar l'estructura aproximada i restaurar-la mitjançant un dels mètodes estàndard dels enllaços anteriors. Això serà molt més correcte i causarà menys problemes.

Font: www.habr.com

Afegeix comentari