Stan zagrożeń w internecie znajduje się obecnie na poziomie standardowym. Nie występują duże epidemie a eksperci z Kaspersky Lab nie zanotowali żadnych poważnych incydentów związanych z bezpieczeństwem. Poziom zagrożenia: 1

XPAJ: Inżynieria wsteczna bootkita dla 64-bitowych systemów Windows

Tagi:

Wiaczesław Rusakow
Ekspert z Kaspersky Lab

Wprowadzenie

Liczba bootkitów systematycznie rośnie. Pojawiają się wszystkie możliwe rodzaje nowych bootkitów: wyrafinowane, proste, służące różnym celom (np. rootkity lub trojany wymuszające okup). Twórcy szkodliwego oprogramowania nie zasypują gruszek w popiele i często analizują złośliwy kod tworzony przez swoich konkurentów.

W dzisiejszych czasach nie jest łatwo zaimponować nowym bootkitem ekspertowi ds. szkodliwego oprogramowania: infekcje sektora rozruchowego zostały dokładnie przestudiowane, a w internecie można znaleźć mnóstwo informacji na ten temat. Jednak, tym razem udało nam się trafić na osobliwy przypadek: szkodnika Xpaj - infekującego pliki, uzupełnionego funkcjonalnością bootkita i posiadającego możliwość działania na systemach operacyjnych Windows x86 i Windows x64. To, co wyróżnia szkodnika spośród innych zagrożeń, to operowanie na systemach Windows x64 z włączoną funkcją PatchGuard. Szkodnik robi to przy pomocy splicingu na poziomie jądra, w celu ochrony przed odczytem lub modyfikacją zainfekowanego sektora rozruchowego.

W niniejszym artykule przeanalizujemy działanie rootkita w systemie operacyjnym Windows 7 x64. Przypadek działania szkodnika w Windows x86 nie jest wart odrębnej analizy, ponieważ szkodnik w mniejszym lub większym stopniu wykorzystuje ten sam algorytm w obu wersjach systemu operacyjnego.


Ładunek testowy?

Z analizy tej samej modyfikacji wirusa Xpaj, przeprowadzonej przez specjalistów z firmy Symantec, można wyciągnąć następujące wnioski:

  • funkcja zarażania plików Xpaj nie infekuje 64-bitowych modułów wykonywalnych, w tym sterowników trybu jądra. Wirus infekuje tylko 32-bitowe pliki wykonywalne (.exe i .dll);
  • zainfekowane pliki nie posiadają mechanizmu samodzielnej replikacji;
  • kod wstrzyknięty z trybu jądra do 64-bitowej aplikacji powoduje jedynie wyświetlenie wiadomości o debugowaniu, nie robi nic poza tym.

Na podstawie powyższych wniosków i statystyk infekcji (patrz poniżej), można przypuszczać, że ten wariant wirusa jest tylko wersją testową. Kolejna modyfikacja szkodnika może być już w pełni funkcjonalna i, kto wie, być może twórcy zaimplementują w niej mechanizm infekowania 64-bitowych plików wykonywalnych wraz ze sterownikami trybu jądra (co będzie oczywiście wymagało wyłączenia sprawdzania podpisu cyfrowego).


Ładowanie

Jak zwykle, wszystko rozpoczyna się od zainfekowanego sektora MBR. Jak w prawie wszystkich poprzednich przypadkach, głównym celem zainfekowanego rekordu rozruchowego jest odczytanie dodatkowych sektorów i przekazanie im kontroli.

Dodatkowe sektory na końcu dysku zawierają moduły, które są ładowane przez bootkita, gdy jest to wymagane. Wszystkie moduły, za wyjątkiem pierwszego, są skompresowane z użyciem APLib.

Pierwszy moduł działa według następującego algorytmu:

  • odczyt oryginalnego, niezainfekowanego kodu MBR i zapisanie go w pamięci w miejscu zarażonego MBR;
  • przechwycenie przerwania 13h, które jest odpowiedzialne za operacje odczytu i zapisu sektorów dysku;
  • przekazanie kontroli do oryginalnego rekordu rozruchowego.

Po przekazaniu kontroli do oryginalnego MBR, system operacyjny będzie kontynuował inicjację. Podczas inicjacji plik jądra i wszystkie niezbędne składniki doczytywane są z dysku. Realizując przechwycenie przerwania bootkit oczekuje na odczyt pliku jądra, obliczając sumę kontrolną od początku pliku i sprawdzając niektóre pola w nagłówku.

Aby kontynuować inicjację bootkitu, twórcy szkodliwego oprogramowania wybrali metodę podobną do użytej w TDL-4, z jedyną różnicą polegającą na tym, że tym razem to plik jądra, a nie plik KDCOM.DLL, jest wybierany jako cel. Kiedy wykryta zostanie próba odczytu pliku jądra, bootkit zapisuje pierwsze 0x120 bajtów (licząc od początku pliku) i nadpisuje nagłówek przy pomocy swojego własnego kodu.

 
Rysunek 1. Modyfikowany nagłówek pliku jądra

Następnie bootkit znajduje wyeksportowaną funkcję o nazwie MmMapIoSpace i przechwytuje ją używając splicingu. Punkt zaczepienia prowadzi do kodu w nagłówku pliku jądra.

 
Rysunek 2. Przechwyt funkcji MmMapIoSpace

Oto jak wygląda prototyp funkcji:

PVOID MmMapIoSpace(
IN PHYSICAL_ADDRESS PhysicalAddress,
IN SIZE_T NumberOfBytes,
IN MEMORY_CACHING_TYPE CacheType
);

Funkcja ta odwzorowuje adres fizyczny w pamięci wirtualnej. Pamiętajmy, że pierwszy moduł bootkita cały czas rezyduje w pamięci fizycznej gdy ta funkcja jest wywoływana po raz pierwszy.

Kontynuacja inicjacji bootkita następuje po wywołaniu przechwyconej funkcji.

 
Rysunek 3. Wywołanie stosu MmMapIoSpace

Przy pierwszym wywołaniu kod w nagłówku pliku jądra przywraca skradzione bajty funkcji MmMapIoSpace i wywołuje oryginalną funkcję, która odwzorowuje adres fizyczny pierwszego modułu bootkita (zobacz Rysunek 1 i Rysunek 5) w pamięci wirtualnej. Następnie kontrola jest przekazywana do odwzorowanego kodu.

 
Rysunek 4. Nagłówek pliku jądra
 
Rysunek 5. Wywołanie oryginalnej funkcji MmMapIoSpace i zawartości pamięci fizycznej

Kontynuacja inicjacji bootkita następuje po przekazaniu kontroli do mapowanej pamięci fizycznej.

 
Rysunek 6. Odwzorowany kod

Późniejsze działanie opiera się na następującym algorytmie:

  • przywrócenie skradzionych 0x120 bajtów na początek pliku jądra;
  • przechwycenie przerwania INT 0x01 (KiDebugTrapOrFault) dla każdego procesora;
  • wyszukanie (po hashu) wyeksportowanej funkcji ZwLoadDriver;
  • przechwycenie ZwLoadDriver przy użyciu splicingu;
  • wyszukanie (po hashu) wyeksportowanej funkcji NtReadFile;
  • przechwycenie NtReadFile przy użyciu splicingu;
  • wyszukanie (po hashu) wyeksportowanej funkcji NtWriteFile;
  • przechwycenie NtWriteFile przy użyciu splicingu;
  • usunięcie punktu zaczepienia z przerwania INT 0x01 (KiDebugTrapOrFault) dla każdego procesora;
  • wywołanie funkcji MmMapIoSpace z jej oryginalnymi parametrami, tzn. przekazanie kontroli do jądra w celu dalszej inicjacji systemu operacyjnego.
 
Rysunek 7. Punkt zaczepienia KiDebugTrapOrFault
 
Rysunek 8. Pierwszy etap przechwycenia ZwLoadDriver, NtReadFile i NtWriteFile

Dalsza inicjacja bootkita jest obsługiwana przez przechwyt funkcji ZwLoadDriver, ponieważ w pierwszym etapie pracy bootkita punkty zaczepienia NtReadFile / NtWriteFile są szkieletami, które przekazują kontrolę oryginalnym funkcjom i nie podejmują żadnych innych działań.

 
Rysunek 9. Szkielet funkcji NtReadFile

Oczywiście jest to specjalna "furtka" przygotowana, aby wstawić przez nią później kod pracy punktów zaczepienia funkcji NtReadFile / NtWriteFile.

Bootkit kontynuuje inicjację po pierwszym wywołaniu funkcji ZwLoadDriver.

Przechwyt funkcji ZwLoadDriver zachodzi na podstawie poniższego algorytmu:

  • otwarcie odsyłacza "\??\physicaldrive0";
  • odczytanie sektorów dysku zawierających trzeci moduł;
  • ekstrakcja APLib;
  • przekazanie kontroli do punktu wejścia trzeciego modułu;
  • wywołanie oryginalnej funkcji ZwLoadDriver.
Co ciekawe, kiedy wywołany zostanie punkt zaczepienia ZwLoadDriver, funkcja jest flagowana jako wywołana - jeszcze przed otwarciem symbolicznego odsyłacza "\??\physicaldrive0". Jeżeli punkt zaczepienia zostanie ponownie wywołany, kontrola jest po prostu przekazywana do oryginalnej funkcji. Zauważmy, że odsyłacz pojawia się w systemie tylko na pewnym etapie inicjacji systemu operacyjnego. A więc, jeśli funkcja ZwLoadDriver zostanie wywołana przez dowolny sterownik na odpowiednio wczesnym etapie uruchamiania systemu operacyjnego - zanim odsyłacz zostanie utworzony - dalsza inicjacja bootkita może zostać zatrzymana!

Trzeci moduł, do którego przekazywana jest kontrola, kończy inicjację bootkita w systemie.

 
Rysunek 10. Główna funkcjonalność trzeciego modułu

Trzeci moduł wykonuje następujące działania:

  • ładuje różne ustawienia;
  • ustawia funkcję zwrotną, która zostanie wywołana po utworzeniu procesu;
  • ustawia funkcję zwrotną, która zostanie wywołana po załadowaniu modułu do pamięci;
  • zastępuje szkielety punktów zaczepienia NtReadFile / NtWriteFile ich funkcjonalnymi wersjami.
 
Rysunek 11. Funkcjonalna wersja punktu zaczepienia NtReadFile

Porównajmy punkt zaczepienia funkcji NtReadFile na Rysunku 11 z Rysunkiem 9.

Jest oczywiste, że punkty zaczepienia NtReadFile / NtWriteFile są niezbędne do ochrony przed odczytem i modyfikacją krytycznych obszarów bootkita.


Funkcje zwrotne

Podczas inicjacji bootkita instalowane są dwie funkcje zwrotne.

Pierwsza funkcja "zabija" wszystkie procesy antywirusowe. Kiedy funkcja jest wywoływana podczas tworzenia dowolnego procesu w systemie, bootkit na podstawie nazwy procesu oblicza sumę kontrolną i porównuje ją z własną wewnętrzną listą sum kontrolnych.


Rysunek 12. Sumy kontrolne nazwy procesu

Jeśli suma kontrolna nazwy procesu pasuje do sumy kontrolnej na liście bootkita, bootkit ładuje instrukcję RET w punkt wejściowy procesu i proces jest przerywany.

 
Rysunek 13. Funkcja zakończenia procesu

Druga funkcja zwrotna jest wywoływana przez bootkit, gdy moduł jest ładowany do pamięci. Wykorzystywana jest do wstrzykiwania kodu w różne procesy, łącznie z procesami popularnych przeglądarek internetowych. Podobnie, jak w przypadku funkcji przerywającej procesy, funkcja ta oblicza sumę kontrolną z nazwy procesu i porównuje ją z wewnętrzną listą sum kontrolnych.

 
Rysunek 14. Funkcja wstrzyknięcia kodu

A co z PatchGuard?

Wspomnieliśmy wcześniej o mechanizmie ochronnym, zintegrowanym z jądrem 64-bitowego systemu operacyjnego Windows. Mechanizm PatchGuard został stworzony do przeciwdziałania modyfikacjom jądra systemu operacyjnego i jego krytycznych struktur, takich jak: różne tablice usług (SSDT, IDT, GDT), obiekty jądra itd. Mechanizm ochrony uaktywnia się na wczesnym etapie inicjacji jądra i co pewien czas skanuje powyższe struktury w poszukiwaniu wprowadzonych modyfikacji. Jeśli jakiekolwiek modyfikacje zostaną zidentyfikowane, wywoływana jest celowa awaria systemu. Mechanizm ten został zaprojektowany przede wszystkim do ochrony przed rootkitami trybu jądra. Niestety posiada poważną wadę: wiele aplikacji antywirusowych i produktów bezpieczeństwa wykorzystuje przechwyty na poziomie jądra do różnych legalnych celów, w tym do działania modułów ochrony proaktywnej.

Wciąż dochodzi do ostrej wymiany zdań w tej sprawie pomiędzy producentami rozwiązań antywirusowych i firmą Microsoft. Niektórzy twierdzą, że firmy antywirusowe nie powinny stosować nieudokumentowanych punków zaczepienia w jądrze systemu i do zatrzymania wniknięcia złośliwego kodu do jądra muszą być używane inne metody. Inni uważają, że Microsoftowi nie udaje się zapewnić wymaganego poziomu bezpieczeństwa i te punkty zaczepienia są niezbędne dla zwiększenia bezpieczeństwa systemu operacyjnego. Kolejna grupa twierdzi, że nie można zaufać systemowi, do którego jądra choć raz przeniknął złośliwy kod - i na nic zdaje się leczenie takich systemów. Wszystkie z tych opinii niosą sporo racji, lecz my nie będziemy dalej roztrząsać tego tematu.

Każde narzędzie ochrony można zhakować lub obejść, w ten czy inny sposób. PatchGuard nie jest tutaj żadnym wyjątkiem. Mechanizm ten został dokładnie zbadany zarówno przez niezależnych badaczy, jak i cyberprzestępców, i wynalezionych zostało kilka metod jego obejścia. Dla przykładu, TDL-4 używa metody koncepcyjnej, w której wykrycie punktu zaczepienia rootkita jest po prostu ignorowane przez mechanizm ochrony. Istnieją również inne metody, np. działające w oparciu o modyfikację programu ładującego i pliku jądra systemu operacyjnego, mające na celu wyłączenie inicjacji PatchGuarda. Jeszcze inna metoda opiera się na modyfikacji zainicjowanego jądra i blokuje uruchomienie mechanizmu skanującego. PatchGuard nie zostanie zainicjowany również wtedy, gdy debuger jądra jest włączony podczas uruchamiania systemu operacyjnego – funkcja ta została fabrycznie wbudowana w system, aby deweloperzy oprogramowania mogli swobodnie testować i debugować swoje sterowniki.

Wirus Xpaj jest interesujący, ponieważ używa jeszcze innego koncepcyjnego sposobu na ominięcie PatchGuarda. Problemem mechanizmu PatchGuard jest to, że inicjuje się w dość późnym etapie uruchamiania systemu operacyjnego. Ponieważ Xpaj to bootkit, może on kontrolować każdy etap uruchamiania systemu operacyjnego i modyfikować jądro, zanim uruchomiony zostanie mechanizm ochronny. W momencie, gdy jądro zostanie zainicjowane, zawiera już wszystkie modyfikacje i punkty zaczepienia Xpaja, więc PatchGuard raczej CHRONI te modyfikacje ZAMIAST je WYKRYWAĆ.

Aby potwierdzić, że PatchGuard rzeczywiście chroni przed przechwyceniem ZwLoadDriver / NtReadFile / NtWriteFile, przeprowadziliśmy mały eksperyment. Uruchomiliśmy zainfekowany system w trybie debugowania, zaczekaliśmy na inicjację, włączyliśmy debuger jądra i przywróciliśmy zmodyfikowane bajty w jednej z przechwyconych funkcji. Po chwili system się zawiesił – PatchGuard wykrył modyfikację jądra w pamięci i zgłosił BSOD.

 
Rysunek 15. Awaria systemu po przywróceniu przechwyconej funkcji

Jakikolwiek dodatkowy komentarz jest zbędny.


Statystyki

Użyliśmy KSN, naszej usługi w chmurze, do zgromadzenia statystyk infekcji sektorów startowych bootkitem Xpaj i informacji o detekcji instalatora szkodnika. Najbardziej użyteczne dane, które mogą zostać wyodrębnione z tych statystyk, dotyczą rozkładu geograficznego występowania bootkita i wersji infekowanych systemów operacyjnych.

 
Rysunek 16. Geograficzny rozkład dystrybucji instalatora
 
Rysunek 17. Geograficzny rozkład infekcji MBR
 
Rysunek 18. Statystyki infekowanych systemów operacyjnych

Wnioski

W roku 2008 zaobserwowaliśmy powrót na scenę starej techniki cyberprzestępczej - infekcji sektora MBR. Od tamtej chwili upłynęło sporo czasu. Dzisiejsze bootkity są owocem nowoczesnej idei "budowania rootkitów". Mechanizm ten na pewno pozostanie w arsenale cyberprzestępców w najbliższej przyszłości.

Zainfekowanie sektora rozruchowego jest bardzo wygodnym sposobem inicjowania złośliwego kodu tak wcześnie, jak to możliwe. Na domiar wszystkiego, oferuje ogromne możliwości w zakresie kontroli uruchamiania systemu operacyjnego.

Rodzina systemów operacyjnych Windows x64 została bardzo rozpowszechniona i twórcy szkodliwego oprogramowania robią wszystko, aby ich złośliwe programy były bardziej uniwersalne i utrzymywały niezmiennie wysoki wskaźnik infekcji.

Firma Microsoft wprowadziła pewne ograniczenia i nowe technologie dla systemów Windows x64, zaprojektowane specjalnie do zwalczania rootkitów. Jednakże w praktyce, ani wymóg, że sterowniki trybu jądra powinny być podpisane ważnymi podpisami cyfrowymi, ani mechanizm PatchGuard nie okazały się skuteczne w poprawie sytuacji. Były i będą rootkity przeznaczone dla 64-bitowych systemów operacyjnych, a ich liczba będzie nieubłaganie rosnąć.