Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero

Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero

antaŭhistorio

Okazis, ke la servilo estis atakita de ransomware viruso, kiu, pro "bonŝanca akcidento", parte lasis la .ibd-dosierojn (krudaj datumdosieroj de innodb-tabeloj) netuŝitaj, sed samtempe tute ĉifris la .fpm-dosierojn ( strukturdosieroj). En ĉi tiu kazo, .idb povus esti dividita en:

  • submetata al restarigo per normaj iloj kaj gvidiloj. Por tiaj kazoj, estas bonega iĝi;
  • parte ĉifritaj tabeloj. Plejparte temas pri grandaj tabloj, por kiuj (kiel mi komprenas) la atakantoj ne havis sufiĉe da RAM por plena ĉifrado;
  • Nu, plene ĉifritaj tabeloj, kiuj ne povas esti restarigitaj.

Eblis determini al kiu opcio apartenas la tabeloj simple malfermante ĝin en iu ajn tekstredaktilo sub la dezirata kodigo (en mia kazo ĝi estas UTF8) kaj simple rigardante la dosieron por la ĉeesto de tekstaj kampoj, ekzemple:

Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero

Ankaŭ, komence de la dosiero vi povas observi grandan nombron da 0 bajtoj, kaj virusoj, kiuj uzas la blokan ĉifradan algoritmon (la plej oftan) kutime ankaŭ influas ilin.
Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero

En mia kazo, la atakantoj lasis 4-bajtan ĉenon (1, 0, 0, 0) ĉe la fino de ĉiu ĉifrita dosiero, kio simpligis la taskon. Por serĉi neinfektitajn dosierojn, la skripto sufiĉis:

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)

Tiel, ĝi rezultis trovi dosierojn apartenantaj al la unua tipo. La dua implicas multe da manlaboro, sed tio, kio troviĝis, jam sufiĉis. Ĉio estus bone, sed vi devas scii absolute preciza strukturo kaj (kompreneble) aperis kazo, ke mi devis labori kun ofte ŝanĝiĝanta tablo. Neniu memoris ĉu la kampotipo estis ŝanĝita aŭ nova kolumno estis aldonita.

Wilds City, bedaŭrinde, ne povis helpi pri tia kazo, tial ĉi tiu artikolo estas verkita.

Iru al la punkto

Estas strukturo de tabelo de antaŭ 3 monatoj, kiu ne koincidas kun la nuna (eble unu kampo, kaj eble pli). Tablostrukturo:

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

en ĉi tiu kazo, vi devas eltiri:

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

Por reakiro, oni uzas bito-post-bajtan analizon de la .ibd-dosiero, sekvita per konvertado de ili en pli legeblan formon. Ĉar por trovi tion, kion ni bezonas, ni nur bezonas analizi datumtipojn kiel int kaj datatime, la artikolo priskribos nur ilin, sed foje ni ankaŭ raportos al aliaj datumtipoj, kiuj povas helpi en aliaj similaj okazaĵoj.

Problemo 1: kampoj kun tipoj DATETIME kaj TEXT havis NULL-valorojn, kaj ili estas simple preterlasitaj en la dosiero, pro tio, ne eblis determini la strukturon restarigin en mia kazo. En la novaj kolumnoj, la defaŭlta valoro estis nula, kaj parto de la transakcio povus esti perdita pro la agordo innodb_flush_log_at_trx_commit = 0, do plia tempo devus esti elspezita por determini la strukturon.

Problemo 2: oni devas konsideri, ke vicoj forigitaj per DELETE ĉiuj estos en la ibd-dosiero, sed kun ALTER TABLE ilia strukturo ne estos ĝisdatigita. Kiel rezulto, la datumstrukturo povas varii de la komenco de la dosiero ĝis ĝia fino. Se vi ofte uzas OPTIMIZE TABLE, tiam vi verŝajne ne renkontos tian problemon.

Atentu, la DBMS-versio influas la manieron kiel datumoj estas stokitaj, kaj ĉi tiu ekzemplo eble ne funkcias por aliaj gravaj versioj. En mia kazo, la vindoza versio de mariadb 10.1.24 estis uzata. Ankaŭ, kvankam en mariadb vi laboras kun InnoDB-tabloj, fakte ili estas XtraDB, kiu ekskludas la aplikeblecon de la metodo kun InnoDB mysql.

Dosiera analizo

En python, datumtipo bajtoj () montras Unikodajn datumojn anstataŭ regula aro de nombroj. Kvankam vi povas vidi la dosieron en ĉi tiu formo, por oportuno vi povas konverti la bajtojn en nombran formon konvertante la bajtan tabelon en regulan tabelon (list(example_byte_array)). Ĉiukaze, ambaŭ metodoj taŭgas por analizo.

Trarigardinte plurajn ibd-dosierojn, vi povas trovi la jenajn:

Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero

Krome, se vi dividas la dosieron per ĉi tiuj ŝlosilvortoj, vi ricevos plejparte eĉ blokojn da datumoj. Ni uzos infimum kiel dividanton.

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

Interesa observo: por tabeloj kun malgranda kvanto da datumoj, inter infimum kaj supremum estas montrilo al la nombro da vicoj en la bloko.

Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero — prova tablo kun 1-a vico

Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero - prova tablo kun 2 vicoj

La vica tabelo[0] povas esti preterlasita. Trarigardinte ĝin, mi ankoraŭ ne povis trovi la krudajn tabelajn datumojn. Plej verŝajne, ĉi tiu bloko estas uzata por konservi indeksojn kaj ŝlosilojn.
Komencante per tabelo[1] kaj tradukante ĝin en numeran tabelon, vi jam povas rimarki kelkajn ŝablonojn, nome:

Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero

Ĉi tiuj estas int-valoroj stokitaj en ĉeno. La unua bajto indikas ĉu la nombro estas pozitiva aŭ negativa. En mia kazo, ĉiuj nombroj estas pozitivaj. El la ceteraj 3 bajtoj, vi povas determini la nombron uzante la sekvan funkcion. Skripto:

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

Ekzemple, 128, 0, 0, 1 = 1128, 0, 75, 108 = 19308.
La tablo havis ĉefan ŝlosilon kun aŭtomata pliigo, kaj ĝi ankaŭ troveblas ĉi tie

Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero

Komparinte la datumojn de la testaj tabeloj, estis rivelita, ke la objekto DATETIME konsistas el 5 bajtoj kaj komenciĝis per 153 (plej verŝajne indikante jarajn intervalojn). Ĉar la DATTIME-gamo estas '1000-01-01' ĝis '9999-12-31', mi pensas, ke la nombro da bajtoj povas varii, sed en mia kazo, la datumoj falas en la periodo de 2016 ĝis 2019, do ni supozos ke 5 bajtoj sufiĉas.

Por determini la tempon sen sekundoj, la sekvaj funkcioj estis skribitaj. Skripto:

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}

Ne eblis skribi funkcian funkcion por la jaro kaj monato, do mi devis haki ĝin. Skripto:

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

Mi certas, ke se vi pasigas n kvanton da tempo, ĉi tiu miskompreno povas esti korektita.
Poste, funkcio kiu resendas dattempan objekton el ĉeno. Skripto:

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)

Sukcesis detekti ofte ripetajn valorojn de int, int, datetime, datetime Reakiro de datumoj de XtraDB-tabeloj sen strukturdosiero uzante bajton-post-bajtan analizon de la ibd-dosiero, ŝajnas, ke ĉi tio estas kion vi bezonas. Krome, tia sinsekvo ne ripetiĝas dufoje per linio.

Uzante regulan esprimon, ni trovas la necesajn datumojn:

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)

Bonvolu noti, ke serĉante per ĉi tiu esprimo, ne eblos determini NULL-valorojn en la postulataj kampoj, sed en mia kazo ĉi tio ne estas kritika. Poste ni trapasas tion, kion ni trovis en buklo. Skripto:

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)

Fakte, jen ĉio, la datumoj de la rezulta tabelo estas la datumoj, kiujn ni bezonas. ###PS.###
Mi komprenas, ke ĉi tiu metodo ne taŭgas por ĉiuj, sed la ĉefa celo de la artikolo estas instigi agadon prefere ol solvi ĉiujn viajn problemojn. Mi pensas, ke la plej ĝusta solvo estus mem ekstudi la fontkodon mariado, sed pro limigita tempo, la nuna metodo ŝajnis esti la plej rapida.

En iuj kazoj, post analizo de la dosiero, vi povos determini la proksimuman strukturon kaj restarigi ĝin per unu el la normaj metodoj el la supraj ligiloj. Ĉi tio estos multe pli ĝusta kaj kaŭzos malpli da problemoj.

fonto: www.habr.com

Aldoni komenton