Konfigurowanie GDB

https://chacker.pl/

QEMU udostępnia interfejs debugowania serwera GDB, który jest domyślnie włączony po przekazaniu opcji -s (skrót od -gdb tcp::1234) w skryptach powłoki run*.sh.

UWAGA: Przed kontynuowaniem należy wykonać czynności opisane w rozdziale 11, aby upewnić się, że GDB i wtyczka GEF są poprawnie zainstalowane.

Po zainstalowaniu GDB i GEF powinieneś móc połączyć się z serwerem debugowania QEMU, uruchamiając polecenie target remote :1234 na konsoli GDB. Moduł jądra udostępnia interfejs /proc/ghh i jest podatny z założenia, ponieważ bardzo łatwo zidentyfikować i wykorzystać luki w zabezpieczeniach związane z dowolnym odczytem i zapisem. Ideą laboratoriów jest skupienie się na zrozumieniu funkcji ograniczających wykorzystanie luk w jądrze i sposobach ich omijania, zamiast znajdowania luk. Uruchommy QEMU i GDB, aby dowiedzieć się więcej o działaniu modułu:

1. Otwórz terminal w folderze ~/GHHv6/ch12, wykonaj skrypt run1.sh i wypisz wyeksportowane funkcje modułu:

  1. Otwórz nowe okno terminala w tym samym folderze, podłącz GDB do serwera GDB QEMU i rozmontuj funkcje ghh_write i ghh_read:

Konfiguracja środowiska i podatny moduł procfs

https://chacker.pl/

Na początek skonfigurujmy środowisko eksploatacji oparte na QEMU, ukierunkowane na uproszczone jądro (5.14.17) i prosty moduł jądra, który został celowo uczyniony podatnym na ataki, aby zademonstrować proces omijania wielu mechanizmów wykonawczych jądra GNU/Linux i łagodzenia zagrożeń w czasie kompilacji. Eksploatacja jądra może być nieco irytująca w porównaniu do eksploatacji binarnej w środowisku użytkownika, ponieważ debugowanie jest nieco uciążliwe, a każda nieudana próba eksploatacji spowoduje panikę jądra i będzie wymagała ponownego uruchomienia całego systemu. Użyjemy QEMU do emulacji systemu operacyjnego, a także do ułatwienia pisania i debugowania eksploitów. Złożoność każdego laboratorium będzie przechodzić od prostej, bezpośredniej eksploatacji do zaawansowanych kroków obejścia łagodzenia, ilustrując postępy poczynione przez programistów systemu operacyjnego w celu uczynienia eksploatacji jądra coraz trudniejszą, ale nie niemożliwą. Najpierw zainstaluj QEMU, uruchamiając następujące polecenia w powłoce roota:

Następnie w folderze ~/GHHv6/ch12 znajdziesz plik skryptu powłoki, który uruchamia cel eksploatacji QEMU skonfigurowany przy użyciu opcji łagodzenia odpowiadających każdemu laboratorium:

  • run1.sh Niestandardowe jądro Linux z wyłączonym Stack Canaries i podatnym modułem jądra bez włączonych zabezpieczeń przed eksploatacją, nadające się do wykorzystania prostej techniki ret2usr. Poprowadzi nas to przez podstawy eksploatacji eskalacji uprawnień, co pomoże nam zrozumieć rozumowanie stojące za każdą ochroną przed eksploatacją i poprawą bezpieczeństwa jądra na przestrzeni lat.
  • run2.sh W tym laboratorium uruchamiany jest ten sam moduł jądra, ale jądro zostało skompilowane ponownie, aby umożliwić ochronę przed eksploatacją Stack Canaries.
  • run3.sh Ochrona przed eksploatacją Stack Canaries, SMEP i KPTI
  • run4.sh Ochrona przed eksploatacją Stack Canaries, SMEP, KPTI i SMAP
  • run5.sh Ochrona przed eksploatacją Stack Canaries, SMEP, KPTI, SMAP i KASLR

UWAGA: Te skrypty zakładają, że sklonowałeś repozytorium GitHub do /home/kali/GHHv6. Jeśli sklonowałeś je do innego katalogu, będziesz musiał ręcznie zaktualizować każdy z plików .sh. Aby uprościć proces udostępniania plików między gościem a hostem, niestandardowe jądro jest kompilowane przy użyciu protokołu Plan 9 Filesystem Protocol z obsługą modułu transportowego VIRTIO. QEMU automatycznie zamontuje udostępniony folder w katalogu domowym użytkownika. W tym udostępnionym folderze możesz również znaleźć ukończone exploity dla każdego laboratorium przedstawionego w tym rozdziale. Oto kilka innych ważnych plików, które są dostarczane:

  • ~/GHHv6/ch12/stackprotector-disabled/bzImage To jest skompresowany obraz jądra z wyłączonym STACKPROTECTOR (StackCanaries) dla pierwszego laboratorium.
  • ~/GHHv6/ch12/bzImage Skompresowany obraz jądra z włączonym STACKPROTECTOR (StackCanaries).
  • vmlinux To nieskompresowany bzImage, który pomaga uprościć proces debugowania, ponieważ dostarcza symbole debugowania. Jeśli musisz go wyodrębnić, najłatwiejszym sposobem jest pobranie skryptu extract-vmlinux1 znajdującego się w katalogu skryptów drzewa jądra.

• initramfs.cpio To jest główny system plików

Exploity jądra Linux

https://chacker.pl/

Jądro Linux oferuje ogromne możliwości eksploatacji. Pomimo tego, że jest nieco onieśmielające, zasady eksploatacji pozostają takie same, jak błędy uszkodzenia pamięci przestrzeni użytkownika, a nieograniczony dostęp do pamięci i innych zasobów zapewnia atakującym nieograniczoną władzę nad systemami, których to dotyczy. Podatny kod i błędy bezpieczeństwa można znaleźć w modułach jądra, sterownikach, wywołaniach systemowych i innych implementacjach zarządzania pamięcią. W ramach ciągłych prób zwiększenia bezpieczeństwa jądra Linux wdrożono wiele ulepszeń bezpieczeństwa i funkcji ograniczających eksploatację. Jednak badacze znaleźli wiele kreatywnych sposobów na obejście tych granic bezpieczeństwa.

Podsumowanie

https://chacker.pl/

Użyliśmy  tu wielowątkowego programu podatnego na podstawowe przepełnienie stosu, aby zbadać, jak działają techniki łagodzenia zagrożeń ASLR, PIE, NX i kanarki stosu oraz jak je ominąć. Łącząc te techniki, mamy teraz lepszy zestaw narzędzi do radzenia sobie z systemami w świecie rzeczywistym i możemy wykorzystać te złożone ataki do bardziej wyrafinowanych eksploitów.

Ominięcie PIE z wyciekiem informacji

https://chacker.pl/

Position Independent Executable (PIE) pomaga pokonać ataki ROP, losując lokalizację mapowań pamięci przy każdym uruchomieniu programu. Za każdym razem, gdy uruchamiasz podatny program, zostanie on załadowany do innego adresu pamięci. W poprzednim laboratorium włączyliśmy ASLR, ale ponieważ PIE było wyłączone, bardzo łatwo było zbudować nasz łańcuch ROP do wycieku libc, ponieważ program był zawsze ładowany do tego samego adresu pamięci. Włączmy PIE i skopiujmy nasz exploit3.py do exploit4.py:

Jeśli spróbujesz uruchomić exploit3.py, nie powiedzie się, ponieważ exploit nie zna adresu bazowego programu. Możemy ominąć tę ochronę, jeśli znajdziemy wyciek informacji, który pomoże nam obliczyć adres bazowy programu. Dlatego zastosujemy następującą strategię:

  1. Użyj funkcji leak_bytes, aby uzyskać adresy canary, SFP i RIP. Interesuje nas wyciek RIP, ponieważ po uwierzytelnieniu wraca on do głównej funkcji programu.
  2. Oblicz adres bazowy programu, odejmując <wyciek RIP> od <odległość do bazy programu>.
  3. Przypisz wynik do elf.address.

Pełne źródło naszego pliku exploit4.py znajdziesz w folderze ~/GHHv6/ch11. Oto najistotniejsze zmiany w pliku:

W (1) wycieka SFP po kanarku; potrzebujemy, aby był częścią naszego ładunku, aby kontynuować wyciek RIP. Aby uczynić rzeczy bardziej przewidywalnymi, ponieważ wiemy, że ASLR nie zmieni najmniej znaczącego bajtu, wyciekamy RIP i nadpisujemy najmniej znaczący bajt 0x6d (2), ponieważ jesteśmy pewni, że nigdy się nie zmienia:

UWAGA: najmniej znaczący bit (LSB) może być inny w Twoim środowisku. Upewnij się, że wybrałeś właściwy.

W (3) obliczamy adres bazowy programu, odejmując

odległość od adresu bazowego do wyciekłego RIP. Oto sposób na uzyskanie odległości między wyciekłym RIP a adresem bazowym programu:

  1. Uruchom ./vuln w oknie.
  2. Uruchom exploit4.py w drugim oknie. Nie martw się, jeśli exploit się nie powiedzie.
  3. Otwórz trzecie okno i uruchom gdb:
  4. Uruchom polecenie vmmap vuln:

  1. Skopiuj adres Fixed leaked_rip i odejmij adres bazowy programu vuln:

Zadziałało! Teraz udało nam się ominąć ASLR, PIE, NX i kanarki stosu. Na wypadek gdybyście się zastanawiali, technika łagodzenia zagrożeń Relocation Read Only (RELRO) chroni pliki binarne przed nadpisywaniem GOT, ale nawet jeśli włączona jest pełna funkcja RELRO, nie powstrzyma nas to przed wykonaniem kodu, ponieważ nadpisywanie GOT nie było częścią naszej strategii.

Obejście ASLR z wyciekiem informacji

https://chacker.pl/

Randomizacja układu przestrzeni adresowej (ASLR) to kontrola ochrony pamięci, która losowo wybiera lokalizacje pamięci segmentu kodu, segmentu stosu, segmentów sterty i obiektów współdzielonych, a także losowo wybiera mapowania mmap(). W naszym exploicie używaliśmy stałego adresu bazowego libc, ale to już nie zadziała, ponieważ nie będziemy w stanie znaleźć adresu dup2, system i /bin/sh. Najpierw włączmy ASLR i skopiujmy exploit2.py do exploit3.py:

Możemy pokonać ASLR, tworząc dwuetapowy exploit.

Etap 1

W etapie 1 exploita wykonamy następujące czynności:

  1. Wyciek stosu kanarka.
  2. Zbudujemy łańcuch ROP, który wywoła funkcję write PLT z dwoma argumentami:
  • Pierwszy argument to liczba 4 (deskryptor pliku accept), aby odczytać dane wyjściowe od naszego klienta. Pamiętaj, że na tym etapie nie możemy użyć dup2, ponieważ nie znamy jeszcze jego adresu.
  • Drugi argument to adres write GOT.

Co to są PLT i GOT? Procedure Linkage Table (PLT) to sekcja tylko do odczytu pliku ELF generowana w czasie kompilacji, w której przechowywane są wszystkie symbole wymagające rozwiązania. Jest ona głównie odpowiedzialna za wywoływanie dynamicznego linkera w czasie wykonywania w celu rozwiązania adresów żądanych funkcji (leniwe linkowanie). Global Offset Table (GOT) jest wypełniana adresami funkcji libc przez dynamiczny linker w czasie wykonywania. Na przykład, gdy budujemy program vuln.c, funkcja write jest kompilowana jako write@plt, a gdy program wywołuje write@plt, wykonuje następujące czynności:

  1. Szuka wpisu GOT dla adresu write.
  2. Jeśli wpis nie istnieje, koordynuje się z dynamicznym linkerem, aby uzyskać adres funkcji i zapisać go w GOT.
  3. Rozwiązuje i przechodzi do adresu zapisanego w write@got.

Krótko mówiąc, wywołamy write@plt, aby wydrukować adres write@got. Poprzez wyciek tego adresu libc możemy obliczyć bazę libc, odejmując <wyciekły adres> od <adresu symbolu write>, jak pokazano tutaj:

Możemy pokonać ASLR, tworząc dwuetapowy exploit.

Etap 1

W etapie 1 exploita wykonamy następujące czynności:

  1. Wyciek stosu kanarka.
  2. Zbudujemy łańcuch ROP, który wywoła funkcję write PLT z dwoma argumentami:
  • Pierwszy argument to liczba 4 (deskryptor pliku accept), aby odczytać dane wyjściowe od naszego klienta. Pamiętaj, że na tym etapie nie możemy użyć dup2, ponieważ nie znamy jeszcze jego adresu.
  • Drugi argument to adres write GOT.

Co to są PLT i GOT? Procedure Linkage Table (PLT) to sekcja tylko do odczytu pliku ELF generowana w czasie kompilacji, w której przechowywane są wszystkie symbole wymagające rozwiązania. Jest ona głównie odpowiedzialna za wywoływanie dynamicznego linkera w czasie wykonywania w celu rozwiązania adresów żądanych funkcji (leniwe linkowanie). Global Offset Table (GOT) jest wypełniana adresami funkcji libc przez dynamiczny linker w czasie wykonywania. Na przykład, gdy budujemy program vuln.c, funkcja write jest kompilowana jako write@plt, a gdy program wywołuje write@plt, wykonuje następujące czynności:

  1. Szuka wpisu GOT dla adresu write.
  2. Jeśli wpis nie istnieje, koordynuje się z dynamicznym linkerem, aby uzyskać adres funkcji i zapisać go w GOT.
  3. Rozwiązuje i przechodzi do adresu zapisanego w write@got.

Krótko mówiąc, wywołamy write@plt, aby wydrukować adres write@got. Poprzez wyciek tego adresu libc możemy obliczyć bazę libc, odejmując <wyciekły adres> od <adresu symbolu write>, jak pokazano tutaj:

W (1) używamy narzędzia ROP Pwntools, aby uprościć budowę naszego łańcucha ROP do wywołania write(4, write@got). W (2) po zwróceniu przez funkcję exploit() naszego wyciekłego write@got obliczamy bazę libc i kontynuujemy budowę/wykonywanie naszego ładunku drugiego etapu:

Pokonywanie kanarków stosu

https://chacker.pl/

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:

  1. Określ, ile bajtów należy zapisać przed zniszczeniem kanarka. Kanarek jest umieszczany przed SFP i RIP.
  2. 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.

Omijanie stosu niewykonywalnego (NX) za pomocą programowania zorientowanego na zwrot (ROP)

https://chacker.pl/

Kompilator GNU gcc zaimplementował ochronę stosu niewykonywalnego od wersji 4.1, aby zapobiec uruchamianiu kodu na stosie. Ta funkcja jest domyślnie włączona i można ją wyłączyć za pomocą flagi –z execstack, jak pokazano tutaj:

Zauważ, że w pierwszym poleceniu flagi RW są ustawione w oznaczeniach Executable and Linkable Format (ELF), a w drugim poleceniu (z flagą – z execstack) flagi RWE są ustawione w oznaczeniach ELF. Flagi oznaczają read (R), write (W) i execute (E). Po włączeniu NX exploit z shellcodem używanym wcześniej nie zadziałałby. Możemy jednak użyć wielu technik, aby ominąć tę ochronę. W tym przypadku ominiemy NX za pomocą return-oriented programming (ROP). ROP jest następcą techniki return-to-libc. Opiera się na kontrolowaniu przepływu programu poprzez wykonywanie fragmentów kodu znalezionych w pamięci, znanych jako gadgets. Gadgets zwykle kończą się instrukcją RET, ale w niektórych sytuacjach gadżety kończące się na JMP lub CALL mogą być również przydatne. Aby skutecznie wykorzystać podatny program, musimy nadpisać RIP adresem funkcji system() biblioteki glibc i przekazać /bin/sh jako argument. Przekazywanie argumentów do funkcji w plikach binarnych 64-bitowych różni się od przekazywania ich w trybie 32-bitowym, gdzie jeśli kontrolujesz stos, kontrolujesz również wywołania funkcji i argumenty. W plikach binarnych 64-bitowych argumenty są przekazywane w rejestrach w kolejności RDI, RSI, RDX, RCX, R8, R9, gdzie RDI jest pierwszym argumentem, RSI drugim itd. Zamiast ręcznie wyszukiwać gadżety, dokończmy pisanie naszego exploita za pomocą Pwntools, aby uprościć proces znajdowania potrzebnych gadżetów i budowania łańcucha ROP. Uruchom gdb, a następnie wykonaj break za pomocą CTRL-C:

Wyświetlmy adresy bazowe libc i kontynuujmy wykonywanie:

Wprowadźmy następujące zmiany do naszego exploita:

  1. Załaduj libc, używając adresu bazowego, który otrzymaliśmy z wyjścia vmmap libc (0x00007ffff7def000).
  2. Użyj narzędzia Pwntools ROP, aby zbudować nasz systemowy łańcuch ROP („/bin/sh”):

Teraz uruchom podatny program bez gdb:

Uruchom exploit w nowym oknie:

Poczekaj chwilę! Mamy powłokę, ale nie możemy jej kontrolować! Nie można wykonywać poleceń z naszego okna exploita, ale możemy wykonywać polecenia w oknie, w którym działa podatny serwer:

Dzieje się tak, ponieważ powłoka wchodzi w interakcję z deskryptorami plików 0, 1 i 2 dla standardowego wejścia (STDIN), standardowego wyjścia (STDOUT) i standardowego błędu (STDERR), ale gniazdo używa deskryptora pliku 3, a accept używa deskryptora pliku 4. Aby rozwiązać ten problem, zmodyfikujemy nasz łańcuch ROP, aby wywołać funkcję dup2() przed wywołaniem system(“/bin/sh”), jak pokazano poniżej. Spowoduje to zduplikowanie deskryptora pliku accept do STDIN, STDOUT i STDERR.

Uruchommy ponownie nasz exploit i sprawdźmy, czy działa

Zadziałało! Udało nam się ominąć ochronę stosu NX, używając prostego łańcucha ROP. Warto wspomnieć, że istnieją inne sposoby na ominięcie NX; na przykład można wywołać mprotect, aby wyłączyć NX w kontrolowanej lokalizacji pamięci, lub można użyć wywołania systemowego sigreturn, aby wypchnąć nowy kontrolowany kontekst z wyłączonym NX.

Nadpisywanie RIP

https://chacker.pl/

Wcześniej skupiliśmy się na eksploatacji 32-bitowych plików binarnych, ale w tym rozdziale skupiamy się na eksploatacji 64-bitowych plików binarnych. Pierwszą różnicą, którą możesz zauważyć, jest to, że nazwy rejestrów zaczynają się od R. Aby wykorzystać lukę w zabezpieczeniach związaną z przepełnieniem bufora, musimy nadpisać RIP. Podczas uruchamiania gdb otwórz nowe okno i połącz się z podatnym serwerem TCP, a następnie wyślij 200 bajtów za pomocą polecenia wzorca cyklicznego Pwntools:

UWAGA: Jeśli polecenie cykliczne nie zostanie znalezione, zainstaluj Pwntools za pomocą sudo, postępując zgodnie z przewodnikiem instalacji.

W oknie, w którym działa gdb, powinieneś zobaczyć naruszenie segmentacji. Użyjmy wbudowanego polecenia wyszukiwania wzorców GEF, aby zobaczyć, ile bajtów należy zapisać przed nadpisaniem RIP:

UWAGA: Po awarii programu pamiętaj, aby uruchomić killall -9 vuln po wyjściu z gdb, a następnie ponownie uruchomić gdb z tymi samymi parametrami.

Zacznijmy pisać nasz exploit z wiedzą, którą mamy do tej pory:

Zapisz i uruchom skrypt Pythona, a w oknie gdb powinieneś móc nadpisać RIP czterema literami B:

Konfigurowanie GDB

https://chacker.pl/

Będziemy używać wtyczki GEF. Możesz wykonać kroki instalacji opisane na jej stronie GitHub:

Po wykonaniu tej czynności otwórz gdb, aby potwierdzić, że skrypt GEF został pobrany i dodany do pliku ~/.gdbinit:

Ponieważ podatny program jest wielowątkowy, musimy polecić gdb debugowanie procesu potomnego po rozwidleniu, gdy łączy się nowy klient TCP. W tym celu należy użyć polecenia set follow-fork-mode child, jak pokazano poniżej: