Kompilowanie i debugowanie programów Windows

https://chacker.pl/

Narzędzia programistyczne nie są dołączone do systemu Windows, ale na szczęście Visual Studio Community Edition pozwala kompilować programy do celów edukacyjnych. (Jeśli masz już licencjonowaną kopię, świetnie — możesz jej swobodnie użyć w tym rozdziale). Możesz bezpłatnie pobrać ten sam kompilator, który Microsoft dołącza do Visual Studio 2019 Community Edition. W tej sekcji pokażemy, jak skonfigurować podstawową stację roboczą do wykrywania luk w zabezpieczeniach systemu Windows. Można również użyć programu Visual Studio 2022.

Podstawowe wykorzystanie systemu Windows

https://chacker.pl/

Microsoft Windows jest zdecydowanie najczęściej używanym systemem operacyjnym, zarówno do użytku profesjonalnego, jak i osobistego. Procenty pokazane na tym rysunku często się zmieniają; jednak daje on dobry pogląd na ogólny udział w rynku systemów operacyjnych.  Windows 10 zdominował z 67 procentami rynku, podczas gdy Windows 7 powoli spada, ale nadal ma prawie 20 procent rynku. Jeśli chodzi o ogólne wykorzystanie i polowanie na exploity 0-day, powinno być stosunkowo jasne, które systemy operacyjne Windows są potencjalnie lukratywnymi celami. Windows 7 często stanowi łatwiejszy cel w porównaniu do Windows 10, ponieważ niektóre funkcje zabezpieczeń i łagodzenia zagrożeń są niedostępne dla Windows 7, takie jak Control Flow Guard (CFG). Przykłady godnych uwagi funkcji i łagodzenia zagrożeń podano później. Często zdarza się, że luka w zabezpieczeniach odkryta w jednej wersji systemu Windows wpływa na wiele innych wersji, starszych i nowszych. Prawdopodobnie w nadchodzących latach udział systemu Windows 11 w rynku znacznie wzrośnie.

Podsumowanie

https://chacker.pl/

Użyliśmy podatnego modułu jądra i różnych konfiguracji jądra, aby przejść przez wiele sposobów łagodzenia zagrożeń i kilka sposobów ich obejścia. Prosty exploit ret2usr został uruchomiony na niezabezpieczonym jądrze, aby zrozumieć podstawy eksploatacji jądra. Następnie zaczęliśmy dodawać funkcje łagodzenia zagrożeń Stack Canaries, SMEP, KPTI, SMAP i KASLR i przeszliśmy przez kilka technik ich obejścia. Te techniki eksploatacji jądra zapewniają przydatną bazę wiedzy, aby rozpocząć wykrywanie wektorów ataków jądra, odkrywanie błędów bezpieczeństwa i zrozumienie możliwych łańcuchów eksploatacji w celu uzyskania pełnej kontroli nad podatnym systemem. Techniki ochrony zmieniają się, a strategie ich pokonania ewoluują.

Pokonywanie losowego układu przestrzeni adresowej jądra (KASLR)

https://chacker.pl/

KASLR5 działa podobnie do ochrony przestrzeni użytkownika ASLR, losując układ adresów bazowych jądra za każdym razem, gdy system jest uruchamiany. Jeśli możemy uzyskać wyciek niezawodnego adresu pamięci, obejście tej ochrony byłoby trywialne. Ponieważ mamy dowolny warunek odczytu, oto kroki, które wykonamy, aby obliczyć bazę jądra:

1. Zmodyfikuj program leak.c, aby uruchomić getchar() przed wysłaniem ładunku. Da nam to czas na dołączenie GDB (lub uszkodzenie GDB, jeśli jest już dołączony) i potwierdzenie, czy adres jest niezawodny. Następnie ponownie skompiluj leak.c po dodaniu getchar(). Kod powinien wyglądać następująco:

  1. Wykonaj proces kilka razy, aby upewnić się, że adres, który próbujemy, zawsze wskazuje na tę samą instrukcję:

Teraz otwórz nowy terminal i pobierz instrukcje, do których wskazują te adresy, używając polecenia x/i GDB. Jeśli powtórzysz to kilka razy, zauważysz, że piąty adres, indeks 4 naszej tablicy wycieków, zawsze wskazuje na tę samą instrukcję:

Wiedząc, że nasz niezawodny adres znajduje się pod indeksem 4 naszej tablicy wycieków, kontynuujmy pracę z wyłączonym KASLR (run4.sh), aby uprościć obliczenia. Nasze kolejne kroki będą następujące:

1. Uruchom run4.sh, a następnie pobierz adres bazowy jądra, odczytując pierwszy wiersz /proc/kallsyms i odejmując piąty adres zwrócony przez plik binarny ./leak, aby uzyskać odległość między wyciekiem a bazą jądra:

Następnie wyjdź z QEMU i użyj Pythona, aby uzyskać odległość między wyciekiem a bazą jądra:

  1. Zmodyfikuj nasz kod źródłowy exploit4.c, aby utworzyć nową zmienną unsigned long, kernel_base, której wartością będzie leak[4] – 0x14c174. Kod powinien wyglądać następująco:

  1. Oblicz odległość każdego adresu statycznego z tymi względnymi do bazy jądra. Naprawmy gadżet pop rdi; ret;, a później możesz powtórzyć ten sam proces ze wszystkimi gadżetami. Po otwarciu QEMU z wyłączonym KASLR (run4.sh) i dołączeniu GDB, odejmij adres bazowy jądra (0xffffffff81000000) od adresu gadżetu pop rdi; ret; (0xffffffff811ad2ec):

Wprowadź zmianę w kodzie exploita, która powinna wyglądać następująco:

Gdy skończysz pobierać adresy względne, źródło Twojego exploita powinno wyglądać tak jak to w ~/GHHv6/ch12/shared/exploit5/exploit.c. Skompilujmy i uruchommy nasz nowy exploit:

Omijanie zapobiegania dostępowi w trybie nadzorcy (SMAP)

https://chacker.pl/

SMAP to funkcja bezpieczeństwa wprowadzona do jądra Linux w 2012 r. przez firmę Intel.3 Polega ona na uczynieniu stron przestrzeni użytkownika niedostępnymi, gdy proces znajduje się w przestrzeni jądra. Tę funkcję włącza się, ustawiając dwudziesty pierwszy bit rejestru CR4 na on

Ta funkcja znacznie komplikuje sprawy, gdy ładunek ROP znajduje się na stronach pamięci trybu użytkownika; jednak ponieważ wszystkie gadżety z naszego poprzedniego exploita znajdują się w przestrzeni jądra, SMAP nie powstrzyma nas przed eskalacją uprawnień! Potwierdźmy to, uruchamiając run4.sh (który włącza funkcję łagodzenia exploitów SMAP) i nasz poprzedni exploit (exploit3):

SMAP skomplikuje sprawy w bardziej ograniczonych sytuacjach, w których potrzebujemy mmap do zbudowania fałszywego stosu w przestrzeni adresowej trybu użytkownika, a następnie użycia gadżetu stack pivot do przygotowania bardziej złożonego łańcucha ROP. Najczęstszą metodą osiągnięcia obejścia SMEP i SMAP było nadużywanie funkcji native_write_cr4 do ustawienia bitów 20 i 21 na wyłączone z rejestru CR4. Jednak począwszy od wersji jądra 5.3,4 CR4 jest przypinane podczas rozruchu, a funkcja native_write_cr4 teraz ponownie ustawia bity SMAP i SMEP, jeśli rejestr CR4 został zmodyfikowany. Nie należy tego uważać za funkcję łagodzącą ROP (taką jak Control Flow Integrity), ale raczej za usunięcie szybkiego, jednorazowego zwycięstwa dla autorów exploitów jądra. Istnieje duże prawdopodobieństwo, że systemy produkcyjne mają wiele modułów jądra i sterowników urządzeń zapewniających wiele przydatnych gadżetów do osiągnięcia tego samego celu. Tak jest w przypadku funkcji ghh_seek wbudowanej w nasze jądro jako przykładu. Jeśli rozmontujesz funkcję ghh_seek, powinieneś zobaczyć kod przeznaczony do innych celów:

Jednakże 3-bajtowa, niespójna interpretacja tych kodów operacji skutkuje bardzo użytecznym gadżetem do modyfikacji rejestru CR4:

Dopóki możemy wykorzystywać i łączyć istniejący kod w gadżetach ROP wpływających na rejestr CR4, ominięcie funkcji łagodzenia nadużyć SMEP i SMAP pozostanie możliwe. Mimo że ominęliśmy już SMAP w naszym poprzednim exploicie, nie chcemy przegapić okazji, aby pokazać, jak ominąć SMAP, modyfikując rejestr CR4 za pomocą gadżetu, który znaleźliśmy dzięki niezrównanej interpretacji tych kodów operacji. Ten nowy exploit będzie o wiele bardziej złożony, ponieważ zbudujemy fałszywy stos w przestrzeni adresowej użytkownika za pomocą mmap, a następnie użyjemy gadżetu stack pivot, aby wykonać łańcuch ROP, który zbudowaliśmy, aby eskalować uprawnienia. Wprowadzimy następujące zmiany do naszego exploita:

W punkcie (1) zaczynamy od przypisania rdi wartości 0x6B0, co jest równoważne z ustawieniem bitów rc4 na 20 i 21 (0000000000011010110000). W punkcie (2) gadżet modyfikuje rc4, a my dodajemy dwa rets, aby upewnić się, że stos pozostanie wyrównany. W punkcie (3) wskakujemy do rax na adres naszego fałszywego stosu, 0xc0d30000, ponieważ aby wskoczyć do naszego fałszywego stosu, użyliśmy gadżetu stack pivot mov esp, eax. Zanim nasz ładunek zostanie wysłany, tworzymy nasz fałszywy stos mmap (5) o długości 0x2000 bajtów, zaczynając od przesunięcia c0d2f000. Powodem tego jest zapewnienie wystarczającej ilości miejsca, jeśli stos będzie musiał się rozrosnąć. Skompilujmy i wykonajmy nasz nowy exploit:

Doskonale! Potwierdziliśmy, że możliwe jest nadpisanie cr4 za pomocą ROP

Omijanie ochrony trybu wykonywania nadzorcy (SMEP) i izolacji tablicy stron jądra (KPTI)

https://chacker.pl/

Teraz podniesiemy poprzeczkę i ominiemy szeroko stosowane funkcje łagodzenia eksploatowania jądra SMEP i KPTI. Funkcja łagodzenia eksploatowania SMEP korzysta z nowoczesnego mechanizmu architektury procesora, który zapobiega pobieraniu kodu znajdującego się w przestrzeni adresowej pamięci trybu użytkownika podczas pracy na wysokich poziomach uprawnień (Ring 0). Gdy SMEP jest włączony, skierowanie rejestru RIP na kod znajdujący się w przestrzeni adresowej pamięci trybu użytkownika spowoduje „oops jądra” i przerwanie zadania powodującego problem. Tę funkcję włącza się, ustawiając dwudziesty bit rejestru CR4 na on

Można sprawdzić, czy funkcja SMEP jest włączona w systemie docelowym, odczytując plik /proc/cpuinfo:

KPTI (Kernel Page-Table Isolation) to funkcja bezpieczeństwa, która zapewnia lepszą izolację między przestrzeniami pamięci w trybie użytkownika i trybie jądra, w celu obrony przed obejściami KASLR i innymi lukami w zabezpieczeniach wycieków pamięci, takimi jak Meltdown. Minimalny zestaw pamięci jądra jest obecny na izolowanych stronach pamięci w trybie użytkownika, aby uniknąć wycieków pamięci jądra niezbędnych w typowych łańcuchach eksploatacji jądra. Jądro Linux korzysta z KPTI od wersji 4.15. Możesz potwierdzić, czy funkcja KPTI jest włączona w systemie docelowym, uruchamiając komunikaty debugowania jądra:

KPTI (Kernel Page-Table Isolation) to funkcja bezpieczeństwa, która zapewnia lepszą izolację między przestrzeniami pamięci w trybie użytkownika i trybie jądra, w celu obrony przed obejściami KASLR i innymi lukami w zabezpieczeniach wycieków pamięci, takimi jak Meltdown. Minimalny zestaw pamięci jądra jest obecny na izolowanych stronach pamięci w trybie użytkownika, aby uniknąć wycieków pamięci jądra niezbędnych w typowych łańcuchach eksploatacji jądra. Jądro Linux korzysta z KPTI od wersji 4.15. Możesz potwierdzić, czy funkcja KPTI jest włączona w systemie docelowym, uruchamiając komunikaty debugowania jądra:

  1. Musimy przekazać wartość zwróconą przez wywołanie prepare_kernel_cred(0) do funkcji commit_creds, co oznacza, że ​​musimy znaleźć sposób na skopiowanie wartości z rax do rdi. Oto pierwszy interesujący gadżet, jaki znajdziemy:

Problem polega na tym, że najpierw musimy się upewnić, że rdi i rdx mają tę samą wartość, aby uniknąć warunkowego skoku w jne 0x3b7945. Aby rozwiązać tę przeszkodę, znaleźliśmy następujące dwa gadżety:

Problem polega na tym, że najpierw musimy się upewnić, że rdi i rdx mają tę samą wartość, aby uniknąć warunkowego skoku w jne 0x3b7945. Aby rozwiązać tę przeszkodę, znaleźliśmy następujące dwa gadżety:

Zmodyfikujmy nasze skrypty, aby dodać te gadżety, a następnie skompilujmy i uruchommy nasz exploit, który powinien wyglądać tak:

Mamy to! Teraz włączmy SMAP i zobaczmy, jak ta redukcja exploitów może na nas wpłynąć.

Pokonywanie Stack Canaries

https://chacker.pl/

Pamięć stosu jądra można chronić przed uszkodzeniem pamięci i atakami przepełnienia w taki sam sposób, jak jej odpowiednik w przestrzeni użytkownika, za pomocą Kernel Stack Canaries. Ta funkcja łagodzenia zagrożeń w czasie kompilacji działa jak Stack Canaries w przestrzeni użytkownika, o którym dowiedzieliśmy się i który wykorzystaliśmy w poprzednim rozdziale. Ponownie skompilowaliśmy niestandardowe jądro z włączoną funkcją CONFIG_STACKPROTECTOR, aby używać Stack Canaries w tym i kolejnych laboratoriach. Aby zobaczyć to w akcji, wykonaj run2.sh i spróbuj nadpisać rejestr RIP podczas dołączania GDB do systemu docelowego. Najpierw otwórz okno terminala w folderze ~/GHHv6/ch12 i wykonaj run2.sh, ale nie uruchamiaj jeszcze exploita:

$  ./run2.sh

W nowym oknie terminala dołącz GDB, a następnie ustaw dwa punkty przerwania, aby zobaczyć, kiedy canary zostanie przypisany i kiedy zostanie sprawdzony przed powrotem z podatnej funkcji. Następnie wygenerujemy wzorzec, który pomoże nam zidentyfikować, gdzie w naszym ładunku powinniśmy umieścić canary, aby został naprawiony po nadpisaniu stosu. Na koniec kontynuujemy wykonywanie. Oto kod:

Teraz skopiuj ten cykliczny wzór z terminala QEMU i zapisz go w interfejsie modułu:

W momencie trafienia pierwszego punktu przerwania, kanarek będzie już skopiowany do rbp-0x10. Sprawdźmy jego wartość i przejdźmy do drugiego punktu przerwania:

W tym momencie zapisany kanarek (rbp-0x10) został skopiowany do rejestru rdx i zostanie odjęty od oryginalnego kanarka. Jeśli wynik nie jest zerem, zostanie wykonane __stack_chk_fail zamiast powrotu. Zobaczmy zawartość rdx i użyjmy narzędzia przesunięcia wzorca, aby zidentyfikować, gdzie kanarek musi zostać umieszczony:

Jeśli będziemy kontynuować wykonywanie, w oknie QEMU pojawi się panika jądra:

Naszym ostatnim krokiem jest wykorzystanie luki w zabezpieczeniach arbitralnego odczytu w celu wycieku adresów pamięci i zidentyfikowanie, czy nasz kanarek jest wyciekany i w którym miejscu. W folderze ~/GHHv6/ch12/shared znajduje się mały program C, który otworzy interfejs /proc/ghh, odczyta 40 bajtów do tablicy unsigned long i zapisze nasz ładunek, aby nadpisać RIP. Najpierw skompilujmy ten program i uruchommy run2.sh:

Podłącz GDB w nowym terminalu, ustaw punkt przerwania po skopiowaniu kanarka do rejestru rax (ghh_write+25) i kontynuuj wykonywanie:

Podłącz GDB w nowym terminalu, ustaw punkt przerwania po skopiowaniu kanarka do rejestru rax (ghh_write+25) i kontynuuj wykonywanie:

Podłącz GDB w nowym terminalu, ustaw punkt przerwania po skopiowaniu kanarka do rejestru rax (ghh_write+25) i kontynuuj wykonywanie:

Podłącz GDB w nowym terminalu, ustaw punkt przerwania po skopiowaniu kanarka do rejestru rax (ghh_write+25) i kontynuuj wykonywanie:

Teraz, gdy udało nam się ominąć ochronę Stack Canary, włączmy ochronę SMEP i KPTI i zobaczmy, jak możemy ją obejść.

ret2usr

https://chacker.pl

Return-to-user to najłatwiejsza technika eksploatacji jądra, porównywalna z podstawowymi technikami opisanymi wcześniej, które pozwoliły nam wykonywać kody powłoki z włączonym NX i wyłączonym ASLR. Głównym celem ret2usr jest nadpisanie rejestru RIP i przejęcie przepływu wykonywania w przestrzeni jądra w celu podniesienia uprawnień bieżącego procesu za pomocą funkcji jądra:

commit_creds(prepare_kernel_cred(0)). Działa to w przypadku bieżącego procesu przestrzeni użytkownika, ponieważ commit_creds instaluje nowe poświadczenia, które mają zostać przypisane do bieżącego zadania. Teraz, gdy mamy nadpisanie RIP, nasza strategia jest następująca:

  1. Znajdź adresy dla prepare_kernel_cred i commit_creds w /proc/kallsyms. Te adresy pozostaną takie same po ponownym uruchomieniu, ponieważ KASLR jest wyłączony.
  2. Zamiast wykonywać kod powłoki, napisz funkcję z wbudowanym asemblerem, która wykona commit_creds(prepare_kernel_cred(0)).
  3. Wróć do przestrzeni użytkownika, używając kodów operacji swapgs i iretq.

Napiszmy teraz, skompilujmy i wykonajmy nasz exploit. Dostarczymy i udokumentujemy pełne źródło, ale kolejne sekcje będą zawierać tylko niezbędne poprawki kodu wymagane do ominięcia każdej techniki łagodzenia exploitów. Pełne źródło tego laboratorium można znaleźć w następującej ścieżce: ~/GHHv6/ch12/shared/exploit1/exploit.c.

W (1) nasz exploit wykonuje kod, aby podnieść uprawnienia naszego zadania w trybie jądra. Po wykonaniu tej czynności musimy przełączyć się z powrotem do przestrzeni użytkownika i wykonać system(“/bin/sh”)(2). Pierwszym problemem, z jakim się mierzymy, jest to, że aby powrócić do przestrzeni użytkownika, instrukcja Interrupt Return (iretq) musi mieć poprawne wartości w rejestrach CS, RFLAGS, SP, SS i RIP, a rejestry te są modyfikowane w obu trybach. Rozwiązaniem jest użycie tego inline assembly do zapisania rejestrów przed przejściem do trybu jądra i przywrócenie ich ze stosu przed wywołaniem instrukcji iretq. W (3) nadpisujemy RIP adresem funkcji escalate_privileges, która zawiera kod niezbędny do wykonania commit_creds(prepare_kernel_cred(0)), używamy instrukcji swapgs do zamiany rejestru GS ​​na wartość w jednym z rejestrów specyficznych dla modelu (MSR), przywracamy rejestry CS, RFLAGS, SP, SS i na koniec kierujemy RIP do funkcji powłoki przed wywołaniem iretq. Zanim przejdziemy dalej, uzyskajmy adres funkcji prepare_kernel_cred (4) i commit_creds (5) w naszym systemie docelowym i zmodyfikujmy skrypt tymi adresami:

Po zmodyfikowaniu linii (4) i (5) adresem prepare_kernel_cred i commit_creds, utworzymy teraz naszą tablicę unsigned long (6) i zainicjujemy ją zerami. Pamiętaj, że odkryliśmy, że RIP może zostać nadpisany na bajcie 24, a ponieważ każdy element naszej tablicy unsigned long ma długość 8 bajtów, będziemy musieli zapisać adres funkcji escalate_privileges (3) na trzecim (24 / 8) elemencie naszej tablicy payload. Na koniec otwieramy /proc/ghh i zapisujemy nasz payload (8). Teraz, gdy wszystko jest gotowe, skompilujmy nasz exploit. Jego uruchomienie powinno spowodować wykonanie powłoki /bin/sh z podwyższonymi uprawnieniami użytkownika root:

Świetnie! Teraz włączmy Stack Canaries, abyśmy mogli zrozumieć, jak to działa i dowiedzieć się, jak to ominąć w tym scenariuszu

Nadpisywanie RIP

https://chacker.pl/

Spróbujmy spowodować awarię modułu jądra poprzez nadpisanie RIP. Aby to zrobić, upewnij się, że oba terminale obsługujące QEMU (run1.sh) i GDB są nadal otwarte. Następnie w oknie GDB wykonaj następujące polecenia:

Teraz skopiuj ten wzór i wyślij go do modułu za pomocą polecenia echo:

Ponieważ mamy panikę jądra, naciśnij CTRL-C, aby wyjść z QEMU. Teraz ponownie uruchom skrypt run1.sh i ponownie podłącz GDB do serwera GDB, używając target remote :1234. Skopiujmy wartość RIP i zobaczmy, ile bajtów musimy zapisać, aby nadpisać RIP:

Posiadając tę ​​wiedzę, możemy zacząć wykorzystywać ten moduł do eskalacji uprawnień.

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: