Kihagyás

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
#include <stdlib.h>

int main(){
   exit(3);
}

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
   0x0806beda <+0>:     mov    0x4(%esp),%ebx (paraméter elhelyezése az ebx regiszterben)
   0x0806bede <+4>:     mov    $0xfc,%eax     (0xfc az exit_group kódja)
   0x0806bee3 <+9>:     call   *%gs:0x10      (system call elvégzése)
   0x0806beea <+16>:    mov    $0x1,%eax      (0x1 az exit kódja)
   0x0806beef <+21>:    int    $0x80          (system call elvégzése)
   0x0806bef1 <+23>:    hlt                   (halt cpu)

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
 .intel_syntax noprefix
 .section .text
     .global _start

 _start:
     mov bl,3
     mov al,1
     int 0x80

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
00001000 <.text>:
 1000:       b3 03                   mov    $0x3,%bl
 1002:       b0 01                   mov    $0x1,%al
 1004:       cd 80                   int    $0x80

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
objdump -d ./shell|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

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
__attribute__((section(".data.sh,\"awx\",@progbits#")))  char shell[] = "\xb3\x03\xb0\x01\xcd\x80";
int main() {
int *ret;
//ret pointer for manipulating return address.
ret = (int *)&ret + 2;//+2 az ebp átugrása miatt, a visszatérési címet tároló helyre mutat majd
(*ret) = (int)shell; //a shell tömb címét tesszük be visszatérési címnek
}

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

Fájlok


Utolsó frissítés: 2022-04-13 06:08:26