Saugumo pataisos analizė
Povilas Tumėnas, 2009-05-18 23:30:01

Vis daugiau programinės įrangos gamintojų į programų kūrimo procesą įtraukia įvairias saugumo gerinimui skirtas priemones - naudojama statinė programinio kodo analizė, fuzzer'iai ir kiti metodai padedantys išnaikinti lengviausiai aptinkamus pažeidžiamumus. Įsilaužėliams pažeidžiamumų paieška apsunkėja, todėl pataisų analizė pažeidžiamumų suradimui ir jo išnaudojimo kodo rašymui tampa vis patrauklesnė.

Dažniausiai laiko tarpas tarp pataisos išleidimo bei jos įdiegimo į didžiąją dalį kompiuterių yra pakankamai didelis, o taip pat kai kurios pataisos dėl įvairių priežasčių būna netgi visai praleidžiamos, ypač didžiosiose įmonėse su "trapia" infrastruktūra. Šiame straipsnyje apžvelgsiu Microsoft pataisų analizės eigą iliustruodamas vieno iš MS09-013 pataisomų pažeidžiamumų suradimu. MS09-013 išviso pataisomi trys pažeidžiamumai:

  • Windows HTTP tarnybos sertifikatų pavadinimo nesutapimo pažeidžiamumas (CVE-2009-0089) - dėl netinkamo sertifikato pavadinimo tikrinimo galima klastoti sertifikatą.
  • Windows HTTP tarnybos vartotojo prisijungimo duomenų atkartojimo pažeidžiamumas (CVE-2009-0550) - netinkamas NTLM vartotojų prisijungimo duomenų apdorojimas įgalina vykdyti užklausas prisijungusio vartotojo kontekste, tai galima panaudoti kodo vykdymui.
  • Windows HTTP tarnybos sveikojo skaičiaus atvirkštinis perpildymas (angl. "underflow") - mus dominantis pažeidžiamumas, kuris Microsoft teigimu dėl neteisingai apdorojamų reikšmių gautų iš web serverio gali privesti prie nuotolinio kodo vykdymo.

Pirmiausia parsisiunčiame pataisymą WindowsXP-KB960803-x86-ENU.exe skirtą Windows XP x86 versijai ir jį išpakuojame. Pataisoms skirtoms operacinei sistemai tą galima padaryti paleidus pataisą su "/x" komandinės eilutės parametru. Platesnį parametrų aprašymą galima gauti paleidus pataisą su parametru "/help". Deja "/x" parametras egzistuoja ne visose Microsoft pataisose, pavyzdžiui Microsoft Office pataisas yra žymiai sunkiau išpakuoti. Susidūrus su pataisa neturinčia dokumentuoto išpakavimo metodo paprasčiausia yra ją įdiegti į, tarkim, virtualią operacinę sistemą stebint visus atliekamus veiksmus failų sistemoje su tokiu įrankiu kaip Procmon. Išpakavę turime štai tokius failus:

Pataisyti failai saugomi aplankuose pavadinimu SPxQFE, bei SPxGDR, kuriuose x atitinka pataisų paketo numerį. QFE (Quick Fix Engineering) nuo GDR (General Distribution Releases) skiriasi tuo, kad į QFE būna integruotos pataisos pataisančios įvairias specifinių klientų problemas (angl. "hotfixes"). Mus domina tik saugumo pažeidžiamumai, todėl analizuosime GDR pataisymų šaką, QFE esantys įvairūs pataisymai tik apsunkintų analizę pridėdami "triukšmo" modifikuoto kodo pavidalu.

Kitas žingsnis pataisos analizavime - modifikuoto ir originalaus failo palyginimas. Dvejetainio kodo lyginimui egzistuoja ne vienas įrankis iš kurių populiariausi yra Zynamics BinDiff, kurio vieno vartotojo licencija kainuoja 900 eurų, bei nemokami DarunGrim2, eEye Binary Diffing Suite, į kurį įeina ir senesnė DarunGrim versija, bei Tenable Security PatchDiff2. Visiems šiems įrankiams reikalingas IDA Pro deasembleris. Šiai analizei naudosiu DarunGrim2. Parsisiuntus įdiegimo failą, jį tereikia įdiegti ir iš jo aplanko perkelti failą DarunGrim2.plw į IDA Pro plugins aplanką. Microsoft failų nagrinėjimą palengvina tai, jog Microsoft didžiajai daliai failų leidžia parsisiųsti derinimo simbolius, kuriuos panaudoję deasembleryje galėsime sužinoti funkcijų, struktūrų bei kai kurių kintamųjų pavadinimus, kas žymiai palengvins mūsų darbą. Priklausomai nuo jūsų turimos IDA versijos jums gali prireikti papildinio derinimo simbolių panaudojimui. Papildinį 5.0 ir naujesnėms IDA versijoms galite rasti čia.

Grįžtame prie mūsų anksčiau išpakuoto pataisymo, kuriame yra tik vienas modifikuotas failas winhttp.dll, susirandame jo originalią versiją sistemoje be įdiegto pataisymo ir įkeliame į IDA analizei, tą patį padarome ir su modifikuotu failu. Pasileidžiame DarunGrim2, spaudžiame Ctrl+N arba "File -> New Diffing From IDA" ir IDA lange su išanalizuota nemodifikuota winhttp.dll versija spaudžiame Alt+8, tą patį padarome ir su pataisytu winhttp.dll. Paspaudus Alt+8 rezultatai iš deasemblerio nusiunčiami į DarunGrim2, kuris gavęs dvi skirtingas analizes atlieka savo darbą ir po kelių sekundžių parodo rezultatus:

Kaip matome skiriasi dvi funkcijos Decode@ChunkFilter bei GetSecAuthMsg. Paspaudę ant GetSecAuthMsg matome originalios ir modifikuotos funkcijos grafus su spalvomis pažymėtais naujais ir modifikuotas kodo blokais:

Raudonai žymimi kodo blokai neegzistuojantys kitame faile, geltonai - modifikuoti, balti - išlikę tokie pat. Galime iškarto pastebėti, jog pataisytame faile pridėtas simbolių tikrinimas tikrinantis ar kažkokia tai eilutė lygi "NTLM", tai sutampa su anksčiau mano aprašytais pažeidžiamumais iš kurių vienas buvo susijęs su NTLM autorizacija. Mūsų šis pažeidžiamumas nedomina, todėl grįžtame prie funkcijų sąrašo ir matome kitą modifikuotą funkciją "Decode@Chunkfilter" ("@" reiškia, jog metodas "Decode" priklauso "Chunkfilter" klasei). Jei turime nors truputį žinių apie HTTP protokolą, kurio klientą dabar analizuojame, būsime girdėję, jog HTTP žinutės gali būti skaidomos gabalais - "chunk'ais". Po greito apsilankymo Wikipedijoje sužinome, jog tai labai paprastas kodavimo metodas gabalus atskiriantis CRLF simboliais, prieš kiekvieną gabalą pridedantis jo dydį 16-ainiu skaičiumi ir gale visų gabalų pridedantis "0". Šioje analizės dalyje galime pasukti vienu iš dviejų kelių: statiškai analizuoti kodą ir bandyti surasti pažeidžiamumą, arba pasinaudojant turima informacija parašyti šią funkciją naudojantį kodą, o po to parašyti kodą jo "sulaužymui". Nors šiuo atveju statinė analizė neatrodo tokia sunki, mes pasirinksime antrą, ne tokį nuobodų variantą. Peržiūrėję MSDN WinHTTP API dokumentaciją, kimbame į darbą ir suprogramuojame mažą HTTP klientą naudojantį WinHTTP API. Nors šiam reikalui turbūt būtų protingiausia naudoti C, tačiau aš naudosiu man "malonesnę" kalbą - Python. Viena iš Python puikių savybių tai nuo 2.5 versijos integruota biblioteka ctypes, įgalinanti labai lengvai sąveikauti su C bibliotekomis. Pasinaudoję ctypes po keliolikos minučių turime štai tokį, veikiantį WinHTTP klientą:

from ctypes import *

winhttp = WinDLL('winhttp.dll')
raw_input()
hSession = winhttp.WinHttpOpen(c_wchar_p('WinHTTP UserAgent/0.0'), 0, 0, 0, 0)
hConnect = winhttp.WinHttpConnect(hSession, c_wchar_p('127.0.0.1'), 80, 0)
hRequest = winhttp.WinHttpOpenRequest(hConnect, c_wchar_p('GET'), c_wchar_p('/'), 0, 0, 0, 0)
winhttp.WinHttpAddRequestHeaders(hRequest, c_wchar_p('Accept-Encoding: gzip, deflate\r\n'), -1, 0)
winhttp.WinHttpSendRequest(hRequest, 0, 0, 0, 0, 0, 0)
winhttp.WinHttpReceiveResponse(hRequest, 0)
size = pointer(c_long(1))
while size.contents.value:
    winhttp.WinHttpQueryDataAvailable(hRequest, size)
    print 'WinHttpQueryDataAvailable:', size.contents.value
    buf = create_string_buffer(size.contents.value)
    down = c_int(0)
    pdown = pointer(down)
    res = winhttp.WinHttpReadData(hRequest, buf, size.contents.value, pdown)
    print 'WinHttpReadData:', res
    print repr(buf.raw)

Jei jums nepatinka Python, galite naudoti C, ar tiesiog nusikopijuot C++ pavyzdį iš MSDN. Taigi turime klientą, reikia serverio. Surandame standartinį HTTP atsaką ir jį truputį modifikavę, per kelias minutes suprogramuojame serverį:

import socket

chunk_size = '100'
data_size = 10
d = 'HTTP/1.1 200 OK\r\n' + \
'Content-Type: text/plain\r\n' + \
'Transfer-Encoding: chunked\r\n' + \
'\r\n' + \
chunk_size + '\r\n' + \
'a' * data_size + '\r\n'\
'0\r\n\r\n'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(0)
conn, address = s.accept()
data = conn.recv(1024)
if data:
    print 'sending..'
    conn.send(d)
conn.close()

Mūsų ieškomas pažeidžiamumas apibūdinamas kaip sveikojo skaičiaus atvirkštinis perpildymas, iš to galime spėti, kad tas sveikasis skaičius bus kažkaip susijęs su duomenų dydžiu. Didžioji dalis sveikųjų skaičių perpildymų priveda prie kodo vykdymo dėl to, jog su jais atliekamos operacijos kaip su skaičiais su ženklu, o po to jie panaudojami kaip sveikieji skaičiai be ženklo įvairiose operacijose su atmintimi. Vienintelė vieta "chunked" kodavime, kurioje aprašomas dydis, tai prieš duomenų gabalą einantis šešioliktainis skaičius. Truputėlį pasižaidę su įvairiom data_size bei chunk_size reikšmėm http serveryje, chunk_size reikšmę pakeičiame į 'FFFFFFFF' - jei tai klasikinis sveikojo skaičiaus perpildymas tai mums reikės didelės reikšmės, kad sveikasis skaičius pakeistų ženklą. Kintamąjį data_size paliekame 10 ir pabandome paleisti mūsų HTTP serverį:

cmd

Ir kaip matome per tokį trumpą laiką sugebėjome sukelti pažeidžiamumą. Iš klaidos pranešimo galime spėti, jog mūsų spėjimas buvo teisingas, kažkokiai operacijai skaitančiai atmintį buvo perduota netinkama reikšmė ir ji bandė nuskaityti nepasiekiamą atminties vietą. Jau turime pažeidžiamumą sukeliantį kodą, todėl galime užbaigti šitą reikalą ir išsiaiškinti, kodėl kyla šis pažeidžiamumas. Pasileidžiame klientą ir prie jo prikabiname derintuvą. Šiam reikalui aš naudosiu OllyDbg. Prisikabinę prie python.exe proceso uždedame breakpoint'ą ant winhttp.dll ChunkFilter::Decode funkcijos, spaudžiame F9 - tęsiame python.exe veikimą ir paleidę http serverį spaudžiame kliento lange ENTER. Sustojame ties Decode funkcija ir pasižiūrėję į steką matome, jai perduotus argumentus:

stekas.png

Buvo perduoti du parametrai: nuoroda į gabalo duomenis ir 0x1B turbūt atitinkantis kažkokį dydį. Su F8 žengiame toliau.

kodas1.png

Pasiekę šitą kodo dalį ir atidžiai stebėdami registrų reikšmes matome, jog tai tiesiog atoi() C funkcijos ekvivalentas paverčiantis ASCII simbolius į skaičius, kurie saugomi ties [ESI+8]. Apsukę kelis ratus pro šitą kodą, pasiekiame:

kodas2.png

Iš [ESI+8] nuskaitomas dydis ir įrašomas į EDI ir po to jis, be jokių patikrinimų, panaudojamas funkcijoje RtlMoveMemory, kurios pagalba, atmintis būtų perstumiama tarp rodyklėmis pažymėtų vietų. Atmintis perstumiama ant šešioliktainio skaičiaus nurodančio gabalo dydį, nes ši funkcija dekoduoja gabalus iš "chunked" kodavimo, t.y. sustumdo atmintį taip, jog lieka tik duomenys viename gabale. Taigi pažeidžiamumas aiškus - gabalo dydis prieš jo panaudojimą atminties operacijose nėra tikrinamas, o tai priveda prie mūsų kontroliuojamo dydžio duomenų perrašymo su mūsų kontroliuojamais duomenimis. Norint parašyti šį pažeidžiamumą išnaudojantį kodą tektų pasirinktį kokį nors vektorių, pavyzdžiui vieną iš Windows tarnybų naudojančių winhttp.dll biblioteką. Peržiūrėjus įvairių Windows procesų įkrautas bibliotekas už akių užkliūna keli naudojantys WinHTTP, pvz.: UPNP tarnyba, explorer.exe, atnaujinimų tarnyba.

Nors tai ir nebuvo sudėtingas pažeidžiamumas tikiuosi , jog pademonstravau pataisų analizės eigą, kurią galėsite pritaikyti ne tik pažeidžiamumų ieškojime, bet ir norėdami sužinoti kas pasikeitė su eiline Windows pataisa.


Komentarai

Vardas:
Komentaras:

Copyright © 2005 - 2010, UAB „Critical Security“