CVE-2019-0708 („BlueKeep“) analizė – „MalwareTech“

Aš sulaikiau šį rašymą, kol koncepcijos įrodymas (PoC) buvo viešai prieinamas, kad nepadarytų jokios žalos. Dabar, kai „github“ yra keletas atsisakymo teikti paslaugas PoC, skelbiu savo analizę.

Kaip visada, pradėjau nuo pataisos modifikuotų dvejetainių failų BinDiff (šiuo atveju yra tik vienas: TermDD.sys). Žemiau matome rezultatus.

TermDD.sys BinDiff prieš ir po pataisų.

Dauguma pakeitimų pasirodė gana kasdieniški, išskyrus „_IcaBindVirtualChannels“ ir „_IcaRebindVirtualChannels“. Abiejose funkcijose buvo tas pats pakeitimas, todėl sutelkiau dėmesį į pirmąją, nes susiejimas greičiausiai įvyktų prieš surišant iš naujo.

Originalūs IcaBindVirtualChannels yra kairėje, pataisyta versija yra dešinėje.

Pridėta nauja logika, keičianti _IcaBindChannel iškvietimą. Jei palyginama eilutė yra lygi „MS_T120“, trečiasis _IcaBindChannel parametras nustatomas į 31.

Atsižvelgiant į tai, kad pakeitimas įvyksta tik tuo atveju, jei v4+88 yra „MS_T120“, galime manyti, kad norint suaktyvinti klaidą, ši sąlyga turi būti teisinga. Taigi, mano pirmasis klausimas: kas yra „v4+88“?

Žvelgdamas į IcaFindChannelByName logiką, greitai radau atsakymą.

„IcaFindChannelByName“ viduje

Naudodamiesi pažangiomis anglų kalbos žiniomis, galime iššifruoti, kad „IcaFindChannelByName“ suranda kanalą pagal jo pavadinimą.

Atrodo, kad funkcija kartoja kanalų lentelę, ieškodama konkretaus kanalo. 17 eilutėje yra a3 ir v6+88 eilučių palyginimas, kuris grąžina v6, jei abi eilutės yra lygios. Todėl galime manyti, kad a3 yra kanalo pavadinimas, kurį reikia rasti, v6 yra kanalo struktūra, o v6+88 yra kanalo pavadinimas kanalo struktūroje.

Remdamasis visa tai, kas išdėstyta aukščiau, padariau išvadą, kad „MS_T120“ yra kanalo pavadinimas. Toliau man reikėjo išsiaiškinti, kaip iškviesti šią funkciją ir kaip nustatyti kanalo pavadinimą į MS_T120.

Nustačiau pertraukos tašką „IcaBindVirtualChannels“, ten, kur vadinama „IcaFindChannelByName“. Vėliau prisijungiau prie KPP su teisėtu KPP klientu. Kiekvieną kartą, kai suveikė pertraukos taškas, aš patikrinau kanalo pavadinimą ir skambučių krūvą.

Skambučių paketas ir kanalo pavadinimas pirmą kartą skambinant į IcaBindVirtualChannels

Pats pirmasis skambutis į IcaBindVirtualChannels skirtas kanalui, kurio noriu, MS_T120. Tolesni kanalų pavadinimai yra „CTXTW“, „rdpdr“, „rdpsnd“ ir „drdynvc“.

Deja, pažeidžiamo kodo kelias pasiekiamas tik tada, kai „FindChannelByName“ pavyksta (ty kanalas jau yra). Tokiu atveju funkcija nepavyksta ir sukuriamas MS_T120 kanalas. Kad suaktyvinčiau klaidą, turėčiau antrą kartą iškviesti IcaBindVirtualChannels kanalo pavadinimu MS_T120.

Taigi dabar mano užduotis buvo išsiaiškinti, kaip iškviesti IcaBindVirtualChannels. Skambučių krūvoje yra IcaStackConnectionAccept, todėl kanalas greičiausiai bus sukurtas prisijungus. Tiesiog reikia rasti būdą, kaip atidaryti savavališkus kanalus po prisijungimo… Galbūt teisėto KPP ryšio uostymas suteiktų šiek tiek įžvalgos.

KPP ryšio sekos fiksavimas

Kanalų masyvas, kaip matyti iš WireShark RDP analizatoriaus

Antrame atsiųstame pakete yra keturi iš šešių kanalų pavadinimų, kuriuos mačiau, perduodamus „IcaBindVirtualChannels“ (trūksta MS_T120 ir CTXTW). Kanalai atidaromi tokia tvarka, kokia jie yra pakete, todėl manau, kad tai kaip tik tai, ko man reikia.

Matant, kad MS_T120 ir CTXTW niekur nenurodyti, o atidaryti prieš kitus kanalus, manau, jie turi atsidaryti automatiškai. Dabar įdomu, kas nutiks, jei įdiegsiu protokolą ir pridėsiu MS_T120 prie kanalų masyvo.

Perkėlus pertraukos tašką į tam tikrą kodą, tik tada, kai „FindChannelByName“ pavyksta, atlikau testą.

Lūžio taškas pasiekiamas į kanalų masyvą įtraukus MS_T120

Nuostabu! Dabar pažeidžiamo kodo kelias nukentėjo, man tereikia išsiaiškinti, ką galima padaryti…

Norėdamas sužinoti daugiau apie kanalo veiklą, nusprendžiau išsiaiškinti, kas jį sukūrė. Nustačiau pertraukos tašką „IcaCreateChannel“, tada pradėjau naują KPP ryšį.

Skambučių krūva, kai paveikiamas IcaCreateChannel lūžio taškas

Po skambučių krūvos žemyn matome perėjimą iš vartotojo į branduolio režimą ntdll!NtCreateFile. Ntdll tiesiog pateikia branduolio pavyzdį, todėl tai neįdomu.

Žemiau yra ICAAPI, kuris yra TermDD.sys vartotojo režimo atitikmuo. Skambutis pradedamas naudojant ICAAPI „IcaChannelOpen“, todėl tai tikriausiai yra „IcaCreateChannel“ vartotojo režimo atitikmuo.

Kadangi „IcaOpenChannel“ yra bendra funkcija, naudojama visiems kanalams atidaryti, nusileisime dar vienu lygiu į „rdpwsx!MCSCreateDomain“.

rdpwsx!MCSCreateDomain kodas

Ši funkcija tikrai daug žadanti dėl kelių priežasčių: Pirma, ji iškviečia „IcaChannelOpen“ užkoduotu pavadinimu „MS_T120“. Antra, jis sukuria IoCompletionPort su grąžinta kanalo rankena (užbaigimo prievadai naudojami asinchroniniam įvesties / išvesties).

Kintamasis pavadinimu „CompletionPort“ yra užbaigimo prievado rankena. Žvelgdami į xrefs prie rankenos, tikriausiai galime rasti funkciją, kuri tvarko įvesties / išvesties į prievadą.

Visos nuorodos į „CompletionPort“

Na, MCSIinitialize tikriausiai yra gera vieta pradėti. Inicijavimo kodas visada yra gera vieta pradėti.

Kodas, esantis MCSIinitialize

Gerai, todėl užbaigimo prievadui sukuriama gija, o įėjimo taškas yra „IoThreadFunc“. Pažiūrėkime ten.

Užbaigimo prievado pranešimų tvarkytuvė

„GetQueuedCompletionStatus“ naudojamas duomenims, išsiųstiems į užbaigimo prievadą (ty kanalą), gauti. Jei duomenys sėkmingai gauti, jie perduodami MCSPortData.

Kad patvirtinčiau savo supratimą, parašiau pagrindinį KPP klientą su galimybe siųsti duomenis KPP kanalais. MS_T120 kanalą atidariau naudodamas anksčiau paaiškintą metodą. Kai atidariau, nustatau MCSPortData pertraukos tašką; tada išsiunčiau eilutę „MalwareTech“ į kanalą.

Pertraukos taškas pasiektas MCSPortData, kai duomenys siunčiami į kanalą.

Tai patvirtina, kad galiu skaityti / rašyti į MS_T120 kanalą.

Dabar pažiūrėkime, ką MCSPortData daro su kanalo duomenimis…

MCSPortData buferio tvarkymo kodas

ReadFile nurodo, kad duomenų buferis prasideda nuo channel_ptr+116. Netoli funkcijos viršaus yra chanel_ptr+120 (4 poslinkis į duomenų buferį) patikrinimas. Jei dword nustatytas į 2, tada funkcija iškviečia HandleDisconnectProviderIndication ir MCSCloseChannel.

Na, tai įdomu. Kodas atrodo kaip tam tikra tvarkytuvė, skirta susidoroti su kanalo prijungimo / atjungimo įvykiais. Išnagrinėjęs, kas paprastai suaktyvintų šią funkciją, supratau, kad MS_T120 yra vidinis kanalas ir paprastai nėra rodomas išorėje.

Nemanau, kad mes čia turime būti…

Būdamas šiek tiek smalsus, išsiunčiau duomenis, reikalingus skambučiui suaktyvinti, į MCSChannelClose. Be abejo, priešlaikinis vidinio kanalo uždarymas negali sukelti problemų, ar ne?

O, ne. Mes sudaužėme branduolį!

Oi! Pažvelkime į klaidų patikrinimą, kad geriau suprastume, kas atsitiko.

Atrodo, kad kai mano klientas atsijungė, sistema bandė uždaryti MS_T120 kanalą, kurį jau buvau uždaręs (dėl to du kartus nemokama).

Dėl kai kurių „Windows Vista“ pridėtų švelninimo priemonių dvigubai nemokamus pažeidžiamumus dažnai sunku išnaudoti. Tačiau yra kažkas geresnio.

Kanalo valymo kodo vidinės dalys veikia, kai ryšys nutrūksta

Viduje sistema sukuria MS_T120 kanalą ir susieja jį su ID 31. Tačiau kai jis susietas naudojant pažeidžiamą IcaBindVirtualChannels kodą, jis susiejamas su kitu ID.

Kodo skirtumas prieš pataisą ir po jo

Iš esmės MS_T120 kanalas susiejamas du kartus (vieną kartą viduje, tada vieną kartą mes). Dėl to, kad kanalas yra susietas pagal du skirtingus ID, gauname dvi atskiras nuorodas į jį.

Kai kanalui uždaryti naudojama viena nuoroda, nuoroda ištrinama, kaip ir kanalas; tačiau kita nuoroda išlieka (žinoma kaip nenaudoti po to). Su likusia nuoroda dabar galima įrašyti branduolio atmintį, kuri mums nebepriklauso.

2 dalis (kaip DoS paversti RCE): https://www.malwaretech.com/2019/09/bluekeep-a-journey-from-dos-to-rce-cve-2019-0708.html