Jądro systemu Windows

https://chacker.pl

Ponieważ jądro systemu Windows jest tak złożone, będziemy mogli omówić tylko podstawy jądra i pewne informacje, które będą potrzebne do zrozumienia exploita w dalszej części rozdziału. Istnieje wiele bardziej kompleksowych zasobów dotyczących jądra systemu Windows i wewnętrznych mechanizmów jądra, w tym Windows Internals, 7th Edition (Parts 1 and 2), Windows Kernel Programming autorstwa Pavla Yosifovicha oraz różne posty na blogach rozsiane po Internecie. Cennymi materiałami źródłowymi są również Windows Software Development Kit (SDK), Windows Driver Kit (WDK) oraz podręczniki procesorów Intel/AMD/ARM. Ponadto będziemy przeglądać koncepcje i wykorzystywać 64-bitowy system Windows (32-bitowy system Windows jest w niektórych przypadkach nieco inny, ale z czasem staje się coraz mniej istotny). Jądro jest implementowane jako warstwa jądra, warstwa wykonawcza i sterowniki. Warstwy jądra i wykonawcze są implementowane w obrazie jądra, ntoskrnl.exe. Warstwa jądra zawiera kod do planowania wątków, blokowania, synchronizacji i podstawowego zarządzania obiektami jądra. Warstwa wykonawcza zawiera kod do egzekwowania zabezpieczeń, zarządzania obiektami, zarządzania pamięcią, rejestrowania i instrumentacji zarządzania systemem Windows, między innymi. Większość sterowników jądra to pliki .sys, ale kilka komponentów jądra to biblioteki DLL, takie jak hal.dll i ci.dll. Plik .sys to przenośny plik wykonywalny, podobnie jak plik EXE lub DLL. Poniższy diagram systemu pokazuje ogólny układ architektury systemu Windows.

Zaczynając od dołu, istnieją aplikacje i usługi w trybie użytkownika, które są uruchamiane na podsystemie Windows (kernel32.dll, user32.dll itd.), są tworzone bezpośrednio dla natywnego API (ntdll.dll i win32u.dll) lub są uruchamiane jako procesy minimalne/pico i komunikują się bezpośrednio z jądrem za pośrednictwem System Service Dispatcher. System Service Dispatcher (znany również jako system call handler) pobiera żądania z trybu użytkownika i wysyła je do jądra. Przechodząc przez linię, powinieneś zauważyć, że adresy idą od niższych w trybie użytkownika do znacznie wyższych w trybie jądra. Pamięć jest segmentowana w ten sposób z powodów historycznych i specyficznych dla procesora. Tak się składa, że ​​istnieją dwie odrębne kanoniczne przestrzenie pamięci z dużą niekanoniczną luką pośrodku, aby podzielić pamięć należącą do przestrzeni jądra (pierścień 0) i przestrzeni użytkownika (pierścień 3). Po stronie trybu jądra znajduje się warstwa jądra, warstwa wykonawcza i sterowniki, jak wspomniano wcześniej. Niektóre sterowniki, takie jak sterowniki graficzne, mogą komunikować się bezpośrednio ze sprzętem, podczas gdy inne będą używać warstwy abstrakcji sprzętowej (HAL). HAL to niezależna od architektury i platformy biblioteka do interakcji ze sprzętem. W ostatnich wersjach systemu Windows 10 (20H1+) HAL jest implementowany wewnątrz obrazu jądra, a hal.dll to po prostu przekazująca biblioteka DLL, która nadal istnieje ze względu na kompatybilność. Nie martw się, jeśli jest to dużo do przyswojenia, ponieważ jest to tylko przegląd komponentów systemu Windows.

Wykorzystanie jądra systemu Windows

https://chacker.pl/

Jądro systemu Windows i pisanie exploitów jądra to ogromne tematy; nauka wewnętrznych mechanizmów jądra i właściwego stosowania tej wiedzy w celu wykorzystania luk w zabezpieczeniach zajmuje lata. Luki te można znaleźć nie tylko w samym jądrze, ale także w rozszerzeniach znanych jako sterowniki. Tu przyjrzymy się, jak skonfigurować debugowanie jądra między dwoma systemami Windows, przeprowadzić inżynierię wsteczną sterownika jądra, a następnie wykorzystać ten sterownik jądra w celu podniesienia naszych uprawnień.

 

Podsumowanie

https://chacker.pl/

Techniki pokazane tu powinny pomóc Ci w opanowaniu podstaw eksploatacji systemu Windows za pomocą przepełnień stosu, a także omijania prostych środków zapobiegawczych, takich jak SafeSEH i DEP. Jak już widziałeś, w systemach operacyjnych Microsoft istnieją różne zabezpieczenia, w zależności od wybranych opcji kompilatora i innych czynników. Każda ochrona wiąże się z nowymi wyzwaniami dla atakujących, co skutkuje grą w kotka i myszkę. Zabezpieczenia, takie jak te oferowane przez Exploit Guard, mogą pomóc zatrzymać gotowe exploity, ale jak już wspomniano, doświadczony atakujący może dostosować exploit, aby ominąć wiele z tych kontroli.

Budowanie łańcucha ROP

https://chacker.pl/

Korzystając z wtyczki Mona PyCommand z corelanc0d3r, możemy znaleźć listę zalecanych gadżetów dla danego modułu (-cp nonull jest używane, aby upewnić się, że żadne bajty zerowe nie są używane jako część łańcuchów ROP):

Wykonanie tego polecenia powoduje utworzenie kilku plików, w tym następujących:

  • Plik rop_chains.txt zawierający ukończone lub częściowo ukończone łańcuchy ROP, których można użyć do obejścia DEP, używając funkcji takich jak VirtualProtect() i VirtualAlloc(). Te łańcuchy mogą zaoszczędzić Ci niezliczonych godzin ręcznego przeglądania i budowania łańcucha ROP.
  • Plik rop.txt zawierający dużą liczbę gadżetów, które mogą być przydatne jako część Twojego exploita. Często zdarza się, że wygenerowane łańcuchy ROP działają od razu po wyjęciu z pudełka. Często będziesz szukać gadżetów, aby zrekompensować ograniczenia, a plik rop.txt może Ci w tym pomóc.
  • Plik o nazwie stackpivot.txt, który będzie zawierał tylko instrukcje stack pivot.
  • W zależności od używanej wersji Mona, mogą zostać wygenerowane inne pliki, takie jak rop_suggestions.txt i pliki XML zawierające ukończone łańcuchy ROP. Ponadto wygenerowane łańcuchy ROP mogą się różnić w zależności od używanej wersji Mona i wybranych opcji. Więcej informacji o funkcji i jej parametrach można znaleźć na stronie Mona usage. Polecenie rop będzie działać przez chwilę i wygeneruje pliki wyjściowe do dowolnego folderu wybranego w Mona za pomocą polecenia !mona config -set workingfolder <PATH>/%p. Zawartość bardzo szczegółowego pliku rop.txt będzie zawierać wpisy takie jak ten:

Na podstawie tego wyniku możesz połączyć ze sobą gadżety, aby wykonać zadanie, budując argumenty dla VirtualProtect() i wywołując je. Nie jest to takie proste, jak się wydaje; musisz pracować z tym, co masz do dyspozycji. Być może będziesz musiał wykazać się kreatywnością. Poniższy kod, uruchomiony względem programu ProSSHD, demonstruje działający łańcuch ROP, który wywołuje VirtualProtect() w celu zmodyfikowania uprawnień, w których znajduje się kod powłoki na stosie, tak aby stał się wykonywalny. DEP został ponownie włączony dla wsshd.exe. Skrypt został nazwany prosshd_dep.py.

Chociaż na początku przestrzeganie tego programu może wydawać się trudne, gdy uświadomisz sobie, że jest to po prostu seria wskaźników do obszarów połączonych modułów, które zawierają cenne instrukcje, po których następuje instrukcja RETN, która po prostu zwraca następny gadżet, wtedy możesz zobaczyć metodę szaleństwa. Istnieje kilka gadżetów do ładowania wartości rejestrów (przygotowując się do wywołania VirtualProtect). Istnieją inne gadżety do kompensowania różnych problemów, aby zapewnić, że prawidłowe argumenty są ładowane do odpowiednich rejestrów. Podczas korzystania z łańcucha ROP wygenerowanego przez Mona, ten autor ustalił, że po prawidłowym wyrównaniu wywołanie VirtualProtect() jest pomyślnie wykonywane; jednak po powrocie z SYSEXIT z Ring0, wracamy zbyt daleko w dół stosu i do środka naszego kodu powłoki. Aby to zrekompensować, ręcznie dodano kilka gadżetów, aby upewnić się, że EBP wskazuje na nasz NOP sled. Można by poświęcić czas na precyzyjne wyrównanie rzeczy, aby tak duże wypełnienie nie było konieczne; jednak ten czas można również poświęcić na inne zadania. Wygenerowany łańcuch ROP może wyglądać zupełnie inaczej niż ten pokazany w tym przykładzie.

W poniższym kodzie najpierw umieszczamy wartość 0xfffffcdf w EAX. Gdy zostanie ona dodana do adresu w EBP, który wskazuje na nasz kod powłoki, zostanie ona przeniesiona o 2^32 i będzie wskazywała na nasz sled NOP.

Aby to obliczyć, wystarczy wykonać podstawowe obliczenia matematyczne, aby upewnić się, że EBP wskazuje na lokalizację wewnątrz sanek NOP. Ostatnia instrukcja wykonuje to dodawanie. Aby zademonstrować stan przed i po, spójrz na poniższe obrazy.

Na tym pierwszym obrazku program jest wstrzymany przed dostosowaniem do EBP. Jak widać, EBP wskazuje na środek shellcode. Następny obraz pokazuje adres, na który wskazuje EBP po dokonaniu dostosowania.

Jak widać, EBP wskazuje na nasz NOP sled, tuż przed shellcode. Shellcode używany w exploicie, wygenerowany za pomocą Metasploit, wiąże shell z portem TCP 31337. Gdy exploitowi pozwolono kontynuować, shellcode jest pomyślnie wykonywany, a port jest otwarty, jak pokazano tutaj z monitem zapory.

Gadżety

https://chacker.pl/

Małe sekcje kodu wymienione w poprzedniej sekcji to to, co nazywamy gadżetami. Słowo kod jest tutaj używane, ponieważ nie musi to być instrukcja używana przez program lub moduł; możesz przejść do adresu w środku zamierzonej instrukcji lub gdziekolwiek indziej w pamięci wykonywalnej, pod warunkiem, że wykonuje ona zadanie, które chcesz wykonać i zwraca wykonanie do następnego gadżetu wskazywanego przez wskaźnik stosu. Poniższy przykład pokazuje zamierzoną instrukcję użytą wewnątrz ntdll.dll pod adresem pamięci 0x778773E2:

Zobacz co się stanie, gdy przejdziemy z 0x778773E2 do 0x778773E3:

Sekwencja kodu nadal kończy się return, ale instrukcja nad return została zmieniona. Jeśli ten kod jest dla nas zrozumiały, możemy go użyć jako gadżetu. Ponieważ następny adres wskazywany przez ESP lub RSP na stosie jest innym gadżetem ROP, polecenie return wywołuje tę następną sekwencję kodu. Ponownie, ta metoda programowania jest podobna do ret2libc i jest w rzeczywistości jej następcą, jak omówiono w rozdziale 10. W ret2libc nadpisujemy wskaźnik return adresem początku funkcji, takiej jak system(). W ROP, gdy tylko uzyskamy kontrolę nad wskaźnikiem instrukcji, wskazujemy mu lokalizację wskaźników do naszych gadżetów i wracamy przez łańcuch. Niektóre gadżety zawierają niechciane instrukcje, które musimy zrekompensować, takie jak POP lub inna instrukcja, która mogłaby negatywnie zmodyfikować stos lub rejestr. Spójrz na deasemblację:

Sekwencja kodu nadal kończy się return, ale instrukcja nad return została zmieniona. Jeśli ten kod jest dla nas zrozumiały, możemy go użyć jako gadżetu. Ponieważ następny adres wskazywany przez ESP lub RSP na stosie jest innym gadżetem ROP, polecenie return wywołuje tę następną sekwencję kodu. Ponownie, ta metoda programowania jest podobna do ret2libc i jest w rzeczywistości jej następcą, jak omówiono w rozdziale 10. W ret2libc nadpisujemy wskaźnik return adresem początku funkcji, takiej jak system(). W ROP, gdy tylko uzyskamy kontrolę nad wskaźnikiem instrukcji, wskazujemy mu lokalizację wskaźników do naszych gadżetów i wracamy przez łańcuch. Niektóre gadżety zawierają niechciane instrukcje, które musimy zrekompensować, takie jak POP lub inna instrukcja, która mogłaby negatywnie zmodyfikować stos lub rejestr. Spójrz na deasemblację:

W tym przykładzie po prostu zmieniliśmy POP EDI na POP EAX. Jeśli naszym celem jest wyzerowanie rejestru EAX, niechciany POP EAX sprawi, że ten gadżet stanie się bezużyteczny. Istnieją inne typy niechcianych instrukcji, z których niektóre mogą być dość trudne do rozwiązania, takie jak dostęp do adresu pamięci, który nie jest mapowany.

 

Programowanie zorientowane na zwrot

https://chacker.pl/

Co więc możemy zrobić, jeśli nie możemy wykonać kodu na stosie? Wykonać go gdzie indziej? Ale gdzie? W istniejących powiązanych modułach jest wiele małych sekwencji kodu, które kończą się instrukcją RETN. Te sekwencje kodu mogą lub nie być wykonywane przez program. Wyobraźmy sobie, że mamy kontrolę nad procesem za pośrednictwem przepełnienia bufora. Jeśli ułożymy serię wskaźników do tych pożądanych sekwencji kodu, na które wskazuje wskaźnik stosu, i wrócimy do każdego z nich po kolei, możemy zachować kontrolę nad procesem i sprawić, by wykonywał nasze polecenia. Nazywa się to programowaniem zorientowanym na zwrot i zostało zapoczątkowane przez Hovava Shachama. Jest to następca technik takich jak ret2libc. Możemy użyć tych gadżetów, aby skonfigurować wywołanie funkcji w celu zmiany uprawnień w pamięci, w której znajduje się nasz kod powłoki, co pozwala nam obejść DEP.

Zapobieganie wykonywaniu danych

https://chacker.pl/

Zapobieganie wykonywaniu danych (DEP) ma na celu zapobieganie wykonywaniu kodu umieszczonego w stercie, stosie i innych sekcjach pamięci, w których wykonywanie kodu nie powinno być dozwolone. Przed rokiem 2004 sprzęt nie obsługiwał tego. W roku 2004 AMD wprowadziło bit NX w swoim procesorze. Po raz pierwszy pozwoliło to sprzętowi rozpoznać stronę pamięci jako wykonywalną lub nie i odpowiednio zareagować. Niedługo potem Intel wprowadził funkcję XD, która robiła to samo. System Windows mógł używać bitu NX/XD od czasu XP SP2 i jest on uważany za dojrzałą i skuteczną kontrolę. Aplikacje można łączyć z flagą /NXCOMPAT, która włączy sprzętowy DEP dla tej aplikacji, w zależności od wersji systemu operacyjnego i obsługi różnych krytycznych funkcji związanych z uprawnieniami i zabezpieczeniami pamięci.

Omijanie SafeSEH

https://chacker.pl/

Jak już wcześniej wspomniano, gdy wyzwolony zostanie wyjątek, system operacyjny umieszcza funkcję except_handler na stosie i wywołuje ją.

Po pierwsze, zauważ, że gdy wyjątek jest obsługiwany, wskaźnik _EstablisherFrame jest przechowywany w ESP+8. Wskaźnik _EstablisherFrame wskazuje w rzeczywistości na szczyt naszego łańcucha obsługi wyjątków. Dlatego jeśli zmienimy wskaźnik _next naszego nadpisanego rekordu wyjątku na instrukcję asemblera EB 06 90 90 (która przeskoczy o 6 bajtów do przodu) i zmienimy wskaźnik _handler gdzieś w udostępnionym pliku DLL/EXE, w sekwencji POP/POP/RETN, możemy przekierować kontrolę programu do naszego obszaru kodu atakującego na stosie. Gdy wyjątek jest obsługiwany przez system operacyjny, zostanie wywołany program obsługi, który rzeczywiście zdejmie 8 bajtów ze stosu i wykona instrukcję wskazywaną na ESP+8 (która jest naszym poleceniem JMP 06), a kontrola zostanie przekierowana do obszaru kodu atakującego na stosie, gdzie może zostać umieszczony kod powłoki.

UWAGA: W tym przypadku musieliśmy przeskoczyć tylko o 6 bajtów do przodu, aby wyczyścić następujący adres i 2 bajty instrukcji skoku. Czasami, ze względu na ograniczenia przestrzeni, może być potrzebny skok wstecz na stosie; w takim przypadku, można użyć liczby ujemnej, aby przeskoczyć wstecz (na przykład EB FA FF FF przeskoczy wstecz o 6 bajtów).

Bezpieczna strukturalna obsługa wyjątków

https://chacker.pl/

Celem ochrony Bezpieczna strukturalna obsługa wyjątków (SafeSEH) jest zapobieganie nadpisywaniu i używaniu struktur SEH przechowywanych na stosie. Jeśli program jest kompilowany i linkowany z opcją linkera /SafeSEH, nagłówek tego pliku binarnego będzie zawierał tabelę wszystkich prawidłowych obsługi wyjątków; tabela ta będzie sprawdzana, gdy zostanie wywołana obsługa wyjątków, aby upewnić się, że znajduje się ona na liście. Sprawdzanie jest wykonywane jako część procedury RtlDispatchException w ntdll.dll, która wykonuje następujące testy:

  • Zapewnia, że ​​rekord wyjątku znajduje się na stosie bieżącego wątku.
  • Zapewnia, że ​​wskaźnik obsługi nie wskazuje z powrotem na stos.
  • Zapewnia, że ​​obsługa jest zarejestrowana na autoryzowanej liście obsługi.
  • Zapewnia, że ​​obsługa znajduje się w obrazie pamięci, który jest wykonywalny.

Jak widać, mechanizm ochrony SafeSEH podejmuje kroki w celu ochrony obsługi wyjątków, ale jak zaraz zobaczysz, nie jest on niezawodny.

Zrozumienie i omijanie typowych zabezpieczeń pamięci systemu Windows

https://chacker.pl/

Jak można było się spodziewać, z czasem atakujący nauczyli się wykorzystywać brak zabezpieczeń pamięci w poprzednich wersjach systemu Windows. W odpowiedzi, mniej więcej w czasie Windows XP SP2 i Server 2003, Microsoft zaczął dodawać zabezpieczenia pamięci, które przez pewien czas były dość skuteczne. Jednak atakujący ostatecznie nauczyli się również obejść te początkowe zabezpieczenia. Jest to ciągła ewolucja technik eksploatacji i zabezpieczeń w celu udaremnienia sukcesu tych technik. Na przestrzeni lat dodano wiele nowych zabezpieczeń, a nawet zestawy narzędzi łagodzących, takie jak Windows Defender Exploit Guard, który zadebiutował w systemie Windows 10 w wersji 1709. Gdy te zabezpieczenia są połączone, mogą one znacznie utrudnić wykorzystanie luki.