Kas kelias savaites išgirstame apie naujus programinės įrangos pažeidžiamumų tipus, iš kurių dauguma surandami ypač aukštame lygmenyje, pvz. naršyklių ar WEB resursų klaidos. Bet šiame tekste apžvelgsiu visai neseniai prisimintą pažeidžiamumų/klaidų klasę, kuri yra kur kas arčiau „geležies“ – pakalbėsime apie procesorių klaidas.
Vienas žinomiausių procesorių pažeidžiamumų yra „Pentium“ „FDIV“ klaida, kuri nors ir buvo sąlyginai nerimta, tačiau kai buvo atrasta sukėlė daug triukšmo IT pasaulyje. „FDIV“ klaidą 1994-ais metais paviešino prof. Thomas Nicely. Šios klaidos paveikti procesoriai atlikdami tam tikras slankiojo kablelio skaičių dalybos operacijas grąžindavo neteisingus rezultatus. Iš pradžių „Intel“ teigė, jog klaida nėra reikšminga, tačiau po konkurentų bei publikos spaudimo „Intel“ pasiūlė pakeisti visus jau pagamintus procesorius su defektais – tai jiems galėjo kainuoti ypač didelius pinigus, bet tik labai maža pirkėjų dalis pasinaudojo šiuo pasiūlymu.
„Pentium 66“ (sSpec=SX837) su FDIV klaida („Wikipedia“). |
---|
2007-aisiais „Intel“ vėl užsitraukė publikos nemalonę po to kai buvo išleisti mikrokodo atnaujinimai populiariai procesorių šeimai – „Intel Core 2“. Po atnaujinimų išleidimo „OpenBSD“ projekto vadovas Theo de Raadt į „OpenBSD“ elektroninio pašto grupę parašė savo kritišką nuomonę apie Intel procesoriuose esančias klaidas. Theo savo pranešime teigė, jog kai kurios klaidos, esančios minėtuose procesoriuose jį tiesiog gąsdina ir jog kelios iš jų yra rimti saugumo pažeidžiamumai, kuriuos būtų įmanoma išnaudoti netgi per nuotolį. Toks pareiškimas IT pasaulyje sukėlė kalbas, jog dangus griūva, tačiau apie praktišką pažeidžiamumų išnaudojimą niekas neužsiminė tik iki šių metų vasaros.
Liepą Kris Kaspersky – rusų kilmės saugumo tyrinėtojas paskelbė, jog spalio mėnesį vyksiančioje HITB (Hack In The Box) konferencijoje jis pademonstruos kaip galima realiai išnaudoti „Intel“ procesorių pažeidžiamumus. Jis teigė: „Aš viešai pateiksiu realų, veikiantį kodą“, kalbėdamas apie pažadą pademonstruoti procesorių pažeidžiamumus pasitelkiant „Java JIT“ (Just In Time) kompiliatorių, „JavaScript“ scenarijus ar atakai naudojant vien tik TCP/IP paketus. Atėjus spaliui sulaukėme jo prezentacijos, tačiau jokio rimtesnio kodo Kasperskis nepateikė: „dėl kai kurių priežasčių parodomojo kodo neviešinsiu“. Taigi kyla klausimas, kokią iš tikro grėsmę kelia procesorių klaidos? Ar sugrius dangus ir jei taip, tai kada?
Didžiosios dalies modernių procesorių klaidos yra viešai prieinamos gamintojų tinklalapiuose. Dokumentuotų klaidų skaičius kartais siekia kelias dešimtis, pvz. naujausios kartos „Intel Core 2“ procesoriams aprašytos maždaug 75 klaidos, „Athlon 64“ procesoriams po apytiksliai 70 klaidų. Tačiau tik labai maža jų dalis gali būti praktiškai panaudota kodo vykdymui.
Be kodo vykdymo, egzistuoja daug kitų sričių, kurioms galime panaudoti procesorių klaidas, pvz.,:
- Antiapgrąžos inžinerijai (anti reverse engineering). Tarkim, jog egzistuoja klaida, kurią panaudojus yra gaunamos neteisingos reikšmės (tokių klaidų yra gana daug). Jei tos reikšmės yra deterministinės, tai yra jei mes galime jas nuspėti, tada galima mūsų kenksmingą kodą užšifruoti su raktu, kuris bus teisingas tik jei kodas bus paleistas ant konkretaus procesoriaus modelio. Apgrąžos inžinerijos specialistui toks šifravimas sukeltų galvos skausmą vien norint išsiaiškinti, jog toks triukas yra naudojamas, be to, specialistas turėtų gauti priėjimą prie to modelio procesoriaus tam, kad galėtų iššifruoti kodą. Taip pat yra daug klaidų, kurios sukelia išimtines situacijas (exceptions) ten kur jos neturėtų būti sukeliamos, neįvykdo kokių nors instrukcijų, specifiniais atvejais nenustato tam tikrų vėliavėlių (flags) – visa tai galima panaudoti kodo „pririšimui“ prie konkretaus procesoriaus bei saugumo specialistų darbo apsunkinimui.
- Anti derinimui. Egzistuoja ne viena klaida, kuri trukdo derinimui – pavyzdžiui, specifiniais atvejais paleidus kodą po derintuvu ištrinami/sugadinami duomenys, esantys derinimo registruose , o tai paprasčiausiai sutrikdytų derintuvo darbą.
- OS, programų, kompiliatorių apsaugų apėjimui – neteisingo atminties nuskaitymo/rašymo klaidos.
- Atsisakymo aptarnauti, duomenų sugadinimo atakoms – nemaža dalis klaidų sustabdo, priverčia procesorių išsijungti, taip pat kai kurių klaidų poveikis aprašomas kaip „nenuspėjamas“ – o tai dažniausiai reiškia, jog procesorius „lūš“.
- Kodo vykdymo atakoms – dalis klaidų gali tiesiogiai privesti prie kodo vykdymo programinėje įrangoje, kurioje šiaip nėra pažeidžiamumų, o kai kurios gali pagelbėti egzistuojančių klaidų programinėje įrangoje išnaudojimui.
Baisiausios, nors ir ne pačios praktiškiausios iš visų – tai kodo vykdymo atakos. Pasinaudojus procesoriaus pažeidžiamumais įmanoma įvykdyti kodą ir „saugios“ programinės įrangos pagalba. Kasperskis savo pranešime šiam tikslui pasiekti paminėjo vieną pažeidžiamumą Intel procesoriams – „Concurrent Multi-processor Writes to Non-dirty Page May Result in Unpredictable Behavior“. Šis pažeidžiamumas egzistuoja daugelyje naujesnių „Intel Core 2“ procesorių. Nors ir rašoma, jog šią spragą galima ištaisyti BIOS lygyje, tačiau apie tai, ar nors vienas BIOS gamintojas jį pataisė niekur nepranešama. Pasak Kaspersky, pasinaudojus šiuo pažeidžiamumu, galima įrašyti norimą reikšmę į bet kurią atminties vietą, tačiau, blogiausia yra tai, jog tam nereikia jokių pažeidžiamumų programų lygyje. Spraga pasireiškia kai du branduoliai rašo duomenis į tą patį „nešvarų“ (atmintis kuri gali būti perkeliama į „swap“ atmintinę) atminties „lapą“. Norint pasinaudoti šiuo konkrečiu pažeidžiamumu, reiktų surasti kodą operacinėje sistemoje ar taikomojoje programoje atliekantį tam tikras operacijas, kurios rašytų į „swapinamą“ atmintį vienu metu ir papildomai reiktų būdo kaip tą kodą priversti pasileisti iš nuotolio su įsilaužėlio valdomais duomenimis. Tokį kodą, manau, įmanoma surasti ir operacinės sistemos TCP/IP protokolo apdorojimo kode – todėl teoriškai šį pažeidžiamumą įmanoma išnaudoti nepriklausomai nuo operacinės sistemos išsiuntus keletą TCP/IP paketų. Praktiškai to pasiekti beveik neįmanoma, tačiau neabejoju, jog artimoje ateityje, greičiausiai jau kitam procesoriaus pažeidžiamumui, sulauksime praktinės procesoriaus spragos išnaudojimo realizacijos.
Kodo vykdymo atakos gresia ne tik Intel procesoriams: skaitant AMD „Athlon 64“ serijos procesorių klaidų sąrašą galima pastebėti kelias klaidas, kurios gali privesti prie kodo vykdymo:
„Certain Reverse REP MOVS May Produce Unpredictable Behavior“ – aprašoma, jog specifiniais atvejais REP MOVS (simbolių eilučių kopijavimo instrukcija) veikianti atbulai, gali privesti prie neteisingo duomenų kiekio bei dydžio nukopijavimo, o tai savo ruožtu gali privesti prie įvairių perpildymo pažeidžiamumų programose kurios yra nuo to apsaugotos.
Sekanti klaida – tai 95-u numeriu „Athlon 64“ klaidų sąraše pažymėta „RET Instruction May Return To Incorrect EIP“. RET instrukcija yra skirta sugrįžimui iš paprogramės, t. y. ji naudojama kodo eigai nukreipti. Ši klaida sukeliama kai paprogramė rekursiškai iškviečiama iš 64 bitų režimo daugiau nei 12-ą kartų ir sugrįžta į adresą, kurio 32 sugrįžimo adreso bitai sutampa su ją iškvietusios 64 bitų rėžimo funkcijos adreso 32 žemesniaisiais bitais. Šią klaidą išnaudoti būtų tikrai nelengva, tačiau – įmanoma, pavyzdžiui, per „Java“ virtualią mašiną. Reiktų išnagrinėti kaip Java kalbos JIT (Just In Time) kompiliatorius generuoja kodą ir parašyti „Java“ programą, kuri sukeltų mums reikalingą situaciją.
Deja, tiesioginio priėjimo prie naujų Intel ar AMD procesorių neturiu, o po ranka yra tik 8 modelio „Athlon XP“, kuriam AMD aprašė vos 8 klaidas, neabejoju, kad jų yra žymiai daugiau, tiesiog procesorius oficialiai yra laikomas pasenusiu, o spaudimas procesorių gamintojams dokumentuoti klaidas sukilo tik pastaraisiais metais, todėl jam niekas klaidų nebedokumentuoja. Liūdniausia, jog 4 iš tų 8 klaidų vis dar egzistuoja naujausio modelio „Athlon 64“ serijos procesoriuose. Viena iš jų – „Software Prefetches May Report A Page Fault“, galėtų būti lengvai panaudojama antiapgrąžos inžinerijai bei procesoriaus atpažinimui -jai išnaudoti tereikia kelių instrukcijų, bet ji yra ištaisyta operacinės sistemos lygyje, todėl pagrindinėse moderniose operacinėse sistemose ji neveikia.
Pabaigai pademonstruosiu, jog elementarus procesorių klaidų išnaudojimas antiapgrąžos inžinerijai nėra sudėtingas. Pavyzdžiui, mano turimame procesoriuje egzistuoja klaida „A Speculative SMC Store Followed by an Actual SMC Store May Cause One-Time Stale Execution“. Ši klaida pasireiškia tuo, jog tam tikroje situacijoje vykdant save modifikuojantį kodą, kai turėtų būti įvykdomas modifikuotas kodas iš tikro įvykdoma ankstesnė nemodifikuota to kodo versija, esanti spartinančioje atmintinėje (cache). Procesoriaus klaidų sąraše ši problema aprašoma gan painiai, todėl visko tiesiogiai neversiu, o pademonstruosiu kodu.
Šios klaidos esmė ta, jog dėl spekuliuojamos kodą modifikuojančios instrukcijos, kuri nėra įvykdoma dėl neteisingo šuolio nuspėjimo, tikrą kodą modifikuojanti instrukcija, turinti pakeisti kodą yra įvykdoma ir pakeičia kodą, tačiau vykdant tą modifikuotą vietą duomenys yra paimami iš spartinančios atmintinės ir yra įvykdomas nepakeistas kodas.
lea reg1, modifikuojamas_kodas ; reg1 laikys modifikuojamo kodo adresą
xor reg, reg ; reikalinga norint nustatyti ZF
jnz šuolis_žemyn ; šioje vietoje šuolis yra neteisingai nuspėjamas ir po juo esanti instrukcija nebus įvykdoma, nes modernūs procesoriai šuolius su sąlyga šokančius žemyn spėja kaip nevykdomus ciklų optimizavimo tikslais. Tą patį pasiekti galėtume ir netiesioginiu šuoliu (pvz., per registrą) ar RET instrukcija, kurios taip pat numatomos kaip nevykdomos.
mov WORD PTR [reg1], 0000h ; spėjama instrukcija kuri modifikuotų kodą. Ji nėra įvykdoma, tačiau ji nuskaitoma ir dėl jos spartinančioji atmintinė saugoma
šuolis_žemyn:
mov WORD PTR [reg1], 9090h ; čia nušokama su jnz instrukcija ir, ji yra įvykdoma, tačiau atmintis pasiekiama per reg1 esantį adresą jau yra spartinančioje atmintinėje pažymėta kaip modifikuota
modifikuojamas_kodas:
jmp kodas_vykdomas_jei_amd_procesorius; prieš tai einanti instrukcija perrašo šį kodą su 9090 – dviem NOP instrukcijom, kurios neatlieka jokios funkcijos, tačiau kai ši instrukcija yra vykdoma, ji yra paimama iš spartinančios atmintinės ir, jei procesorius turi minėtą klaidą, šuolis vis dėl to yra įvykdomas.
kodas_jei _ne_amd_procesorius; čia esantis kodas būtų įvykdomas, jei procesorius neturėtų klaidos.
Tai nevisai pilnas paaiškinimas: nesigilinau į spartinančiosios atmintinės specifiką, tiesiog norėjau parodyti, jog paprastesnes klaidas tikrai nėra sunku išnaudoti.
Šiuo metu jaudintis dėl procesorių klaidų sukeliamų pažeidžiamumų per daug nereiktų. Rimtesniems pažeidžiamumams išnaudoti dažniausiai reikia ypač specifinių sąlygų, o tai žymiai apsunkina jų praktišką išnaudojimą, tačiau neabejoju, jog po kelių metų sulauksime įvairių įrankių bei naujų metodų jų panaudojimui. Greičiausiai pirmieji kenksmingo kodo pavyzdžiai, panaudojantys procesorių klaidas, bus „rootkitai“, nes jie dažniausiai yra itin specifiniai (pvz., veikiantys tik ant vienos operacinės sistemos versijos). Žinant atakuojamos sistemos procesoriaus modelį ir panaudojant jo klaidas, „rootkitus“ bus galima dar „giliau“ paslėpti. Taip pat, mano manymu, dar viena sfera, kurioje greičiausiai bus pradėtos naudoti procesorių klaidos – tai „shellkodo“ kūrimas. Jau dabar, komerciniai įrankiai, tokie kaip „Immunity Canvas“, sugeneruoja „shellkodą“, pritaikytą konkrečiai operacinės sistemos ar programos versijai, todėl nemanau, jog teks ilgai laukti kol atsiras „shellkodo“ generatoriai konkretiems procesoriams „shellkodo“ veikimo metu išnaudojantys procesorių klaidas.