“Descriptor tables in kernel exploitation” – nowy artykuł

Cześć!

Jakiś czas (kilka tygodni) temu, mieliśmy wspólnie z Gynvaelem Coldwindem okazję przeprowadzić pewne badania, dotyczące zastosowania Globalnej oraz Lokalnej Tabeli Deskryptorów (ang. Global / Local Descriptor Table) w kontekście exploitacji błędów klasy write-what-where condition, na systemach Microsoft Windows, rodzina NT. Owocem naszej pracy jest krótki artykuł w języku angielskim, opisujący poszczególne kroki, podejmowane w celu eskalacji uprawnień przy użyciu wymienionych wyżej struktur. Jak zwykle, dostępny jest przykładowy kod źródłowy podatnego sterownika, oraz exploitów prezentujących działanie poszczególnych technik.

W tym miejscu chciałbym szczególnie podziękować Unavowedowi oraz Agnieszce Zerka za komentarze oraz pomoc w trakcie składania owej publikacji.

Kompletna paczka, zawierająca plik PDF “GDT and LDT in Windows kernel vulnerability exploitation” (wraz z załączonym plikiem source.zip) znajduje się tutaj (682 kB).

Spis treści:
1. Abstract
2. The need of a stable exploit path
3. Windows GDT and LDT
4. Creating a Call-Gate entry in LDT
4.1. 4-byte write-what-where exploitation
4.2. 1-byte write-what-where exploitation
4.3. Custom LDT goes User Mode
5. Summary
+ References
+ Attachments

Zachęcam do komentowania!

x86 Kernel Memory Space Visualization (KernelMAP v0.0.1)

What I would like to write about today is a subject I have been playing with for quite some time – Windows kernel vulnerability exploitation techniques. While digging through various articles and other materials, I appeared to find bunches of interesting facts that are worth being described here. The post presented today aims to describe various ways of obtaining kernel-mode addresses from the user-mode (application) level.

One could ask, what would we want to retrieve any internal system addresses for. Well, it is indeed a very good question – as for me, the kernel addresses become most useful in the vulnerability exploitation process. Since a majority of bugs found in device drivers belong, directly (pointer validation) or indirectly (pool buffer overflow), to the write-what-where condition family, one must know the exact address to be overwritten before performing the operation. This basically means that the more information about kernel memory layout we can gather, the more stable and effective attacks can be conducted.

The idea I am writing about is not new, for sure. A great part of kernel exploits programmers has already used such techniques in their source code. However, I haven’t ever found any publication that would thoroughly describe every possible vector of obtaining somewhat “sensitive” kernel data (addresses) from within user-mode. Hence, I would like to present a short introduction of each method I could think of – a longer article will presumably be released within a few days. Huh, let’s get to the point, already!

NtQuerySystemInformation function

Before trying to retrieve any information from the kernel, one should firstly realize, what are the possible “communication channels”, that could be used to get the desired data from. The most basic method division could look like this:

  • Kernel communication – calling some of the exported system routines and receive the data we are interested in, through the output buffer
  • Processor communication – directly using some of the processor characteristics (i.e. instruction set) in order to query for some processor-specific values that the system is obliged to fill.

All in all, the kernel isn’t meant to release too much information about itself to the user (since every leaked piece of data could potentially help the attacker to hack the machine); due to this fact, there are special routines designed to handle queries about the system state, configuration etc. As it turns out, these system calls (named NtQuery*Information) can provide very miscellaneous kinds of information that not every low-level coder is aware of. I strongly advice you to take a look and test the functions below, using many different arguments:

Even thought these syscalls are either documented very poorly or not documented at all, some independent researchers have already managed to describe a great part of them – their work is publicly available, for example here. For our purposes (global system info), the last function on the list seems to be the most useful one – it is, indeed. As one can see, the first routine parameter is _SYSTEM_INFORMATION_CLASS – a single enum containing all the possible request types, shown below:

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemInformationClassMin = 0,
SystemBasicInformation = 0,
SystemProcessorInformation = 1,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemPathInformation = 4,
SystemNotImplemented1 = 4,
SystemProcessInformation = 5,
SystemProcessesAndThreadsInformation = 5,
SystemCallCountInfoInformation = 6,
SystemCallCounts = 6,
SystemDeviceInformation = 7,
SystemConfigurationInformation = 7,
SystemProcessorPerformanceInformation = 8,
SystemProcessorTimes = 8,
SystemFlagsInformation = 9,
SystemGlobalFlag = 9,
SystemCallTimeInformation = 10,
SystemNotImplemented2 = 10,
SystemModuleInformation = 11,
SystemLocksInformation = 12,
SystemLockInformation = 12,
SystemStackTraceInformation = 13,
SystemNotImplemented3 = 13,
SystemPagedPoolInformation = 14,
SystemNotImplemented4 = 14,
SystemNonPagedPoolInformation = 15,
SystemNotImplemented5 = 15,
SystemHandleInformation = 16,
SystemObjectInformation = 17,
SystemPageFileInformation = 18,
SystemPagefileInformation = 18,
SystemVdmInstemulInformation = 19,
SystemInstructionEmulationCounts = 19,
SystemVdmBopInformation = 20,
(...)
} SYSTEM_INFORMATION_CLASS;

(you can find the complete definition in the standard ddk\ntapi.h header file). As the name implies, there are really plenty of information to get – the only thing required is the knowledge of how to the input/output structures for each request looks like. At this point, we are particularly interested in three query types – SystemModuleInformation, SystemHandleInformation and SystemLocksInformation. Moreover, SystemObjectInformation could be also useful, under specific circumstances. Let’s go through these requests and find out, what information can we get.

SystemModuleInformation

As far as my observations go, this request is the most commonly used type, across kernel-mode exploits. To understand why, one should first take a look at what this operation returns:

typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
ULONG     Unknown1;
ULONG     Unknown2;
PVOID  Base;
ULONG  Size;
ULONG  Flags;
USHORT  Index;
/* Length of module name not including the path, this
field contains valid value only for NTOSKRNL module */
USHORT    NameLength;
USHORT  LoadCount;
USHORT  PathLength;
CHAR  ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG  Count;
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

What the listing presents is a main structure, containing the number of module information entries returned. Right after this value, Count SYSTEM_MODULE_INFORMATION_ENTRY structures follow, each containing information about one, specific executable image, loaded inside the kernel-mode address space.

As the names themselves suggest, after calling NtQuerySystemInformation(SystemModuleInformation,…) and passing a properly-sized buffer, the application obtains the Name, ImageBase and ImageSize of every single device driver (excluding those that are hidden by rootkits, of course ;) ). This includes the very first Windows kernel images like ntosknrl.exe (or other types of the system core), HAL.dll (hardware support), win32k.sys (std graphic device driver) and so on. Because of the fact that most write-what-where attacks are based on modyfing the ntosknrl.exe memory regions (such as the [HalDispatchTable+4] technique), obtaining the kernel ImageBase  value is an essential part of the entire exploitation. Some very educational articles covering kernel-mode exploitation techniques can be found here (Analyzing local privilege escalations in win32k), here (Exploiting Common Flaws in Drivers) and here (Exploiting Windows Device Drivers).

SystemHandleInformation

Another interesting request type, already used by some rootkit detection mechanisms (RootkitAnalytics.com). The general purpose is providing information about all the active HANDLE objects present in the system memory. Performing a NtQuerySystemInformation(SystemHandleInformation,…) will result in filling the output buffer with structures of the following definition:

typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG  ProcessId;
UCHAR  ObjectTypeNumber;
UCHAR  Flags;
USHORT  Handle;
PVOID  Object;
ACCESS_MASK  GrantedAccess;
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

Not too many fields, this time; however, the most important part of the struct is present – PVOID Object. This is where we can find another kernel-mode pointer (inaccessible from user-mode, of course). Apart from the address itself, the HANDLE is also described with the creator process ID, type of the object and most importantly – the HANDLE value itself. Therefore, the object identification is very easy to perform and should not cause too much of a problem to the coder. More information will follow in the upcoming paper :)

SystemLocksInformation

This time, what we are getting is the information regarding locks used by the kernel. Locks in Windows are special “multiple reader single writer” synchronization mechanisms, otherwise known as “resources”.  The output structure definition follows:

typedef struct _SYSTEM_LOCK_INFORMATION {
PVOID  Address;
USHORT  Type;
USHORT  Reserved1;
ULONG  ExclusiveOwnerThreadId;
ULONG  ActiveCount;
ULONG  ContentionCount;
ULONG  Reserved2[2];
ULONG  NumberOfSharedWaiters;
ULONG  NumberOfExclusiveWaiters;
} SYSTEM_LOCK_INFORMATION, *PSYSTEM_LOCK_INFORMATION;

where the PVOID Address value points to a ERESOURCE structure in the kernel memory. These structures can be initialized using the ExInitializeResourceLite routine and are said to be documented in DDK (Windows NT 2000 Native API Reference).

These are, more or less, all the places (known by me), one can request K-M addresses from. Even though only three sources could seem to be little, it is enough to create a really impressive (imho) kernel memory map, as you will see in a few minutes. If you – the blog reader – are aware of any other kind of system information request leading to kernel address “leak”, please let me know through e-mail / post comments – I will be more than happy to add it to this list.

Processor specific structures

Apart from asking the system kernel to provide some information about its memory layout, one can also use direct application -> processor communication in order to read addresses related to some of the architectural structures that the system has to implement to work correctly. To be more precise, these structures are Global Descriptor Table (per processor/core) and Interrupt Descriptor Table (per processor/core) plus structures implemented inside GDT (Task State Segment, Local Descriptor Table etc).

To start playing with these structures, one should begin by reading Intel Software Developer’s Manuals: Volume 1 (Basic Architecture) and Volume 3A, 3B (System Programming Guide) – all of these can be found here. The most interesting instructions here appear to be SGDT and SIDT, storing the GDTR and IDTR registers in user-specified memory.

What is more, the system itself also makes some segment-related API functions available; these are:

It should be noted (once more), that each processor has its own GDT/IDT structure. Hence, in order to retrieve all the addresses possible, it is necessary to make sure that a specified thread/routine is executed in the context of a chosen processor. This can be achieved by using SetThreadAffinityMask or SetProcessAffinityMask API functions. Please refer to the KernelMAP source code to get more information about how to implement it in practice.

KernelMAP v0.0.1

Despite some strictly theoretical deliberations, I would also like to present a simple program of mine. Its main purpose is to gather all (or most, at least) information about kernel-mode memory layout and show it to the user in the most attractive way possible. The application consists of two windows: the first, text window prints some basic, statistical information based on the data provided by kernel. Second, graphical window is responsible for the real visualization. Its size is equal 1024×512 (400×200 hexdecimally), and every virtual page is represented by a single pixel on the board. These pixels have various colors associated to themselves, depending on what type of data the page in consideration contains.

As the above description might not give you any idea of how it looks like on a real system, some screenshots from various systems follow:

Windows XP SP3

Windows Vista SP2

Windows 7 SP0

Some strictly technical info: this program is designed to be compiled using MinGW GCC compiler and is probably not MS VC++ compatible. In order to make it work correctly, the program needs to find the SDL.dll, libpng3.dll and zlib1.dll libraries – you can find them inside the package.

A complete ZIP file, including the source code, executable and external DLL files can be downloaded from here (387kB)

Since I am myself very curious about how the kernel memory layout looks like on different systems that I don’t have access to, you should feel encouraged to make your own shot and share (I hope this is not too much of information disclosure ;D). Furthermore, if you find any bugs in the existing code, or would like it to be extended with some additional functionalities (like new kernel addresses I don’t know about, yet), please let me know.

Every single comment is very welcome!

Have fun!

Tabela graficznych wywołań systemowych Windows

Każdemu, kto choć raz poważniej zajmował się mechanizmami działania jądra systemu Windows, zdarzyło się zapewne potrzebować pełnej listy numerów wywołań systemowych (wraz z ich definicjami). Tablica tychże wywołań, będąca jedną z najważniejszych części procesu komunikacji pomiędzy aplikacjami użytkownika a jądrem, bardzo często wykorzystywana jest zarówno do czysto praktycznych celów (zakładanie hooków na konkretne syscalle w celu modyfikacji sposobu działania systemu itp.), jak i teoretycznych badaniach czy dyskusjach.

Biorąc pod uwagę powyższe fakty, popularność opracowanej przez Metasploit Project – pełnej (choć nie do końca) – listy wywołań systemowych nie powinna nikogo dziwić. Lista ta obejmuje większość aktualnie używanych systemów operacyjnych z rodziny Windows NT – począwszy od Windows NT4, a skończywszy na Windows Vista SP0. Ponadto, tabela ta oprócz samych numerów wywołań odpowiadających konkretnym symbolom jądra, udostępnia również pełne definicje wszystkich uwzględnionych funkcji.

Warto jednak zauważyć, że opisywana tabela zawiera informacje dotyczące jedynie części wywołań – interfejsu obrazu jądra (ntoskrnl.exe). W żaden sposób nie uwzględniono w niej graficznej części syscalli, eksportowanych przez zewnętrzny moduł kernela – plik win32k.sys. Podczas własnych badań nad działaniem funkcji interfejsu graficznego Windows, pojawiła się potrzeba łatwego dostępu do numerów opisujących wywołania o numerach większych od 0×1000 (tego typu numery używane są do komunikacji z graficzną częścią jądra). Ponieważ jednak nie udało mi się odnaleźć spisu porównywalnego jakościowo do rozwiązania prezentowanego przez Metasploit, postanowiłem na własną rękę stworzyć w pełni kompletną listę graficznych wywołań systemowych.

Aktualna wersja tabeli graficznych wywołań systemowych Windows dostępna jest pod adresem http://j00ru.vexillium.org/win32k_syscalls/.

W zamyśle, projekt ma na celu obejmować wszystkie 32-bitowe systemy Microsoft Windows rodziny NT, włączając Windows 7. Luki widoczne w tabeli wynikają z faktu, że w chwili obecnej nie mam dostępu do wszystkich uwzględnionych wersji systemu. W dniu dzisiejszym nie znajdziemy również definicji handlerów obecnych w tabeli – proszę jednak pamiętać, że cały projekt jest wciąż w początkowej fazie testowania. W miarę możliwości, strona będzie konsekwentnie uzupełniana o numery występujące w kolejnych systemach; tak, by w niedalekiej przyszłości dorównać kompletnemu zestawieniu tworzonemu przez Metasploit.

Pomimo dołożenia wszelkich starań w celu utrzymania dokładności informacji zawartych w tabeli, prawdopodobnie nie jest ona wolna od błędów. Zachęcam więc wszystkich zainteresowanych do zgłaszania ewentualnych usterek czy niedopatrzeń znajdujących się na stronie. Wszelkie uwagi dotyczące projektu (czy też dane uzupełniające) są również bardzo mile widziane!

Have fun!

Znajdowanie adresów tablicy SSDT

W dniu dzisiejszym chciałbym poruszyć temat odnajdywania adresów nieeksportowanych funkcji jądra (handlerów syscalli) z poziomu trybu użytkownika. Jest to mój własny, autorski pomysł, na który wpadłem podczas rozmów dotyczących sposobów exploitacji podatności sterowników kernel-mode, na platformie Windows 32 (pozdrowienia dla sUN8Hclf!). Pomimo to, nie jestem w stanie zagwarantować, że opisany sposób nie został wymyślony i opisany przez niezależnych autorów kilka miesięcy/lat temu. Jeśli ktoś z czytelników jest świadomy istnienia podobnej publikacji, proszę o kontakt (z przyjemnością udostępnię materiał uzupełniający do tego, o czym dziś piszę) w celu wyjaśnienia sprawy. Przejdźmy więc do sedna…

Sam temat praktycznego wykorzystywania luk bezpieczeństwa znajdujących się w kodzie jądra lub jednego z jego modułów (sterowników) jest zbyt obszerny, by omawiać go tutaj w całości. Techniczne aspekty eksploitacji zostały już opisane przez wielu badaczy, a rezultaty ich badań znaleźć można między innymi tutaj:

Podstawowym problemem, z jakim zazwyczaj spotyka się początkujący reseacher badający podatności trybu kernela to możliwość praktycznego jej wykorzystania. W momencie, kiedy uda się już doprowadzić do wykonania kodu w kontekście jądra oraz utworzyć relatywnie stabilne środowisko, zastanawiamy się, co dalej. W rzeczywistości każda funkcjonalność, jaką chcielibyśmy zaimplementować wymaga użycia zewnętrznych funkcji eksportowanych przez pewien moduł trybu kernela. W 99% przypadków jest to po prostu główny obraz wykonywalny jądra (zwykle ntoskrnl.exe). Istnieje wiele metod znajdowania jego adresu bazowego (Finding Ntoskrnl.exe Base Address @ Uninformed), które w zdecydowanej większości przypadków spełniają nasze wymagania – tym tematem nie będę się więc dziś zajmował.

Kolejnym krokiem w stronę stworzenia funkcjonalnego payloadu jest znalezienie adresów wirtualnych konkretnych, interesujących nas funkcji. W najprostszym przypadku, kiedy mamy zamiar operować tylko i wyłącznie na funkcjach eksportowanych, wszystko rozbija się o umiejętne parsowanie wewnętrznych struktur formatu Portable Executable, co zrealizować można w kilkunastu bądź kilkudziesięciu instrukcjach. Często spotykanym rozwiązaniem jest wprowadzenie hashowania nazw zewnętrznych symboli, na podstawie bardzo prostego algorytmu korzystającego jedynie z operacji przesunięcia bitowego, przykładowo:

;
; ASSUMPTIONS: ESI = string to hash (input)
;              EAX = return value (output)
;
GenerateNameHash:
  xor eax, eax              ; Zero out the EAX (hash) value

@HashLoop:
  rol eax, 13               ; Rotate left by one
  xor al, byte [esi]        ; Xor with the current char

  inc esi                   ; Increment pointer
  cmp byte [esi], 0         ; Check if NULL
  jnz @HashLoop             ; If not, carry on
  ret                       ; EAX is already set, we have nothing to do - return

W znacznej większości przypadków, użycie dolnego słowa wyniku (16 bitów) w zupełności wystarcza do pełnej identyfikacji nazwy funkcji, a więc oszczędzamy w tym miejscu od kilku do kilkunastu bajtów!.Przydatność tej optymalizacji zależy oczywiście od rozważanej podatności, jednak bardzo często zależy nam na redukcji rozmiaru payloadu do absolutnego minimum. Mowa tutaj jednak o sytuacji, w której otrzymujemy dostęp do publicznie dostępnych adresów obrazu jądra, co nie jest specjalnie trudne do osiągnięcia. Moim zdaniem, znacznie ciekawszym tematem badań jest wyszukiwanie adresów “wewnętrznych” funkcji, które eksportowane nie są, a więc do znajdowania których używane są metody trudniejsze, najczęściej uzależnione od konkretnej wersji systemu operacyjnego. Pomimo tego, że nie istnieje zbyt wiele sposobów rozwiązywania przedstawionego problemu w sposób uniwersalny, istnieją pewne szczególne sytuacje, w których jesteśmy w stanie pobrać adres zadanej, wewnętrznej funkcji, spełniającej pewne założenia.

W tym szczególnym przypadku chodzi o funkcje należące do SSDT (System Service Descriptor Table) – tabeli wskaźników funkcji zajmujących się obsługą podstawowych wywołań systemowych. Zdecydowana większość syscall handlerów nie jest bezpośrednio eksportowana przez jądro, jednak okazuje się bardzo przydatna w momencie tworzenia zaawansowanego payloadu ring-0. Ponadto, należy zauważyć, że pobranie dowolnego adresu funkcji znajdującej się w tabeli deskryptorów wywołań systemowych jest zadaniem trywialnym z poziomu sterownika, jeśli tylko dysponujemy ID konkretnego wywołania. W takim przypadku, jedynym problemem jest pobranie wersji systemu operacyjnego, w celu dopasowania odpowiedniego numeru interesującej nas funkcji jądra.

Zadanie to nie jest jednak równie banalne w trybie użytkownika – tutaj sprowadzamy problem do rozwiązań częściowo heurystycznych, które nie dają stuprocentowej gwarancji działania niezależnie od wybranego systemu. Oto, jak prezentują się kolejne etapy działania programu ilustrującego metodę, której dotyczy ten post:

  • Załadowanie obrazu jądra w w kontekst naszego procesu – ponieważ w dalszych etapach działania programu, kluczowe okażą się dane znajdujące się w pliku kernela (najczęściej ntoskrnl.exe), ładujemy jego obraz wykonywalny do pamięci wirtualnej trybu użytkownika. Daje nam to możliwość odwoływania się do “lokalnych” adresów eksportowanych funkcji w prosty sposób, a więc pozwala również na obliczanie przesunięcia dowolnego adresu względem ImageBase prawdziwego jądra. Ponieważ nie traktujemy ładowanego pliku jak zwykłej biblioteki DLL, musimy upewnić się, że nie zostaną wykonane żadne dodatkowe operacje, prócz samego mapowania pamięci pliku kernela (np. traktowanie punktu wejścia jako standardowej funkcji DllMain etc). Jest to możliwe dzięki rozszerzonej funkcjonalności funkcji LoadLibraryEx. Jedną z możliwych flag parametru dwFlags jest DONT_RESOLVE_DLL_REFERENCES, opisane w następujący sposób:

If this value is used, and the executable module is a DLL, the system does not call DllMain for process and thread initialization and termination. Also, the system does not load additional executable modules that are referenced by the specified module.

  • Wybranie jednej, konkretnej funkcji, którą można znaleźć zarówno w SSDT, jak i na liście eksportów kernela, np. NtCreateFile, NtCreateEvent, NtConnectPort lub NtClose. Funkcja ta jest dla nas istotnie ważna, ponieważ posiadamy jej dokładny adres w pamięci kernel-side (na podstawie przeliczeń ImageBase prawdziwego i tymczasowego obrazu jądra), a także jesteśmy w stanie wyznaczyć adresy wszystkich innych funkcji znajdujących się w tablicy wywołań systemowych, bazując na jej położeniu.
  • Pobranie informacji o adresie bazowym i wielkości załadowanego obrazu jądra, co umożliwia nam jedna z funkcji biblioteki Process Status API, mianowicie GetModuleInformation.
  • Pobranie rzeczywistego adresu jądra systemu, potrzebnego do ustalenia miejsca wszystkich interesujących nas funkcji, których poszukujemy. W tym przypadku, pomocne okazują się funkcje EnumDeviceDrivers (PSAPI) oraz GetDeviceDriverBaseName. Dzięki nim jesteśmy w stanie listować i filtrować wszystkie aktywne moduły jądra, wraz z nim samym. Poniższy kod ilustruje działanie funkcji pobierającej adresu bazowego modułu o podanej nazwie:

DWORD GetDriverBaseAddr(const char* BaseName)
{
  static LPVOID BaseAddresses[4096]; // XXX: let's assume there are at most 4096 active device drivers
  DWORD cbNeeded;

  /* Get a list of all the drivers' Image Base Addresses */
  if(!EnumDeviceDrivers(BaseAddresses,sizeof(BaseAddresses),&cbNeeded)) return 0;
  CHAR FileName[MAX_PATH];

  /* Go thru the entire list */
  for( int i=0;i<(int)(cbNeeded/sizeof(LPVOID));i++ )
  {
    /* For each image base, retrieve the driver's name */
    GetDeviceDriverBaseNameA(BaseAddresses[i],FileName,sizeof(FileName));

    /* In case of the current module being kernel, return its base */
    if(!_stricmp(FileName,BaseName)) return (DWORD)BaseAddresses[i];
  }

  /* Should never get here */
  return 0;
}
  • Skanowanie pamięci załadowanego obrazu jądra (user-mode) w poszukiwaniu adresu wybranej funkcji (w moim przypadku – NtCreateFile). Jest to pierwsza – i jedyna – faza działania algorytmu, która reprezentuje heurystyczne podejście. Ma ona za zadanie odnaleźć miejsce w SSDT, gdzie zapisany jest wskaźnik na wybraną wcześniej funkcję. Technika ta w szczególnych przypadkach może prowadzić do błędnych wyników (w sytuacji odnalezienia więcej niż jednej sygnatury), dlatego zaleca się wprowadzenie dodatkowych warunków przeszukiwania – Skoro wiemy, że jedynym interesującym nas rezultatem jest miejsce wewnątrz SSDT, możemy założyć, że adresy sąsiadujące z aktualnie rozpatrywaną wartością, również powinny wskazywać na pamięć NTOSKRNL.EXE. Okazuje się, że podane wyżej warunki wystarczają, by ograniczyć liczbę “fałszywych alarmów” praktycznie do zera (na wszystkich testowanych przeze mnie systemach).Oto odpowiedni kod, wykonujący opisane skanowanie pamięci:
    for( PUCHAR i=(PUCHAR)KernelImageStart;i<(PUCHAR)KernelImageEnd-sizeof(DWORD);i++ )
    {
      if(( *(DWORD*)(i+0) == SearchedFunctions[0].Address ) &&
      ( *(DWORD*)(i-4) >= OrgKernelStart && *(DWORD*)(i-4) <= OrgKernelEnd ) &&
      ( *(DWORD*)(i+4) >= OrgKernelStart && *(DWORD*)(i+4) <= OrgKernelEnd ) )
      {
        printf("[+] Function pointer found at [0x%.8x]\n",(UINT)i);
        SearchedFunctions[0].SsdtAddress = (DWORD)i;
        break;
      }
    }
  • Odczytywanie numerów wywołań systemowych potrzebnych funkcji. Istnieje bardzo prosty i jednocześnie skuteczny sposób dynamicznego odczytywania numerów wywołań systemowych dla dowolnego wrappera znajdującego się w NTDLL, bez konieczności sprawdzania wersji systemu operacyjnego czy też statycznego definiowania interesujących nas wartości w kodzie. Wykorzystujemy tutaj specyficzną budowę funkcji przekazujących wykonywanie do kernela, którą zaobserwować można już na dwóch przykładach:
    .text:7C90D090                ; __stdcall NtCreateFile(x, x, x, x, x, x, x, x, x, x, x)
    .text:7C90D090                _NtCreateFile@44 proc near
    .text:7C90D090
    .text:7C90D090 B8 25 00 00 00 mov     eax, 25h
    .text:7C90D095 BA 00 03 FE 7F mov     edx, 7FFE0300h
    .text:7C90D09A FF 12          call    dword ptr [edx]
    .text:7C90D09C C2 2C 00       retn    2Ch

    oraz

    .text:7C90D580                ; __stdcall NtOpenFile(x, x, x, x, x, x)
    .text:7C90D580                _NtOpenFile@24  proc near
    .text:7C90D580
    .text:7C90D580 B8 74 00 00 00 mov     eax, 74h
    .text:7C90D585 BA 00 03 FE 7F mov     edx, 7FFE0300h
    .text:7C90D58A FF 12          call    dword ptr [edx]
    .text:7C90D58C C2 18 00       retn    18h

    Jak widać, dla zadanej funkcji ZwXXX eksportowanej przez ntdll.dll, jesteśmy w stanie pobrać odpowiadający jej numer wywołania poprzez odczytanie 32-bitowej wartości spod adresu [NazwaFunkcji+1]. Ma to związek z faktem, że pierwszą instrukcją wrapperów ntdll jest instrukcja

    mov eax, SYSCALL_ID

    gdzie SYSCALL_ID to pełna, 32-bitowa wartość.
    W naszym przypadku, kod odpowiedzialny za pobieranie numerów kolejnych funkcji mógłby przedstawiać się następująco:

    /* Get the SyscallId values for each function from the user-mode (ntdll.dll) code
    */
    for( ULONG i=0;SearchedFunctions[i].FunctionName;i++ )
    {
      HMODULE hNtdll = GetModuleHandle("ntdll.dll");
      FARPROC pFunc  = GetProcAddress(hNtdll,SearchedFunctions[i].FunctionName);
    
      /* Ignore invalid entries
       */
      if(pFunc==NULL)
        continue;
    
      SearchedFunctions[i].SyscallId = *(DWORD*)(((DWORD)pFunc)+1);
    }
  • Przeliczanie adresów funkcji SSDT, poprzez wykonywanie kolejnych kroków:
    • Pobranie wartości (wskaźnika) znajdującego się pod adresem:
      (BaseFunction.Address + (BaseFunction.SyscallId - CurrentFunction.SyscallId)*sizeof(PVOID))

      czyli wartości wyliczonej poprzez przesunięcie adresu funkcji bazowej (NtCreateFile) w przód lub w tył, w zależności od numeru poszukiwanej funkcji.

    • Przetłumaczenie otrzymanego wskaźnika na adres trybu jądra:
      CurrentFunction.KernelAddress = CurrentFunction.Address - LocalKernelImageBase + RealKernelImageBase

W ten sposób jesteśmy w stanie otrzymać adres dowolnej funkcji obsługującej wywołanie systemowe, pod warunkiem, że mamy do dyspozycji jej odpowiednik eksportowany przez ntdll.dll (nie jest to konieczne w przypadku użycia stałych numerów interesujących nas wywołań – tracimy wtedy na kompatybilności).

Należy jednak zauważyć, iż podana metoda umożliwia jedynie otrzymanie adresów funkcji znajdujących się w jądrze systemu – w dalszym ciągu nie jesteśmy w stanie czytać ani modyfikować danych znajdujących się pod tymi adresami. Technika ta sama w sobie nie nadaje się więc w żadnym wypadku do wszelkiego rodzaju walidacji poprawności zawartości systemowej tablicy wywołań. Sprawia jednak, że adresy wszystkich funkcji SSDT, których zechcielibyśmy użyć w naszym payloadzie, mogą zostać obliczone i zintegrowane z shellcode jeszcze przed samym wykorzystaniem podatności, co w znacznym stopniu poprawia komfort pisania samego exploita.

Kompletny kod ilustrujący działanie przedstawionej techniki jest dostępny tutaj(3kB).

Serdecznie zapraszam do komentowania pomysłu!

Monitorowanie listy procesów Windows, cz. 1

W pierwszej części posta (patrz Wstrzymywanie procesów w systemie Windows, cz. 1) starałem się omówić znane oraz mniej popularne techniki pozwalające na wstrzymanie działania wątków lub całych procesów działających pod kontrolą systemu Microsoft Windows. Zapowiedziałem również, że w części kolejnej opisany zostanie konkretny sposób modyfikacji programu TaskMgr.exe, rozszerzający go o interesującą nas funkcjonalność. Aby jednak przejść do sedna – zmiany kodu wykonywalnego aplikacji – musimy najpierw zwrócić uwagę na pewien ważny fakt. Mianowicie, zależy nam, by owej modyfikacji ulegała każda działająca w systemie instancja Task Managera. Już w tym momencie możemy obrać więc jedną z trzech możliwych dróg:

  • Dokonać jednorazowej zmiany zawartości pliku systemowego TaskMgr.exe na dysku twardym.
  • Stworzyć dodatkowy plik wykonywalny, nazywany loaderem – skojarzony z Task Managerem i uruchamiany jako jego debugger.
  • Pozostawić dane dysku twardego bez zmian – modyfikować jedynie pamięć wirtualną wszystkich działających procesów, spełniających konkretne wymagania (w tym przypadku – ścieżka obrazu wykonywalnego)

Z każdą z przedstawionych wyżej opcji wiążą się pewne wady. W przypadku fizycznej modyfikacji danych HDD, ingerujemy w zawartość pliku systemowego, co samo w sobie jest działaniem niebezpiecznym i zazwyczaj niepożądanym. Co więcej, jako, że system Windows przechowuje kopie zapasowe aplikacji systemowych, musielibyśmy pozbyć się lub zmodyfikować wszystkie istniejące w systemie pliki zapasowe menedżera zadań. Zaciekawiony tematem czytelnik powinien zainteresować się tematami Winows File Protection oraz Windows Resource Protection.

Ciekawym pomysłem jest stworzenie swoistego loadera, czyli programu, który uruchamiany jest za każdym razem, kiedy następuje próba utworzenia procesu TaskMgr.exe. Jeśli jesteśmy w stanie sprawić, aby nasz kod uruchamiany był bezpośrednio przed menedżerem, siłą rzeczy wiemy o każdej nowej istniejącej instancji interesującej nas aplikacji. Zadaniem programu ładującego byłaby więc tylko i wyłącznie modyfikacja pamięci w kontekście konkretnego procesu, co nie powinno stanowić większego problemu. Kojarzenie zewnętrznych aplikacji z plikami wykonywalnymi o konkretnej nazwie możliwe jest przy użyciu klucza rejestru o nazwie:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options

W naszym przypadku, jedyną wprowadzoną w rejestrze zmianą byłoby dodanie klucza o nazwie TaskMgr.exe, a następnie stworzenie w jego kontekście wartości tekstowej o nazwie “Debugger”, przyjmującej ścieżkę loadera, przykładowo:

C:\Windows\system32\TaskMgr_loader.exe

Więcej na temat Image File Execution Options można przeczytać tutaj.

Moim zdaniem, zdecydowanie lepszym wyborem jest modyfikacja pamięci wirtualnej aktywnych procesów, bez jakiejkolwiek ingerencji w trwałe dane partycji systemowej. Metoda ta zwiększa również praktyczne możliwości zmiany sposobu działania programu – mamy przecież dostęp do wszelkich danych (oraz modułów), z których korzysta omawiany proces. Przykładowo, założenie inline hooka na jedną z importowanych funkcji (w naszym przypadku – kernel32.SetPriorityClass) jest znacznie łatwiejsze do wykonania na działającym procesie, niż poprzez modyfikację kodu obrazu wykonywalnego samego TaskMgr.exe.

Wybierając trzecią metodę, musimy jednak zastanowić się nad sposobem jej implementacji. Monitorowanie listy działających w systemie procesów nie jest problemem trywialnym – ponadto, istnieje wiele sposobów jego rozwiązania, różniących się zarówno skutecznością, potrzebnymi do działania przywilejami, jak i używanymi mechanizmami. Uważam, że w tej sytuacji warto przyjrzeć się bliżej każdemu z nich, a następnie wybrać ten, który w danej chwili najlepiej spełnia nasze oczekiwania.

W dzisiejszym poście postaram się omówić jedną z możliwości śledzenia aktywnych aplikacji – najbardziej chyba intuicyjne okresowe pobieranie listy procesów i porównywanie wyników ze starszymi wersjami, w celu wyodrębnienia interesujących nas różnic.

Oto kilka kwestii, które zwracają naszą uwagę już na pierwszy rzut oka:

  • Problem doboru odpowiedniego “czasu uśpienia”. Jako, że omawiamy technikę aktywną, odpytującą system operacyjny co pewien ściśle ustalony okres czasu, istnieje prawdopodobieństwo “przegapienia” procesu, który umiejętnie wstrzeli się w czas pomiędzy kolejnymi zapytaniami. Można oczywiście odpowiednio dostosowywać wartość delay’u tak, by wyeliminować taką możliwość, jednak im częściej pobierana będzie lista procesów, tym większy nakład czasu procesora jest wymagany. W tej sytuacji konieczne jest znalezienie tzw. “złotego środka”, który pozwoli nam zarówno na w miarę skuteczne monitorowanie listy procesów, jak również obniżenie potrzebnego czasu procesora do minimum.
  • Pytanie o źródło (pewnych?) informacji o liście procesów oraz odpowiedni interfejs zapewniający dostęp do tych danych.

O ile pierwszy problem może zostać rozwiązany w sposób doświadczalny, zależnie od potrzeb i upodobań programisty, o tyle druga kwestia wymaga głębszego przemyślenia. Okazuje się, że istnieje całkiem spora liczba systemowych interfejsów (lepiej lub gorzej udokumentowanych), pozwalających na dostęp do interesujących nas informacji. Należą do nich między innymi:

  • ToolHelp32 - zbiór funkcji eksportowanych przez kernel32.dll, które wprowadzone zostały już w Microsoft Windows 3.1. Udostępniają informacje dotyczące listy stert, modułów, wątków i procesów. Kompatybilne ze wszystkimi wersjami od Windows Me/9x w górę, wyłączając Windows NT 4.0. Zakres enumeracji procesów zależy od aktualnych uprawnień użytkownika, w którego kontekście wywoływane są funkcje interfejsu.
  • Process Status API (PSAPI) – dobrze opisana biblioteka pomocnicza Windows, której zadaniem jest ułatwienie programiście dostępu do informacji dotyczących procesów, modułów oraz sterowników jądra. Jedną z eksportowanych funkcji jest EnumProcesses, pozwalająca na pobranie listy identyfikatorów wszystkich procesów działających w systemie. Należy zauważyć, że otrzymane w ten sposób informacje są bardzo skromne, gdyż nie dostajemy żadnych dodatkowych danych na temat konkretnych procesów – chcąc się do nich dostać, jesteśmy zmuszeni otworzyć uchwyt do obiektu aplikacji (np. używając funkcji OpenProcess), co nie zawsze jest możliwe z powodów związanych z przywilejami i prawami użytkownika systemu
  • NtQuerySystemInformation(SystemProcessInformation) – nieudokumentowana, wewnętrzna funkcja udostępniana przez ntdll.dll. Umożliwia pobranie kompletnych informacji nt. rozważanego procesu – wynikiem jej działania jest tablica struktur SYSTEM_PROCESS_INFORMATION. W rzeczywistości dwie wyżej opisane metody sprowadzają się do wywołania funkcji NtQuerySystemInformation z parametrem SystemProcessInformation, a następnie odpowiedniej interpretacji zwróconych wyników. Oznacza to, że w momencie, kiedy rezultaty zwracane przez opisywane wywołanie systemowe zostaną przefiltrowane w celu ukrycia konkretnego procesu, funkcja EnumProcesses oraz interfejs ToolHelp32 również zwrócą zmodyfikowane wyniki.
  • NtQuerySystemInformation(SystemHandleInformation) – kolejne, nieudokumentowane zachowanie wewnętrznej funkcji WindowsNT, polegające na enumeracji wszystkich otwartych w systemie uchwytów i zwróceniu jej wyników użytkownikowi. Po odpowiedniej interpretacji dostarczonych informacji (m.in. wykryciu, które z uchwytów należą do obiektów procesów), jesteśmy w stanie utworzyć kompletną listę działających w systemie procesów, niezależną od listy struktur EPROCESS znajdującej się w pamięci jądra. Technika ta jest powszechnie używana jako sposób na detekcję rootkitów, które skupiając się na ukrywaniu na poziomie jądra systemu, zapomniały o takich elementach trybu użytkownika, jak CSRSS. Więcej informacji można znaleźć można tutaj (HPD with CSRSS Process Handle Enumeration).
  • OpenProcess + GetLastError API – brutalne użycie funkcji, której zadaniem jest otwieranie uchwytu do wyznaczonego obiektu procesu. Jak wynika z autorskich badań, otrzymanie pełnej listy aktywnych procesów jest możliwe poprzez próbę odwołania się do wszystkich możliwych identyfikatorów procesów, a następnie sprawdzanie wartości zwracanej przez funkcję OpenProcess (lub GetLastError). Technika ta nie ma jednak nic wspólnego z efektywnością, a więc na pewno nie powinna być używana jako metoda na okresowe pobieranie informacji o procesach.
  • Windows Management Instrumentation (WMI) – “zestaw protokołów i rozszerzeń systemu Windows umożliwiających zarządzanie i dostęp do zasobów komputera, takich jak adaptery sieciowe, aktualnie otwarte programy, lista procesów, odczyty z wbudowanych czujników temperatury, odczytów woltomierzy itp” (via Wikipedia).
  • Client/Server Runtime Subsystem (CSRSS) – korzystamy z faktu, ze proces CSRSS.EXE zajmuje się pełną obsługą własnej listy procesów, która jest w założeniu całkowicie niezależna od łańcucha EPROCESS (kernel-mode). Jest to możliwe dzięki sytuacji, w której każdy nowo utworzony proces “rejestrowany” jest przez swojego rodzica w kontekście omawianego subsystemu, przy użyciu standardowych funkcji komunikacji z CSRSS (takich jak ntdll.CsrClientCallServer). Dysponując odpowiednimi prawami bezpieczeństwa (pozwalającymi na otworzenie i odczytanie pamięci wirtualnej, aplikacji działającej na prawach SYSTEM), jesteśmy w stanie pobrać pełną listę procesów o których wie CSRSS, nie opierając się na żadnych informacjach znajdujących się w jądrze! Technika ta została dokładnie zbadana oraz zaimplementowana przez badacza Diablo i opisana tutaj.

W chwili obecnej, wszystkie wymienione mechanizmy mogą zostać uznane za funkcjonalne (można spotkać się z przynajmniej jednym kodem źródłowym ilustrującym pracę danego sposobu). Jak widać, okazuje się, że nawet w kwestii tak prostej, jak listowanie działających w systemie procesów, można znaleźć naprawdę wiele równoważnych (choć nie do końca) rozwiązań. Powyższa lista może jednak nie być w pełni kompletna, gdyż zawiera jedynie techniki, które mi osobiście przyszły do głowy – standardowo zachęcam wszystkich chętnych do dzielenia się własnymi pomysłami w komentarzach! ;>

Skoro wiemy już, w jaki sposób możliwe jest okresowe pobieranie listy procesów, w następnych postach postaram się opisać inne sposoby na zarządzanie listą procesów, które nie wymagają okresowego pytania systemu o aktualny stan. Po zapoznaniu czytelnika ze wszystkimi (wymyślonymi dotąd) możliwościami monitorowania istniejących procesów, pokażę konkretny sposób modyfikacji samego TaskMgr.EXE, co obiecałem już w poprzednim poście.

Dobranoc!

Linki:

  1. Windows File Protection
  2. Windows Resource Protection
  3. Image File Execution Options
  4. Tool Help Library
  5. Process Status API
  6. SYSTEM_PROCESS_INFORMATION structure
  7. HPD with CSRSS Process Handle Enumeration
  8. Windows Instrumentation Management
  9. Client/Server Runtime Subsystem
  10. EPROCESS Structure
  11. CsrWalker

TraceHook v0.0.2

Jako że okazało się, iż znalazłem ostatnimi dniami chwilę czasu na powrót do rozwoju projektu TraceHook, postanowiłem oznaczyć to co powstało w wyniku kilkugodzinnej sesji kolejnym numerem – 0.0.2. Do tej pory aplikacja miała charakter czysto praktyczny – była opisana pod konkretny problem i środowisko, jednak powoli staram się uzupełniać go o opcje, które mogą okazać się przydatne w kontekście szerszej publiczności.

Główna idea oraz sposób działania nie uległ zmianie – w dalszym ciągu chodzi o kontrolę i zrzucanie pamięci rodzin procesów oznaczonych jakie malware (jako, że pod ten rodzaj oprogramowania tworzony jest TraceHook). Silnikiem całego mechanizmu jest sterownik systemowy, który zajmuje się bezpieczną obsługą aktualnej listy procesów oraz otrzymywaniem i zarządzaniem notyfikacji o zdarzeniach związanych z tworzeniem i zabijaniem programów.

Aplikacja wzbogacona została o nowy format pliku zrzutu – Windows MiniDump. W przeciwieństwie do pełnego zrzutu surowej pamięci, w całości odbywającego się w trybie jądra, plik minidump tworzony jest z trybu użytkownika. Przeniesienie funkcji dumpującej w user-mode otwiera możliwość rozbudowy projektu o kolejne, bezpieczne funkcje implementujące coraz to nowe formaty, w zależności od potrzeb samego użytkownika.

Kompletna paczka (EXE + źródła): TraceHook.zip (50 kB)

Opcje:

-=*( TraceHook v0.0.2 by j00ru//vx )*=-
 Usage: TraceHook.exe <target executable> [options]

 Available options:
 -pPATH       Sets the dump destination directory to PATH.
              The default dump path is C:\dump.

 -iINTERVAL   Turns the counter mode on. Makes the application terminate
              and automatically dump all the monitored processes after the
              specified amount of time.
              Note: The INTERVAL value is the number of seconds to wait till
                    dumping all processes.

 -m           Activates the MiniDump mode.
 -h           Displays this message.

– CHANGELOG –

2009-10-03: TraceHook v0.0.2
 * Added dump path manipulation option.
 * Added time interval option.
 * Added additional dump file format - Windows MiniDump.
 * Fixed a Denial of Service vulnerability described at http://j00ru.vexillium.org/?p=141#comment-69
 * Fixed many other minor code issues

– TODO –

*** TraceHook v0.0.2 --> v0.0.3
 - Move the dumping process entirely to user-mode, for easier development and safer execution
 - Change the ProcessList structure to sth based on LIST_ENTRY (safer solution ?)
 - Add more dump file formats, i.e. module-only dumps
 - Add support for multiple malware process trees
 - Create some kind of GUI, user-friendly interface

Zachęcam do radosnego ściągania i testowania aplikacji! ;>

Słów kilka o SecDay 2k9

Po kolejnym miesiącu idle’owania, postanowiłem (jak zwykle?) wziąć się poważniej za rozwój bloga i zacząć regularnie postować. Co z tego wyjdzie – zobaczymy. Aktualny wpis poświęcony jest zakończonej przedwczoraj konferencji SecDay 2009, odbywającej się we Wrocławiu. Jak zdążyłem już wspomnieć tutaj, miałem przyjemność poruszyć IMHO bardzo interesujący temat bootkitów – szczególnie w kontekście działania na maszynach pod kontrolą systemów operacyjnych z rodziny Microsoft Windows NT.

Na początek kilka słów o samej konferencji – tematy, które zostały w jej trakcie omówione dotyczyły wielu odrębnych dziedzin, związanych z ogólno pojętym bezpieczeństwem komputerowym. Jako, że samo wydarzenie odbywało się przez dwa dni (21-22 września), uczestnicy wysłuchali w sumie 14 wykładów trwających zwykle ~1h. Jak zdążył zaznaczyć już Gynvael Coldwind, w roli prelegentów pojawiło się wielu ludzi zarówno z Hispasecu, jak i teamu Vexillium:

  • Przemysław “Memek” Świercz (Vexillium)
  • Marcin “Icewall” Noga (Hispasec)
  • Adam “pi3″ Zabrocki (Hispasec)
  • Gynvael Coldwind (Hispasec, Vexillium)

Większość omawianych tematów była bardzo interesująca (zwłaszcza te, z którymi nie spotykam się na co dzień), a materiały części z nich są publicznie dostępne już w chwili pisania tego posta:

Powyższa lista będzie uzupełniana jeszcze przed najbliższy czas.Z tego co mi wiadomo, pełny komplet prezentacji ma pojawić się na stronie http://www.secday.pl/ w przeciągu kilku dni.

Na koniec udostępniam własną prezentację oraz krótki słowniczek wyrazów obcych, rozdawany na samej konferencji:

  1. Prezentacja [pptx] (Microsoft PowerPoint 2k7)
  2. Prezentacja [pdf]
  3. Materiały konferencyjne [pdf]

Jeśli ktoś bardzo chciałby uzyskać dostęp do kodu źródłowego przykładowego bootkita prezentowanego podczas konferencji, kontakt jest do znalezienia w dziale “O mnie”.

Update: Zdecydowałem się jednak upublicznić kod SecDay2k9 Bootkit. Kompletna, kompilowalna paczka jest obecnie do ściągnięcia tutaj.

Do zobaczenia za rok! ;>

Publiczny TraceHook v0.0.1

Dzisiejszy post chciałbym rozpocząć od krótkiej uwagi w stronę wszystich komentujących – pomimo postów pojawiających się w dwóch językach, okazuje się, że komentarze w podobny sposób rozdzielane nie są (i w najbliższym czasie nie będą). W związku z tym zwracam się z prośbą o używanie języka angielskiego zarówno w komentarzach czytanych po polsku, jak i po angielsku.

Co się tyczy samego tematu posta, wprowadziłem dziś kilka drobnych poprawek do projektu TraceHook oraz postanowiłem, że udostepnię go szerszej publiczności – w razie zgłaszanych błędów/propozycji rozbudowy, będę miał wiekszą motywację, żeby wrócić do jego rozwoju.

TraceHook to niewielka aplikacja śledząco-zrzucająca, w zamyśle przeznaczona głównie do obserwowania działania i zbierania informacji o określonym malware działającym w obrębie maszyny wirtualnej. Na początku wybierany jest program bazowy, uruchamiany bezpośrednio przez TraceHook. Oprócz głównej aplikacji, którą monitorujemy, rejestrowane są także zdarzenia procesów-dzieci, tworzonych w czasie działania. Kiedy wykonane zostają wszystkie kroki pozwalające na empiryczną analizę zachowania malware, program zrzuca całą dostępną przestrzeń adresową procesu do pliku znajdującego się w ściśle ustalonym katalogu C:\dump.

Sam silnik aplikacji jest modułem jądra (sterownikiem), otrzymujący informacje o nowo-powstałych procesach korzystając z udokumentowanej funkcji PspCreateProcessNotifyRoutine. Jako taki nie podpina się bezpośrednio do żadnego z procesów działających w sytemie.

Kompletna paczka (EXE + źródła): TraceHook.zip (40 kB)

Przykład działania:

– User-mode TraceHook.EXE log –

-=*( TraceHook v0.0.1 by j00ru//vx )*=-
 [INFO]  Loading driver... Success!
 [INFO]  Opening driver... Success!
 [INFO]  Querying for the driver version...
 [INFO]  Driver Version: TraceHook driver v0.0.1 by j00ru//vx
 [INFO]  Adding the current ProcessId to the monitor list.
 [INFO]  Creating the C:\dump directory for dump files.
 [INFO]  Creating the target process...Success!
 [INFO]  TraceHook module is active, monitoring the malware processes.
 [INFO]  Press ENTER to stop the analysis.
 [INFO]  A control event has been detected!
 [INFO]  Sending a FINISH signal to the driver...
 [INFO]  Unloading the driver from operating system.

– Kernel-mode TraceHook.SYS log –

00000000    0.00000000    [+] TraceHook.SYS: DriverEntry() called.
 00000001    0.00003157    [+] TraceHook.SYS: Create notify-routine successfully set.
 00000002    0.00005196    [+] TraceHook.SYS: Registering an Unload routine.
 00000003    0.00015896    [+] TraceHook.SYS: The EPROCESS name offset is: 0x0174
 00000004    0.00037826    [+] TraceHook.SYS: Creating a named device for the driver.
 00000005    0.00046291    [+] TraceHook.SYS: Allocating initial table of 4096 DWORD elements.
 00000006    0.00048749    [+] TraceHook.SYS: Initializing synchronization spin-locks.
 00000007    0.00206144    [+] TraceHook.SYS: HandleQueryVersion() called.
 00000008    0.00248272    [+] TraceHook.SYS: HandleAddProcess() called.
 00000009    0.01245382    [+] TraceHook.SYS: NotifyRoutine() called:
 00000010    0.01246862         ParentId:  0x00000198
 00000011    0.01248092         ProcessId: 0x00000290
 00000012    0.01249265         Create:    TRUE
 00000013    0.01252142    [+] TraceHook.SYS: Adding the 0x00000290 process to malware list.
 00000014    4.90317774    [+] TraceHook.SYS: NotifyRoutine() called:
 00000015    4.90319204         ParentId:  0x00000290
 00000016    4.90320396         ProcessId: 0x00000774
 00000017    4.90321541         Create:    TRUE
 00000018    4.90324879    [+] TraceHook.SYS: Adding the 0x00000774 process to malware list.
 00000019    7.22620678    [+] TraceHook.SYS: NotifyRoutine() called:
 00000020    7.22622156         ParentId:  0x00000290
 00000021    7.22623444         ProcessId: 0x000006c8
 00000022    7.22624683         Create:    TRUE
 00000023    7.22628021    [+] TraceHook.SYS: Adding the 0x000006c8 process to malware list.
 00000024    9.93577003    [+] TraceHook.SYS: NotifyRoutine() called:
 00000025    9.93578339         ParentId:  0x00000290
 00000026    9.93579578         ProcessId: 0x00000774
 00000027    9.93580723         Create:    FALSE
 00000028    9.93584347    [+] TraceHook.SYS: Dumping the virtual memory of PID: 0x00000774.
 00000029    9.93639183    [+] TraceHook.SYS: Dump file successfully created.
 00000030    12.97541142    [+] TraceHook.SYS: Dumping done, exiting.
 00000031    17.54358864    [+] TraceHook.SYS: HandleRemoveProcess() called.
 00000032    17.54365158    [+] TraceHook.SYS: HandleFinish() called.
 00000033    17.54456329    [+] TraceHook.SYS: NotifyRoutine() called:
 00000034    17.54457664         ParentId:  0x00000290
 00000035    17.54459000         ProcessId: 0x000006c8
 00000036    17.54460335         Create:    FALSE
 00000037    17.54463768    [+] TraceHook.SYS: Dumping the virtual memory of PID: 0x000006c8.
 00000038    17.54517174    [+] TraceHook.SYS: Dump file successfully created.
 00000039    17.62910080    [+] TraceHook.SYS: NotifyRoutine() called:
 00000040    17.62911415         ParentId:  0x00000198
 00000041    17.62912750         ProcessId: 0x00000290
 00000042    17.62913895         Create:    FALSE
 00000043    17.62916946    [+] TraceHook.SYS: Dumping the virtual memory of PID: 0x00000290.
 00000044    17.62970161    [+] TraceHook.SYS: Dump file successfully created.
 00000045    23.02032471    [+] TraceHook.SYS: Dumping done, exiting.
 00000046    23.89743805    [+] TraceHook.SYS: Dumping done, exiting.
 00000047    23.90094757    [+] TraceHook.SYS: Finish signal successfully handled.
 00000048    24.23270798    [+] TraceHook.SYS: NotifyRoutine() called:
 00000049    24.23274612         ParentId:  0x00000490
 00000050    24.23275948         ProcessId: 0x00000198
 00000051    24.23279381         Create:    FALSE
 00000052    24.23301888    [+] TraceHook.SYS: DriverUnload() called.
 00000053    24.23304558    [+] TraceHook.SYS: Create notify routine removed.

– TODO –

*** TraceHook v0.0.1 –> v0.0.2

 - Add a time interval option - the application would wait i.e. 1 minute and then automatically
 dump the entire process tree, instead of waiting for the user's action
 - Add a dumping path option (instead of fixed C:\dump)
 - Change the ProcessList structure to sth based on LIST_ENTRY (safer solution ?)

Jak zwykle, wszelkie uwagi mile widziane ;>

Wstrzymywanie procesów w systemie Windows, cz. 1

Ostatnimi czasy zaStarCraftczął dręczyć mnie dosyć nietypowy problem – gra w StarCrafta była mocno utrudniona ze względu na ilość działających w tle procesów, wliczając w to kilka instancji IDA, wirtualne maszyny oraz najbardziej uciążliwą… przeglądarkę Firefox. Biorąc pod uwagę fakt, że jest to aplikacja dość pamięciożerna (nie mówiąc o CPU) oraz samą ilość otwartych zakładek wahającą się w granicach 150-200, a także procesor, który bardzo szybko się nagrzewa, wspomniana wcześniej gra, pomimo swoich lat, zaczęła ostro przycinać.

Najbardziej intuicyjnym rozwiązaniem jest w tym przypadku pozbycie się procesu firefox.exe (czy to w sposób delikatny, czy tez zwyczajnie zabijając proces) – reszta nie stanowiła już większego problemu – wykonywanie wirtualnych maszyn można przecież zatrzymać itd. O ile samo zamykanie przeglądarki nie jest trudne ani uciążliwe, problem pojawiał się po skończonej grze, kiedy chciałem wrócić do normalnej pracy – ponowne załadowanie kilkuset zakładek mimo dobrego łącza zajmuje całkiem pokaźna ilość czasu. Kiedy taka sytuacja przytrafia się kilka razy dziennie, zaczyna to być skrajnie frustrujące ;)

Jak można domyśleć się po tytule posta, zdecydowałem się na pewne rozwiązanie pośrednie, mianowicie wstrzymywanie konkretnych procesów na czas, w którym potrzebowałem przydzielić więcej zasobów maszyny konkretnemu procesowi (w tym przypadku grze komputerowej). Podejście to posiada kilka istotnych wad, o których będę wspominał w miarę opisywania konkretnych implementacji tego pomysłu – których, jak się okazuje, istnieje całkiem sporo. Cały post podzielony został na dwie części – w pierwszej z nich przedstawione zostaną wszystkie (lepsze oraz gorsze) autorskie sposoby na wstrzymanie zdalnego procesu, w drugiej natomiast opisany zostanie sam proces dodawania wspomnianej funkcjonalności do dobrze znanej aplikacji Task Manager, będącej podstawowym narzędziem pracy każdego administratora systemu Windows. Przejdźmy jednak do sedna problemu…

Jeśli rzucić okiem na Google, okazuje się, że istnieje całkiem sporo narzędzi służących do wstrzymywania działania konkretnych wątków/całych procesów ([1], [2], [3]). Można domyślać się, że skoro wypuszczonych została taka ilość programów oferujących wymaganą przez nas funkcję, napisanie własnego nie może być zbyt trudne (zapewne system udostępnia specjalny interfejs slużący do tego typu zabiegów) – tak w istocie jest.

Pierwszym udokumentowanym narzędziem w naszych rekach są funkcje SuspendThread oraz ResumeThread. Okazuje się, ze istnieje możliwość zatrzymywania poszczególnych wątków (w szczególności wszystkich) w obrębie zdalnego procesu, jeśli tylko jesteśmy w stanie otworzyć do niego uchwyt z prawem THREAD_SUSPEND_RESUME. Na tym założeniu opiera się rozwiązanie zaprezentowane na portalu CodeProject[1]. Autor wspomnianego artykułu listuje wszystkie watki otwartego procesu, używając zestawu funkcji z tzw. ToolHelp32 API[6]. Bardziej brutalnym podejściem do problemu listowania aktywnych wątków mogłaby być próba otwarcia wątków o wszystkich możliwych numerach identyfikacyjnych i, w przypadku sukcesu, kolejne ich zatrzymywanie. Rozwiązanie to ma jednak kilka poważnych wad, których nie można przemilczeć:

  • Istnieją aplikacje wielowątkowe, które silnie polegają na kolejności wykonywanych operacji. Mogą one źle znosić zatrzymywanie/wznawianie kolejnych wątków – jest to w praktyce bardzo niebezpieczne podejście (sytuacja opisana przez samego autora aplikacji).
  • W pewnych okolicznościach może mieć miejsce sytuacja wyścigu (en. race condition). W momencie, kiedy połowa wątków została już obsłużona, któryś z pozostałych wątków tworzy kolejny, umieszczany w pierwszej części listy. W takiej sytuacji program “nie zauważy” nowo powstałego wątku i pozwoli na jego dalsze wykonywanie, co może prowadzić do kolejnych błędów, a w konsekwencji do crashu aplikacji.
  • Mimo, ze proces, którego wszystkie watki zostały wstrzymane może zostać uznany za całkowicie “bezbronny”, nie jest to do końca prawda. W dalszym ciągu możliwe jest zdalne tworzenie nowych, aktywnych wątków w obrębie danego procesu – ma to miejsce przykładowo w przypadku procesów konsolowych, w kontekście których CSRSS ma zwyczaj tworzyć specjalne wątki obsługujące zdarzenia okna konsoli (Ctrl+C, Ctrl+Break, Właściwości Okna itd).

Powyższe rozwiązanie jest o tyle dobre, ze korzysta z aktywnie wspieranych funkcji API systemu, co do których istnienia w kolejnych wersjach systemu nie ma większych wątpliwości. W dokumentacji znajduje się jednak adnotacja mówiąca, ze opisane wyżej funkcje powinny być stosowane głównie w programach typu debugger, w sytuacji w której aplikacja “nadzorująca” posiada całkowitą kontrole nad sposobem wykonywania analizowanego procesu. Okazuje się jednak, ze istnieją również inne sposoby na czasowe wstrzymanie aktywnego procesu!

Pomimo faktu, ze nie istnieje żadna publicznie dostępna funkcja SuspendProcess, która byłaby w stanie w całości wykonać oczekiwane przez nas zadanie, warto pogrzebać głębiej – same funkcje systemowe (oraz wrappery dostępne w bibliotece systemowej ntdll.dll) mogą nierzadko okazać się niezwykle przydatne, kiedy zawiodą metody dobrze znane i powszechnie uznane za stabilne. Na stronie Windows System Call Table[10] dostępna jest szczegółowa lista syscalli Windowsa, począwszy od wersji NT SP3, a kończąc na Windows Vista SP0. Jak można zaobserwować, funkcja o nazwie NtSuspendProcess została wprowadzona wraz z Windowsem XP SP0 i działa na wszystkich nowych wersjach tego systemu. Wydaje się to być całkiem dobrą alternatywą dla rozwiązania manualnego, biorąc pod uwagę fakt, że w tym wypadku samo jądro odpowiedzialne jest za zablokowanie dostępu do wątków oraz uniknięcie niepożądanych sytuacji wyścigu. Jako, ze kod jądra Windows XP oraz 2003 Server jest dostępny w obrębie projektu Windows Research Kernel[12], możemy zajrzeć wgłąb syscalla, by dowiedzieć się, co naprawdę stoi za eksportowaną funkcją kernela:

(...)
if (ExAcquireRundownProtection (Process->RundownProtect)) {

for (Thread = PsGetNextProcessThread (Process, NULL);
Thread != NULL;
Thread = PsGetNextProcessThread (Process, Thread)) {

PsSuspendThread (Thread, NULL);
}

ExReleaseRundownProtection (Process->RundownProtect);

Status = STATUS_SUCCESS;
} else {
Status = STATUS_PROCESS_IS_TERMINATING;
}

return Status;
(...)

Jak widać, funkcja nie wykonuje zasadniczo dodatkowego kodu – iterując po wszystkich dostępnych watkach, zatrzymuje każdy kolejny, dbając jednocześnie o to, by nie doszło do sytuacji zabicia aktualnie wstrzymywanego procesu (ExAcquireRundownProtection (&Process->RundownProtect)). Rozwiązanie to posiada pewną istotną przewagę nad poprzednimi – cały proces wykonywany jest przez jądro systemu. Z drugiej strony, korzystając z niedokumentowanego interfejsu API, nie możemy mieć pewności, że w kolejnej wersjisystemu funkcja nie zostanie z pewnych przyczyn usunięta (choć jest to skrajnie nieprawdopodobne).

Po pobieżnym omówieniu dwóch bardziej intuicyjnych metod, przyszedł czas na ciekawsze pomysły. Wykorzystaliśmy prawdopodobnie wszystkie metody służące bezpośrednio do wstrzymywania pewnych jednostek egzekucji (wątków, procesów). Oprócz nich, system udostępnia również pewne funkcjonalności, których skutkiem ubocznym jest interesujący nas efekt. Jedna z takich funkcjonalności jest możliwość debugowania zdalnych procesów. Jak można łatwo zauważyć, w momencie podpiecia sie aplikacji debuggera pod proces docelowy, wszystkie watki debugowanej aplikacji zostają tymczasowo wstrzymane – jest to dobrze udokumentowane zachowanie funkcji DebugActiveProcess[7] oraz DebugActiveProcessStop[8]. Użycie tych funkcji w kontekście naszego procesu uważam za jedno z lepszych posunięć – zarówno ich użycie jak i działanie jest bardzo proste z punktu widzenia programisty. Do funkcji wstrzymującej proces istnieje również funkcja odwrotna, ktora pozwala na całkowite przywrócenie poprawnego wykonywania programu. Ponadto, nie jesteśmy zmuszeni do wywoływania żadnych dodatkowych funkcji API – jedynym parametrem DebugActiveProcess(Stop) jest numer identyfikacyjny procesu.

Poniżej znajduje sie przykład bardzo prostej aplikacji suspendujacej/resumujacej proces oznaczony PIDem przekazanym jako argument programu:

#define _WIN32_WINNT 0x0600
#include <cstdio>
#include <windows.h>
using namespace std;

int main(int argc, char** argv)
{
DWORD PID;
BOOL Ret;

if(argc!=2)
{
puts("Usage: suspend.exe <PID>");
return 1;
}

PID = atoi(argv[1]);
printf("[+] DebugActiveProcess: 0x%.8x\n",(Ret=DebugActiveProcess(PID)));
if(!Ret)
{
printf("[-] Function failed. LastError: 0x%.8x\n",(UINT)GetLastError());
return 1;
}

printf("[+] Click ENTER to resume the process...");
getchar();

printf("[+] DebugActiveProcessStop: 0x%.8x\n",DebugActiveProcessStop(PID));
return 0;
}

Jak widać, opisywane funkcje SA banalne w użyciu i zadowalająco spełniają swoja role. Musimy jednak pamiętać o oczywistych wadach związanych ze specyfika działania używanych funkcji. Przykładowo, przez cały czas, w którym jesteśmy podpięci do procesu docelowego, nie jest możliwe jego normalne debugowanie (zewnętrzny debugger nie posiada możliwości dopięcia się do interesującego nas procesu). Jest to jednak wada znikoma, która w rzeczywistości nie powinna być szczególnym utrudnieniem. Poza tym, w przypadku zniknięcia procesu debuggera (w tym przypadku suspend.exe), nastąpi zamkniecie wszystkich debugowanych przez niego programów. Byłby to w istocie poważny problem, gdyby nie fakt, ze istnieje funkcja SetDebugKillOnExit[9], która umożliwia nam ustawienie debugowanych procesów jako “niezależnych” – w przypadku zabicia debuggera, wszystkie procesy-dzieci zostają zwyczajnie odpięte od zewnętrznego procesu i normalnie kontynuują wykonywanie.

Ostatnia metoda, która przyszła mi do głowy to użycie systemowego procesu CSRSS.EXE i efektów ubocznych pewnych działań na obsługiwanym przez niego oknie konsoli. Jako, ze w systemie Windows aplikacje konsolowe maja bardzo ograniczony dostęp do wyglądu i zachowania konsoli w obrębie której działają, szczegółów dotyczących obsługi wyglądu powinniśmy szukać w kodzie procesu Client/Server Runtime Subsystem[11] (wyżej wspomniany CSRSS), a konkretnie modułu odpowiedzialnego za tworzenie oraz obsługę zdarzeń okien konsoli – \Windows\system32\winsrv.dll. Mój pomysł opiera się na spostrzeżeniu, ze w momencie, w którym użytkownik aplikacji decyduje się na użycie jednej z opcji dostępnej w menu kontekstowym, która przestawia konsole w inny tryb (przykład – tryb Oznacz), dostęp do obowiązującego bufora konsoli zostaje zablokowany, aż do czasu powrotu do trybu wcześniejszego.

Warto zwrócić uwagę na fakt, ze metoda ta stosuje się tylko dla procesów:

  • Używających okna konsoli oraz aktywnie z niego korzystających (wykonujących operacje wymagające dostępu do bufora ekranu)
  • Lokalnych – metoda nie ma znajduje zastosowania w przypadku zdalnych procesów – wynika to z (1)

Jest to rozwiązanie skrajnie niepraktyczne, jednak obrazuje pewien schemat myślenia – często aby osiągnąć wymagany przez nas cel niekoniecznie musimy odwoływać się do konkretnych (udokumentowanych lub nie) funkcji API – bardzo często lepszym rozwiązaniem może być wykorzystanie efektów ubocznych działania mechanizmów obecnych w systemie, które w pewnym stopniu możemy przystosować “pod siebie”.

Cztery opisane wyżej metody mogą (z lepszym lub gorszym skutkiem) zostać użyte do wstrzymania zdalnego procesu – niewykluczone, ze istnieją również inne, których nie udało mi się wymyślić. Kompletna paczka z imlpementacją wszystkich wymienionych technik dostępna jest tutaj.

W następnym poście pokażę, w jaki sposób możemy zmodyfikować działanie TaskMgr.exe, tak by udostępniał on możliwość wstrzymania aplikacji na żądanie, bez konieczności uruchamiania dodatkowych programów narzędziowych. Na koniec garść przydatnych linków, które mogą rzucić nieco światła na omawiany temat – zachęcam do dzielenia się opiniami oraz nowymi, ciekawymi pomysłami w komentarzach!

Linki:

  1. CodeProject Win32 Process Suspend/Resume tool
  2. PsSuspend v1.06
  3. Command Line Process Viewer/Killer/Suspender
  4. SuspendThread
  5. ResumeThread
  6. Tool Help Functions
  7. DebugActiveProcess
  8. DebugActiveProcessStop
  9. DebugSetProcessKillOnExit
  10. Windows System Call Table
  11. Client/Server Runtime Subsystem
  12. Windows Research Kernel

DebugSetProcessKillOnExit

Nadchodząca konferencja SecDay

Chciałbym z radością poinformować wszystkich czytelników mojego skromnego bloga, że będę miał okazję wystąpić na nadchodzącej konferencji poświęconej ogólno pojętemu bezpieczeństwu komputerowemu – SecDay! ;)

Tematem prezentacji jest praktyczne podejście do tworzenia tzw. bootkitów – bloków kodu umieszczanych na nośnikach bootowalnych, będących w stanie przejąć całkowitą kontrolę nad systemem operacyjnym zainstalowanym na lokalnym dysku twardym. Postaram się pokazać, jak proste jest zawładnięcie maszyną, do której potencjalny włamywacz ma fizyczny dostęp, przedstawiając kolejne kroki, jakie wykonuje “niewidzialny” bootkit w celu osiągnięcia wyznaczonego celu (ominięcie procesu autoryzacji użytkowników etc). Więcej dowiecie się na samej konferencji, na którą serdecznie zapraszam!