ProHoster > Blog > Uprava > Obnovitev podatkov iz tabel XtraDB brez strukturne datoteke z analizo bajt za bajtom datoteke ibd
Obnovitev podatkov iz tabel XtraDB brez strukturne datoteke z analizo bajt za bajtom datoteke ibd
prazgodovina
Zgodilo se je, da je strežnik napadel izsiljevalski virus, ki je po »srečnem naključju« delno pustil nedotaknjene datoteke .ibd (datoteke s surovimi podatki tabel innodb), hkrati pa popolnoma šifriral datoteke .fpm ( strukturne datoteke). V tem primeru bi lahko .idb razdelili na:
predmet obnove s standardnimi orodji in vodniki. Za takšne primere obstaja odlična postati;
delno šifrirane tabele. Večinoma so to velike tabele, za katere (kot razumem) napadalci niso imeli dovolj RAM-a za popolno šifriranje;
No, popolnoma šifrirane tabele, ki jih ni mogoče obnoviti.
Kateri možnosti pripadajo tabele, je bilo mogoče ugotoviti tako, da jo preprosto odprete v katerem koli urejevalniku besedila pod želenim kodiranjem (v mojem primeru je to UTF8) in si preprosto ogledate datoteko za prisotnost besedilnih polj, na primer:
Prav tako lahko na začetku datoteke opazite veliko število 0 bajtov, virusi, ki uporabljajo algoritem za šifriranje blokov (najpogostejši), pa običajno vplivajo tudi nanje.
V mojem primeru so napadalci na koncu vsake šifrirane datoteke pustili 4-bajtni niz (1, 0, 0, 0), kar je poenostavilo nalogo. Za iskanje neokuženih datotek je bil dovolj skript:
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 je izkazalo, da najdemo datoteke, ki pripadajo prvi vrsti. Pri drugem je veliko ročnega dela, a je bilo že najdenega dovolj. Vse bi bilo v redu, vendar morate vedeti popolnoma natančna struktura in (seveda) se je pojavil primer, da sem moral delati s pogosto previjalno mizo. Nihče se ni spomnil, ali je bil spremenjen tip polja ali dodan nov stolpec.
Wilds City žal ni mogel pomagati v takšnem primeru, zato nastaja ta članek.
Pojdi na stvar
Obstaja struktura tabele izpred 3 mesecev, ki se ne ujema s trenutno (morda eno polje in morda več). 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)
);
v tem primeru morate ekstrahirati:
id_point int(11);
id_user int(11);
date_start DATUM ČAS;
date_finish DATUM ČAS.
Za obnovitev se uporabi analiza datoteke .ibd po bajtih, ki ji sledi pretvorba v bolj berljivo obliko. Ker moramo, da bi našli tisto, kar potrebujemo, analizirati samo tipe podatkov, kot sta int in datatime, bo članek opisal le njiju, včasih pa se bomo sklicevali tudi na druge tipe podatkov, ki lahko pomagajo pri drugih podobnih incidentih.
Problem 1: polja s tipoma DATETIME in TEXT so imela vrednosti NULL in so v datoteki preprosto preskočena, zaradi tega v mojem primeru ni bilo mogoče določiti strukture za obnovitev. V novih stolpcih je bila privzeta vrednost ničelna in del transakcije bi se lahko izgubil zaradi nastavitve innodb_flush_log_at_trx_commit = 0, zato bi bilo treba za določitev strukture porabiti dodaten čas.
Problem 2: treba je upoštevati, da bodo vse vrstice, izbrisane z DELETE, v datoteki ibd, vendar z ALTER TABLE njihova struktura ne bo posodobljena. Zaradi tega se lahko struktura podatkov razlikuje od začetka do konca datoteke. Če pogosto uporabljate OPTIMIZE TABLE, je malo verjetno, da boste naleteli na takšno težavo.
Обратите внимание, različica DBMS vpliva na način shranjevanja podatkov in ta primer morda ne bo deloval za druge večje različice. V mojem primeru je bila uporabljena Windows različica mariadb 10.1.24. Tudi, čeprav v mariadb delate s tabelami InnoDB, v resnici so XtraDB, kar izključuje uporabnost metode z InnoDB mysql.
Analiza datoteke
V pythonu, vrsta podatkov bajti() prikaže podatke Unicode namesto običajnega niza številk. Čeprav si lahko ogledate datoteko v tej obliki, lahko zaradi priročnosti pretvorite bajte v številsko obliko tako, da pretvorite matriko bajtov v navadno matriko (seznam(example_byte_array)). V vsakem primeru sta obe metodi primerni za analizo.
Po ogledu več datotek ibd lahko najdete naslednje:
Poleg tega, če datoteko razdelite na te ključne besede, boste dobili večinoma enakomerne bloke podatkov. Kot delitelj bomo uporabili infimum.
table = table.split("infimum".encode())
Zanimiva ugotovitev: pri tabelah z majhno količino podatkov je med infimumom in supremumom kazalec na število vrstic v bloku.
— preskusna miza s 1. vrstico
- testna miza z 2 vrsticama
Tabelo niza vrstic[0] lahko preskočite. Ko sem jo pregledal, še vedno nisem mogel najti neobdelanih podatkov tabele. Najverjetneje se ta blok uporablja za shranjevanje indeksov in ključev.
Če začnete s tabelo [1] in jo prevedete v številsko matriko, lahko že opazite nekaj vzorcev, in sicer:
To so int vrednosti, shranjene v nizu. Prvi bajt označuje, ali je število pozitivno ali negativno. V mojem primeru so vse številke pozitivne. Iz preostalih 3 bajtov lahko določite številko z naslednjo funkcijo. Skript:
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 primer, 128, 0, 0, 1 = 1Ali 128, 0, 75, 108 = 19308.
Tabela je imela primarni ključ s samodejnim povečevanjem in ga najdete tudi tukaj
Po primerjavi podatkov iz testnih tabel je bilo ugotovljeno, da objekt DATETIME obsega 5 bajtov in se začne s 153 (najverjetneje označuje letne intervale). Ker je obseg DATTIME od '1000-01-01' do '9999-12-31', menim, da se lahko število bajtov razlikuje, vendar v mojem primeru podatki spadajo v obdobje od 2016 do 2019, zato bomo predpostavili da je 5 bajtov dovolj.
Za določitev časa brez sekund so bile napisane naslednje funkcije. Skript:
Prepričan sem, da lahko ta nesporazum popravite, če porabite n časa.
Nato funkcija, ki vrne predmet datuma in časa iz niza. Skript:
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)
Uspelo zaznati pogosto ponavljajoče se vrednosti iz int, int, datetime, datetime , zdi se, da je to tisto, kar potrebujete. Poleg tega se takšno zaporedje ne ponovi dvakrat na vrstico.
Z uporabo regularnega izraza najdemo potrebne podatke:
Upoštevajte, da pri iskanju s tem izrazom ne bo mogoče določiti vrednosti NULL v zahtevanih poljih, vendar v mojem primeru to ni kritično. Nato gremo skozi tisto, kar smo našli v zanki. Skript:
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)
Pravzaprav je to vse, podatki iz niza rezultatov so podatki, ki jih potrebujemo. ###PS.###
Razumem, da ta metoda ni primerna za vsakogar, vendar je glavni cilj članka spodbuditi ukrepanje in ne rešiti vseh vaših težav. Mislim, da bi bila najbolj pravilna rešitev, da sami začnete preučevati izvorno kodo mariadb, vendar se je zaradi omejenega časa trenutna metoda zdela najhitrejša.
V nekaterih primerih boste po analizi datoteke lahko določili približno strukturo in jo obnovili z eno od standardnih metod iz zgornjih povezav. To bo veliko bolj pravilno in povzročalo manj težav.