Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen

Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen

forhistorie

Det skete så, at serveren blev angrebet af en ransomware-virus, som ved en "heldig ulykke" delvist efterlod .ibd-filerne (rådatafiler fra innodb-tabeller) uberørte, men samtidig fuldstændig krypterede .fpm-filerne ( strukturfiler). I dette tilfælde kunne .idb opdeles i:

  • underlagt restaurering gennem standardværktøjer og vejledninger. Til sådanne tilfælde er der en fremragende blive;
  • delvist krypterede tabeller. For det meste er der tale om store borde, hvor (som jeg forstår) angriberne ikke havde nok RAM til fuld kryptering;
  • Nå, fuldt krypterede tabeller, der ikke kan gendannes.

Det var muligt at bestemme, hvilken mulighed tabellerne tilhører ved blot at åbne den i en hvilken som helst teksteditor under den ønskede kodning (i mit tilfælde er det UTF8) og blot se filen for tilstedeværelsen af ​​tekstfelter, for eksempel:

Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen

Også i begyndelsen af ​​filen kan du observere et stort antal 0 bytes, og vira, der bruger blokkrypteringsalgoritmen (den mest almindelige) påvirker dem normalt også.
Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen

I mit tilfælde efterlod angriberne en 4-byte streng (1, 0, 0, 0) i slutningen af ​​hver krypteret fil, hvilket forenklede opgaven. For at søge efter uinficerede filer var scriptet nok:

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)

Det viste sig således at finde filer tilhørende den første type. Det andet indebærer en masse manuelt arbejde, men det, der blev fundet, var allerede nok. Alt ville være fint, men du skal vide det helt præcis struktur og der opstod (selvfølgelig) en sag om, at jeg skulle arbejde med et ofte skiftende bord. Ingen huskede, om felttypen blev ændret eller en ny kolonne blev tilføjet.

Wilds City kunne desværre ikke hjælpe med sådan en sag, hvorfor denne artikel bliver skrevet.

Tættere på punktet

Der er en struktur af en tabel fra 3 måneder siden, der ikke falder sammen med den nuværende (evt. ét felt, og evt. flere). Tabelstruktur:

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

i dette tilfælde skal du udtrække:

  • id_point int(11);
  • id_user int(11);
  • date_start DATO TID;
  • date_finish DATO TID.

Til gendannelse bruges en byte-for-byte-analyse af .ibd-filen, efterfulgt af at konvertere dem til en mere læsbar form. Da vi for at finde det, vi har brug for, kun skal analysere datatyper såsom int og datatime, vil artiklen kun beskrive dem, men nogle gange vil vi også henvise til andre datatyper, som kan hjælpe i andre lignende hændelser.

Opgave 1: felter med typerne DATETIME og TEXT havde NULL-værdier, og de springes simpelthen over i filen, på grund af dette var det ikke muligt at bestemme strukturen, der skulle gendannes i mit tilfælde. I de nye kolonner var standardværdien null, og en del af transaktionen kunne gå tabt på grund af indstillingen innodb_flush_log_at_trx_commit = 0, så der skulle bruges ekstra tid på at bestemme strukturen.

Opgave 2: det skal tages i betragtning, at rækker slettet via DELETE alle vil være i ibd-filen, men med ALTER TABLE vil deres struktur ikke blive opdateret. Som et resultat kan datastrukturen variere fra begyndelsen af ​​filen til dens slutning. Hvis du ofte bruger OPTIMIZE TABLE, er det usandsynligt, at du støder på et sådant problem.

Bemærk venligst, DBMS-versionen påvirker den måde, data gemmes på, og dette eksempel virker muligvis ikke for andre større versioner. I mit tilfælde blev Windows-versionen af ​​mariadb 10.1.24 brugt. Også selvom du i mariadb arbejder med InnoDB-tabeller, er de faktisk det XtraDB, hvilket udelukker anvendeligheden af ​​metoden med InnoDB mysql.

Filanalyse

I python, datatype bytes() viser Unicode-data i stedet for et almindeligt sæt tal. Selvom du kan se filen i denne formular, kan du for nemheds skyld konvertere bytes til numerisk form ved at konvertere byte-arrayet til et regulært array (list(example_byte_array)). Under alle omstændigheder er begge metoder velegnede til analyse.

Efter at have kigget flere ibd-filer igennem, kan du finde følgende:

Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen

Desuden, hvis du deler filen med disse nøgleord, får du for det meste lige datablokke. Vi vil bruge infimum som en divisor.

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

En interessant observation: for tabeller med en lille mængde data er der mellem infimum og supremum en pointer til antallet af rækker i blokken.

Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen — prøvebord med 1. række

Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen - testbord med 2 rækker

Rækketabellen[0] kan springes over. Efter at have kigget den igennem, var jeg stadig ikke i stand til at finde de rå tabeldata. Mest sandsynligt bruges denne blok til at gemme indekser og nøgler.
Startende med tabel[1] og oversætte det til et numerisk array, kan du allerede bemærke nogle mønstre, nemlig:

Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen

Disse er int-værdier gemt i en streng. Den første byte angiver, om tallet er positivt eller negativt. I mit tilfælde er alle tal positive. Ud fra de resterende 3 bytes kan du bestemme antallet ved hjælp af følgende funktion. Manuskript:

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

For eksempel, 128, 0, 0, 1 = 1Eller 128, 0, 75, 108 = 19308.
Tabellen havde en primær nøgle med auto-increment, og den kan også findes her

Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen

Efter at have sammenlignet dataene fra testtabellerne, blev det afsløret, at DATETIME-objektet består af 5 bytes og begyndte med 153 (højst sandsynligt angiver årlige intervaller). Da DATTIME-intervallet er '1000-01-01' til '9999-12-31', tror jeg antallet af bytes kan variere, men i mit tilfælde falder dataene i perioden fra 2016 til 2019, så vi vil antage at 5 bytes nok.

For at bestemme tiden uden sekunder blev følgende funktioner skrevet. Manuskript:

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}

Det var ikke muligt at skrive en funktionel funktion for år og måned, så jeg måtte hacke den. Manuskript:

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

Jeg er sikker på, at hvis du bruger n mængde tid, kan denne misforståelse rettes.
Dernæst en funktion, der returnerer et datetime-objekt fra en streng. Manuskript:

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)

Formået at detektere hyppigt gentagne værdier fra int, int, datetime, datetime Gendannelse af data fra XtraDB-tabeller uden en strukturfil ved hjælp af byte-for-byte-analyse af ibd-filen, det ser ud til, at det er det, du har brug for. Desuden gentages en sådan sekvens ikke to gange pr. linje.

Ved hjælp af et regulært udtryk finder vi de nødvendige data:

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)

Bemærk venligst, at når du søger med dette udtryk, vil det ikke være muligt at bestemme NULL-værdier i de påkrævede felter, men i mit tilfælde er dette ikke kritisk. Så gennemgår vi det, vi fandt, i en løkke. Manuskript:

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)

Faktisk er det alt, dataene fra resultatarrayet er de data, vi har brug for. ###PS.###
Jeg forstår, at denne metode ikke er egnet for alle, men hovedmålet med artiklen er at tilskynde til handling i stedet for at løse alle dine problemer. Jeg tror, ​​den mest korrekte løsning ville være at begynde at studere kildekoden selv mariadb, men på grund af begrænset tid syntes den nuværende metode at være den hurtigste.

I nogle tilfælde, efter at have analyseret filen, vil du være i stand til at bestemme den omtrentlige struktur og gendanne den ved hjælp af en af ​​standardmetoderne fra linkene ovenfor. Dette vil være meget mere korrekt og forårsage færre problemer.

Kilde: www.habr.com

Tilføj en kommentar