Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen

Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen

forhistorie

Det skjedde slik at serveren ble angrepet av et løsepengevirus, som ved en "heldig ulykke" delvis lot .ibd-filene (rådatafiler fra innodb-tabeller) være urørt, men samtidig fullstendig krypterte .fpm-filene ( strukturfiler). I dette tilfellet kan .idb deles inn i:

  • gjenstand for restaurering gjennom standardverktøy og guider. For slike tilfeller er det en utmerket bli;
  • delvis krypterte tabeller. For det meste er dette store tabeller, som (som jeg forstår) angriperne ikke hadde nok RAM for full kryptering;
  • Vel, fullt krypterte tabeller som ikke kan gjenopprettes.

Det var mulig å bestemme hvilket alternativ tabellene tilhører ved ganske enkelt å åpne den i et hvilket som helst tekstredigeringsprogram under ønsket koding (i mitt tilfelle er det UTF8) og ganske enkelt se på filen for tilstedeværelse av tekstfelt, for eksempel:

Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen

I begynnelsen av filen kan du også observere et stort antall 0 byte, og virus som bruker blokkkrypteringsalgoritmen (den vanligste) påvirker dem vanligvis også.
Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen

I mitt tilfelle la angriperne igjen en 4-byte streng (1, 0, 0, 0) på slutten av hver kryptert fil, noe som forenklet oppgaven. For å søke etter uinfiserte filer var skriptet 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)

Dermed viste det seg å finne filer tilhørende den første typen. Det andre innebærer mye manuelt arbeid, men det som ble funnet var allerede nok. Alt ville vært bra, men du må vite det helt presis struktur og (selvfølgelig) dukket det opp en sak om at jeg måtte jobbe med et ofte skiftende bord. Ingen husket om felttypen ble endret eller en ny kolonne ble lagt til.

Wilds City kunne dessverre ikke hjelpe med en slik sak, og det er derfor denne artikkelen blir skrevet.

Kom til poenget

Det er en struktur på en tabell fra 3 måneder siden som ikke sammenfaller med den gjeldende (eventuelt ett felt, og muligens flere). Tabellstruktur:

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 tilfellet må du trekke ut:

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

For gjenoppretting brukes en byte-for-byte-analyse av .ibd-filen, etterfulgt av å konvertere dem til en mer lesbar form. Siden for å finne det vi trenger, trenger vi kun å analysere datatyper som int og datatime, artikkelen vil kun beskrive dem, men noen ganger vil vi også referere til andre datatyper, som kan hjelpe i andre lignende hendelser.

1 problem: felt med typene DATETIME og TEXT hadde NULL-verdier, og de hoppes ganske enkelt over i filen, på grunn av dette var det ikke mulig å bestemme strukturen som skulle gjenopprettes i mitt tilfelle. I de nye kolonnene var standardverdien null, og en del av transaksjonen kan gå tapt på grunn av innstillingen innodb_flush_log_at_trx_commit = 0, så det måtte brukes ekstra tid på å bestemme strukturen.

2 problem: det bør tas i betraktning at rader slettet via DELETE vil alle være i ibd-filen, men med ALTER TABLE vil ikke strukturen deres oppdateres. Som et resultat kan datastrukturen variere fra begynnelsen av filen til slutten. Hvis du ofte bruker OPTIMIZE TABLE, er det lite sannsynlig at du vil støte på et slikt problem.

Vær oppmerksom, påvirker DBMS-versjonen måten data lagres på, og dette eksemplet fungerer kanskje ikke for andre hovedversjoner. I mitt tilfelle ble Windows-versjonen av mariadb 10.1.24 brukt. Også, selv om du i mariadb jobber med InnoDB-tabeller, er de det faktisk XtraDB, som utelukker anvendeligheten av metoden med InnoDB mysql.

Filanalyse

I python, datatype bytes() viser Unicode-data i stedet for et vanlig sett med tall. Selv om du kan se filen i dette skjemaet, kan du for enkelhets skyld konvertere bytene til numerisk form ved å konvertere byte-matrisen til en vanlig matrise (list(example_byte_array)). Begge metodene egner seg uansett for analyse.

Etter å ha sett gjennom flere ibd-filer, kan du finne følgende:

Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen

Dessuten, hvis du deler filen med disse nøkkelordene, får du stort sett jevne datablokker. Vi vil bruke infimum som en divisor.

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

En interessant observasjon: for tabeller med en liten mengde data, mellom infimum og supremum er det en peker til antall rader i blokken.

Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen — testtabell med 1. rad

Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen - testbord med 2 rader

Radmatrisetabellen[0] kan hoppes over. Etter å ha sett gjennom det, klarte jeg fortsatt ikke å finne de rå tabelldataene. Mest sannsynlig brukes denne blokken til å lagre indekser og nøkler.
Ved å starte med tabell[1] og oversette den til en numerisk matrise, kan du allerede legge merke til noen mønstre, nemlig:

Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen

Dette er int-verdier lagret i en streng. Den første byten angir om tallet er positivt eller negativt. I mitt tilfelle er alle tall positive. Fra de resterende 3 bytene kan du bestemme antallet ved å bruke følgende funksjon. Manus:

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 hadde en primærnøkkel med auto-increment, og den finnes også her

Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen

Etter å ha sammenlignet dataene fra testtabellene, ble det avslørt at DATETIME-objektet består av 5 byte og begynte med 153 (mest sannsynlig indikerer årlige intervaller). Siden DATTIME-området er '1000-01-01' til '9999-12-31', tror jeg antallet byte kan variere, men i mitt tilfelle faller dataene i perioden fra 2016 til 2019, så vi vil anta at 5 byte nok.

For å bestemme tiden uten sekunder ble følgende funksjoner skrevet. Manus:

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 mulig å skrive en funksjonell funksjon for året og måneden, så jeg måtte hacke den. Manus:

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 bruker mye tid, kan denne misforståelsen rettes.
Deretter en funksjon som returnerer et datetime-objekt fra en streng. Manus:

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)

Klarte å oppdage ofte gjentatte verdier fra int, int, datetime, datetime Gjenopprette data fra XtraDB-tabeller uten en strukturfil ved å bruke byte-for-byte-analyse av ibd-filen, det ser ut som dette er det du trenger. Dessuten gjentas ikke en slik sekvens to ganger per linje.

Ved å bruke et regulært uttrykk finner vi de nødvendige dataene:

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)

Vær oppmerksom på at når du søker med dette uttrykket, vil det ikke være mulig å bestemme NULL-verdier i de obligatoriske feltene, men i mitt tilfelle er dette ikke kritisk. Så går vi gjennom det vi fant i en løkke. Manus:

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, det er alt, dataene fra resultatmatrisen er dataene vi trenger. ###PS.###
Jeg forstår at denne metoden ikke passer for alle, men hovedmålet med artikkelen er å be om handling i stedet for å løse alle problemene dine. Jeg tror den mest korrekte løsningen vil være å begynne å studere kildekoden selv mariadb, men på grunn av begrenset tid, syntes den nåværende metoden å være den raskeste.

I noen tilfeller, etter å ha analysert filen, vil du kunne bestemme den omtrentlige strukturen og gjenopprette den ved å bruke en av standardmetodene fra koblingene ovenfor. Dette vil være mye mer korrekt og forårsake færre problemer.

Kilde: www.habr.com

Legg til en kommentar