Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd

Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd

prehistoria

Ocorreu que o servidor foi atacado por un virus ransomware que, por un "afortunado accidente", deixou parcialmente intactos os ficheiros .ibd (arquivos de datos en bruto das táboas innodb), pero ao mesmo tempo cifrou completamente os ficheiros .fpm ( ficheiros de estrutura). Neste caso, .idb podería dividirse en:

  • suxeitos a restauración mediante ferramentas e guías estándar. Para tales casos, hai un excelente converterse;
  • táboas parcialmente cifradas. Principalmente trátase de táboas grandes, para as que (segundo entendo) os atacantes non tiñan RAM suficiente para o cifrado completo;
  • Ben, táboas totalmente cifradas que non se poden restaurar.

Foi posible determinar a que opción pertencen as táboas simplemente abrindoa en calquera editor de texto baixo a codificación desexada (no meu caso é UTF8) e simplemente vendo o ficheiro para a presenza de campos de texto, por exemplo:

Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd

Ademais, ao comezo do ficheiro pódese observar un gran número de bytes 0, e os virus que utilizan o algoritmo de cifrado de bloques (o máis común) adoitan afectarlles tamén.
Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd

No meu caso, os atacantes deixaron unha cadea de 4 bytes (1, 0, 0, 0) ao final de cada ficheiro cifrado, o que simplificaba a tarefa. Para buscar ficheiros non infectados, o script foi suficiente:

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í, resultou atopar ficheiros pertencentes ao primeiro tipo. O segundo implica moito traballo manual, pero o que se atopou xa foi suficiente. Todo estaría ben, pero hai que saber estrutura absolutamente precisa e (por suposto) xurdiu un caso de que tiven que traballar cunha mesa que cambiaba frecuentemente. Ninguén recordaba se se cambiou o tipo de campo ou se engadiu unha nova columna.

Wilds City, por desgraza, non puido axudar con tal caso, polo que se está a escribir este artigo.

Chegar ao grano

Existe unha estrutura dunha táboa de hai 3 meses que non coincide coa actual (posiblemente un campo, e posiblemente máis). Estrutura da táboa:

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

neste caso, cómpre extraer:

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

Para a recuperación, utilízase unha análise byte a byte do ficheiro .ibd, seguida de convertelo nunha forma máis lexible. Dado que para atopar o que necesitamos, só necesitamos analizar tipos de datos como int e datatime, o artigo só os describirá, pero ás veces tamén nos referiremos a outros tipos de datos, que poden axudar noutros incidentes similares.

Problema 1: os campos cos tipos DATETIME e TEXT tiñan valores NULL, e simplemente omítense no ficheiro, por iso non foi posible determinar a estrutura a restaurar no meu caso. Nas novas columnas, o valor predeterminado era nulo e podería perderse parte da transacción debido á configuración innodb_flush_log_at_trx_commit = 0, polo que habería que gastar máis tempo para determinar a estrutura.

Problema 2: hai que ter en conta que as filas eliminadas mediante DELETE estarán todas no ficheiro ibd, pero con ALTER TABLE non se actualizará a súa estrutura. Como resultado, a estrutura de datos pode variar dende o principio ata o final do ficheiro. Se usas a miúdo OPTIMIZE TABLE, é pouco probable que te atopes con tal problema.

Preste atención, a versión do DBMS afecta a forma en que se almacenan os datos e é posible que este exemplo non funcione para outras versións principais. No meu caso, utilizouse a versión de Windows de mariadb 10.1.24. Ademais, aínda que en mariadb traballas con táboas InnoDB, de feito o son XtraDB, que exclúe a aplicabilidade do método con InnoDB mysql.

Análise de arquivos

En python, tipo de datos bytes () mostra datos Unicode en lugar dun conxunto normal de números. Aínda que pode ver o ficheiro neste formulario, por comodidade pode converter os bytes en forma numérica convertendo a matriz de bytes nunha matriz normal (list(example_byte_array)). En calquera caso, ambos os métodos son axeitados para a análise.

Despois de buscar varios ficheiros ibd, podes atopar o seguinte:

Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd

Ademais, se divide o ficheiro por estas palabras clave, obterá maiormente incluso bloques de datos. Usaremos infimum como divisor.

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

Unha observación interesante: para as táboas cunha pequena cantidade de datos, entre infimum e supremum hai un punteiro para o número de filas do bloque.

Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd - táboa de probas con 1a fila

Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd - táboa de proba con 2 filas

Pódese omitir a táboa de matriz de filas[0]. Despois de miralo, aínda non puiden atopar os datos da táboa en bruto. O máis probable é que este bloque se use para almacenar índices e claves.
Comezando coa táboa[1] e traducíndoa nunha matriz numérica, xa podes notar algúns patróns, a saber:

Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd

Estes son valores int almacenados nunha cadea. O primeiro byte indica se o número é positivo ou negativo. No meu caso, todos os números son positivos. A partir dos 3 bytes restantes, pode determinar o número mediante a seguinte función. Guión:

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 exemplo, a 128, 0, 0, 1 = 1Ou 128, 0, 75, 108 = 19308.
A táboa tiña unha clave principal con incremento automático e tamén se pode atopar aquí

Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd

Despois de comparar os datos das táboas de proba, revelouse que o obxecto DATETIME consta de 5 bytes e comezou con 153 (o máis probable é que indica intervalos anuais). Dado que o intervalo DATTIME é de '1000-01-01' a '9999-12-31', creo que o número de bytes pode variar, pero no meu caso, os datos caen no período de 2016 a 2019, polo que asumiremos que 5 bytes son suficientes.

Para determinar o tempo sen segundos, escribíronse as seguintes funcións. Guión:

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}

Non era posible escribir unha función funcional para o ano e o mes, polo que tiven que hackeala. Guión:

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

Estou seguro de que se pasa n tempo, este malentendido pódese corrixir.
A continuación, unha función que devolve un obxecto de data e hora dunha cadea. Guió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)

Xestionado para detectar valores repetidos con frecuencia de int, int, datetime, datetime Recuperación de datos de táboas XtraDB sen un ficheiro de estrutura mediante a análise byte a byte do ficheiro ibd, parece que isto é o que necesitas. Ademais, tal secuencia non se repite dúas veces por liña.

Usando unha expresión regular, atopamos os 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)

Teña en conta que ao buscar usando esta expresión, non será posible determinar valores NULL nos campos obrigatorios, pero no meu caso isto non é crítico. Despois repasamos o que atopamos nun bucle. Guión:

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 realidade, iso é todo, os datos da matriz de resultados son os datos que necesitamos. ###PD.###
Entendo que este método non é adecuado para todos, pero o obxectivo principal do artigo é impulsar a acción en lugar de resolver todos os seus problemas. Creo que a solución máis correcta sería comezar a estudar vostede mesmo o código fonte mariadb, pero debido ao tempo limitado, o método actual parecía ser o máis rápido.

Nalgúns casos, despois de analizar o ficheiro, poderás determinar a estrutura aproximada e restaurala mediante un dos métodos estándar das ligazóns anteriores. Isto será moito máis correcto e causará menos problemas.

Fonte: www.habr.com

Engadir un comentario