Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke

Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke

prapovijest

Dogodilo se da je poslužitelj napao ransomware virus, koji je "sretnom nesrećom" djelomično ostavio .ibd datoteke (datoteke sirovih podataka innodb tablica) netaknute, ali je istovremeno potpuno kriptirao .fpm datoteke ( datoteke strukture). U ovom slučaju, .idb se može podijeliti na:

  • predmet obnove standardnim alatima i vodilicama. Za takve slučajeve postoji odličan postati;
  • djelomično šifrirane tablice. Uglavnom su to velike tablice, za koje (koliko sam shvatio) napadači nisu imali dovoljno RAM-a za punu enkripciju;
  • Pa, potpuno šifrirane tablice koje se ne mogu vratiti.

Bilo je moguće odrediti kojoj opciji pripadaju tablice jednostavnim otvaranjem u bilo kojem uređivaču teksta pod željenim kodiranjem (u mom slučaju to je UTF8) i jednostavnim pregledom datoteke za prisutnost tekstualnih polja, na primjer:

Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke

Također, na početku datoteke možete uočiti veliki broj 0 bajtova, a virusi koji koriste algoritam blok enkripcije (najčešći) također pogađaju i njih.
Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke

U mom slučaju, napadači su ostavili niz od 4 bajta (1, 0, 0, 0) na kraju svake šifrirane datoteke, što je pojednostavilo zadatak. Za traženje nezaraženih datoteka bila je dovoljna skripta:

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)

Tako se pokazalo da su pronađene datoteke koje pripadaju prvoj vrsti. Drugi uključuje puno ručnog rada, ali ono što je pronađeno već je dovoljno. Sve bi bilo u redu, ali treba znati apsolutno precizna struktura i (naravno) pojavio se slučaj da sam morao raditi sa stolom za presvlačenje. Nitko se nije sjetio je li promijenjen tip polja ili je dodan novi stupac.

Wilds City, nažalost, nije mogao pomoći u takvom slučaju, zbog čega nastaje ovaj članak.

Bliže stvarima

Postoji struktura tablice od prije 3 mjeseca koja se ne poklapa s trenutnom (moguće jedno polje, a moguće i više). Struktura tablice:

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

u ovom slučaju morate izdvojiti:

  • id_point int(11);
  • id_user int(11);
  • date_start DATUM VRIJEME;
  • date_finish DATUM VRIJEME.

Za oporavak se koristi analiza .ibd datoteke bajt po bajt, nakon čega slijedi njihovo pretvaranje u čitljiviji oblik. Budući da da bismo pronašli ono što nam treba, moramo analizirati samo tipove podataka kao što su int i datatime, članak će opisati samo njih, ali ponekad ćemo se pozvati i na druge tipove podataka, koji mogu pomoći u drugim sličnim incidentima.

Problem 1: polja s tipovima DATETIME i TEXT imala su NULL vrijednosti i jednostavno su preskočena u datoteci, zbog toga nije bilo moguće odrediti strukturu za vraćanje u mojem slučaju. U novim je stupcima zadana vrijednost bila null, a dio transakcije mogao bi se izgubiti zbog postavke innodb_flush_log_at_trx_commit = 0, pa bi se moralo potrošiti dodatno vrijeme da se odredi struktura.

Problem 2: treba uzeti u obzir da će svi redovi izbrisani preko DELETE biti u ibd datoteci, ali s ALTER TABLE njihova struktura neće biti ažurirana. Kao rezultat toga, struktura podataka može varirati od početka do kraja datoteke. Ako često koristite OPTIMIZE TABLE, malo je vjerojatno da ćete naići na takav problem.

Imajte na umu, verzija DBMS-a utječe na način pohranjivanja podataka i ovaj primjer možda neće raditi za druge glavne verzije. U mom slučaju korištena je Windows verzija mariadb 10.1.24. Također, iako u mariadb-u radite s InnoDB tablicama, zapravo jesu XtraDB, što isključuje primjenjivost metode s InnoDB mysql.

Analiza datoteke

U pythonu, vrsta podataka bajtovi() prikazuje Unicode podatke umjesto uobičajenog skupa brojeva. Iako možete vidjeti datoteku u ovom obliku, radi praktičnosti možete pretvoriti bajtove u numerički oblik pretvaranjem niza bajtova u regularni niz (list(example_byte_array)). U svakom slučaju obje su metode prikladne za analizu.

Nakon pregledavanja nekoliko ibd datoteka, možete pronaći sljedeće:

Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke

Štoviše, ako datoteku podijelite ovim ključnim riječima, dobit ćete uglavnom jednake blokove podataka. Koristit ćemo infimum kao djelitelj.

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

Zanimljivo opažanje: za tablice s malom količinom podataka, između infimuma i supremuma nalazi se pokazivač na broj redaka u bloku.

Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke — ispitna tablica s 1. redom

Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke - ispitna tablica s 2 reda

Niz redaka table[0] može se preskočiti. Nakon što sam je pregledao, još uvijek nisam uspio pronaći neobrađene podatke tablice. Najvjerojatnije se ovaj blok koristi za pohranjivanje indeksa i ključeva.
Počevši od table[1] i prevodeći je u numerički niz, već možete primijetiti neke obrasce, naime:

Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke

Ovo su int vrijednosti pohranjene u nizu. Prvi bajt označava da li je broj pozitivan ili negativan. U mom slučaju svi brojevi su pozitivni. Od preostala 3 bajta možete odrediti broj pomoću sljedeće funkcije. Skripta:

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

Na primjer, 128, 0, 0, 1 = 1Ili 128, 0, 75, 108 = 19308.
Tablica je imala primarni ključ s auto-inkrementom, a nalazi se i ovdje

Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke

Usporedbom podataka iz testnih tablica otkriveno je da se objekt DATETIME sastoji od 5 bajtova i počinje sa 153 (što najvjerojatnije označava godišnje intervale). Budući da je DATTIME raspon '1000-01-01' do '9999-12-31', mislim da broj bajtova može varirati, ali u mom slučaju podaci padaju u razdoblje od 2016. do 2019., pa ćemo pretpostaviti da je dovoljno 5 bajtova.

Za određivanje vremena bez sekundi napisane su sljedeće funkcije. Skripta:

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}

Nije bilo moguće napisati funkcionalnu funkciju za godinu i mjesec pa sam je morao hakirati. Skripta:

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

Siguran sam da ako potrošite n vremena, ovaj se nesporazum može ispraviti.
Zatim, funkcija koja vraća objekt datuma i vremena iz niza. Skripta:

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)

Uspio otkriti često ponavljane vrijednosti iz int, int, datetime, datetime Oporavak podataka iz XtraDB tablica bez strukturne datoteke pomoću analize bajt po bajt ibd datoteke, izgleda da je ovo ono što vam treba. Štoviše, takav se niz ne ponavlja dva puta po retku.

Pomoću regularnog izraza nalazimo potrebne podatke:

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)

Imajte na umu da prilikom pretraživanja pomoću ovog izraza neće biti moguće odrediti NULL vrijednosti u potrebnim poljima, ali u mom slučaju to nije kritično. Zatim prolazimo kroz ono što smo pronašli u petlji. Skripta:

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)

Zapravo, to je sve, podaci iz niza rezultata su podaci koji su nam potrebni. ###P.S.###
Razumijem da ova metoda nije prikladna za sve, ali glavni cilj članka je potaknuti akciju, a ne riješiti sve vaše probleme. Mislim da bi najispravnije rješenje bilo da sami počnete proučavati izvorni kod mariadb, no zbog ograničenog vremena trenutna se metoda činila najbržom.

U nekim slučajevima, nakon analize datoteke, moći ćete odrediti približnu strukturu i vratiti je pomoću jedne od standardnih metoda s gornjih veza. To će biti mnogo ispravnije i uzrokovati manje problema.

Izvor: www.habr.com

Dodajte komentar