Failo formato analizė
Povilas Tumėnas, 2010-02-04 13:28:46

Dirbant su įvairia programine įranga tenka susidurti su įvairiausiais failų formatais. Didžioji dalis uždaro kodo programų, ypač senesnės nenaudoja žmonėms lengvai  įskaitomų ar bent jau dokumentuotų formatų tokių kaip XML ar netgi paprasčiausių INI failų nustatymams ar duomenims saugoti. Kartais dirbant su tokiomis programomis prireikia duomenis saugomus tais formatais apdoroti tokiu būdu, kurio nepalaiko programa arba tiesiog prireikia išsiaiškinti failo formatą pavyzdžiui tam, jog būtų galima parašyti fuzzer'į.

Šiame straipsnyje apžvelgsiu vieno paprasto dvejetainių failų formato analizės eigą. Analizuosiu foobar2000 multimedijos grotuvo žymių papildinio (angl. "bookmarks plugin"), su kuriuo galima išsaugoti žymes į kokią nors specifinę dainos vietą, saugomų duomenų formatą. Šį formatą pasirinkau neatsitiktinai - pats naudoju šį grotuvą kartu su papildiniu kurio tikslus pavadinimas yra foo_uie_bookmarks (analizuojamas bus naujausios šio papildinio versijos 0.3.0 duomenų formatas). Išsiaiškinti šį formatą man prireikė ne dėl saugumo priežasčių - tiesiog perkėliau savo muzikos kolekciją į kitą diską ir nebeveikė žymės, o failo vietos pakeitimo funkcijos šis papildinys neturi. Nors priežastis dėl kurios aš analizavau būtent šitą duomenų formatą nėra susijusi su saugumu tačiau pati analizės eiga bei jos rezultatas galėtų būti panaudotas pavyzdžiui "protingo" fuzzer'io programavimui kuris "suprastų" šį formatą.

Analizei naudojau du įrankius: FileInsight hex redaktorių bei Python 2.5 ir Python biblioteką duomenų apdorojimui Construct. FileInsight naudoju dėl to, jog jis turi visas padoraus hex redaktoriaus funkcijas bei keletą papildomų skirtų tokio tipo darbui - pvz. gali paprastai dekoduoti duomenis įvairiais metodais bei palaiko Python scenarijų rašymą.  Construct pasirinkau dėl to, jog tai mano manymu viena paprasčiausių bei  lengviausiai panaudojamų tokio tipo bibliotekų.

Taigi pradėkime nuo to, jog foo_uie_bookmarks saugo savo duomenis bookmarks.dat faile bookmarks aplanke kuris yra foobar'o instaliacijos aplanke. Testavimui į foobar'ą įkėliau tris paprastus mp3, vieną wav bei vieną mp3 su cue failu. Įkėliau kelis skirtingus failus tam, kad būtų išnaudojamas visas bookmarks plėtinio funkcionalumas, nežinojau ar wav failas turės papildomai įtakos, tačiau numaniau, jog cue failas turės nes jis suskaido vieną mp3 failą į kelis įrašus nors mp3 failas naudojamas tik vienas.

file_format_foo1.png

Štai taip atrodė mano paruoštas grojaraštis.

file_format_settings.png
 
Bookmarks papildinio nustatymuose įjungiau visus parametrus nurodančius saugoti visus įmanomus duomenis tam, jog faile būtų išsaugomi visi įmanomi duomenys.

Po to išsaugojau kelias žymes visais įmanomais variantais kuriuos galėjau sugalvot (pvz.: grojant mp3, jam negrojant, grojant mp3 su cue failu ir t.t.).

file_format_bookmarks.png

Kaip matot kiekviena žymė parametrų gali turėti pakankamai daug. Pasiruošęs testavimui bookmarks.dat failą, jį atsidariau hex redaktoriuje.

file_format_hex1.png
 
Žiūrint nuo failo pradžios galima pastebėti, jog pirmi 4 baitai skiriasi nuo kitų. Tai iškart man priminė "magiškus" skaičius. "Magiški" skaičiai naudojami daugelyje failų formatų bei protokolų kaip konstanta identifikuojanti formatą, pavyzdžiui visi PE failai prasideda raidėmis "MZ" (0x4D 0x5A),JPEG failai prasideda skaičiais 0xFF 0xD8, zip archyvai prasideda "PK" (0x50 0x4B), o šio formato failai greičiausiai prasideda "magiškais" skaičiais 0xCE 0x92 0xCE 0x9C. Norėdamas įsitikinti, kad mano spėjimas teisingas ištryniau bookmarks.dat ir vėl išsaugojau kelias skirtingas žymes. Mano spėjimas pasitvirtino nes baitai išliko tie patys bei to tuo pačiu sužinojau naujos informacijos - nepasikeitė ir penktasis baitas kurio reikšmė yra 0x02. Jei jis būtų didesnis būtų galima spėti, jog jis atitinka žymių skaičių ar kokią nors paprastą kontrolinę sumą, bet jam esant 0x02 vienintelis logiškas spėjimas yra tai, jog jis atitinka failo formato versiją. Tam įsitikinti parsisiunčiau senesnę (0.2.6) papildinio versiją ir ja pasinaudodamas išsaugojau kelias žymes. Senesnės versijos bookmarks.dat faile pirmi keturi baitai išliko tie patys tačiau pasikeitė penktas , jo reikšmė senesnėje versijoje yra 0x01 taigi galime drąsiai teigti, jog penktasis baitas žymi failo formato versiją.

Po formato versiją identifikuojančio baito seka skaičius, trys nuliai bei simbolių eilutė, tačiau apie tai vėliau, dabar parodysiu kaip galime su Construct apsirašyti jau turimą informaciją.

from construct import *

header = Struct('hdr',
    ULInt32('magic'),
    ULInt8('version')
)

Kaip ir minėjau Construct naudotis gan paprasta ir kadangi joje duomenys aprašomi deklaratyviai tai nereikia rašyti kodo apdorojančio duomenis, užtenka aprašyti duomenų struktūrą. Deklaravus  struktūrą 'hdr'  bei du jos narius 'magic' ir 'version' 32 ir 8 bitų duomenų tipus atitinkamai ir gaunasi duomenų struktūra su kuria galima apdoroti visų bookmarks bibliotekos failų antraštes. Visus įmanomus Construct duomenų tipus galite rasti jos tinklalapyje.

Po antraštės seka skaičius bei trys nuliai ir simbolių eilutė kurioje matosi, jog yra saugomas žymės "name" parametras, po to vėl skaičius, trys nuliai ir žymės "time" parametras. Manau akivaizdu, jog 15 tai po to sekančios simbolių eilutės dydis saugomas "little endian" adresavimo būdu. Norėdamas įsitikinti pasinaudojau FileInsight python scenarijų rašymo galimybe.

file_format_hex2.png

Šiame paveikslėlyje matosi, jog pažymėjus simbolių eilutę bei iškvietus getSelectionLength() funkciją gaunamas rezultatas 0x15 kuris atitinka skaičių esantį prieš ją. Tokio tipo simbolių eilutės vadinamos pascalio simbolių eilutėmis nes jos naudojamos Pascal šeimos programavimo kalbose. Construct taip pat palaiko šį duomenų tipą.

Žinant šią informaciją jau galima daugmaž numanyti apie šio formato struktūrą, bet prieš pradėdamas rašyti kodą norėjau įsitikinti, jog jis sudarytas tik iš antraštės bei įrašų susidedančių iš pascal tipo simbolių eilučių. Nusprendžiau tiesiog peržiūrėti ar kiekvienas žymės parametras turi atitinkamus duomenis. Peržiūrėdamas parametrus, ten kur turėtų būti "playlist content" radau tik 4 nulius, tai reiškia, jog nesvarbu ar žymė turi parametrą ar ne - jis vis tiek yra įrašomas kaip nulinio ilgio simbolių eilutė. Tą žinant jau galima parašyti kodą deklaruojantį žymės struktūrą.

record = Struct('rec',
    PascalString('name', length_field = ULInt32('length')),
    PascalString('time', length_field = ULInt32('length')),
    PascalString('playlist', length_field = ULInt32('length')),
    PascalString('added_on', length_field = ULInt32('length')),
    PascalString('location', length_field = ULInt32('length')),
    PascalString('comment', length_field = ULInt32('length')),
    PascalString('subsong', length_field = ULInt32('length')),
    PascalString('volume', length_field = ULInt32('length')),
    PascalString('playlist_content', length_field = ULInt32('length')),
    PascalString('flags', length_field = ULInt32('length')),
    PascalString('prestart', length_field = ULInt32('length')),
    PascalString('last_played_on', length_field = ULInt32('length')),
    PascalString('playback_queue', length_field = ULInt32('length'))
)
 

Štai taip atrodo žymės  struktūra kuri susideda iš 13 PascalString simbolių eilučių kurių ilgis aprašomas 32 baitų duomenų tipo ULInt32. Jų eilę nustačiau pagal bookmarks papildinio parametrų stulpelių eiliškumą. Belieka sujungti antraštės ir žymės įrašo struktūras į vieną.

fine_structure = Struct('bookmarks_file',
    header,
    GreedyRepeater(record)
)

Šitą viso failo struktūrą sudaro anksčiau aprašyta "header" struktūra bei su GreedyRepeater  funkcija  pakartojama record struktūra vieną ar daugiau kartų.

Turint tiek kodo galima pilnai apdoroti bei modifikuoti visus foo_uie_bookmarks duomenų failus.
Pavyzdžiui įrašius visą šitą kodą į failą ir pridėjus vieną eilutę:

print fine_structure.parse(open(r'bookmarks.dat', 'rb').read())

file_format_cmd.png

Matome, jog viskas apdorojama teisingai. Taip pat su keliomis eilutėmis kodo galime modifikuoti duomenis.

p = fine_structure.parse(open(r'bookmarks.dat', 'rb').read())
for book_rec in p.rec:
    book_rec.comment = 'test'
    book_rec.location = book_rec.location.replace('Music', 'Files\\Music')
open('bookmarks2.dat', 'wb').write(fine_structure.build(p))

Užtenka šitiek kodo norint perrašyti visų įrašų comment laukelius bei kiekviename location laukelyje pakeisti 'Music' į 'Files\Music'

file_format_bookmarks2.png
 
Taip atrodo pakeistas bookmarks.dat failas atidarytas per foobar2000.

Tikiuosi pademonstravau, jog bent jau paprastesnių failų formatų analizė nėra tokia sudėtinga kaip atrodo iš pirmo žvilgsnio, tiesiog reikia panaudoti atitinkamus įrankius bei turėti šiek tiek žinių. O tiems kurie nelabai suprato kuom tai susiję su saugumu skiriu šį ekrano atvaizdą kurį padariau po apytiksliai 2-ų minučių žaidimo su anksčiau pademonstruotu kodu.

file_format_crash.png


Komentarai

Vardas:
Komentaras:

Copyright © 2005 - 2010, UAB „Critical Security“