Dėl rimtos „BlueKeep“ pagrįsto kirmino pavojaus sulaikiau šį rašymą, kad išvengčiau laiko juostos pailginimo. Dabar, kai RCE (nuotolinio kodo vykdymo) koncepcijos įrodymas buvo išleistas kaip Metasploit dalis, manau, kad dabar galiu tai paskelbti saugu.
Šis straipsnis bus mano ankstesnės analizės tęsinys.
Kaip minėjau ankstesniame straipsnyje, galime atlaisvinti su MS_T120 kanalu susietą duomenų struktūrą. Vien struktūros atlaisvinimas nėra labai naudingas, o jos turinio kontrolė yra naudinga. Naudojant UAF (naudojimas po-nemokamas), tikslas yra išlaisvinti objektą, tada vietoj jo paskirti netikrą. Pakeitę tikro objekto turinį savo duomenimis, įgyjame platesnę kodo, naudojančio jį, valdymą. Tai, ką galime padaryti su savo netikra kanalo struktūra, visiškai priklauso nuo to, kam ši struktūra naudojama (apie tai pakalbėsime vėliau).
Suaktyvinus UAF, MS_T120 kanalo struktūra panaikinama, bet vis tiek tinkama naudoti. Norėdami išnaudoti šį UAF, turime paskirstyti naujus duomenis tuo pačiu adresu, kuriame anksčiau buvo kanalo struktūra.
Kanalų struktūros paskirstomos ne puslapių telkinyje. Norėdami užgrobti atlaisvintą struktūrą, turime turėti galimybę paskirstyti savavališkus duomenis ne puslapių telkinyje. Norėdami išsiaiškinti būdą, išanalizavau ExAllocatePool variantų (malloc branduolio atitikmens) iškvietimus.
Tai iš esmės yra purškimo ant krūvos šventasis gralis. Duomenys, siunčiami į kanalą, yra saugomi Nepuslapių telkinyje, todėl galime atlikti bet kokio dydžio ne puslapių telkinio paskirstymą, kuriame yra bet kokių norimų duomenų!
Vienintelis įspėjimas yra tai, kad šis paskirstymas yra skirtas IOCP eilei, iš kurios kanalo tvarkyklės apdoroja pranešimus. Kai pranešimas perskaitomas, jis pašalinamas iš eilės ir panaikinamas. Tačiau keletas tyrėjų nustatė, kad tam tikras kanalas niekada neskaito pranešimų iš eilės ir palieka juos paskirstyti tol, kol bus nutrauktas ryšys.
Dabar, jei išsiunčiame tokio pat dydžio paskirstymus kaip kanalo struktūra, jis greičiausiai pateks į išlaisvinto kanalo paliktą skylę (tai vadinama krūvos purškimu). Norėdami padidinti sėkmingo paskirstymo tikimybę, galime atlikti tai, kas vadinama krūvos priežiūra, kad įsitikintume, jog niekas daugiau tuo adresu nepaskirtas; tačiau tai sudėtinga tema, kurioje daug rašinių, todėl čia nesileisiu į ją.
Kanalo struktūrą, kurią galime pakeisti, grąžina funkcijos IcaFindChannel ir IcaFindChannelByName. Norint sužinoti, ką galima padaryti manipuliuojant šia kanalo struktūra, turėjau rasti, kur ji buvo naudojama. Atlikęs IcaFindChannel xref, radau šį kodą.
Aukščiau pateiktas kodas patikrina, ar kanalo struktūros „vtable“ laukas yra ne nulis, tada, jei taip, atšaukia nuorodą ir atlieka netiesioginį iškvietimą į jį. Norėdami tai išnaudoti, turėtume nukreipti channel_struct->vtable į kokią nors mūsų valdomą atmintį, tada toje atmintyje išsaugoti žymeklį į mūsų apvalkalo kodą.
RCE atminties išdėstymas turėtų atrodyti taip.
Deja, yra tam tikrų problemų.
- Mums reikia netikros kanalo struktūros, kad būtų nurodyta netikra VTable, bet mes neturime netikros VTable adreso.
- Mums reikia netikros VTable, kad būtų nurodytas apvalkalo kodas, bet mes neturime apvalkalo kodo adreso.
Bet nebijokite, visada yra išeitis!
Kaip žmogus, dirbantis tik su „Windows 10“, iš pradžių ėjau sudėtingesniu keliu, tuo pačiu metu bandydamas apeiti ASLR (adresų erdvės išdėstymo atsitiktinį nustatymą) ir DEP (duomenų vykdymo prevenciją, AKA No Execute). Tai buvo tada, kai Ryanas Hansonas (@ryHanson) davė man tokią užuominą: „Tame purškime yra kažkas dar ypatingesnio. Atminkite, kad tai yra „Win7“.
Žinoma! Tai „Windows 7“! Kalbant apie branduolio atmintį be puslapių, DEP nėra. Tiesiogine prasme galime tiesiog įrašyti apvalkalo kodą tiesiai į branduolį (atminkite, kad mano paaiškintas purškimo krūvos metodas leidžia mums paskirstyti savavališkus duomenis ne puslapių telkinyje).
Patobulintas atminties išdėstymas dabar atrodo maždaug taip.
Mums nereikia ROP grandinės, kad galėtume įrašyti apvalkalo kodą į vykdomąją atmintį, kaip tai darytume vartotojo režimu, galime tiesiog išpurkšti jį į puslapių neturintį telkinį. Tik paskutinė problema: kaip gauti paskirstymo adresą, kad būtų galima nustatyti VTable žymeklį kanalo struktūroje?
„Windows 7“ ir senesnėse versijose ne puslapių telkinys prasideda fiksuotu adresu (Nt!MmNonPagedPoolStart). Mes žinome, kur prasideda ne puslapių rinkinys, bet nežinome, kur ne puslapių rinkinyje yra mūsų apvalkalo kodas… Bet mes galime tai šiek tiek kontroliuoti.
Atmintis be puslapių yra šventa, nes jos dydis yra ribotas dėl puslapių trūkumo. Dėl to branduolys paprastai vengia jo naudoti bet kam, ko nebūtinai turi būti. Tai reiškia, kad atminties naudojimas yra gana mažas.
Ką galime padaryti, tai pasirinkti adresą (tarkime, 500 MB į puslapių neturintį telkinį), kuris greičiausiai nebus paskirstytas dėl mažo atminties naudojimo. Tada išpurškiame pakankamai apvalkalo kodo kopijų, kad užpildytume nepuslapių telkinį už mūsų pasirinkto adreso, todėl labai tikėtina, kad apvalkalo kodo kopija bus ten.
Kadangi dauguma objektų, kurie dažnai skiriami ne puslapių telkinyje, yra labai maži, galime sukurti daug skylių, mažesnių nei mūsų apvalkalo kodo dydis (iki pasirinkto adreso), užtikrinant, kad ten būtų paskirstyti kiti skyrimai.
Tada galime nustatyti VTable žymeklį savo netikrų kanalų struktūroje mūsų pasirinktu adresu, todėl apvalkalo kodas bus vykdomas kiekvieną kartą, kai kanalas gauna pranešimą.
Viena iš priežasčių, kodėl šis pažeidžiamumas yra toks pavojingas, yra tai, kad nėra realių paveiktos sistemos mažinimo priemonių. DEP nebuvimas branduolyje ir daug gerai dokumentuotų statinių adresų reiškia, kad adreso nutekėjimo + ROP grandinės poreikis yra pašalintas. Be to, VTable buvimas jau suteikia savavališką vykdymo primityvą, todėl DoS paversti RCE reikia labai mažai.
Šaukite nulis 0x0 ir Ryanas Hansonas už puikų darbą su „BlueKeep“, taip pat visiems, kurie sulaikė informaciją apie pažeidžiamumą, kad organizacijoms duotų mėnesių pataisyti.