Auditing the System Call Table

When malicious, kernel-level code is installed on the system, one action it may take is to hook various system services. What this means is that it takes some standard piece of operating system functionality and replaces it with its own code, allowing it to alter the way all other programs use the OS. For example, it may hook functions involved in opening registry keys, and modify their output so as to hide registry keys the rootkit uses. As system calls are the primary interface between user and kernel mode, the system call table is a popular place to do such hooking.

It's worth noting that many security products also make heavy use of hooking. One common example is antivirus software; among the many functions it hooks is NtCreateProcess (used, as the name suggests, to start a new process) so that it can do its on-demand scanning of any newly launched programs. For this reason, it's not safe to assume that any hooking of system calls is malicious; in fact, some of the most suspicious-looking things initially often turn out to be security software.

Still, it may be quite useful to be able to examine the system call table of a memory image during an investigation, in order to detect any hooks that shouldn't be there. To do this, we'll first look at how system calls work in Windows and lay out the data structures that are involved. I'll then describe a Volatility plugin that examines each entry in the system call table, gives its symbolic name, and then tells what kernel module owns the function it points to. If you want to skip the learning experience and get straight to the plugin, you can download it here and place it in your memory_plugins directory. You'll also need to get my library for list walking and place it in "forensics/win32".

If you look at any of the native API functions, like ZwCreateFile, you'll notice that they all look almost identical:
lkd> u nt!ZwCreateFile
nt!ZwCreateFile:
804fd724 b825000000 mov eax,25h
804fd729 8d542404 lea edx,[esp+4]
804fd72d 9c pushfd
804fd72e 6a08 push 8
804fd730 e83cf10300 call nt!KiSystemService (8053c871)
804fd735 c22c00 ret 2Ch
We see that the function just places the value 0x25 into eax, points edx at the stack, and calls nt!KiSystemService. It turns out that this value, 0x25, is the system call number that corresponds to the CreateFile function.

Without going into too much detail about how KiSystemService works, the function essentially takes the value in the eax register, and then looks up that entry in a global system call table. The table contains function pointers to the actual kernel-land functions that implement that system call.

But, of course, the situation isn't quite as simple as that. In fact, Windows is designed to allow third party developers to add their own system calls. To support this, each _KTHREAD contains a member named ServiceTable which is a pointer to a data structure that looks like this:
typedef struct _SERVICE_DESCRIPTOR_TABLE {
SERVICE_DESCRIPTOR_ENTRY Descriptors[4];
} SERVICE_DESCRIPTOR_TABLE;

typedef struct _SERVICE_DESCRIPTOR_ENTRY {
PVOID KiServiceTable;
PULONG CounterBaseTable;
LONG ServiceLimit;
PUCHAR ArgumentTable;
} SERVICE_DESCRIPTOR_ENTRY;
As you can see, we can actually have up to four separate system service tables per thread! In practice, however, we only see the first two entries in this array filled in: the first one points to nt!KiServiceTable, which contains the functions that deal with standard OS functionality, and the second points to win32k!W32pServiceTable, which contains the functions for the GDI subsystem (managing windows, basic graphics functions, and so on). For system call numbers up to 0x1000, the first table is used, while for the range 0x1000-0x2000 the second table is consulted (this may generalize for 0x2000-0x3000 and 0x3000-0x4000, but I haven't tested it).

To take a look at the contents of these two tables, we can use the dps command in WinDbg, which takes a memory address and then attempts to look up the symbolic name of each DWORD starting at that address. To examine the full table, you should pass dps the number of DWORDS you want to examine -- the exact number will be the value found in the ServiceLimit member for the table you're interested in. For example:
lkd> dps nt!KiServiceTable L11c
805011fc 80598746 nt!NtAcceptConnectPort
80501200 805e5914 nt!NtAccessCheck
80501204 805e915a nt!NtAccessCheckAndAuditAlarm
80501208 805e5946 nt!NtAccessCheckByType
[...]
8050128c 8060be48 nt!NtCreateEventPair
80501290 8056d3ca nt!NtCreateFile
80501294 8056bc5c nt!NtCreateIoCompletion
[...]
Note that NtCreateFile is the 0x25th entry in the table, as we expected. On a system with no hooks installed, all functions in nt!KiServiceTable will point into the kernel (ntoskrnl.exe), and all functions in win32k!W32pServiceTable will be be inside win32k.sys. If they don't, it means the function has been hooked.

The plugin for Volatility, then, works as follows. First, we go over each thread in each process, and gather up all distinct pointers to service tables. We examine all of them in case one thread has had its ServiceTable changed while the others remain untouched. Then we display each entry in each (unique) table, along with the name it usually has (in an unhooked installation), and what driver the function belongs to. Here's some sample output:
$ python volatility ssdt -f xp-laptop-2005-07-04-1430.img
Gathering all referenced SSDTs from KTHREADs...
Finding appropriate address space for tables...
SSDT[0] at 804e26a8 with 284 entries
Entry 0x0000: 0x805862de (NtAcceptConnectPort) owned by ntoskrnl.exe
Entry 0x0001: 0x8056fded (NtAccessCheck) owned by ntoskrnl.exe
Entry 0x0002: 0x8058945b (NtAccessCheckAndAuditAlarm) owned by ntoskrnl.exe
[...]
Entry 0x0035: 0xf87436f0 (NtCreateThread) owned by wpsdrvnt.sys
[...]
SSDT[1] at bf997780 with 667 entries
Entry 0x1000: 0xbf93517d (NtGdiAbortDoc) owned by win32k.sys
Entry 0x1001: 0xbf946c1f (NtGdiAbortPath) owned by win32k.sys
[...]
Here we can see that the NtCreateThread function has been hooked by wpsdrvnt.sys. A little Googling shows that this driver is a part of Sygate Personal Firewall -- as mentioned before, security products are the most common non-malicious software that hooks kernel functions.

In closing, I should mention one caveat to using this tool: at the moment, the names of the system calls are hardcoded with the values derived from WinDbg on Windows XP SP2. As demonstrated by the Metasploit System Call Table page, the order and number of entries in the system call table change between different versions of Windows, so make sure that you only analyze SP2 images with this plugin! As always, patches are welcome if you want to adapt this to deal with other versions of Windows.

Now go forth, and catch those rootkits!

Comments

Jamie Butler said…
If you want to find the function names without hardcoding them, you can use ntdll.dll. Since all functions in the SSDT begin with Nt, you can parse ntdll.dll's exports looking for all functions beginning with Nt. These functions correspond to the SSDT function but are in userland. These stubs in userland move the SSDT index into EAX and then later call SYSENTER or INT 2E. However, the move instruction is the first instruction in the ntdll.dll stub. Here is what it looks like in Windbg:

lkd> u ntdll!NtCreateProcess
ntdll!ZwCreateProcess:
7c90d754 b82f000000 mov eax,2Fh

Because you parsed ntdll.dll's exports you have the name of the function and now you have its index. Use this method to build your name table for the SSDT and your algorithm will know the names across platforms.

Jamie Butler
H. Carvey said…
Brendan,

Very, very cool stuff! I like how you explain it, and then just come right out with, "...here's the module to do the actual work...".

Great work!

Popular posts from this blog

Someone’s Been Messing With My Subnormals!

Decrypting LSA Secrets

SysKey and the SAM