Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä

Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä

esihistoria

Kävi niin, että palvelimeen hyökkäsi lunnasohjelmavirus, joka "onnen sattuman" johdosta jätti osittain .ibd-tiedostot (innodb-taulukoiden raakadatatiedostot) koskematta, mutta samalla salasi .fpm-tiedostot kokonaan ( rakennetiedostot). Tässä tapauksessa .idb voidaan jakaa seuraavasti:

  • voidaan palauttaa vakiotyökalujen ja oppaiden avulla. Tällaisissa tapauksissa on olemassa erinomainen tulla;
  • osittain salatut taulukot. Useimmiten nämä ovat suuria taulukoita, joille (ymmärtääkseni) hyökkääjillä ei ollut tarpeeksi RAM-muistia täydelliseen salaukseen;
  • No, täysin salatut taulukot, joita ei voida palauttaa.

Oli mahdollista määrittää, mihin vaihtoehtoon taulukot kuuluvat, avaamalla se missä tahansa tekstieditorissa halutulla koodauksella (minun tapauksessani se on UTF8) ja yksinkertaisesti katsomalla tiedostoa tekstikenttien esiintymisen varalta, esimerkiksi:

Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä

Myös tiedoston alussa voi havaita suuren määrän 0 tavua ja lohkosalausalgoritmia käyttävät virukset (yleisin) vaikuttavat yleensä niihinkin.
Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä

Minun tapauksessani hyökkääjät jättivät 4-tavuisen merkkijonon (1, 0, 0, 0) jokaisen salatun tiedoston loppuun, mikä yksinkertaisti tehtävää. Saastumattomien tiedostojen etsimiseen komentosarja riitti:

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)

Näin ollen kävi ilmi, että löydettiin ensimmäiseen tyyppiin kuuluvia tiedostoja. Toinen sisältää paljon manuaalista työtä, mutta se, mitä löydettiin, oli jo tarpeeksi. Kaikki olisi hyvin, mutta sinun on tiedettävä täysin tarkka rakenne ja (tietysti) tapahtui tapaus, että minun piti työskennellä usein vaihtuvan pöydän kanssa. Kukaan ei muistanut, onko kentän tyyppiä muutettu vai lisätty uusi sarake.

Valitettavasti Wilds City ei voinut auttaa tällaisessa tapauksessa, minkä vuoksi tämä artikkeli kirjoitetaan.

Lähempänä asiaa

On olemassa kolmen kuukauden takaisen taulukon rakenne, joka ei ole sama kuin nykyinen (mahdollisesti yksi kenttä ja mahdollisesti useampi). Taulukon rakenne:

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

tässä tapauksessa sinun on purettava:

  • id_point int(11);
  • id_user int(11);
  • date_start TREFFIAIKA;
  • date_finish TREFFIAIKA.

Palautukseen käytetään .ibd-tiedoston tavukohtaista analyysiä, jonka jälkeen tiedostot muunnetaan luettavampaan muotoon. Koska tarvitsemamme löytämiseksi tarvitsemme vain analysoida tietotyyppejä, kuten int ja datatime, artikkelissa kuvataan vain ne, mutta joskus viitataan myös muihin tietotyyppeihin, joista voi olla apua muissa vastaavissa tapahtumissa.

1-ongelma: kentillä tyypeillä DATETIME ja TEXT oli NULL-arvot, ja ne yksinkertaisesti ohitetaan tiedostossa, tämän vuoksi minun tapauksessani ei ollut mahdollista määrittää palautettavaa rakennetta. Uusissa sarakkeissa oletusarvo oli nolla, ja osa tapahtumasta saattoi menettää asetuksen innodb_flush_log_at_trx_commit = 0 vuoksi, joten rakenteen määrittämiseen joutuisi käyttämään lisäaikaa.

2-ongelma: on otettava huomioon, että DELETE-toiminnolla poistetut rivit ovat kaikki ibd-tiedostossa, mutta ALTER TABLE -toiminnolla niiden rakennetta ei päivitetä. Tämän seurauksena tietorakenne voi vaihdella tiedoston alusta sen loppuun. Jos käytät usein OPTIMISE TABLE -ohjelmaa, et todennäköisesti kohtaa tällaista ongelmaa.

Kiinnitä huomiota, DBMS-versio vaikuttaa tietojen tallennustapaan, eikä tämä esimerkki välttämättä toimi muissa pääversioissa. Minun tapauksessani käytettiin mariadb 10.1.24:n windows-versiota. Lisäksi vaikka mariadb:ssä työskentelet InnoDB-taulukoiden kanssa, itse asiassa ne ovat XtraDB, mikä sulkee pois menetelmän sovellettavuuden InnoDB mysql:n kanssa.

Tiedostoanalyysi

Pythonissa tietotyyppi tavua() näyttää Unicode-tiedot tavallisen numerosarjan sijasta. Vaikka voit tarkastella tiedostoa tässä muodossa, voit mukavuuden vuoksi muuntaa tavut numeeriseen muotoon muuntamalla tavutaulukon tavalliseksi taulukoksi (list(example_byte_array)). Joka tapauksessa molemmat menetelmät soveltuvat analysointiin.

Kun olet käynyt läpi useita ibd-tiedostoja, löydät seuraavat tiedot:

Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä

Lisäksi, jos jaat tiedoston näillä avainsanoilla, saat enimmäkseen tasaisia ​​tietolohkoja. Käytämme jakajana infimumia.

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

Mielenkiintoinen havainto: taulukoissa, joissa on pieni tietomäärä, infimumin ja supremumin välissä on osoitin lohkon rivien lukumäärään.

Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä — testitaulukko 1. rivillä

Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä - testipöytä 2 rivillä

Rivitaulukkotaulukko[0] voidaan ohittaa. Selailun jälkeen en edelleenkään löytänyt raakataulukon tietoja. Todennäköisimmin tätä lohkoa käytetään indeksien ja avainten tallentamiseen.
Alkaen taulukosta[1] ja kääntämällä sen numeeriseksi taulukoksi, voit jo huomata joitain kuvioita, nimittäin:

Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä

Nämä ovat merkkijonoon tallennettuja int-arvoja. Ensimmäinen tavu osoittaa, onko luku positiivinen vai negatiivinen. Minun tapauksessani kaikki luvut ovat positiivisia. Lopuista 3 tavusta voit määrittää numeron käyttämällä seuraavaa toimintoa. Käsikirjoitus:

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

Esimerkiksi 128, 0, 0, 1 = 1Tai 128, 0, 75, 108 = 19308.
Taulukossa oli ensisijainen avain automaattisella lisäyksellä, ja se löytyy myös täältä

Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä

Verrattaessa testitaulukoiden tietoja paljastui, että DATETIME-olio koostuu 5 tavusta ja alkoi 153:lla (todennäköisimmin osoittaen vuosittaisia ​​intervalleja). Koska DATTIME-alue on '1000-01-01' - '9999-12-31', tavujen määrä voi mielestäni vaihdella, mutta minun tapauksessani data osuu ajanjaksolle 2016-2019, joten oletamme että 5 tavua riittää.

Ajan määrittämiseksi ilman sekunteja kirjoitettiin seuraavat funktiot. Käsikirjoitus:

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}

Vuodelle ja kuukaudelle ei ollut mahdollista kirjoittaa toimivaa funktiota, joten minun piti hakkeroida se. Käsikirjoitus:

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

Olen varma, että jos käytät n aikaa, tämä väärinkäsitys voidaan korjata.
Seuraavaksi funktio, joka palauttaa datetime-objektin merkkijonosta. Käsikirjoitus:

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)

Onnistui havaitsemaan usein toistuvat arvot int, int, datetime, datetime Tietojen palauttaminen XtraDB-taulukoista ilman rakennetiedostoa käyttämällä ibd-tiedoston tavu-tavu-analyysiä, näyttää siltä, ​​​​että tämä on mitä tarvitset. Lisäksi tällaista sekvenssiä ei toisteta kahdesti riviä kohden.

Säännöllisen lausekkeen avulla löydämme tarvittavat tiedot:

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)

Huomaa, että kun haet tällä lausekkeella, ei ole mahdollista määrittää NULL-arvoja vaadituissa kentissä, mutta minun tapauksessani tämä ei ole kriittinen. Sitten käymme läpi sen, mitä löysimme. Käsikirjoitus:

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)

Itse asiassa siinä kaikki, tulostaulukon tiedot ovat tarvitsemamme tiedot. ###PS.###
Ymmärrän, että tämä menetelmä ei sovi kaikille, mutta artikkelin päätavoite on pikemminkin nopeuttaa toimia kuin ratkaista kaikki ongelmasi. Mielestäni oikea ratkaisu olisi aloittaa lähdekoodin opiskelu itse MariaDB, mutta rajallisen ajan vuoksi nykyinen menetelmä vaikutti nopeimmalta.

Joissakin tapauksissa tiedoston analysoinnin jälkeen voit määrittää likimääräisen rakenteen ja palauttaa sen jollakin yllä olevista linkeistä olevista vakiomenetelmistä. Tämä on paljon oikeampaa ja aiheuttaa vähemmän ongelmia.

Lähde: will.com

Lisää kommentti