Investigating Memory Analysis Tools – SSDT Hooking via Pointer Replacement

In this blogpost we will briefly explain a well known Syscall hooking technique (a more detailed explanation can be gathered from e.g. used by multiple malware samples (like the laqma trojan) and right after discuss how some memory analysis tools have trouble in the analysis and/or reporting of these.

Before we go further, I just shortly wanted to say, that this post is not intended to be a bashing of any tool. We have the greatest respect for all the effort and work which has been and most probably will be done in the future.

Table of Contents
A short introduction in system services dispatching
An additional Tool – TBD
Support for additional SSDTs?

A short introduction in System Services Dispatching

Before we dive into the topic, we need to clarify how Syscall hooks are implemented and which system components are involved. This chapter however, is only a quick wrap up, so take a look at the Infosec Institute link above for more detailed information.

Windows uses two access modes to protect critical system data from being accessed and/or modified from user applications: kernel mode and user mode. Code executed by a user runs in user mode (user application) and operating system code (e.g. Executive, Kernel, Device drivers,Windowing and Graphics, Hardware abstraction layer (HAL)) runs in kernel mode. The abstract privilege separation concept is enforced by the architecture of the processor. Windows uses privilege level 0 (or ring 0 – highest privileged level) for kernel mode and privilege level 3 (or ring 3 – lowest privileged level) for user mode. As user applications are executed in user mode and kernel code in kernel mode, there needs to be a defined method how service requests are transmitted between these two processor access modes. The most used method (interface) is called “System Call”.

Explaining the whole system call process is quite extensive, nevertheless it makes sense to at least describe the basics to understand SSDT hooks.

Typically, a system call is a service routine (kernel function) located in the kernel, which manages e.g. I/O service requests from user land. For example, when a file needs to be saved to disk, the user land application needs to request a file handle from the kernel, after that the file can be written, and then the file content is committed to the disk. The part which initiates the change between the two processor access modes and the information which service routine will be called, is located in the user space (system call prolog:located in the “ntdll.dll”). The executing part of a system call is implemented in the kernel because only high-privileged code can handle kernel resources (system call epilog).

Microsoft implements and stores important system call information within two kernel structures: the KSYSTEM_SERVICE_TABLE (the fields of this structure can be figured out by analyzing e.g. KiSystemService or KeAddSystemServiceTable) and an array of function pointers ServiceTableBase.

PULONG ServiceTableBase;//Pointer to the array (SSDT) of service routine addreses.
PULONG ServiceCountTableBase;//Not used.
ULONG NumberOfServices;//The number of elements in the SSDT array.
PUCHAR ArgumentTableBase;//pointer to the array (SSPT) of arguments.

The kernel holds two KSYSTEM_SERVICE_TABLE structs: KeServiceDescriptorTable and KeServiceDescriptorTableShadow. KeServiceDescriptorTable contains the native system call table and KeServiceDescriptorTableShadow contains also the native system call table and in addition the system call table for GUI threads.

KeServiceDescriptorTable KeServiceDescriptorTableShadow
8297ab00 8288cf8c nt!KiServiceTable //ServiceTableBase
8297ab04 00000000 //ServiceCountTableBase
8297ab08 00000191 //NumberOfServices
8297ab0c 8288d5d4 nt!KiArgumentTable //ArgumentTableBase
8297ab40 8288cf8c nt!KiServiceTable //ServiceTableBase
8297ab44 00000000 
8297ab48 00000191 /
8297ab4c 8288d5d4 nt!KiArgumentTable /
8297ab50 8fb94000 win32k!W32pServiceTable 
8297ab54 00000000 
8297ab58 00000339 /
8297ab5c 8fb9502c win32k!W32pArgumentTable 


Note: Which one of these tables is used when and why goes beyond the scope of this article.

ServiceTableBase is a pointer to an array of function pointers. These pointers are the pointers (offsets) to the system service routines (native functions), which are used during the system call process. When calling a system service routine from user mode, software interrupts or architecture specific instructions are used to switch from user mode to kernel mode. During this process the service routine number is stored in a register and a specific part of this number is used as index into the array to reference the service routine.

There are two arrays KiServiceTable and W32pserviceTable. The primary array KiServiceTable defines the core system service routines implemented in the NT Kernel Module (ntoskrnl.exe). The second array W32pserviceTable stores GUI functions provided by the Subsystem Kernel Module (win32k.sys).

KiServiceTable W32pServiceTable
8288cf8c 82a8c0ba nt!NtAcceptConnectPort
8288cf90 828d070d nt!NtAccessCheck
8288cf94 82a1ae2b nt!NtAccessCheckAndAuditAlarm
8288cf98 828339f8 nt!NtAccessCheckByType
8288cf9c 82a8d99c nt!NtAccessCheckByTypeAndAuditAlarm
8288cfa0 8290d25a nt!NtAccessCheckByTypeResultList
8fb94000 8fb1f91a win32k!NtGdiAbortDoc
8fb94004 8fb37f22 win32k!NtGdiAbortPath
8fb94008 8f9872c1 win32k!NtGdiAddFontResourceW
8fb9400c 8fb2ea37 win32k!NtGdiAddRemoteFontToDC
8fb94010 8fb39675 win32k!NtGdiAddFontMemResourceEx
8fb94014 8fb20173 win32k!NtGdiRemoveMergeFont


Ok, let’s see how this works:

dps nt!KeServiceDescriptorTable L4
82982b00 82894f8c nt!KiServiceTable //ServiceTableBase
82982b04 00000000
82982b08 00000191
82982b0c 828955d4 nt!KiArgumentTable

This is the system call prolog of the win32 function CreateFile located in the “ntdll.dll”. Let us just focus on the system call number.

uf ntdll!NtCreateFile
777c56b0 b842000000 mov eax,42h //system call number
777c56b5 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub
777c56ba ff12 call dword ptr [edx]
777c56bc c22c00 ret 2Ch

Adding the offset 42h to the ServiceTableBase and we get the real address of the system call.

dps nt!KiServiceTable + (42h*4) L1
828ab094 82a7fd90 nt!NtCreateFile

So as already mentioned above, every system call is identified by a number which is an index into KiServiceTable or W32pserviceTable.




How would an API Hook now look like?

All you need to do is, determine the pointer to the system function, which will be hooked (either in the KiServiceTable or W32pserviceTable),o and overwrite the corresponding function pointer with a pointer to your hooking function (e.g. located in your malicious driver).

Note: In the Windows x64 kernel, simply overwriting a pointer of a system function is not possible anymore. The Kernel Patch Protection “PatchGuard” is intended to protect critical kernel structures from being easily modified from unauthorized entities. Patch Guard is designed to protect e.g. the following critical structures:


  • SSDT (System Service Descriptor Table)
  • GDT (Global Descriptor Table)
  • IDT (Interrupt Descriptor Table)

Nevertheless, directly hooking the SSDT should be still possible, especially with x86 architectures.

As we now have a rough understanding on how rootkits may hook APIs, let’s have a look at the analysis tools.
All following tests have been done on a Windows 7 SP1 x86 VM using unsigned drivers.



Volatility is the best known open source memory analysis tool and has been also the starting point of this research. More specifically, it was this line from “The Art of Memory Forensics” (which is a really great book from some members of the Volatility Project, which we personally can recommend everybody interested in memory forensics), that is used to filter for malicious pointers:

$ python -f laqma.vmem ssdt --profile=WinXPSP3x86 | egrep -v '(ntoskrnl\.exe|win32k\.sys)'

The problem with the “egrep” part, when generally applied to memory samples, will come clear by looking at an excerpt of the normal output of the ssdt module (using the most current version from GIT at the time of writing):

[x86] Gathering all referenced SSDTs from KTHREADs...
Finding appropriate address space for tables...
SSDT[0] at 80501030 with 284 entries
Entry 0x0000: 0x8059849a (NtAcceptConnectPort) owned by ntoskrnl.exe
Entry 0x0001: 0x805e5666 (NtAccessCheck) owned by ntoskrnl.exe
Entry 0x0002: 0x805e8ec4 (NtAccessCheckAndAuditAlarm) owned by ntoskrnl.exe
SSDT[1] at bf997600 with 667 entries
Entry 0x1000: 0xbf934ffe (NtGdiAbortDoc) owned by win32k.sys
Entry 0x1001: 0xbf946a92 (NtGdiAbortPath) owned by win32k.sys
Entry 0x1002: 0xbf8bf295 (NtGdiAddFontResourceW) owned by win32k.sys

For each SSDT entry, the corresponding module name is printed. The expected result when now applying the egrep expression is to see only pointers to potentially malicious modules or no pointers at all (if only the NT module and the GUI subsystem is referenced). But what happens if a kernel module is named “EVIL_ntoskrnl.exe” or “win32k.sys_EVIL.sys”?

As bashing (especially regex bashing) is not the intention of this post, and more important the given command line sample is most probably not intended for general usage but only for the specific case mentioned in the book (the laqma trojan), let’s take a look further.

This regex let us start thinking what might happen, if an SSDT pointer points to a module with the exact same name as the legitimate one. At this point Dominik came into play, as he loooves to write windows kernel drivers because it is so easy going 😉 So after quite a while, we had two kernel drivers, one hooking the first SSDT and one for the second. When now choosing corresponding names for these files (in this case “ntoskrnl.exe” and “win32k.sys”) and loading the drivers, the output with volatility looks like this (stripped down to the relevant parts):

[x86] Gathering all referenced SSDTs from KTHREADs...
Finding appropriate address space for tables...
SSDT[0] at 8269dd9c with 401 entries
Entry 0x0000: 0x82899c28 (NtAcceptConnectPort) owned by ntoskrnl.exe
Entry 0x0001: 0x826e040d (NtAccessCheck) owned by ntoskrnl.exe
Entry 0x0002: 0x82829b68 (NtAccessCheckAndAuditAlarm) owned by ntoskrnl.exe
Entry 0x0105: 0x94b7540a (NtQuerySystemInformation) owned by ntoskrnl.exe
Entry 0x0106: 0x82889ddd (NtQuerySystemInformationEx) owned by ntoskrnl.exe
Entry 0x0107: 0x82897af7 (NtQuerySystemTime) owned by ntoskrnl.exe
SSDT[1] at 8de66000 with 825 entries
Entry 0x1000: 0x8ddf3d37 (NtGdiAbortDoc) owned by win32k.sys
Entry 0x1001: 0x8de0bc23 (NtGdiAbortPath) owned by win32k.sys
Entry 0x1002: 0x8dc671ac (NtGdiAddFontResourceW) owned by win32k.sys
Entry 0x1103: 0x8de0d5f8 (NtGdiRemoveFontMemResourceEx) owned by win32k.sys
Entry 0x1104: 0x8dd8eed1 (NtGdiResetDC) owned by win32k.sys
Entry 0x1105: 0x94b6f41e (NtGdiResizePalette) owned by win32k.sys
Entry 0x1106: 0x8dccc6a9 (NtGdiRestoreDC) owned by win32k.sys
Entry 0x1107: 0x8dd94aad (NtGdiRoundRect) owned by win32k.sys

Can you spot the hooks?
Look for the entries 0x0105 and 0x1105. Anything suspicious?

So even if you now use another regex, the output will not show any of those hooks. The only way you might indicate a hook is the pointer address which points at a much higher address. But the difference might not be in all cases so conspicuous as in this case.

Note: The function names are not resolved via offsets (which is normally done in the context with PDB files) but are statically referenced to specific entries depending on the current profile. So with Windows 7, 32Bit and Service Pack 1, Entry 0x105 will always state NtQuerySystemInformation, no matter where the pointer points to.

After coming to this point we wondered what the result would be using other tools. The tools chosen were Rekall, GMER and ****.


Rekall is a fork of volatility but extensively rewritten. Especially the ssdt module seems completely rewritten and provides in fact a better output for our hooks (using version 1.4.1):

************** Table 0 @ 0x8269dd9c **************
Entry Target Symbol
---------- ---------- ------
0x0 0x82899c28 nt!NtAcceptConnectPort
0x1 0x826e040d nt!NtAccessCheck
0x2 0x82829b68 nt!NtAccessCheckAndAuditAlarm
0x103 0x829245d3 nt!NtQuerySystemEnvironmentValue
0x104 0x82924bc7 nt!NtQuerySystemEnvironmentValueEx
0x105 0x94b7540a nt + 0x140a
0x106 0x82889ddd nt!NtQuerySystemInformationEx
0x107 0x82897af7 nt!NtQuerySystemTime
************** Table 1 @ 0x8de66000 **************
Entry Target Symbol
---------- ---------- ------
0x0 0x8ddf3d37 win32k!NtGdiAbortDoc
0x1 0x8de0bc23 win32k!NtGdiAbortPath
0x2 0x8dc671ac win32k!NtGdiAddFontResourceW
0x103 0x8de0d5f8 win32k!NtGdiRemoveFontMemResourceEx
0x104 0x8dd8eed1 win32k!NtGdiResetDC
0x105 0x94b6f41e win32k + 0x141e
0x106 0x8dccc6a9 win32k!NtGdiRestoreDC
0x107 0x8dd94aad win32k!NtGdiRoundRect

In the case of the first SSDT, rekall resolves the “ntoskrnl” name to “nt” but does not resolve the offset to a valid function name which makes it easy to spot. We tried to fool rekall by modifying the debugging information within the PE header according to the current NT and win32k module (adjusting the GUID and the PDB file name) but it didn’t change anything. This was the time to take a look in the code and do some debugging (luckily rekall is also open source). During this process and a few more dumps, we identified a promising scenario. In the case of the kernel module we were out of luck with our simple approach, but when hooking the shadow with “win32k.sys”, on some memory dumps we got this result:

************** Table 1 @ 0x8f2c6000 **************
Entry Target Symbol
---------- ---------- ------
0x0 0x8f253d37 win32k + 0x193d37
0x1 0x8f26bc23 win32k + 0x1abc23
0x103 0x8f26d5f8 win32k + 0x1ad5f8
0x104 0x8f1eeed1 win32k + 0x12eed1
0x105 0x9535255c win32k!xxxConnectService + 0x125
0x106 0x8f12c6a9 win32k + 0x6c6a9

As you can see, our hook is now the only pointer which is resolved to a function name (xxxConnectService) with an additional offset of 0x125. It seems like it uses one module as the base in respect to the address space, which means the module in use “wins”. If we would do the effort of creating an appropriate driver and redirect all pointers to our driver with correct offsets, the output would probably look quite normal. Sure, the attack scenario is rather theoretical as it works only
sometimes (didn’t had the time to dive deeper into the rekall code to find the relevant parts), and the effort of providing hooks for all 824 Syscalls … (Dominik? 😉 ), but it would be possible and should be something to keep in mind.

After contacting Michael Cohen (rekall) and Michael Ligh (volatility) and a little feedback round – thanks for that btw. – , the first idea was to show additional information like the path to the binary, which however can easily be manipulated with access to the relevant kernel objects. The most promising approach from our point of view would be an additional test (included in the ssdt plugin), which checks whether or not there is more than instance of the NT module/the Win32 subsystem. If that’s the case, a simple line at the beginning of the respective table output, stating this fact should be enough to indicate that there might be something wrong.

Until there might be some patch/improvement to this plugin, it is easy to do this check manually with the already available plugins “modules” and “modscan” (following the output of rekall’s modules plugin; some columns and lines have been stripped):

Name        Base        File
----------  ----------  -----------
win32k.sys  0x8f0c0000  C:\Windows\System32\win32k.sys
win32k.sys  0x95351000  C:\Users\troopers\Desktop\win32k.sys

Even if the second line would state the correct file path, the fact that “win32k.sys” (or the NT module in the other case) appears more than once is suspicious and deserves a deeper look ; )


While it seems that gmer (version 2.1.19357) does not detect Win32 Subsystem hooks at all, our simple trick didn’t hide us from its detection regarding NT module hooks:

gmer ssdt


An additional Tool (To be disclosed)

Since we are still in the middle of a disclosure process with the vendor, the information on this tool will be added soon (hopefully).

Support for additional SSDTs?

At this point we wondered, whether additional SSDTs are covered by Volatility/Rekall and if they are capable of detecting hooks. As mentioned in various books/online resources, some AV products and the IIS use an additional SSDT and they describe the SERVICE_DESCRIPTOR_TABLE like this:

KSYSTEM_SERVICE_TABLE table3; //(empty)
KSYSTEM_SERVICE_TABLE table4; //(empty)


So we installed several AVs and used different Windows/IIS Versions but weren’t able to identify one instance using an additional SSDT.

Our next approach was to manually add another SDT entry on Windows 7 using the relevant function KeAddSystemServiceTable:

KeAddSystemServiceTable (
 IN PULONG_PTR Base, //Supplies the address of the system service table dispatch table.
 IN PULONG Count [OPTIONAL], //Supplies an optional pointer to a table of per system service counters.
 IN ULONG Limit, //Supplies the limit of the service table.
 IN PULONG Arguments, //Supplies the address of the argument count table.
 IN ULONG Index //Supplies index of the service table (position in the struct).

Bottom line: We were out of luck by getting a running Windows with a third SSDT/an additional SDT entry. So we had a look at the code of KeAddSystemServiceTable. for different Windows versions:


Windows XP SP3: KeAddSystemServiceTable Windows Vista SP0: KeAddSystemServiceTable Windows 7 SP1: KeAddSystemServiceTable
mov edi,edi
push ebp
mov ebp,esp
cmp dword ptr [ebp+18h],3
ja nt!KeAddSystemServiceTable+0x6b
xor al,al
pop ebp
ret 14h
mov edi,edi
push ebp
mov ebp,esp
cmp dword ptr [ebp+18h],1
ja nt!KeAddSystemServiceTable+0x6b
xor al,al
pop ebp
ret 14h
mov edi,edi
push ebp
mov ebp,esp
cmp dword ptr [ebp+18h],1
ja nt!KeAddSystemServiceTable+0x6b
xor al,al
pop ebp
ret 14h




As can be seen above, since Windows Vista SP0 it is not possible to add an additional KSYSTEM_SERVICE_TABLE. The function KeAddSystemSericeTable checks if the Index value is larger than 1 (cmp dword ptr [ebp+18h], 1     ja nt!KeAddSystemServiceTable+0x6b) and if so, nothing will be done. It also verifies if KeServiceDescriptorTable and KeServiceDescriptorTableShadow are already occupied (if the values 0 or 1 are supplied as index), and if that’s the case will also do nothing. So you cannot just overwrite an existing entry (at least not with this function 😉 ) or add a third one. The support for multiple KSYSTEM_SERVICE_TABLEs has changed across versions of Windows since the initial release of windows NT and in all that time, as far as we know, it has never been documented, so we wanted to share this little Bit of information.


Frank and Dominik