Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi.

Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi.

Aizvēsture

Tā sagadījās, ka serverim uzbruka izspiedējvīruss, kas “laimīgas nejaušības” rezultātā daļēji atstāja neskartus .ibd failus (innodb tabulu neapstrādātus datu failus), bet tajā pašā laikā pilnībā šifrēja .fpm failus ( struktūras faili). Šajā gadījumā .idb var iedalīt:

  • pakļauta atjaunošanai, izmantojot standarta rīkus un rokasgrāmatas. Šādiem gadījumiem ir lielisks kļūt;
  • daļēji šifrētas tabulas. Pārsvarā tās ir lielas tabulas, kurām (cik saprotu) uzbrucējiem nepietika RAM pilnai šifrēšanai;
  • Nu, pilnībā šifrētas tabulas, kuras nevar atjaunot.

Bija iespējams noteikt, kurai opcijai tabulas pieder, vienkārši atverot to jebkurā teksta redaktorā ar vēlamo kodējumu (manā gadījumā tas ir UTF8) un vienkārši apskatot failu, lai noteiktu teksta lauku klātbūtni, piemēram:

Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi.

Tāpat faila sākumā var novērot lielu skaitu 0 baitu, un vīrusi, kas izmanto bloku šifrēšanas algoritmu (visbiežāk), parasti ietekmē arī tos.
Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi.

Manā gadījumā uzbrucēji katra šifrētā faila beigās atstāja 4 baitu virkni (1, 0, 0, 0), kas vienkāršoja uzdevumu. Lai meklētu neinficētus failus, pietika ar skriptu:

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)

Tādējādi izrādījās, ka tika atrasti faili, kas pieder pirmajam tipam. Otrais ir saistīts ar lielu roku darbu, bet ar atrasto jau pietika. Viss būtu labi, bet jums tas jāzina absolūti precīza struktūra un (protams) radās gadījums, ka jāstrādā ar bieži mainīgu galdu. Neviens neatcerējās, vai tika mainīts lauka veids vai pievienota jauna kolonna.

Wilds City diemžēl nevarēja palīdzēt ar šādu gadījumu, tāpēc tiek rakstīts šis raksts.

Nonāc pie lietas

Ir tabulas struktūra no 3 mēnešiem, kas nesakrīt ar pašreizējo (iespējams, viens lauks un, iespējams, vairāk). Tabulas struktūra:

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

šajā gadījumā jums ir jāizņem:

  • id_point int(11);
  • id_user int(11);
  • date_start DATUMS LAIKS;
  • date_finish DATUMS LAIKS.

Atkopšanai tiek izmantota .ibd faila analīze pa baitiem, kam seko to konvertēšana lasāmākā formā. Tā kā, lai atrastu to, kas mums nepieciešams, mums ir jāanalizē tikai tādi datu tipi kā int un datatime, rakstā tiks aprakstīti tikai tie, bet dažreiz mēs atsauksimies arī uz citiem datu veidiem, kas var palīdzēt citos līdzīgos incidentos.

1. problēma: laukiem ar tipiem DATETIME un TEXT bija NULL vērtības, un tās vienkārši tiek izlaistas failā, tāpēc manā gadījumā nebija iespējams noteikt atjaunojamo struktūru. Jaunajās kolonnās noklusējuma vērtība bija null, un daļa no darījuma varēja tikt zaudēta iestatījuma innodb_flush_log_at_trx_commit = 0 dēļ, tāpēc struktūras noteikšanai būtu jāpavada papildu laiks.

2. problēma: jāņem vērā, ka rindas, kas izdzēstas caur DELETE, visas būs ibd failā, bet ar ALTER TABLE to struktūra netiks atjaunināta. Rezultātā datu struktūra var atšķirties no faila sākuma līdz beigām. Ja jūs bieži izmantojat OPTIMIZE TABLE, tad visticamāk, ka nesastapsies ar šādu problēmu.

Обратите внимание, DBVS versija ietekmē datu glabāšanas veidu, un šis piemērs var nedarboties citām galvenajām versijām. Manā gadījumā tika izmantota mariadb 10.1.24 Windows versija. Turklāt, lai gan mariadb jūs strādājat ar InnoDB tabulām, patiesībā tās ir XtraDB, kas izslēdz metodes pielietojamību ar InnoDB mysql.

Failu analīze

Python, datu tips baiti () parāda unikoda datus parastās skaitļu kopas vietā. Lai gan varat skatīt failu šajā veidlapā, ērtības labad varat pārvērst baitus ciparu formā, pārvēršot baitu masīvu parastā masīvā (list(example_byte_array)). Jebkurā gadījumā analīzei ir piemērotas abas metodes.

Pārskatot vairākus ibd failus, varat atrast šādu informāciju:

Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi.

Turklāt, sadalot failu pēc šiem atslēgvārdiem, jūs iegūsit lielākoties vienmērīgus datu blokus. Mēs izmantosim infimu kā dalītāju.

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

Interesants novērojums: tabulām ar nelielu datu apjomu starp infimum un supremum ir rādītājs uz rindu skaitu blokā.

Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi. — testa tabula ar 1. rindu

Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi. - testa tabula ar 2 rindām

Rindu masīva tabulu[0] var izlaist. Pārskatot to, es joprojām nevarēju atrast neapstrādātos tabulas datus. Visticamāk, šis bloks tiek izmantots indeksu un atslēgu glabāšanai.
Sākot ar tabulu[1] un pārvēršot to ciparu masīvā, jūs jau varat pamanīt dažus modeļus, proti:

Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi.

Šīs ir int vērtības, kas tiek saglabātas virknē. Pirmais baits norāda, vai skaitlis ir pozitīvs vai negatīvs. Manā gadījumā visi skaitļi ir pozitīvi. No atlikušajiem 3 baitiem varat noteikt skaitli, izmantojot šādu funkciju. Skripts:

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

Tā, piemēram, 128, 0, 0, 1 = 1Vai 128, 0, 75, 108 = 19308.
Tabulai bija primārā atslēga ar automātisko palielināšanu, un to var atrast arī šeit

Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi.

Salīdzinot testa tabulu datus, atklājās, ka objekts DATETIME sastāv no 5 baitiem un sākās ar 153 (visticamāk, norādot gada intervālus). Tā kā DATTIME diapazons ir no '1000-01-01' līdz '9999-12-31', domāju, ka baitu skaits var atšķirties, bet manā gadījumā dati ir laika posmā no 2016. līdz 2019. gadam, tāpēc pieņemsim ka pietiek ar 5 baitiem.

Lai noteiktu laiku bez sekundēm, tika uzrakstītas šādas funkcijas. Skripts:

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}

Nevarēja uzrakstīt funkcionālo funkciju gadam un mēnesim, tāpēc nācās to uzlauzt. Skripts:

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

Esmu pārliecināts, ka, pavadot n daudz laika, šo pārpratumu var labot.
Pēc tam funkcija, kas atgriež datuma un laika objektu no virknes. Skripts:

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)

Izdevās noteikt bieži atkārtotas vērtības no int, int, datetime, datetime Datu atkopšana no XtraDB tabulām bez struktūras faila, izmantojot ibd faila baitu analīzi., izskatās, ka tas ir tas, kas jums nepieciešams. Turklāt šāda secība netiek atkārtota divas reizes katrā rindā.

Izmantojot regulāro izteiksmi, mēs atrodam nepieciešamos datus:

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)

Lūdzu, ņemiet vērā, ka, veicot meklēšanu, izmantojot šo izteiksmi, obligātajos laukos nebūs iespējams noteikt NULL vērtības, taču manā gadījumā tas nav kritiski. Tad mēs ejam cauri tam, ko atradām cilpā. Skripts:

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)

Faktiski tas arī viss, dati no rezultātu masīva ir mums nepieciešamie dati. ###PS.###
Es saprotu, ka šī metode nav piemērota visiem, taču raksta galvenais mērķis ir drīzāk rosināt rīkoties, nevis atrisināt visas jūsu problēmas. Manuprāt, pareizākais risinājums būtu pašam sākt pētīt pirmkodu mariadb, taču ierobežotā laika dēļ pašreizējā metode šķita ātrākā.

Dažos gadījumos pēc faila analīzes varēsiet noteikt aptuveno struktūru un atjaunot to, izmantojot kādu no standarta metodēm no iepriekš minētajām saitēm. Tas būs daudz pareizāk un radīs mazāk problēmu.

Avots: www.habr.com

Pievieno komentāru