The Quest for the SSDTs
(originally published on codeproject.com)
Unlike the pursuit for the Holy Grail, this quest will succeed but the journey will be a lil bit tricky.
Have you heard about software repositories where authors often drop code without a single line of relevant explanation? Of course you did, but here we must not do the same.
I have the obligation to provide relevant information on a difficult topic, in a go-easy way and within the space of a single page article, in the hope that in the end everything will make sense to everybody.
This may not be that easy, but let’s move on through 5 steps and hope for the best:
- Requirements and Forewarnings
- What are and where are the SSDTs
- How SSDTs are used
- Why are the SSDTs so important?
- Our 32-bit and 64-bit code to find the SSDTs
Requirements and Forewarnings
- Some knowledge of C language is important in order to understand our code – if you don’t have it, you can still continue reading and try to grasp some insight (if you don’t already have, of course) about what we are writing about.
- For Kernel Mode snooping, it is useful to know how to use Windbg, or its command line brother KD. Proper Kernel debugging requires 2 computers, but the target computer can be lodged in a virtual machine (unless we are testing some hardware drivers, which is not the case) so that you don’t have to mess with cables to establish a connection.
- We have tested the Kernel Drivers in all the operating systems they are intended for (i.e., 32-bit Windows 7 and Windows 10 till RedStone 2 or version 1703, 64-bit releases of Windows 7, 8, 8.1 and 10 till Redstone 2, and 64-bit server editions that use the same codebases, namely 2008 R2, 2012 and 2016). Even though the tests were successful, you must only test these Drivers, and any drivers in general, in computers (or virtual machines) you use exclusively for testing.
What Are and Where Are the SSDTs
SSDTs are hidden inside some structures. We must explain and locate them first before we finally locate and explain the SSDTs.
Service Descriptor Table
A Service Descriptor Table is a Kernel structure, shown below, that contains 4 System Service Table (SST) entries.
There are two Service Descriptor Tables in the system, the
nt!KeServiceDescriptorTable and the
nt!KeServiceDescriptorTableShadow (we will use the notation of prefixing structures and function names with the module name, in this case
nt! means from ntoskrl.exe or ntkrnlmp.exe).
System Service Table (SST)
A SST structure, shown below, contains the
ServiceTable field, which is a pointer to the first element of an array of pointers to Kernel routines, in the case of 32-bit operating systems, or to an array of RIP-Relative addresses (and some extra information about the number of arguments) to the base address pointed to by it, in the case of 64-bit operating systems.
Let’s now use
Windbg to have a look at a 32-bit Service Descriptor Table, in this case the
nt!KeServiceDescriptorTable, and its contained SST structures:
81a2a180 8190f20c nt!KiServiceTable
81a2a18c 8190f930 nt!KiArgumentTable
Now, let’s have a look at the other Service Descriptor Table, the
81a2a140 8190f20c nt!KiServiceTable
81a2a14c 8190f930 nt!KiArgumentTable
81a2a150 98f00000 win32k!W32pServiceTable
81a2a15c 98f01628 win32k!W32pArgumentTable
81a2a16c 819245b2 nt!FinalExceptionHandlerPad58
SSDT is an acronym for System Service Dispatch Table.
As mentioned above, there is an array of pointers, in the 32-bit case, or an array of RIP-Relative addresses (plus additional information), in the 64-bit case, that is pointed to by the
ServiceTable field of a SST (System Service Table) – this array is nothing more, nothing less than a SSDT.
Looking at the
Windbg outputs above, we see that there is one clearly identified SST table in the case of
nt!KeServiceDescriptorTable and two in the case of
nt!KeServiceDescriptorTableShadow. Although we see more data in there, from available information, we can only state that from the possible 4 SST entries, the
nt!KeServiceDescriptorTable uses only the first one – it describes the SSDT for the Windows Native APIs exported by ntoskrnl.exe. The
nt!KeServiceDescriptorTableShadow uses 2 SST entries, the first is a copy of the
nt!KeServiceDescriptorTable, the second one,
win32k!W32pServiceTable, describes the SSDT for the User and GDI routines exported by win32k.sys.
How SSDTs are Used
SSDTs provide functionality both to User Mode applications (indirectly, of course) and to Kernel Mode drivers.
We are going to have a look at the User Mode case.
When a User Mode application invokes, directly or indirectly, some Windows API function, many times one or more Kernel routines will be called in the process. Kernel Mode is entered from User Mode through the Sysenter (or Syscall for 64-bit) ASM instruction (in the old days, it was interrupt 0x2E – which is still there, although probably not used that much). The exact Kernel service routine will be specified by a number, called Dispatch ID, entered in the EAX register before the
The first 12 bits of the Dispatch ID is an index into one of the SSDTs. Bits 12 and 13 specify which SSDT. This means that Dispatch IDs up to 0xFFF will be serviced by the
nt!KiServiceTable SSDT and Dispatch IDs between 0x1000 and 0x1FFF will be serviced by the
At this point, if all this is new for you, you may feel a bit lost – the subject is indeed dense and tricky, but try to stick with us.
A 32-bit Example
To clarify a bit the ideas, let’s use
Windbg once again and imagine a User Mode call to
ntdll!NtCreateFile (ntdll.dll is a User Mode DLL containing, among other things, dispatch stubs to Kernel Mode system services):
77ab3250 b875010000 mov eax,175h
77ab3255 e803000000 call ntdll!NtCreateFile+0xd (77ab325d)
77ab325a c22c00 ret 2Ch
77ab325d 8bd4 mov edx,esp
77ab325f 0f34 sysenter
As you see, the Dispatch ID is 0x175. It will be resolved in the
nt!KiServiceTable SSDT to the item in the 0x176th position (the list is zero based), which is
8190f20c 818c573a nt!NtAccessCheck
8190f210 818cbfd8 nt!NtWorkerFactoryWorkerReady
8190f214 81b033b8 nt!NtAcceptConnectPort
190f7d8 81ad7b18 nt!NtCreateTimer2
8190f7dc 81af323a nt!NtCreateIoCompletion
8190f7e0 81a66958 nt!NtCreateFile
A 64-bit Example
Windbg, let’s imagine a User Mode call to
00000000`777ac080 4c8bd1 mov r10,rcx
00000000`777ac083 b852000000 mov eax,52h
00000000`777ac088 0f05 syscall
00000000`777ac08a c3 ret
Here, we see that the Dispatch ID that
ntdll!NtCreateFile (same as
ntdll!ZwCreateFile in User Mode) is using is 0x52.
This Dispatch ID corresponds to 0x53th item in the
fffff800`02a78148 030b4fc7 <- This is our entry
Bits 4-31 of 0x030b4fc7 correspond to the RIP-Relative address in relation to the base of the
nt!KiServiceTable. Bits 0-3 are related to the number of arguments and will not be used here.
To obtain the function address, we shift the value 4 bits to the right and add the result to the table base linear address:
0x030b4fc7 >> 4 = 0x30b4fc
Now add it to the table linear base address:
0xfffff80002a78000 + 0x30b4fc = 0xfffff80002d834fc
Yes, on target!
Why are the SSDTs so Important?
There are many important data structures in the System, they are all central and contribute to the expected functionality of the whole.
Still, SSDTs have been deserving more hype and attention by the general public than the others.
By now, you may already have a feeling about the reasons for that. In the 32-bit times (not so old, actually) bad people from the dark corners of the Internet used to produce (actually they still do, although 32-bit Operating Systems are unfortunately for them in short demand these days) malicious software running in Kernel Mode (so called Rootkits) that modify entries in either the
nt!KiServiceTable or the
win32k!W32pServiceTable diverting System calls to their own code in order to cause troubles. Not only bad people got used to play with the SSDTs, many security products, namely antivirus, used to hook the SSDTs as well in order to receive an immediate alert on virus attacks.
However, 64-bit Windows releases, since Windows XP 64-bit and Windows Server 2003 64-bit SP1, introduced a strong protection feature called Kernel Patch Protection (generally known by the term PatchGuard). PatchGuard makes periodic checks to make sure that a certain number of critical System structures, including the SSDTs, were not modified in the meantime. Security software, namely antivirus, was forced to search for less efficient alternatives. Authors of Rootkits suffered a violent backlash but not a complete defeat – from time to time, they come up with new but short-lived (at least, we want to think they are) ways to bypass the PatchGuard – when aware, Microsoft will produce a new patch to their PatchGuard and issue a new Security Update.
In addition to PatchGuard, compulsory Driver Signature with Class 3 certificates from selected Certification Authorities was a great contribution to System safety.
Our 32-bit and 64-bit Code to Find the SSDTs
The purpose of our code is not to hook any System functions in the SSDTs, actually it is relatively easy to do that when feasible (PatchGuard makes things much less feasible) – there is a vast literature on the subject, even published in books that sell well and hundreds of conferences hosted by specialists have been held on the subject.
Somewhat surprisingly, the tricky part in all this is to find out where the SSDTs really are. Particularly, the
win32k!W32pServiceTable SSDT pointed to by the second SST entry of the
nt!KeServiceDescriptorTab1eShadow is indeed shadowed. On the other hand, locate the
nt!KiServiceTable SSDT for 32-bit Operating Systems is straightforward, the symbol is exported and can be linked with – just read its value. However, the symbol for
nt!KiServiceTable is not exported in 64-bit Windows.
What we will do in our code is locate the
nt!KeServiceDescriptorTab1eShadow. If we succeed (of course, we will), we will know the whereabouts of both the
nt!KiServiceTable and the
win32k!W32pServiceTable – two birds with one shot.
We have built both the Kernel Mode driver and the User Mode application (which also launches the driver and unloads it in the end) with a single Visual Studio 2015 solution – one project for the driver (both 32-bit and 64-bit) and another for the driver (both 32-bit and 64-bit).
The Windows Driver Kit version 10 must be installed in the computer in order to compile our project without modifications.
The User Mode application has slightly different behaviors when compiled for 32-bit or for 64-bit.
So, when testing you must launch the 32-bit driver with the 32-bit application and the 64-bit driver with the 64-bit application.
The code for the driver uses undocumented (or opaque, as Microsoft uses to say) structures and System functions. These undocumented elements work for the range of Operating Systems (mentioned above) we have tested the driver on.
The Kernel Driver
The driver is loaded on demand and does not run at boot time – this means that even if the System crashes due to a Bugcheck, you will never remain in an endless catastrophic loop.
After performing its job, the driver will be unloaded and the Registry entries self-cleaned. In addition, if it is the 64-bit driver, it will be deleted from the %SYSTEM32%\Drivers folder (where it was copied to). The 32-bit driver will launch from the same folder the launcher application is in and, of course, will not be deleted.
If you want to build the driver for 64-bit and test it without an acceptable Class 3 Certificate (Test Certificates do not work here), beware that you need to set the machine into Test Mode. This is done by launching bcdedit.exe from an elevated command prompt and issue the command:
bcdedit.exe -set TESTSIGNING ON
When you are over with the tests, launch again
bcdedit, issue the same command with
OFF instead of
ON and reboot.
To accomplish its mission, the driver uses different methods in 32-bit and 64-bit.
- Enumerates all running process looking for the process PID sent from User Mode (by default, it will be the
PIDof csrss.exe, a process always present).
- Once found, it enumerates all csrss.exe‘s threads, looking for a GUI thread -they have the
ETHREAD– Executive Thread Block) not
- GUI threads are guaranteed to have the
Very easy indeed, however the positions of the
ServiceTable entries within the
KTHREAD structure may change between Windows releases (they are opaque structures).
Windbg and see how the
KTHREAD structure has been in Windows 10 till Redstone2.
+0x000 Header : _DISPATCHER_HEADER
+0x010 SListFaultAddress : Ptr32 Void
+0x018 QuantumTarget : Uint8B
+0x020 InitialStack : Ptr32 Void
+0x024 StackLimit : Ptr32 Void
+0x028 StackBase : Ptr32 Void
+0x02c ThreadLock : Uint4B
+0x030 CycleTime : Uint8B
+0x038 HighCycleTime : Uint4B
+0x03c ServiceTable : Ptr32 Void <------
+0x0e0 WaitBlockFill10 : [<span class="code-digit">68</span>] UChar
+0x124 Win32Thread : Ptr32 Void <------
Here, we use an even easier approach:
- Read the MSR CPU register using the value
(0xC0000082). This will return the address of the 64-bit service call dispatcher (
nt!kiSystemCall64). No farther than 512 bytes away is the function
nt!KiSystemServiceRepeat. When we disassemble it, we find a reference to
kd> u nt!KiSystemServiceRepeat
fffff803`f28060a4 4c8d15d5e72700 lea r10,[nt!KeServiceDescriptorTable (fffff803`f2a84880)]
fffff803`f28060ab 4c8d1d4eb42600 lea r11,[nt!KeServiceDescriptorTableShadow (fffff803`f2a71500)]
fffff803`f28060b2 f7437840000000 test dword ptr [rbx+78h],40h
- All we have to do is search for the pattern, extract the RIP-Relative address and then calculate the linear address of
The User Mode Application
The User Mode Application will perform the following tasks:
- Load the Kernel Driver.
- Issue an IOCTL command to request it to find the addresses of
- Collect the answer and display it on screen.
- Finally, unload the Kernel Driver leaving the System clean.
This application must be run as Administrator.
(1) Intel® 64 and IA-32 Architectures Software Developer’s Manual