A Return oriented Programming ( ROP ) a szoftver sérülékenységeinek kihasználására szolgáló módszer , amellyel a támadó végrehajthatja a számára szükséges kódot, ha a rendszerben védelmi technológiák vannak, például olyan technológia, amely megtiltja bizonyos memóriaoldalakról származó kód futtatását [ 1] . A módszer abban rejlik, hogy a támadó átveheti az irányítást a hívási verem felett , megtalálhatja a kódban a szükséges műveleteket végrehajtó utasításokat , amelyeket "gadgeteknek" neveznek, és végrehajthatja a "gadgeteket" a kívánt sorrendben [2]. A "gadget" általában egy visszatérési utasítással végződik, és a meglévő kódban (programkódban vagy megosztott könyvtárkódban ) a fő memóriában található . A támadó a modulok szekvenciális végrehajtását valósítja meg a visszatérési utasítások segítségével, és úgy rendezi el a modulok sorozatát, hogy végrehajtsa a kívánt műveleteket. A támadás még olyan rendszereken is megvalósítható, amelyek rendelkeznek az egyszerűbb támadások megelőzésére szolgáló mechanizmusokkal.
A Return Oriented Programming a puffertúlcsordulási támadás továbbfejlesztett változata . Ebben a támadásban a támadó egy hibát használ a programban, amikor a függvény nem ellenőrzi (vagy hibásan ellenőrzi) a határokat, amikor a felhasználótól kapott adatok pufferébe ír. Ha a felhasználó a pufferméretnél több adatot küld, akkor a többlet adat a többi helyi változónak szánt memóriaterületbe kerül, és felülírhatja a visszatérési címet is. Ha a visszatérési címet felülírják, akkor a függvény visszatérésekor a vezérlés átkerül az újonnan írt címre.
A puffertúlcsordulási támadás legegyszerűbb változatában a támadó kódot ("payload") tol a verembe, majd felülírja a visszatérési címet az éppen írt utasítások címével. Az 1990-es évek végéig a legtöbb operációs rendszer nem nyújtott védelmet ezekkel a támadásokkal szemben. A Windows rendszerek 2004-ig nem rendelkeztek védelemmel a puffertúlcsordulási támadások ellen. [3] Végül az operációs rendszerek elkezdtek foglalkozni a puffertúlcsordulási sérülékenységek kihasználásával a memória bizonyos oldalainak nem végrehajthatóként való megjelölésével (ez az úgynevezett "adatvégrehajtás-megelőzés"). Ha az adatvégrehajtás megakadályozása engedélyezve van, a gép megtagadja a kód végrehajtását a "csak adatok" jelzésű memóriaoldalakon, beleértve a köteget tartalmazó oldalakat is. Ez megakadályozza, hogy a hasznos terhet a veremre tolja, majd ráugorjon, felülírva a visszatérési címet. Később úgy tűnt, hogy az adatvégrehajtás-megelőzés hardveres támogatása fokozza a védelmet.
Az adatvégrehajtás megakadályozása a fent leírt módszerrel megakadályozza a támadást. A támadó a megtámadott programban és a megosztott könyvtárakban már található kódra korlátozódik. A megosztott könyvtárak, például a libc azonban gyakran tartalmaznak rendszerhívásokat indító funkciókat és egyéb hasznos funkciókat a támadók számára, amelyek lehetővé teszik ezeknek a funkcióknak a támadások során történő használatát.
A könyvtár visszatérési támadása a puffer túlcsordulást is kihasználja. A visszatérési címet felülírja a kívánt könyvtári függvény belépési pontja. A visszatérési cím feletti cellák is felülírásra kerülnek, hogy paramétereket adjanak át a függvénynek vagy a többszörös lánchívásoknak. Ezt a technikát először Alexander Peslyak (Solar Designer néven ismert) vezette be 1997-ben, [4] és azóta kibővítették, hogy lehetővé tegye a függvényhívások korlátlan láncát. [5]
A 64 bites hardverek és operációs rendszerek elterjedésével egyre nehezebbé vált a könyvtári visszatérési támadás végrehajtása: a 64 bites rendszerekben alkalmazott hívási konvenciókban az első paraméterek nem a veremben, hanem a függvénynek adódnak át. regisztereket. Ez megnehezíti a támadás során meghívandó paraméterek előkészítését. Ezenkívül a megosztott könyvtárak fejlesztői elkezdték eltávolítani vagy korlátozni a "veszélyes" funkciókat, például a rendszerhívás-burkolókat a könyvtárakból.
A támadás következő fejlesztési köre a könyvtári funkciók részeinek felhasználása volt a teljes funkciók helyett. [6] Ez a technika a függvények azon részeit keresi, amelyek az adatokat a veremből a regiszterekbe tolják. Ezen részek gondos kiválasztása lehetővé teszi a szükséges paraméterek elkészítését a regiszterekben a függvény hívásához az új konvenció szerint. Továbbá a támadást ugyanúgy hajtják végre, mint a könyvtári visszaküldést.
A Return-Oriented Programming kiterjeszti a kódkölcsönzési megközelítést azáltal, hogy a támadónak Turing-teljes funkcionalitást biztosít, beleértve a ciklusokat és elágazásokat . [7] Más szóval, a visszatérés-orientált programozás lehetőséget biztosít a támadónak bármilyen művelet végrehajtására. Hovav Shaham 2007-ben publikálta a módszer leírását [8] , és egy olyan programon demonstrálta, amely a szabványos C könyvtárat használja, és puffertúlcsordulási sebezhetőséget tartalmaz. A megtérülés-orientált programozás mind kifejezőerőben, mind védekező intézkedésekkel szembeni ellenállásában felülmúlja a fent leírt egyéb támadástípusokat. A támadások elleni küzdelem fenti módszerei egyike sem, beleértve a veszélyes funkciók eltávolítását a megosztott könyvtárakból, nem hatékony a visszatérés-orientált programozás ellen.
Ellentétben a visszatérő könyvtárba támadással, amely teljes függvényeket használ, a visszatérés-orientált programozás kis utasítássorozatokat használ, amelyek visszatérési utasítással végződnek, az úgynevezett "gadgeteket". A minialkalmazások például meglévő függvények végződései. Egyes platformokon azonban, különösen az x86 -on , a kütyük előfordulhatnak "a sorok között", vagyis amikor egy meglévő utasítás közepétől dekódolnak. Például a következő utasítássorozat: [8]
teszt edi , 7 ; f7 c7 07 00 00 00 setnz bájt [ ebp -61 ] ; 0f 95 45 c3amikor a dekódolás egy bájttal később kezdődik, ad
mov dword [ edi ], 0 f000000h ; c7 07 00 00 00 0f xchg ebp , eax ; 95 inc ebp ; 45 ret ; c3Ezenkívül a modulok lehetnek az adatokban, valamilyen okból a kód részben. Ennek az az oka, hogy az x86 utasításkészlet meglehetősen sűrű, ami azt jelenti, hogy jó eséllyel egy tetszőleges bájtfolyamot tényleges utasítások folyamaként értelmezünk. Másrészt a MIPS architektúrában minden utasítás 4 bájt hosszú, és csak a 4 bájt többszörösének megfelelő címekhez igazított utasítások hajthatók végre. Ezért nincs mód arra, hogy "sorok közötti olvasással" új sorozatot kapjunk.
A támadás a puffertúlcsordulási sebezhetőséget használja ki. Az aktuális függvény visszatérési címe felülíródik az első modul címével. A verem következő pozíciói a következő modulok címeit és a modulok által használt adatokat tartalmazzák.
Az x86-os platform eredeti verziójában a kütyük ugrások nélkül egymás után elrendezett utasítások láncai, amelyek egy közeli visszatérési utasítással végződnek. A támadás kiterjesztett változataiban a láncutasítások nem feltétlenül szekvenciálisak, hanem közvetlen ugrási utasításokkal kapcsolódnak össze. Valamint a végső utasítás szerepét egy másik visszatérési utasítás is betöltheti (x86-ban van távoli visszatérési utasítás, közeli és távoli visszatérési utasítások veremtörléssel), indirekt ugrási utasítás, vagy akár indirekt hívási utasítás is. Ez megnehezíti a támadási módszer elleni küzdelmet.
Vannak eszközök a kütyük automatikus megtalálására és a támadások tervezésére. Ilyen eszköz például a ROPgadget. [9]
Számos módszer létezik a visszatérés-orientált programozás elleni védekezésre. [10] A legtöbb a programkód és a könyvtárak viszonylag tetszőleges címen való elhelyezkedésére támaszkodik, így a támadó nem tudja pontosan megjósolni a kütyükben esetleg hasznos utasítások helyét, és ezért nem tud felépíteni egy modulláncot a támadáshoz. A módszer egyik megvalósítása, az ASLR , a program minden egyes futtatásakor más címen tölti be a megosztott könyvtárakat. Bár ezt a technológiát széles körben használják a modern operációs rendszerekben, sebezhető az információszivárgási támadásokkal és más olyan támadásokkal szemben, amelyek lehetővé teszik egy ismert könyvtári funkció helyzetének meghatározását. Ha egy támadó meg tudja határozni egy funkció helyét, meg tudja határozni az összes utasítás helyét a könyvtárban, és visszatérés-orientált programozási támadást hajthat végre.
Nemcsak a teljes könyvtárakat, hanem a programok és könyvtárak egyes utasításait is átrendezheti. [11] Ehhez azonban kiterjedt futásidejű támogatásra van szükség, például dinamikus fordításra, hogy a permutált utasításokat a megfelelő végrehajtási sorrendbe állítsa vissza. Ez a módszer megnehezíti a kütyük megtalálását és használatát, de magas költségekkel jár.
A kBouncer [12] megközelítése annak ellenőrzése, hogy a return utasítás átadja-e a vezérlést közvetlenül a hívási utasítást követő utasításnak. Ez nagymértékben csökkenti a lehetséges kütyük számát, ugyanakkor jelentős teljesítménycsökkenést is okoz. [12] Ráadásul a visszatérés-orientált programozás kiterjesztett változatában a kütyük nem csak visszatérési utasítással, hanem indirekt ugrás- vagy hívásutasítással is összekapcsolhatók. Egy ilyen kiterjedt támadás ellen a kBouncer hatástalan lesz.