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:

Konfiguracja podatnego programu i środowiska

https://chacker.pl/

Najpierw przeanalizujmy podatny program, którego będziemy używać w tym rozdziale. Program vuln.c jest dostarczany w folderze ~/GHHv6/ch11, a w każdym laboratorium będziemy go kompilować ponownie, umożliwiając różne techniki łagodzenia zagrożeń. Podatny program to prosty wielowątkowy serwer TCP, który żąda od użytkownika podania hasła z prostą podatnością na przepełnienie stosu w funkcji auth. Zacznijmy od skompilowania programu vuln.c tylko z ochroną przed niewykonywalnym stosem (NX):

Aby sprawdzić, czy usługa działa, uruchommy ją w tle i użyjmy netcata, aby się z nią połączyć:

Wyłączymy losową modulację układu przestrzeni adresowej (ASLR), aby skupić się na pominięciu NX, a następnie włączymy ją ponownie

Zaawansowane eksploity Linuksa

https://chacker.pl/

Teraz, gdy masz już podstawy , jesteś gotowy, aby studiować bardziej zaawansowane eksploity Linuksa. Dziedzina ta stale się rozwija, a hakerzy ciągle odkrywają nowe techniki, a programiści wdrażają środki zaradcze. Niezależnie od tego, jak podchodzisz do problemu, musisz wyjść poza podstawy. Powiedziawszy to, możemy zajść tylko tak daleko w tej książce — twoja podróż dopiero się zaczyna.

Podsumowanie

https://chacker.pl/

Podczas eksploracji podstaw exploitów Linuksa, zbadaliśmy kilka sposobów na pomyślne przepełnienie bufora w celu uzyskania podwyższonych uprawnień lub zdalnego dostępu. Wypełniając więcej miejsca niż bufor przydzielił, możemy nadpisać Extended Stack Pointer (ESP), Extended Base Pointer (EBP) i Extended Instruction Pointer (EIP), aby kontrolować elementy wykonywania kodu. Powodując przekierowanie wykonania do kodu powłoki, który dostarczamy, możemy przejąć wykonywanie tych plików binarnych, aby uzyskać dodatkowy dostęp. Upewnij się, że ćwiczysz i rozumiesz koncepcje wyjaśnione w tym rozdziale. W następnym rozdziale, obejmującym zaawansowane exploity Linuksa, skupimy się na bardziej zaawansowanych i nowoczesnych koncepcjach eksploatacji 64-bitowego Linuksa.

Budowanie exploita

https://chacker.pl/

Doświadczony badacz może łatwo napisać własny shellcode exploita od podstaw; my jednak po prostu skorzystamy z pakietu shellcraft Pwntools. Jednym z wielu przydatnych shellcode’ów, które zawiera, jest funkcja findpeersh. Znajdzie ona deskryptor pliku naszego bieżącego połączenia gniazda i uruchomi na nim wywołanie systemowe dup2, aby przekierować standardowe wejście i wyjście przed uruchomieniem powłoki:

Uruchommy ponownie gdb i uruchommy nasz exploit; powinniśmy odzyskać naszą powłokę:

Zadziałało! Po uruchomieniu exploita odzyskaliśmy powłokę na naszym własnym połączeniu. Teraz możemy wykonywać polecenia w naszej interaktywnej powłoce.

Określanie wektora ataku

https://chacker.pl/

Gdy już wiemy, gdzie EIP jest nadpisywane, musimy określić, na jaki adres na stosie musimy wskazać, aby wykonać ładunek. Aby to zrobić, modyfikujemy nasz kod, aby dodać sanki NOP. Daje nam to większy obszar do skoku, tak że jeśli wydarzy się coś drobnego i nasza lokalizacja trochę się zmieni, nadal wylądujemy gdzieś w naszych instrukcjach NOP. Dodając 32 NOP, powinniśmy nadpisać ESP i mieć dodatkową elastyczność dla adresów, do których można przeskoczyć. Pamiętaj, że żaden adres zawierający „\x00” nie zadziała, ponieważ jest to traktowane jako zakończenie ciągu.

Po ponownym uruchomieniu gdb i uruchomieniu nowego kodu exploita powinniśmy zobaczyć, że EIP jest nadpisany przez 0x42424242 (BBBB). Dzięki nowym zmianom powinniśmy móc sprawdzić nasz stos, aby zobaczyć, gdzie znajduje się NOP sled:

Widzimy, że EIP został nadpisany w (1). W (2) wartości są wypełnione naszymi instrukcjami NOP. Jeśli wskoczymy do środka naszego NOP sled w 0xffffd418 (3), powinno to nas doprowadzić bezpośrednio do naszego shellcode.