BOF kihasználása¶
Bevezetés¶
Az előző anyagban a C/C++ programok egy jellemző sérülékenységéről, a buffer overflow-ról volt szó. A támadó a sérülékenységet kihasználva eltéríti a programot a normális lefutástól. Egyelőre csak olyan példát néztünk, amikor az input segítségével átírtuk egy függvény visszítérési címét egy másik függvény kezdőcímére. Azonban a támadó nem feltétlenül csak rejtett függvényeket vagy másik if-else ágat akar lefuttatni, hanem tetszőleges utasításokat. Utasításokat injektál a sérülékeny programba és eléri, hogy a végrehajtás az utasításokra fusson. Ez például shellcodeolással valósítható meg.
Shellcode¶
A shellcode futattható utasításokból áll, melyek például egy (root jogosultságú) shell megnyitását eredményezik. Innen ered a neve is. A gyakorlatban tetszőleges olyan művelet elvégzése van belekódolva, amit a támadó szeretne elvégeztetni az áldozat gépével. Ez lehet shell nyitás, backdoor nyitás, fájlok letörlése, adatlopás stb.
A támadó eléri, hogy a shellcode ott legyen a program egy tömbjében (pl megadja neki mint inputot, beleteszi egy fájlba, amit a program beolvas stb), majd eléri, hogy a végrehajtás ráfusson. Az előző anyagban említve van egy védelmi vonal, amivel a GCC defaultból ellátja a programokat. A verem nem végrehajthatónak van megjelölve. Ez a védelem pont a shellcodeok működését blokkolja (a beinjektált kód nem tud futtatható utasításként értelmeződni).
Kikapcsolás: -z execstack kapcsolóval
Shellcode készítés¶
Ahhoz, hogy jobban megértsük a működésüket (és megtapasztaljuk, hogy elkészítésük nem egyszerű) kézzel létrehozunk egy shellcodeot. A lehető legegyszerűbb shellcodeot készítjük el. Az exit() system call-t fogjuk vele megvalósítani.
Természetesen manapság már automatikusan is generálhatóak. Pl. a Metasploit nevű programmal. Itt találtok egy referenciát a generálásra: https://subscription.packtpub.com/book/networking_and_servers/9781788473736/5/ch05lvl1sec40/generating-shellcode-using-metasploit
A program többféle architektúrára, platformra is tud generálni shell-t nyitó shellcodeokat, megszabadítva minket némi szenvedéstől.
Exit shellcode lépései¶
Készítünk egy programot, ami használja az exit() hívást.¶
Statikus linkeléssel lefordítjuk, így benne lesz a binárisban az exit kódja is, amit megvizsgálunk. Ihletet merítünk a saját kódunk megírásához. Sajnos az nem működőképes, hogy egy ilyen magasszintű nyelven megírt programot egy az egyben felhasználunk shellcodenak.
1 2 3 4 5 |
|
Fordítás: gcc shell.c -o shell -m32 -static -g
Az -m32 kapcsoló azért van ott most is, mert a létrehozott shellcode 32 bites lesz majd.
GDB-s vizsgálat: disas _exit
1 2 3 4 5 6 |
|
Az exit_group system call-t ki fogjuk hagyni. Ez nem csak a hívó szálat terminálja, hanem a processzben futó összeset. Mivel szeretnénk, hogy a shellcode minél kisebb legyen ezt a jelenleg nem szükséges részt kihagyjuk.
System call-ok kódjai: http://publicclu2.blogspot.com/2013/05/32-bit-linux-system-call-signal-errno.html
Linux rendszeren a system call-ok elvégzésének tradicionális módja azt int 0x80 utasítás használata, amivel kernel módba léptetjük a cpu-t. Az exit_group meghívása itt például egy másik, hatékonyabb megvalósítással van meghívva.
Mi szerepeljen az elkészítendő shellcodeban?
- Regiszterek beállítása: ebx-ben a paraméter, eax-ben a system call kódja (1-es)
- Interrupt utasítás
Assembly kód megírása¶
1 2 3 4 5 6 7 8 |
|
Fordítás: gcc -s shell.s -nostdlib -m32 -o shell
-nostdlib: ne problémázzon a linker a main függvény hiánya miatt.
Vizsgálat az objdump programmal¶
Az elkészült binárist vizsgáljuk meg az alábbi módon:
objdump -D shell
A .text szekció:
1 2 3 4 |
|
Jobboldalt viszont látjuk az assembly utasításainkat. Bal oldalt az utasítások memóriacíme láthatóak. A középső oszlop az érdekes: ezek a CPU-nak leküldendő opkódok (operáció kód). A végrehajtandó utasítás kódszáma, hexadecimálisan megadva (az assembly annyiban segít nekünk, hogy a konkrét opkódok helyett az olyan mnemonikokat használhatjuk, mint pl a mov). Egy opkód referencia az éreklődőknek: http://ref.x86asm.net/coder32.html
Opkódok kinyerése¶
Ahhoz, hogy az előbbi assembly kódot injektálható(bb) formában viszont lássuk szükséges kinyerni az opkódokat. Ezt többféle módon is megtehetjük, pl. ezzel az utasítással:
1 |
|
Az eredmény:
"\xb3\x03\xb0\x01\xcd\x80"
Az objkódok hexadecimális számok, ezért ha karakter tömbbe akarjuk elhelyezni őket fontos, hogy escape-eljünk. Nehogy tényleg karakterként legyenek értelmezve.
Shellcode működőképességének tesztelése¶
Egy egyszerű kis C programmal lehet megvalósítani.
1 2 3 4 5 6 7 |
|
Fordítás: gcc shellcode_exit_tester.c -o sk -m32
A -z execstack opcióra most nincs szükség, mert a tömb nem a vermen van. Viszont így is át kell állítani a data memóriaterületet futtahatóra (attribute rész). A -m32 azért kell mert ez a shellcode nem működik 64 bites programon.
Az előbbi lépések egy nagyon egyszerű shellcode összeállítását szemléltetik. Bonyolultabb funkciók elvégzése (pl shell spawn) az architektúrától függően sokkal több küzdelembe, játszadozásba kerülhet.
Leírás egy ilyenről: http://phrack.org/issues/49/14.html