Ten wpis jest częścią cyklu “Od zera do bughuntera” – listę wszystkich postów znajdziesz tutaj.
Zamiast łamać kolejne urządzenie i oferować “rybę”, czytelnik otrzyma “wędkę” aby móc samemu wykorzystywać podatności typu Buffer Overflow.
W tym poście przedstawię praktyczne wykorzystanie techniki “[Return-oriented programming][1]“, wraz z przykładowym kodem exploita.
Od dobrych paru lat systemy operacyjne wspierają zabezpieczenie zwane Data Execution Prevention (DEP). Podstawowym założeniem tego mechanizmu jest to, że żaden segment pamięci nie może być zapisywalny i wykonywalny w tym samym momencie (oznaczane inaczej jako W^X).
Dla atakującego oznacza to, że nie może umieścić swojego [shellcode’u][2] na stosie i przekazać do niego sterowania.
Aby rozwiązać ten problem powstała technika, która wykorzystywała rezydujące w pamięci funkcje bibliotek czyli [ret2libc][3]. ROP jest udoskonaleniem tej techniki, z tą różnicą, że nie odwołuje się do istniejących funkcji bibliotecznych, a fragmentów dowolnego kodu w programie, zakończonych instrukcją ret.
W codziennej pracy wykorzystuję GDB + PEDA (polecam!). Ładuję daną binarkę do GDB i wydaję polecenie checksec.
DEP jest oznaczony jako NX, który jak widać dla testowanej binarki jest aktywny**.**
Spełnionych musi być kilka warunków:
Wszystkie kroki będą wykonywane na systemie Ubuntu 15.10 x86.
Podatnym programem, na który będę pisał exploita jest ćwiczenie z kursu [Modern Binary Exploitation][7], a konkretniej binarka o nazwie [lab5b][8].
Polecam pobranie i kompilację z przełącznikami podanymi w komentarzu. Zadaniem exploita będzie uruchomienie zdalnego shella za pomocą netcata na porcie 6789.
Program jest bardzo prosty, a jego podatność przepełnienia bufora jest oczywista i zawiera się w linijce o numerze 12 – funkcja [gets()][9] załaduje tyle znaków do bufora ile zostanie podane na standardowym wejściu.
#include <stdlib.h> #include <stdio.h>
/* gcc -fno-stack-protector –static -o lab5B lab5B.c */
int main() {
char buffer[128] = {0};
printf("Insert ROP chain here:\n");
gets(buffer);
return EXIT_SUCCESS;
}
Naszym zadaniem jest znalezienie fragmentów, które nam to umożliwią nam zbudowanie poniższego kawałka kodu.
char *execve_env[1] = {NULL};
char *execve_args[7]= { “/bin//nc”, “-lnp”, “6789”, “-tte”, “/bin//sh”, NULL };
execve("/usr/bin/nc", execve_args, execve_env);
Oprócz wiedzy, co chcemy napisać, musimy również wiedzieć jak napisać, aby zadziałało – czyli znać konwencję wywołania funkcji [execve()][10] na poziomie procesora.
Chodzi o odpowiednie umieszczenie argumentów w rejestrach CPU:
Autorzy kursu MBE zalecają statyczną kompilację binarki, przez co liczba “ROP gagdets” dostępnych w binarce jest ogromna i bardzo łatwo jest znaleźć to czego nam potrzeba.
ROP Gagdets dostępne w binarce najszybciej można znaleźć za pomocą narzędzia [ROPgadget][11], grepując po wynikach (jeżeli wiemy czego szukamy 😉 )
Użycie narzędzia jest bardzo proste i sprowadza się do wydania polecenia ROPgagdet –binary [ścieżka do binarki].
Patrząc na kod payloadu możemy określić potrzebne fragmenty kodu (instrukcje):
Dodatkowo będziemy potrzebowali jeszcze trochę miejsca w pamięci (w segmencie .data) na stos naszego exploita.
Gdy dane wejściowe są mniejsze niż bufor w programie lab5b, stos wygląda w nastepujący sposób:
Natomiast dla nas punktem wyjścia jest taki wygląd stosu:
Poniżej przedstawiam kod źródłowy gotowego exploita w Pythonie wraz z komentarzem każdej linijki kodu (w tym wypadku jest to najlepsze rozwiązanie – post i tak jest już wystarczająco długi 😉 )