Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke

Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke

prapovijest

Dogodilo se da je server napadnut virusom ransomware-a, koji je “sretnom nesrećom” djelimično ostavio .ibd fajlove (datoteke sirovih podataka innodb tabela) netaknutim, ali je istovremeno u potpunosti šifrirao .fpm fajlove ( strukturne datoteke). U ovom slučaju, .idb se može podijeliti na:

  • podložan restauraciji pomoću standardnih alata i vodiča. Za takve slučajeve postoji odličan postati;
  • djelomično šifrovane tabele. Uglavnom su to velike tabele, za koje (kako sam shvatio) napadači nisu imali dovoljno RAM-a za potpunu enkripciju;
  • Pa, potpuno šifrovane tabele koje se ne mogu vratiti.

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

Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke

Također, na početku datoteke možete uočiti veliki broj 0 bajtova, a virusi koji koriste algoritam blok šifriranja (najčešći) obično pogađaju i njih.
Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke

U mom slučaju, napadači su ostavili niz od 4 bajta (1, 0, 0, 0) na kraju svakog šifrovanog fajla, što je pojednostavilo zadatak. Za traženje neinficiranih fajlova dovoljna je bila 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đeni fajlovi koji pripadaju prvom tipu. Drugi uključuje dosta ručnog rada, ali ono što je pronađeno već je bilo dovoljno. Sve bi bilo u redu, ali morate znati apsolutno precizna struktura i (naravno) pojavio se slučaj da sam morao raditi sa stolom koji se često mijenja. Niko se nije sjetio da li je tip polja promijenjen ili je dodana nova kolona.

Wilds City, nažalost, nije mogao pomoći u ovakvom slučaju, zbog čega i pišemo ovaj članak.

Pređite na stvar

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

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 DATETIME;
  • date_finish DATETIME.

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

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

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

Obrati pažnju, verzija DBMS-a utječe na način na koji se podaci pohranjuju, a ovaj primjer možda neće raditi za druge glavne verzije. U mom slučaju je korištena windows verzija mariadb 10.1.24. Takođe, iako u mariadb-u radite sa InnoDB tabelama, u stvari jesu XtraDB, što isključuje primenljivost metode sa InnoDB mysql.

Analiza fajlova

U pythonu, tip podataka bajtovi() prikazuje Unicode podatke umjesto regularnog skupa brojeva. Iako možete pregledati datoteku u ovom obliku, zbog praktičnosti možete pretvoriti bajtove u numerički oblik tako što ćete konvertirati niz bajtova u običan niz (list(example_byte_array)). U svakom slučaju, obje metode su pogodne za analizu.

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

Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke

Štaviše, ako podijelite datoteku prema ovim ključnim riječima, dobit ćete uglavnom čak i blokove podataka. Koristićemo infimum kao djelitelj.

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

Zanimljivo zapažanje: za tabele sa malom količinom podataka, između infimuma i supremuma postoji pokazivač na broj redova u bloku.

Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke — test tablica sa 1. redom

Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke - test tabela sa 2 reda

Tabela niza redova [0] može se preskočiti. Nakon što sam ga pregledao, još uvijek nisam mogao pronaći neobrađene podatke tabele. Najvjerovatnije se ovaj blok koristi za pohranjivanje indeksa i ključeva.
Počevši od tabele[1] i prevodeći je u numerički niz, već možete uočiti neke obrasce, i to:

Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke

Ovo su int vrijednosti pohranjene u nizu. Prvi bajt pokazuje 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.
Tabela je imala primarni ključ sa automatskim inkrementom, a može se naći i ovdje

Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke

Upoređujući podatke iz testnih tabela, otkriveno je da se objekat DATETIME sastoji od 5 bajtova i počinje sa 153 (najvjerovatnije označava godišnje intervale). S obzirom da je DATTIME raspon od '1000-01-01' do '9999-12-31', mislim da broj bajtova može varirati, ali u mom slučaju podaci spadaju u period od 2016. do 2019., pa ćemo pretpostaviti tih 5 bajtova dovoljno.

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 hakovati. 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 nesporazum se može ispraviti.
Zatim, funkcija koja vraća objekt datuma i vremena iz stringa. 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 je otkriti često ponavljane vrijednosti od int, int, datetime, datetime Oporavak podataka iz XtraDB tablica bez strukture datoteke korištenjem bajt-po-bajt analize ibd datoteke, izgleda da je ovo ono što vam treba. Štaviše, takav niz se ne ponavlja dva puta po redu.

Koristeći regularni izraz, 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 obaveznim 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. ###PS.###
Razumijem da ova metoda nije prikladna za sve, ali glavni cilj članka je podstaknuti akciju, a ne riješiti sve svoje probleme. Mislim da bi najispravnije rješenje bilo da sami počnete proučavati izvorni kod mariadb, ali zbog ograničenog vremena, činilo se da je trenutna metoda najbrža.

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. Ovo će biti mnogo ispravnije i uzrokovati manje problema.

izvor: www.habr.com

Dodajte komentar