System Service Descriptor Table - SSDT

What is SSDT

System Service Dispatch Table or SSDT, simply is an array of addresses to kernel routines for 32 bit operating systems or an array of relative offsets to the same routines for 64 bit operating systems.

SSDT is the first member of the Service Descriptor Table kernel memory structure as shown below:

typedef struct tagSERVICE_DESCRIPTOR_TABLE {
    SYSTEM_SERVICE_TABLE nt; //effectively a pointer to Service Dispatch Table (SSDT) itself
    SYSTEM_SERVICE_TABLE win32k;
    SYSTEM_SERVICE_TABLE sst3; //pointer to a memory address that contains how many routines are defined in the table
    SYSTEM_SERVICE_TABLE sst4;
} SERVICE_DESCRIPTOR_TABLE;

SSDTs used to be hooked by AVs as well as rootkits that wanted to hide files, registry keys, network connections, etc. Microsoft introduced PatchGuard for x64 systems to fight SSDT modifications by BSOD'ing the system.

In Human Terms

When a program in user space calls a function, say CreateFile, eventually code execution is transfered to ntdll!NtCreateFile and via a syscall to the kernel routine nt!NtCreateFile.

Syscall is merely an index in the System Service Dispatch Table (SSDT) which contains an array of pointers for 32 bit OS'es (or relative offsets to the Service Dispatch Table for 64 bit OSes) to all critical system APIs like ZwCreateFile, ZwOpenFile and so on..

Below is a simplified diagram that shows how offsets in SSDT KiServiceTable are converted to absolute addresses of corresponding kernel routines:

Effectively, syscalls and SSDT (KiServiceTable) work togeher as a bridge between userland API calls and their corresponding kernel routines, allowing the kernel to know which routine should be executed for a given syscall that originated in the user space.

Service Descriptor Table

In WinDBG, we can check the Service Descriptor Table structure KeServiceDescriptorTable as shown below. Note that the first member is recognized as KiServiceTable - this is a pointer to the SSDT itself - the dispatch table (or simply an array) containing all those pointers/offsets:

Let's try and print out a couple of values from the SSDT:

As mentioned earlier, on x64 which is what I'm running in my lab, SSDT contains relative offsets to kernel routines. In order to get the absolute address for a given offset, the following formula needs to be applied:

RoutineAbsoluteAddress=KiServiceTableAddress+(routineOffset>>>4)RoutineAbsoluteAddress = KiServiceTableAddress + (routineOffset >>> 4)

Using the above formula and the first offset fd9007c4 we got from the KiServiceTable, we can work out that this offset is pointing to nt!NtAccessCheck:

We can confirm it if we try to disassemble the nt!NtAccessCheck - routine addresses (fffff801`91dcb4ec) and first instructions (mov r11, rsp) of the above and below commands match:

If we refer back to the original drawing on how SSDT offsets are converted to absolute addresses, we can redraw it with specific values for syscall 0x1:

Finding a Dispatch Routine for a Given Userland Syscall

As a simple exercise, given a known syscall number, we can try to work out what kernel routine will be called once that syscall is issued. Let's load the debugging symbols for ntdll module:

Let's now find the syscall for ntdll!NtCreateFile:

...we can see the syscall is 0x55:

Offsets in the KiServiceTable are 4 bytes in size, so we can work out the offset for syscall 0x55 by looking into the value the KiServiceTable holds at position 0x55:

We see from the above that the offset for NtCreateFile is 01fa3007. Using the formula discussed previously for working out the absolute routine address, we confirm that we're looking at the nt!tCreateFile kernel routine that will be called once ntdll!NtCreateFile issues the 0x55 syscall:

Let's redraw the earlier diagram once more for the syscall 0x55 for ntdll!NtCreateFile:

Finding Address of All SSDT Routines

As another exercise, we could loop through all items in the service dispatch table and print absolute addresses for all routines defined in the dispatch table:

Nice, but not very human readable. We can update the loop a bit and print out the API names associated with those absolute addresses:

References

Last updated