Neseniai, analizuojant kai kurias Microsoft pataisas, teko susidurti su vienu iš Microsoft naudojamų pataisų įdiegimo metodu - "hotpatching". Tiesą pasakius tai su mano analize buvo beveik visiškai nesusiję, tačiau pati technologija patraukė mano dėmesį, todėl nusprendžiau išsiaiškinti kaip ji veikia ir kam ją galime panaudoti.
"Hotpatching" - pataisų įdiegimo metodas, kuris lietuviškai vadintųsi "karštas pataisymas". Žodis "karštas" šios technologijos pavadinime reiškia tai, jog pataisa įdiegiama veikiančioje sistemoje/programoje t.y. nenutraukiant jos veikimo. Nors Microsoft ir turi patentą šiai technologijai, kurį jie gavo 2004-ais metais, tačiau mano žiniomis panašūs metodai buvo naudojami jau labai seniai. Tačiau šis straipsnis ne apie programinio kodo patentų absurdiškumą, mane domina tik šios technologijos panaudojimas Microsoft produktų kontekste.
Šios technologijos esminis principas yra tam tikrų dvejetainių sukompiliuotų funkcijų pakeitimas atmintyje kitomis funkcijomis. Pavyzdžiui : randamas pažeidžiamumas ar defektas tam tikroje funkcijoje, ta pati, jau pataisyta, funkcija yra perkompiliuojama ir jos dvejetainis kodas yra įkeliamas į proceso ar tvarkyklės atmintį ir senoji funkcija nukreipiama į naują pataisytąją.
Tokio tipo pataisų įdiegimas tinka tik kai kuriais atvejais. Dėl stabilumo bei saugumo hotpatching'as naudojamas tik tada kai pataisymas yra pakankamai mažas bei neturi jokių pašalinių efektų. Programa turi veikti lygiai taip pat sukeitus nepataisytą funkciją su perkompiliuotąja. Kadangi dabartinėje Windows architektūroje neįmanoma užtikrinti, jog kodo vykdymas nebus pertrauktas ar pradėtas vykdyti diegiant pataisymą todėl tai naudojama tik tada kai pataisymas yra paprastas - pavyzdžiui pridedamas papildomas duomenų dydžio tikrinimas prieš juos apdorojant ar pakeičiamas kokios nors operacijos ženklas. Jei pataisyta funkcija būtų nesuderinama su senąja tai gali turėti katastrofiškų padarinių. Paimkim tokį pavyzdį: programoje egzistuoja funkcija, kuri tris kartus iškviečia kažkokią tai kitą funkciją kurioje yra klaida. Diegiant pataisymą tokiai funkcijai jis gali būti įdiegiamas po pirmo tos klaidingos funkcijos paleidimo todėl pirmą kartą būtų vykdomas senasis kodas ir po to du kartus naujasis ir jei jos būtų nesuderinamos tai geriausiu atvejų būtų gražinama klaidinga reikšmė, o blogiausiu - sugadinami kokie nors svarbūs duomenys.
Hotpatch'inimas pradėtas naudoti Windows XP SP2, Windows 2003 SP0 bei vėlesnėse sistemose. Microsoft hotpatch'inimo realizacijos esminė dalis yra jų kompiliatorių palaikomas parametras /hotpatch, kuris užtikrina, jog kiekvienos funkcijos pirmoji instrukcija užima du baitus. Kodėl tai naudinga paaiškinsiu vėliau. Taigi sukompiliavus programą su /hotpatch parametru kiekviena funkcija prasidės su instrukcija mov edi, edi kuri perkelia edi reikšmę atgal į edi, t.y. efektyviai nedaro nieko bei užima lygiai 2 baitus. Tai galime pamatyti "prisikabinę" prie kurio nors iš procesų su kokiu nors debugger'iu ar tiesiog atidarę kokį nors Microsoft kompiliuotą .exe, .dll ir t.t. failą deasembleryje. Pavyzdžiui, atsidarę kernel32.dll per OllyDbg matome, jog didžioji dalis funkcijų prasideda mov edi, edi bei prieš funkcijos pradžią visur yra po 5-ias NOP instrukcijas.

Paveikslėlyje pademonstruotos atsitiktinai paimtos funkcijos. Šitoks vietos perrašymui palikimo metodas yra naudojamas dėl to, jog mov edi, edi įdiegiant pataisą yra perrašoma su artimo šuolio instrukcija su kuria galima nušokti 127 baitus į abi puses, o tos 5-ios NOP instrukcijos yra perrašomos su tolimo šuolio instrukcija su kuria jau yra šokama į naują pataisytą funkciją. Būtų galima prie kiekvienos funkcijos prirašyti tik 5-ių baitų instrukciją ir ją perrašyti su tolimo šuolio instrukcija, tačiau taip nedaroma dėl stabilumo. 2-ų baitų instrukcijos perrašymas yra nedaloma operacija, t.y. jei perrašoma funkcija būtų iškviečiama tada kai perrašomas mov edi, edi tai programos vykdymas nušoktų į tą vietą tik tada kai ta instrukcija būtų perrašyta arba prieš ją perrašant bet ne viduryje perrašymo. Jei būtų naudojama tik 5-ių baitų šuolio instrukcija tai įmanoma, jog viduryje jos perrašymo į tą vietą būtų nukreiptas programos vykdymas ir būtų vykdoma pusiau perrašyta instrukcija kas greičiausiai privestų prie neaiškių instrukcijų vykdymo ir lūžimo.
Pataisymo metu nukreipta viena iš praeitame paveikslėlyje rodytų funkcijų atrodytų daugmaž taip:

Sužinojome hotpachin'imo veikimo principus, dabar galime eiti prie jo panaudojimo. Vienas pakankamai akivaizdus šios technologijos panaudojimas tai lengvas funkcijų nukreipimas ar perėmimas. Tarkime, jei rašome programą kuri perims iš kitos programos kreipinius į tam tikrus Microsoft API ir ką nors padarys su perduodamais duomenimis, pvz. juos išfiltruos ir perduos atgal, tai galime pasinaudoti "saugiu" Microsoft būdu funkcijų nukreipimui neperrašinėdami funkcijos dalies, kas būtų buvę privaloma be paliktos vietos šuolio instrukcijoms. Kitas pavyzdys būtų - kokio nors pažeidžiamumo ištaisymas su pataisa kuriai Microsoft dėl kažkokių priežasčių nepadarė hotpatch palaikymo. Šitą toliau ir pademonstruosiu.
Taigi tarkime, jog turiu Windows XP SP3 serverį, kuris negali būti išjungtas, nes jame veikia kažkoks servisas, kurio perkrovimas pridarytų man daug nuostolių. Kažkas atranda pažeidžiamumą mano naudojamoje operacinėje sistemoje ir išleidžia exploitą su kuriuo galima vykdyti kodą nuotolinėje sistemoje. Microsoft išleidžia pataisą, tačiau ji nepalaiko hotpatchinimo. Man iškyla klausimas ar verta instaliuoti pataisą bei perkrauti sistemą, ar bandyti gyventi be jos bandant apsisaugoti su ugniasiene ar kokia kita apsaugos sistema iki numatomo eksploatacinio sistemos perkrovimo, kuomet bus instaliuojamos visos pataisos. Tačiau išanalizavęs tą pataisą, aš pastebiu tai, jog ji labai paprasta - pakeičiama tik viena funkcija, todėl galime pabandyti ją įdiegti hotpatch metodu rankomis. Blogiausia kas gali nutikti tai sistema nulūš ir man teks ją perkrauti.
Šiam pataisų diegimo metodui pademonstruoti pataisą pasirinkau gan atsitiktinai - įsijungiau virtualią sistemą kuri jau kurį laiką nebuvo atnaujinama, peržiūrėjau siūlomų pataisų dydžius ir išrinkau vieną kuri užėmė mažai bei turėjo platesnį aprašymą - pataisą MS09-069 pažeidžiamumui. Tai pažeidžiamumas LSASS tarnyboje kurį išnaudojus galima sukelti paslaugų paneigimą ("DoS"). Informacija apie tai kaip ir kur sukeliamas pažeidžiamumas manęs nelabai domina, todėl į tai nesigilinsiu. Parsisiuntęs pataisą pirmiausia norėjau įsitikinti, jog galiu ją panaudoti hotpatch'inimui. Ją išpakavęs radau vieną modifikuotą failą - oakley.dll. Ši biblioteka pažiūrėjus su ProcessExplorer (paspaudus CTRL+F bei įrašius oakley.dll) tėra naudojama vieno proceso - lsass.exe. Norėdamas galutinai įsitikinti nusprendžiau sulyginti senąją oakley.dll su pataisytąja versija. Tam panaudojau DarumGrim2. Lyginimo proceso nebedemonstruosiu, apie tai galite paskaityti seniau mano rašytame straipsnyje apie pataisų analizavimą - "Saugumo pataisos analizė".

Matome, jog tarp dviejų versijų pakeista tik viena funkcija - _SortPayloadList.

Išanalizavęs oakley.dll su IDA pamačiau, jog į _SortPayloadList tėra kreipiamasi tik iš vienos vietos - _process funkcijos. Toliau peržiūrėjau patį _SortPayloadList funkcijos kodą bei pastebėjau, jog jame nėra kviečiami kokie nors API kurie galėtų sukelti problemų dėl sinchronizacijos bei jog nėra naudojami globalūs kintamieji - tai tiesiog paprasta rūšiavimo funkcija.
Perėjau prie pataisos diegimo - tai atlikau įkeldamas atnaujintą dll versiją į lsass.exe proceso atmintį bei pasinaudojęs derintuvu rankomis nukreipiau seną _SortPayloadList į pataisytąją.
Dll įkėlimui į lsass.exe atmintį panaudojau įrankį inject.exe iš įrankių rinkinio PE Tools. Tam kad būtų galima įkeltį ką nors į tokio proceso kaip lsass.exe atmintį reikia turėti aukštesnes teises nei Administrator, todėl panaudojęs įrankį supershell pasileidau cmd.exe su NT SYSTEM/AUTHORITY teisėmis bei per jį paleidęs inject.exe su lsass PID bei įkeliamo dll'o vieta diske parametrais įkeliau oakley_patched.dll - pataisytą oakley.dll versiją.

Beliko "prisikabinti" su derintuvu prie lsass ir nukreipti funkciją. Tam panaudosiu OllyDBG.

Susiradau įkelto oakley modulio pataisytą _SortPayloadList funkciją bei nusirašiau jos adresą - 0x00ECD2CA. Po to susiradau nepataisytą, pažeidžiamą _SortPayloadList funkciją originaliame oakley.dll ir perrašiau ją su šuoliu į pataisytąją funkciją.

Ir turiu hotpatch'ingo pagalba ištaisytą MS09-069 pažeidžiamumą.
Tokio pataisų diegimo metodo serveriams tiekiantiems kritiškas tarnybas NEPATARIU naudot, nebent PILNAI suvokiate ką darote, nes šis metodas tikrai pavojingas sistemos stabilumui, neįmanoma prognozuoti ar sistema tikrai išliks stabili po tokios grubios operacijos. Tačiau jei kada nors prireiks "užlopyti" kritinį pažeidžiamumą, žinosite, jog tai įmanoma atlikti ir be sistemos perkrovimo, tačiau nenuspėjamo stabilumo kaina.

