IT sferoje vis didėjant susirūpinimui dėl programinės įrangos saugumo, per pastaruosius kelis metus, operacinių sistemų kūrėjai atkreipė dėmesį į apsaugų nuo įvairių kodo vykdymo atakų kūrimą, bei tobulinimą. Tokios apsaugos kaip ASLR (adresų erdvės išdėstymo atsitiktinumo užtikrinimas), DEP/NX (duomenų vykdymo prevencija/neleidimas vykdyti), steko slapukai, įvairios heap‘o apsaugos tapo daugelio modernių operacinių sistemų dalimi.
Šios apsaugos žymiai apsunkina kodo vykdymo atakas - seniau buvę ypač paprastai išnaudojami pažeidžiamumai reikalauja vis daugiau laiko ir jėgų jų išnaudojimui. Tačiau į priekį pasistūmėjo ne tik apsaugos: vis tobulėja ir įrankiai, padedantys tyrinėti minėtus pažeidžiamumus bei žymiai palengvinantys jų suradimą ir išnaudojimą. Vienas tų įrankių yra „Immunity Debugger“ – naujos kartos derintuvas („debugger“), pritaikytas apgražos inžinerijai ir pažeidžiamumų tyrimams.
„Immunity Debugger“ (toliau sutrumpintai „ID“) tai „Immunity“ įmonės, besispecializuojančios pažeidžiamumų tyrinėjime, produktas. Parsisiųsti jį nemokamai galite iš „Immunity“ tinklalapio.
ID pagrindinis langas:
Pagrindiniai šio įrankio privalumai yra „Python“ programavimo kalbos integracija bei kodo biblioteka, pritaikyta saugumo tyrimams. Programavimo kalba „Python“, „Immunity Debugger“ scenarijų rašymui, buvo pasirinkta neatsitiktinai – vis daugiau įrankių saugumui, bei apgražos inžinerijai yra suprogramuoti su šia lanksčia ir vis populiarėjančia programavimo kalba. Be to, didžioji dalis „Immunity“ produktų, tokių kaip „Immunity Canvas“ yra taip pat parašyti su „Python“. Šios kalbos kol kas mokėti nebūtina, tačiau patarčiau ja pasidomėti, nes saugumo sferoje ji šiuo metu viena populiariausių įvairių įrankių rašymui.
ID vartotojo sąsaja beveik nesiskiria nuo įprasto „OllyDbg“ - jei kada nors teko naudotis pastaruoju tai naudodami ID jausitės kaip namie. Taipogi ID turi integruotą „OllyDbg“ plėtinį „cmdline“ kuris įgalina vykdyti su WinDbg suderinamas komandas, kurių sąrašą galite rasti „Cmdbox.hlp“.
ID architektūra:
„Immunity Debugger“ galima praplėsti bei jam rašyti scenarijus keliais būdais. Architektūros paveikslėlyje aukščiausiame lygyje pavaizduoti keturi scenarijų rašymo būdai:
„PyPlugins“ – „Python“ scenarijai, patalpinti „PyPlugins“ direktorijoje, paleidžiami paspaudus F4 - jie neišveda jokių duomenų. Jie retai naudojami, skirti įvairiam „tyliam“ duomenų apdirbimui bei funkcionalumo praplėtimui.
„PyHooks“ – talpinami „PyHooks“ direktorijoje, paleidžiami automatiškai kiekvieną kartą įjungus programą. Šio tipo scenarijai skirti įvairių procedūrų nukreipimui bei perėmimui.
„PyScripts“ – paprasti „Python“ scenarijai, nebūtinai naudojantys „Immunity“ API, paleidžiami vidiniame interpretatoriuje. Jie gali būti paleidžiami paspaudus Alt + F3, arba pasirinkus atitinkamą piktogramą mygtukų juostoje. Abiejais atvejais bus parodomas failų pasirinkimo dialogo langas kuriame galima pasirinkti norimą scenarijų.
„PyCommands“ – scenarijai, talpinami „PyCommands“ direktorijoje, paleidžiami arba per mygtukų juostą arba komandų lange įvedus komandos pavadinimą prirašant priekyje šauktuką, pvz.:
Nemažos dalies komandų funkcionalumas yra implementuotas įvairiuose nepriklausomuose įrankiuose, tačiau daug patogiau turėti viską vienoje vietoje. Kai kurios komandos yra unikalios šiam įrankiui ir ypač palengvina pažeidžiamumų tyrimus.
Vienas sunkiausiai išnaudojamų pažeidžiamumo tipų šiuo metu – heap‘o perpildymai. ID gali palengvinti mūsų kančią šioje srityje. Kartu su ID pateikiamios šios komandos specifiškai skirtos heap‘ui:
!chunkanalizehook – įgalina sužinoti specifinio heap‘o gabalo būseną tam tikru momentu. Perdavus šiai komandai adresą bei išraišką (pvz. EDX + 4), kodo vykdymui pasiekus duotą adresą bus parodoma heap‘o būsena bei turinys, esantis ties „EDX + 4“ - tai labai padeda teisingo heap‘o išdėstymo paieškoje bei formavime norint ką nors jame perrašyt.
!funsniff – parodo funkcijos darbo su heap‘u raidą. Perdavus funkcijos adresą parodo visus tos funkcijos atminties išskyrimus/atlaisvinimus, taipogi automatiškai suranda atminties nutekėjimus, dvigubo atlaisvinimo („double free“) klaidas.
!heap – pagrindinė komanda darbui su heap‘u:parodo įvairią informaciją, gabalus, bei jų turinį, įgalina išsaugoti heap‘o būseną bei ją vėliau sulyginti su pasikeitusia (pvz. po perpildymo) būsena, atpažįsta heap‘e esančių duomenų tipus.
!hookheap – nukreipia RtlAllocateHeap/RtlFreeHeap funkcijas bei parodo informaciją jas vykdant.
!hippie – beveik tas pats funkcionalumas kaip ir hookheap tik naudoja kitą, greitesnį, mechanizmą kodo nukreipimui bei turi rezultatų filtravimą.
!lookaside – parodo heap‘o lookaside masyvą.
!searchheap – specifinių heap’o gabalų paieška - leidžia ieškoti pagal ieškomo, bei šalia esančių gabalų dydį, flag‘us ir t.t.
Kai kurių iš šių komandų veikimą, bei bendrą darbo tėkmę su ID iliustruosiu paparastu pavyzdžiu. Tarkim, jog turime paprastą C programą:
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
void f();
void f1();
int main(int argc, char *argv[]) {
getchar();
HANDLE hHeap;
char *data, *data1;
DWORD *ftable;
hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 1024, 4096);
data = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 64);
ftable = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 8);
ftable[0] = (DWORD)&f;
ftable[1] = (DWORD)&f1;
strcpy(data, argv[1]);
((void(*)())ftable[0])();
((void(*)())ftable[1])();
return 0;
}
void f() {
printf("1n");
return;
}
void f1() {
printf("2n");
return;
}
Joje yra nesudėtingas ir lengvai išnaudojamas heap‘o perpildymas: išskiriami du atminties gabalai iš heap‘o , antrame gabale sukuriamas funkcijų nuorodų sąrašas, po to į pirmą atminties gabalą įrašomi duomenys, perduodami per komandinę eilutę. Heap‘ o struktūrų neatakuosim, nes dabar, egzistuojant visoms heap‘o apsaugoms, tai įmanoma tik ypač specifiniais atvejais, todėl tiesiog perrašysim funkcijų nuorodas. Dabar įsivaizduokime, kad mes viso to nežinome, o matome tik tai, kad paleidus mūsų programą su ilgu argumentu ji lūžta.
Taigi paleidžiam šią programą su argumentu „test“ bei prikabinam ID - tai daryti reikia todėl, kad paleidus programą iš derintuvo bus naudojama debug heap‘o versija, o to mums nereikia. Prisikabinę paspaudžiam ALT+F9, tam, kad sugrįžtume į pavyzdžio kodą. ID sustoja ties getchar funkcija, peržiųrim heap‘ą su heap komanda:
Matom tris jau sukurtus heap‘us, tačiau šiuo metu dar neįvykdyta nei viena operacija su heap‘u, todėl spaudžiam F9, sustabdom ir vėl sustojam po getchar. Paleidę komandą !heap matome, jog vietoj trijų heap‘ų turime keturis:
Dabar pasinaudoję ta pačia !heap komanda, tačiau nurodę specifinį heap‘ą su parametru –h bei pridėję parametrą –d kuris automatiškai aptinka duomenų tipus:
Išvestoje informacijoje matome, jog mūsų įvestas tekstas „test“ eina tiesiai prieš funkcijų nuorodų sąrašą ir jo dydis yra 0x48 ( atėmus 8 baitus, kurie sudaro headerį, gausime tikrąjį dydį). Taigi mes perrašę 64 (pirmo gabalo) + 8 (antro gabalo galvutė) + 4 (pirmoji nuoroda į funkciją) perrašysime funkcijos nuorodą ir jau galėsime nukreipti kodo eigą į mūsų norimą vietą.
Šiame pavyzdyje kol kas panaudojau tik vieną komandą kelis kartus ir jau galima sakyti, jog turime kodo vykdymą, nors realiose programose dažnai tenka gan ilgai analizuoti kodo tėkmę, norint nustatyti heap‘o išdėstymą ar reikalingas prielaidas. Taipogi pavyzdyje patogiai išsidėsčiusios funkcijų nuorodos, kurias galime perrašyti, nors tai nelabai ir skiriasi nuo realių programų, nes dažniausiai tenka ieškoti įvairių nuorodų į funkcijas, o jų heap‘e pasitaiko nemažai. Didesnėse programose heap‘e dažnai galima rasti įvairių nuorodų į funkcijas, grįžtąmojo iškvietimo („callback“) funkcijų nuorodų (šio tipo nuorodas į heap‘ą patalpina kai kurios winapi funkcijos), įvairias nuorodas į objektų virtualias funkcijas pvz. c++ kode ir t.t.
ID galimybės nesibaigia ties heap‘o analize, egzistuoja dar daug įvairių komandų, palengvinančių nuobodų darbą. Taigi, dar kelios išskirtinės komandos:
!duality – ieško duomenų, kurie gali būti panaudojami kaip instrukcijos. Tarkim kodo nukreipimui jums reikia specifinės instrukcijos, kurios nėra nei vienoje programos vykdomojo kodo dalyje, todėl duality automatiškai ieškos jūsų norimo kodo gabalo tarp visų įkrautų duomenų. Kartais reikalingos kodo sekos tiesiog nebūna tarp programos kodo ar užkrautų bibliotekų, tačiau kartu su programa užsikrauna įvairūs failai, pvz. unicode koduotės, kurių duomenys taipogi gali būti interpretuojami kaip kodas.
! findantidep – automatiškai suranda adresus kodo, kuris gali būti panaudotas DEP išjungimui.
!findpacker – bando surasti su kuom supakuota/užšifruota programa.
!hidedebug – paslėpia derintuvą nuo daugumos anti-debugg‘inimo metodų.
!modptr – nukreipia visas rastas nuorodas į funkcijas ir parodo kada jos yra kviečiamos.
!packets – parodo visus paketus gautus standartinėmis tinklo funkcijomis.
!safeseh – parodo visas išimtinių situacijų funkcijas užregistruotas SafeSEH metodu.
Panaudokim vieną iš šių komandų pavyzdžiui. Šįkart kaip parametrą perduodam 76 „a“ simbolius ir po paleidimo, sustojus ties getchar einam iki „CALL EAX“ – vietos, kurioje iškviečiama mūsų perrašyta funkcijos nuoroda. Nukreipti kodo eigą turbūt norėsime į kokį nors shellkodą, kuris bus patalpintas heap‘e, tačiau tiesiai šokti tuo adresu negalime, nes heap‘o adresas gali kisti su kiekvienu programos paleidimu, todėl pasižiūrime kokie adresai yra saugomi registruose bei steke – panaudodami kokią nors nuorodą į shellkodą galėsime pasiekti jį netiesiogiai.
Matome, jog egzistuoja ne vienas būdas pasiekti mūsų kontroliuojamus duomenis pvz.: JMP ECX + 77, ar JMP [ESP + 4], ar tiesiog RET.
Pasirinkę paprasčiausią variantą – RET, jį išbandome: paleidžiame komandą duality, kuri tuoj pat grąžina 45 rezultatus:
Kaip matome, nemaža dalis rastų instrukcijų yra ne kodo dalyse - pvz ketvirtoji yra surasta mano minėtame unicode lokalizacijos faile.
Taigi dabar jau turime viską ko reiktų sėkmingam kodo vykdymui: žinome kiek duomenų reikia, jog perrašytume funkcijų nuorodas bei žinome kaip nukreipti vykdymo eigą į mūsų valdomus duomenis. Patikimam išnaudojimo kodui, kuris įvykdęs šelkodą toliau testų programos vykdymą, o ne tiesiog nulūžtų, to, aišku, nepakanka, tačiau tiesiog norėjau pademonstruoti dalį šio įrankio galios. Kiekvieną iš mano minėtų komandų galima laisvai modifikuoti ar nagrinėti – visos jos parašytos su „Python“, todėl norint tikrai įvaldyti šį įrankį reiktų žinoti bent „Python“ pradmenis. Taipogi paminėsiu, jog ID nėra be trūkūmų – vartotojo sąsaja atrodo nevisiškai užbaigta, yra klaidų immunity bibliotekoje bei komandose, kurias mokant „Python“, galima ir pačiam pataisyti, nes immlib platinama su atvirojo kodo licenzija.
Šiam kartui tiek, laukite kitos dalies kurioje greičiausiai pademonstruosiu visą kokio nors realaus pažeidžiamumo nagrinėjimo eigą panaudojant ID ir/arba ID scenarijų tam tikslui rašymą.