StackGuard opiera się na systemie umieszczania „kanarków” między buforami stosu a danymi stanu ramki. Jeśli przepełnienie bufora próbuje nadpisać RIP, kanarek zostanie uszkodzony i zostanie wykryte naruszenie. Poniższa ilustracja pokazuje uproszczony układ umieszczania kanarka przed zapisanym wskaźnikiem ramki (SFP) i RIP. Pamiętaj, że SFP służy do przywracania wskaźnika bazowego (RBP) do ramki stosu funkcji wywołującej.
Skompiluj vuln.c, aby włączyć ochronę stosu kanarkowego:
Teraz możemy uruchomić napisany przez nas dotychczas exploit i zobaczyć ochronę Stack Canary w akcji, ale najpierw zróbmy kopię naszego exploita:
Zgodnie z oczekiwaniami, eksploit się nie powiódł, ponieważ proces potomny uległ awarii z błędem „wykryto rozbijanie stosu ***: terminated”, jak pokazano poniżej:
Aby ominąć tę ochronę, musimy spowodować wyciek lub brutalną próbę w celu naprawy kanarka. Ponieważ kanarek jest definiowany podczas ładowania programu, a serwer TCP jest wielowątkowy, każdy proces potomny zachowa tego samego kanarka, co jego proces nadrzędny. Wykorzystamy to zachowanie, aby brutalnie włamać się do kanarka. Strategia brutalnej próby jest następująca:
- Określ, ile bajtów należy zapisać przed zniszczeniem kanarka. Kanarek jest umieszczany przed SFP i RIP.
- Iteruj od 0 do 255, szukając następnego prawidłowego bajtu. Jeśli bajt jest nieprawidłowy, uszkodzimy kanarka, a dziecko zostanie zakończone. Jeśli bajt jest prawidłowy, serwer TCP zwróci „Nieprawidłowe hasło”. Najpierw otwórzmy program za pomocą gdb i ustawmy punkt przerwania przed sprawdzeniem kanarka:
Wyślijmy wzór cykliczny z innego okna:
Teraz wróć do okna gdb. Możesz zobaczyć, że RSI trzyma 8 bajtów, które rozbiły kanarka. Użyjmy polecenia pattern search, aby dowiedzieć się, ile bajtów musimy zapisać przed nadpisaniem kanarka:
Zmodyfikujmy nasz exploit:
Przeanalizujmy zmiany, które wprowadziliśmy do naszego exploita. W punkcie (1) piszemy funkcję exploita, która przyjmuje dwa argumenty: ładunek do wysłania i informację, czy musimy aktywować tryb interaktywny. Ta funkcja połączy się, wyśle ładunek i zwróci True, jeśli serwer TCP zwróci „Invalid” (2). Oznacza to, że bieżący kanarek jest prawidłowy; w przeciwnym razie zwraca False (3), aby kontynuować iterację. W punkcie (4) piszemy funkcję leak_bytes, która przyjmuje dwa argumenty: prefiks ładunku i nazwę wyciekających bajtów. Wykona ona osiem iteracji (aby wyciekło 8 bajtów) (5), od 0 do 255 (6), wysyłając ładunek + current_byte (7). Jeśli exploit zwróci True, dodamy ten bajt do bieżącego ładunku (8), a następnie wstawimy go do tablicy leaked_bytes (9). Po zakończeniu (10) zwróci tablicę leaked_bytes. W (11) tworzymy nowy ładunek z 72 As + wyciekły kanarek + 8 bajtów wypełnienia + nasz poprzedni łańcuch ROP. Na koniec w (12) wywołujemy funkcję exploita z ostatecznym ładunkiem i określamy, że tryb interaktywny powinien być włączony.
Uruchommy podatny program w jednym oknie, a nasz exploit2.py w innym oknie:
Udało nam się! Udało nam się naprawić canary, stosując metodę brute-force. Teraz nasz exploit jest w stanie ominąć dwie techniki łagodzenia exploitów: NX i stack canary. Następnie włączymy i ominiemy ASLR.