Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd

Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd

Prehistoria

Sucedió que el servidor fue atacado por un virus ransomware que, por un “accidente afortunado”, dejó parcialmente intactos los archivos .ibd (archivos de datos sin procesar de las tablas innodb), pero al mismo tiempo cifró completamente los archivos .fpm ( archivos de estructura). En este caso, .idb se podría dividir en:

  • sujeto a restauración mediante herramientas y guías estándar. Para tales casos, existe una excelente convertirse;
  • tablas parcialmente cifradas. En su mayoría, se trata de tablas grandes para las cuales (según tengo entendido) los atacantes no tenían suficiente RAM para el cifrado completo;
  • Bueno, tablas totalmente cifradas que no se pueden restaurar.

Fue posible determinar a qué opción pertenecen las tablas simplemente abriéndolas en cualquier editor de texto con la codificación deseada (en mi caso es UTF8) y simplemente viendo el archivo para ver si hay campos de texto, por ejemplo:

Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd

Además, al principio del archivo se puede observar una gran cantidad de 0 bytes, y los virus que utilizan el algoritmo de cifrado de bloques (el más común) suelen afectarlos también.
Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd

En mi caso, los atacantes dejaron una cadena de 4 bytes (1, 0, 0, 0) al final de cada archivo cifrado, lo que simplificó la tarea. Para buscar archivos no infectados, bastaba con el script:

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)

Así, resultó encontrar archivos pertenecientes al primer tipo. El segundo implica mucho trabajo manual, pero lo encontrado ya fue suficiente. Todo estaría bien, pero necesitas saberlo. estructura absolutamente precisa y (por supuesto) surgió el caso de que tuve que trabajar con una mesa que cambiaba con frecuencia. Nadie recordaba si se cambió el tipo de campo o se agregó una nueva columna.

Desafortunadamente, Wilds City no pudo ayudar en tal caso, razón por la cual se escribe este artículo.

Más cerca del punto

Hay una estructura de una tabla de hace 3 meses que no coincide con la actual (posiblemente un campo, y posiblemente más). Estructura de la tabla:

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 este caso, necesitas extraer:

  • id_point int(11);
  • id_user int(11);
  • date_start FECHA Y HORA;
  • date_finish FECHA Y HORA.

Para la recuperación, se utiliza un análisis byte por byte del archivo .ibd, seguido de su conversión a un formato más legible. Dado que para encontrar lo que necesitamos solo necesitamos analizar tipos de datos como int y datatime, el artículo solo los describirá, pero en ocasiones también nos referiremos a otros tipos de datos, que pueden ayudar en otros incidentes similares.

Problema 1: los campos de tipo DATETIME y TEXT tenían valores NULL y simplemente se omiten en el archivo, por lo que no fue posible determinar la estructura a restaurar en mi caso. En las nuevas columnas, el valor predeterminado era nulo y parte de la transacción podría perderse debido a la configuración innodb_flush_log_at_trx_commit = 0, por lo que se tendría que dedicar tiempo adicional para determinar la estructura.

Problema 2: se debe tener en cuenta que las filas eliminadas mediante DELETE estarán todas en el archivo ibd, pero con ALTER TABLE su estructura no se actualizará. Como resultado, la estructura de datos puede variar desde el principio del archivo hasta su final. Si utiliza OPTIMIZE TABLE con frecuencia, es poco probable que encuentre ese problema.

Nota, la versión de DBMS afecta la forma en que se almacenan los datos y es posible que este ejemplo no funcione para otras versiones principales. En mi caso se utilizó la versión de Windows de mariadb 10.1.24. Además, aunque en mariadb se trabaja con tablas InnoDB, en realidad son XtraDB, lo que excluye la aplicabilidad del método con InnoDB mysql.

análisis de archivo

En Python, tipo de datos bytes () muestra datos Unicode en lugar de un conjunto normal de números. Aunque puede ver el archivo de esta forma, para mayor comodidad puede convertir los bytes a formato numérico convirtiendo la matriz de bytes en una matriz normal (lista (ejemplo_byte_array)). En cualquier caso, ambos métodos son adecuados para el análisis.

Después de revisar varios archivos ibd, puede encontrar lo siguiente:

Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd

Además, si divide el archivo por estas palabras clave, obtendrá bloques de datos en su mayoría pares. Usaremos el mínimo como divisor.

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

Una observación interesante: para tablas con una pequeña cantidad de datos, entre el mínimo y el supremo hay un puntero al número de filas del bloque.

Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd — mesa de prueba con la primera fila

Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd - mesa de prueba con 2 filas

Se puede omitir la tabla de matriz de filas [0]. Después de revisarlo, todavía no pude encontrar los datos sin procesar de la tabla. Lo más probable es que este bloque se utilice para almacenar índices y claves.
Comenzando con la tabla [1] y traduciéndola a una matriz numérica, ya puede notar algunos patrones, a saber:

Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd

Estos son valores int almacenados en una cadena. El primer byte indica si el número es positivo o negativo. En mi caso, todos los números son positivos. De los 3 bytes restantes, puede determinar el número utilizando la siguiente función. Guion:

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

Por ejemplo, 128, 0, 0, 1 = 1O 128, 0, 75, 108 = 19308.
La tabla tenía una clave principal con incremento automático y también se puede encontrar aquí.

Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd

Después de comparar los datos de las tablas de prueba, se reveló que el objeto DATETIME consta de 5 bytes y comienza con 153 (lo más probable es que indique intervalos anuales). Dado que el rango DATTIME es '1000-01-01' a '9999-12-31', creo que la cantidad de bytes puede variar, pero en mi caso, los datos caen en el período de 2016 a 2019, por lo que asumiremos Esos 5 bytes son suficientes.

Para determinar el tiempo sin segundos, se escribieron las siguientes funciones. Guion:

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 fue posible escribir una función funcional para el año y el mes, así que tuve que piratearla. Guion:

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

Estoy seguro de que si dedica n tiempo, este malentendido se puede corregir.
A continuación, una función que devuelve un objeto de fecha y hora a partir de una cadena. Guion:

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)

Se las arregló para detectar valores repetidos con frecuencia de int, int, datetime, datetime Recuperar datos de tablas XtraDB sin un archivo de estructura mediante análisis byte a byte del archivo ibd, parece que esto es lo que necesitas. Además, dicha secuencia no se repite dos veces por línea.

Usando una expresión regular, encontramos los datos necesarios:

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)

Tenga en cuenta que al buscar con esta expresión, no será posible determinar valores NULL en los campos requeridos, pero en mi caso esto no es crítico. Luego repasamos lo que encontramos en un bucle. Guion:

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)

En realidad, eso es todo, los datos de la matriz de resultados son los datos que necesitamos. ###PD.###
Entiendo que este método no es adecuado para todos, pero el objetivo principal del artículo es impulsar la acción en lugar de resolver todos sus problemas. Creo que la solución más correcta sería empezar a estudiar el código fuente usted mismo. mariadb, pero debido al tiempo limitado, el método actual parecía ser el más rápido.

En algunos casos, después de analizar el archivo, podrá determinar la estructura aproximada y restaurarlo utilizando uno de los métodos estándar de los enlaces anteriores. Esto será mucho más correcto y causará menos problemas.

Fuente: habr.com

Añadir un comentario