Temat: Rootkit stworzony w oparciu o technikę "SSDT hooking". Autor: Damian Tykałowski I. Cel projektu Jako temat projektu wybrałem stworzenie rootkit'a, który działałby w oparciu o technikę o nazwie "SSDT (System Service Descriptor Table) hooking", czyli tłumacząc na język polski, "modyfikacja" tablicy wskaźników funkcji zajmujących się obsługą podstawowych wywołań systemowych. System posiada tablicę, w której przechowuje adresy funkcji, które są wywoływane poprzez Win32API takich jak np. CreateWindow() czy WriteFile(). Wykorzystując opisywaną technikę, istnieje możliwość zamiany funkcji w tej tablicy, bądź modyfikacji w celu ukrycia intruza w systemie, tj. rootkita. Polega to na tym, że modyfikujemy daną funkcję dodając do niej warunki, które pozwalają nam na ukrycie aktywności rootkita w systemie np. poprzez ukrycie jego plików na dysku (najczęściej poprzez zamianę funkcji FindFirstFile() i FindNextFile()) czy też ukrycie naszego programu na liście uruchomionych procesów w systemie. Modyfikacja wyżej wymienionej tablicy daje nam ogromne możwliości i bardzo utrudnia wykrycie takiego intruza w systemie. Celem projektu jest stworzenie takiego programu, którego zadaniem jest jak najlepiej ukryć się w systemie za pomocą opisanej techniki, przy czym przejąłby on kontrolę nad systemem użytkownika i dawał możliwość wykonywania różnych operacji na komputerze ofiary. 2. Wstęp Należy zacząć od tego, czym jest rootkit. A więc termin ten odnosi się do narzędzia bądź zestawu narzędzi, którego zadaniem jest przejąć kontrolę nad systemem jednocześnie pozostając cały czas w ukryciu, tak aby użytkownik nie miał świadomości, że jego komputer jest kontrolowany. Dzieje się to poprzez szereg technik, m.in. ukrywanie rootkita na liście procesów, ukrywanie aktywnych połączeń TCP/IP związanych z intruzem, ukrywanie plików na dysku itp. Używa się do tego różnych technik, jedną z bardziej popularnych technik jest modyfikacja tablicy SSDT, tak jak jest to w temacie. Zarys tej techniki został opisany wyżej. Należy rozgraniczyć tu pewną granicę pomiędzy tym, czym jest malware a czym jest tak naprawdę rootkit. Malware jest tworzony w celu przechwycenia różnych, cennych informacji bądź innych podobnych celach np. wykonywanie różnych zadań z poziomu użytkownika dla osiągnięcia korzyści atakującego. Malware jest też często o wiele bardziej łatwy do wykrycia niż rootkity. Natomiast głównym zadaniem rootkita jest jedno - pozostać niewykrytym, jak tylko to długo możliwe. Jeżeli na komputerze zostanie wykryta jego obecność, to znaczy że zawiódł. Drugim, również ważnym zadaniem, jest przejęcie praw roota/administratora na komputerze. W ten sposób umożliwia to atakującemu pełną kontrolę nad ofiarą, przy czym ofiara nie powinna być niczego świadoma. Malware jest często tworzony na szerszą skalę, np. w celu budowy botnetów, podczas gdy rootkity mają pewne wyspecjalizowane cele, tj. komputer osoby posiadającej ważne informacje lub dostęp do wrażliwych danych, bądź pewien sektor gospodarki, np elektrownia atomowa. Rootkity są tworzone pod konkretne platformy, np. kernel w wersji 3.0.2 podatny na atak, nie oznacza, że kernel 2.6.32 tego samego systemu również jest podatny. Tytułowe aplikacje działają w oparciu o różne techniki, aby oszukać użytkownika
i oprogramowanie, które zajmuje się wyszukiwaniem szkodliwych programów na komputerze. Ja skupie się na technice modyfikacji tablicy SSDT. Najpierw postaram się opisać niezbędne podstawy, aby jak najlepiej zrozumieć na czym polega ta technika, a następnie przejdę do opisu samej techniki, na czym ona polega, w jaki sposób działa. Następnie po rozważaniach teoretycznych zajmę się implementacją przedstawionego rozwiązania. Będę zapisywał na bieżąco swoje wnioski i spostrzeżenia z procesu implementacji, aby na końcu wyciągnąć wnioski. 3. Teoria Podobnie jak zwykłe programy posiadają swoje tablice, gdzie przechowują adresy poszczególnych funkcji, tak kernel posiada swoją tablicę, gdzie trzyma lokacje, gdzie znajdują się funkcje systemowe, odpowiedzialne m.in. za komunikację ze sprzętem i zarządzanie zasobami. Użytkownik wywołując funkcję, posłusznie czeka na odpowiedź, nie zagłębiając się w to co się dziele dalej z wywołaniem funkcji. A tak naprawdę wywołanie przechodzi długą drogę, zanim trafi do warstwy sprzętowej w postaci 0 i 1 (gdyż tylko taka postać jest akceptowalna), a następnie tą samą drogą wraca odpowiedź. Po drodze znajduje się kilka innych etapów, nas będzie interesować zwłaszcza System Services, czyli SSDT. Poniżej uproszczony schemat:
SSDT jest tworzona podczas inicjalizacji ntoskrnl.exe (kernel image systemów z rodziny Windows NT) i jest używana przez KiSystemService() ( KiSystemService jest funkcją w kernelu, która jest uruchamiana za każdym razem, gdy wywoływana jest usługa systemowa i zajmuje się odszukiwaniem Entry Pointów funkcji wywoływanych z natywnego API). Tak naprawdę jest to ostatnia brama pomiędzy aktualnie wywoływaną funkcją z kernela a wywołaniem z wyższego poziomu). Warto tutaj przytoczyć przykład jak wygląda droga wywołania dla CloseHandle:
Struktura SSDT wygląda następująco: typedef struct SERVICE_DESCRIPTOR_TABLE { PNTPROC pservicetable; PULONG pdwcountertable; ULONG dwservicelimit; PUCHAR pargumenttable; } SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE; W systemie są obecne dwie tablice: KeServiceDescriptorTable i KeServiceDescriptorTableShadow, ntoskrnl exportuje tylko pierwszą. Obie z nich są zdefiniowane jako tablice SERVICE_DESCRIPTOR_TABLE i tak np. SERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable[index]. W jednej i drugiej tablicy pod indexem 0, znajdują się adresy Nt API. Dla przykładu załóżmy, że mamy do zhookowania funkcję NtCreateFile. Schemat postępowania wygląda następująco: - najpierw potrzebujemy znaleźć wejście danej funkcji w SSDT (KeServiceDescriptorTable). Punktem wejściowym dla naszej funkcji jest KeServiceDescriptorTable[0].pServiceTable[*(PULONG) ((PUCHAR) NtCreateFile + 1 ))] - odczytujemy znajdującą się tam wartość i zachowujemy ją do późniejszego użytku - zmieniamy wartość wpisu, tak aby wskazywał na naszą funkcję, która ma zostać wywołana Jednak nie można zapomnieć o tym, że tablica, którą chcemy zmodyfikować znajduje się w pamięci przeznaczonej tylko do odczytu. Aby ominąć ten problem, musimy zmodyfikować rejestr kontroly CR0 w odpowiedni sposób. Rejestr ten ma 32-bity długości, a na jego 17 bicie znajduje się flaga WP (Write Protection), która określa zdolność zapisu procesora na stronach w pamięci oznaczonych tylko do odczytu. Jeżeli CR0.WP = 0, wtedy jest możliwość modyfikacji tablicy SSDT, co umożliwia wykonanie ataku. Do wykonania tej czynnośći wystarczy prosta wstawka assemblerowa. Po tym możemy przejść do hookowania naszej tablicy. Potrzebujemy wskaźnika do SSDT, adresu naszej funkcji i adresu funkcji, którą chcemy podmienić. Po odpowiedniej podmianie adresów, warto zachować oryginalny adres funkcji, aby go przywrócić, kiedy już nie będzie potrzeby użycia naszej funkcji. Nie wspomniałem o jednej ważnej rzeczy jak dotąd, mianowicie aby zmodyfikować SSDT potrzebujemy dostępu z poziomu ring0, dlatego aby załadować nasz rootkit, potrzebujemy sterownika, który działa właśnie na tym poziomie. Mała dygresja, w ramach tematu - w obecnych czasach stawia się na technikę DKOM (Direct Kernel Object Modification), gdyż opisywana przeze mnie technika stała się dosyć prosta do wykrycia. Poza tym tą samą technikę stosuje się w różnych programach antywirusowych, które
stanowią kolejną warstwę na drodze żądania, ale powoli odchodzi się od niej ze względu na łatwość popełnienia błędu podczas takiej manipulacji. Echem odbił się atak na antywirusy wykorzystujące właśnie ten sposób, wtedy pokazano, że programy AV opierające się na tym mechaniźmie, okazały się podatne na atak. 4. Bibliografia The Rootkit Arsenal: Escape and Evasion, Bill Blunden Rootkits for Dummies, Larry Stevenson, Nancy Altholz http://shift32.wordpress.com/2011/10/14/inside-kisystemservice/ http://windows-internals.blogspot.com/2009/02/system-call-hooking-i_8590.html http://www.theregister.co.uk/2010/05/07/argument_switch_av_bypass/