In the previous article we’ve seen that whether we’re using the int 0x2e interrupt or sysenter instruction, the same method in kernel is being used. We also identified that the KiSystemService is being called in both cases. In this article, we’ll take a look at the details of how this actually happens.

Whenever we use the 0x2e interrupt or a sysenter instruction, the system service number is being used to determine which system call will be invoked. We’ve already seen that the system service number is being passed in an EAX register, which is a 32-bit register on IA-32 machines. However, it isn’t immediately clear how that value is later being used.

The first thing that comes to mind is that it’s just an index into some table, which holds the pointers to system routines that will be invoked. This is pretty close to how the value is actually being used, but we should probably mention that 32-bits are not used as an index, because we would have to have a 4GB-big table of pointers or a multiple level table, which is impractical and isn’t needed.

The system service number is comprised of the following parts:

  • bits 0-11: the number of the system service to call
  • bits 12-13: used service descriptor table
  • bits 14-31: not used

We can see that only the lower 12-bits are used as an index into the table, which is 4096 bytes in size. But there are also 2 bits (from 12-13) that are used to select the appropriate service descriptor table, which means that we can have at most 4 service descriptor tables.

In Windows, the SSDT (System Service Dispatch Table) is a table that points to kernel functions that are handled in ntoskrnl.exe. The ntoskrnl.exe is responsible for various tasks, like hardware virtualization, process and memory management, cache managing, process scheduling, etc. [5] In Windows systems, only two tables are used and they are named KeServiceDescriptorTable and KeServiceDescriptorTableShadow.

On the picture below, we can see the address and the first element of both tables in memory.

Both of these tables contain SST (System Service Tables) structures that have the following elements (summarized from [4]):

  • ServiceTable: pointer to the SSDT array of addresses that point to kernel functions
  • CounterTable: not used
  • ServiceLimit: number of elements in SSDT array
  • ArgumentTable: pointer to the array of arguments SSPT (System Service Parameter Table)

The picture below shows the structures of SSTs in both tables:

On the picture above, we can see the first SST of the KeServiceDescriptorTable, which is 16 bytes long. This is the SST that points to the SSDT that contains the Windows core functions. The values of the SST are as follows:

  • ServiceTable: 80501b8c (pointer to KiServiceTable)
  • CounterTable: not used
  • ServiceLimit: 0x11c (hex) = 286 (dec)
  • ArgumentTable: 80502000 (pointer to KiArgumentTable)

The KeServiceDescriptorTableShadow contains two SSTs which occupy the first 32 bytes. We can see that the first SST is the same as the one present in the KeServiceDescriptorTable table, while the second is used to point to the functions in the win32k.sys kernel driver, which takes care of Windows graphical user interface.

Let’s present the fields of this SST:

  • ServiceTable: bf99e900 (pointer to W32pServiceTable)
  • CounterTable: not used
  • ServiceLimit: 0x29b (hex) = 667 (dec)
  • ArgumentTable: bf99f610 (pointer to W32pArgumentTable)

Let’s summarize what we’ve just done. The KeServiceDescriptorTable table is referenced if the 12-13 bits in the system service number is set to 0x00, while the KeServiceDescriptorTableShadow is referenced if the 12-13 bits are set to 0x01. The other two values 0x10 and 0x11 are currently not being used. This means that the value in the EAX register, which is the system service number, can hold the following values (presenting the 16-bit values):

  • 0000xxxx xxxxxxxx: used by KeServiceDescriptorTable, where the x’s can be 0 or 1, which further implies that the first table is used if the system service numbers are from 0x0 – 0xFFF.
  • 0001yyyy yyyyyyyy: used by KeServiceDescriptorTableShadow, where y’s can be 0 or 1, which further implies that the second table is used if the system service numbers are from 0x1000 – 0x1FFF.

This means that the system service numbers in EAX register can only be in the range of 0x0000 – 0x1FFFF, and all other values are invalid.

To dump the Windows core functions from the KiServiceTable table, we can use the Windbg “dps KiServiceTable” command as follows (note that only the first part of the functions is presented):

Let’s also dump the first part of the functions contained in the W32pServiceTable table. This can be seen below, where we used the “dps W32pServiceTable” command to display the functions:

Did you notice that the KiServiceTable contains core Windows functions, while the W32pServiceTable table contains the graphical functions as we already mentioned? The above outputs confirm that.

Presenting the Example

Below we can see the example we’ll be using in this part of the article. The example is simply calling the ZwQuerySystemInformation function directly from the ntdll.dll library. We can’t call the function directly, which is why we must first load the ntdll.dll library and then get the address of the ZwQuerySystemInformation function. We get back the address in memory where the function is located, so we must apply the function prototype in order to be able to call the function. What the program actually does is detect whether the system debugger is currently debugging the operating system or not.

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include <Winternl.h>

int _tmain(int argc, _TCHAR* argv[]) {
    __asm { int 3 }
    typedef long NTSTATUS;
    #define STATUS_SUCCESS    ((NTSTATUS)0L)
    HANDLE hProcess = GetCurrentProcess();

                 BOOLEAN DebuggerEnabled;
                 BOOLEAN DebuggerNotPresent;

    enum SYSTEM_INFORMATION_CLASS { SystemKernelDebuggerInformation = 35 };
    typedef NTSTATUS  (__stdcall *ZW_QUERY_SYSTEM_INFORMATION)(IN SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength);
    ZW_QUERY_SYSTEM_INFORMATION ZwQuerySystemInformation;

    /* load the ntdll.dll */
    HMODULE hModule = LoadLibrary(_T("ntdll.dll"));
    ZwQuerySystemInformation = (ZW_QUERY_SYSTEM_INFORMATION)GetProcAddress(hModule, "ZwQuerySystemInformation");
    if(ZwQuerySystemInformation == NULL) {
        printf("Error: could not find the function ZwQuerySystemInformation in library ntdll.dll.");
    printf("ZwQuerySystemInformation is located at 0x%08x in ntdll.dll.\n", (unsigned int)ZwQuerySystemInformation);

    if (STATUS_SUCCESS == ZwQuerySystemInformation(SystemKernelDebuggerInformation, &amp;Info, sizeof(Info), NULL)) {
            if (Info.DebuggerEnabled &amp;&amp; !Info.DebuggerNotPresent) {
                printf("System debugger is present.");
            else {
                printf("System debugger is not present.");

    /* wait */

    return 0;

While running the program under Windbg kernel debugger, we’ll be presented with the following output from the program:

We can see that the program correctly identified that the system debugger is present. The picture below presents the disassembled instruction of the ZwQuerySystemInformation function:

We can see that the ZwQuerySystemInformation function implementation in ntdll.dll library is just a routine that calls into the kernel and doesn’t actually provide the service itself. In the code above, we’re storing the 0xAD hexadecimal number into the EAX register, the system service number we’ve been talking about in the article. Since the 0xAD number is in range of 0x000-0xFFF, we’re effectively using the KeServiceDescriptorTable table to get all the information that we need to call the kernel function.

Let’s first take a look at the address that is loaded into the EDX register:

In the code above, we’re reading the address 0x7c90e510 at the memory of the EDX register and calling it. The 0x7c90e510 address is the address of the KiFastSystemCall function as we can see in the output below:

The KiFastSystemCall is executing the sysenter instruction, which should call the appropriate system function in the kernel. We already know that when we execute the sysenter instruction, the KiFastCallEntry function is called, which is why we need to set a breakpoint on that function as follows:

After that we can run the program with the g command and the function will be hit as shown below:

Let’s disassemble the whole KiFastCallEntry function to figure out what the function does. The disassembled instructions can be seen below:

kd> u KiFastCallEntry l100
8053d600 b923000000      mov     ecx,23h
8053d605 6a30            push    30h
8053d607 0fa1            pop     fs
8053d609 8ed9            mov     ds,cx
8053d60b 8ec1            mov     es,cx
8053d60d 8b0d40f0dfff    mov     ecx,dword ptr ds:[0FFDFF040h]
8053d613 8b6104          mov     esp,dword ptr [ecx+4]
8053d616 6a23            push    23h
8053d618 52              push    edx
8053d619 9c              pushfd
8053d61a 6a02            push    2
8053d61c 83c208          add     edx,8
8053d61f 9d              popfd
8053d620 804c240102      or      byte ptr [esp+1],2
8053d625 6a1b            push    1Bh
8053d627 ff350403dfff    push    dword ptr ds:[0FFDF0304h]
8053d62d 6a00            push    0
8053d62f 55              push    ebp
8053d630 53              push    ebx
8053d631 56              push    esi
8053d632 57              push    edi
8053d633 8b1d1cf0dfff    mov     ebx,dword ptr ds:[0FFDFF01Ch]
8053d639 6a3b            push    3Bh
8053d63b 8bb324010000    mov     esi,dword ptr [ebx+124h]
8053d641 ff33            push    dword ptr [ebx]
8053d643 c703ffffffff    mov     dword ptr [ebx],0FFFFFFFFh
8053d649 8b6e18          mov     ebp,dword ptr [esi+18h]
8053d64c 6a01            push    1
8053d64e 83ec48          sub     esp,48h
8053d651 81ed9c020000    sub     ebp,29Ch
8053d657 c6864001000001  mov     byte ptr [esi+140h],1
8053d65e 3bec            cmp     ebp,esp
8053d660 759a            jne     nt!KiFastCallEntry2+0x47 (8053d5fc)
8053d662 83652c00        and     dword ptr [ebp+2Ch],0
8053d666 462cff        test    byte ptr [esi+2Ch],0FFh
8053d66a 89ae34010000    mov     dword ptr [esi+134h],ebp
8053d670 0f854afeffff    jne     nt!Dr_FastCallDrSave (8053d4c0)
8053d676 8b5d60          mov     ebx,dword ptr [ebp+60h]
8053d679 8b7d68          mov     edi,dword ptr [ebp+68h]
8053d67c 89550c          mov     dword ptr [ebp+0Ch],edx
8053d67f c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h
8053d686 895d00          mov     dword ptr [ebp],ebx
8053d689 897d04          mov     dword ptr [ebp+4],edi
8053d68c fb              sti
8053d68d 8bf8            mov     edi,eax
8053d68f c1ef08          shr     edi,8
8053d692 83e730          and     edi,30h
8053d695 8bcf            mov     ecx,edi
8053d697 03bee0000000    add     edi,dword ptr [esi+0E0h]
8053d69d 8bd8            mov     ebx,eax
8053d69f 25ff0f0000      and     eax,0FFFh
8053d6a4 3b4708          cmp     eax,dword ptr [edi+8]
8053d6a7 0f8345fdffff    jae     nt!KiBBTUnexpectedRange (8053d3f2)
8053d6ad 83f910          cmp     ecx,10h
8053d6b0 751a            jne     nt!KiFastCallEntry+0xcc (8053d6cc)
8053d6b2 8b0d18f0dfff    mov     ecx,dword ptr ds:[0FFDFF018h]
8053d6b8 33db            xor     ebx,ebx
8053d6ba 0b99700f0000    or      ebx,dword ptr [ecx+0F70h]
8053d6c0 740a            je      nt!KiFastCallEntry+0xcc (8053d6cc)
8053d6c2 52              push    edx
8053d6c3 50              push    eax
8053d6c4 ff15e4305580    call    dword ptr [nt!KeGdiFlushUserBatch (805530e4)]
8053d6ca 58              pop     eax
8053d6cb 5a              pop     edx
8053d6cc ff0538f6dfff    inc     dword ptr ds:[0FFDFF638h]
8053d6d2 8bf2            mov     esi,edx
8053d6d4 8b5f0c          mov     ebx,dword ptr [edi+0Ch]
8053d6d7 33c9            xor     ecx,ecx
8053d6d9 8a0c18          mov     cl,byte ptr [eax+ebx]
8053d6dc 8b3f            mov     edi,dword ptr [edi]
8053d6de 8b1c87          mov     ebx,dword ptr [edi+eax*4]
8053d6e1 2be1            sub     esp,ecx
8053d6e3 c1e902          shr     ecx,2
8053d6e6 8bfc            mov     edi,esp
8053d6e8 3b35d48a5580    cmp     esi,dword ptr [nt!MmUserProbeAddress (80558ad4)]
8053d6ee 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (8053d89c)
8053d6f4 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8053d6f6 ffd3            call    ebx
8053d6f8 8be5            mov     esp,ebp
8053d6fa 8b0d24f1dfff    mov     ecx,dword ptr ds:[0FFDFF124h]
8053d700 8b553c          mov     edx,dword ptr [ebp+3Ch]
8053d703 899134010000    mov     dword ptr [ecx+134h],edx

Let’s take a look at the first instruction in the KiFastCallEntry function where the value in register EAX is being used, which means that the system service number is being used to do something. Those instructions can be seen below:

8053d68d 8bf8            mov     edi,eax
8053d68f c1ef08          shr     edi,8
8053d692 83e730          and     edi,30h
8053d695 8bcf            mov     ecx,edi

At first, those instructions may seem weird, but they soon start to make a lot of sense. All of the operations can be seen on the picture below. We start with a whole system service number that is stored in the EAX register and then moved to the EDI register. This can be seen on the first part of the picture where the lower 12 bits is the actual system service number and the middle two bits determine the SSDT table to be used, while the upper 18 bits are not used.

The shr instruction moves all the bits in the EDI register to the right by 8. This is seen on the middle part of the picture where the lower 4 bits are used to represent the now-corrupted system service number, and the middle two bits are still the SSDT number. The upper 26 bits are not used. The next and instruction nulls the lower four bits, thus leaving only the middle two bits unaltered. At the end of the above code, the SSDT number is stored in the 4-5 bit of the ECX register.

Since we’re passing the system service number 0xAD in the EAX register, we should really set a conditional breakpoint in WinDbg, because otherwise we won’t be able to manage the execution the way we want. This is because the KiFastCallEntry function is called so many times by the kernel itself that it doesn’t make sense to manually check whether the EAX register contains the right system service number. We should set the conditional breakpoint on the 0x8053d68d address and check whether the value in the EAX register is 0xAD (our system service number).

We can set the conditional breakpoint like this:

kd> bp 8053d68d "j @eax = 0x000000ad '';'gc'"
kd> bl
 0 e 8053d68d     0001 (0001) nt!KiFastCallEntry+0x8d "j @eax = 0x000000ad '';'gc'"

After that we should start the program normally and observe what happens. The execution should take considerably more time, since WinDbg must compare the value in the EAX register every time it passed that code point, which happens a lot.

Let’s take a look at an example at a point where it hits the breakpoint, which means that the value in the EAX register should be set to 0xAD:

kd> g
8053d6dd 8bf8            mov     edi,eax

kd> r eax, ecx, edi
eax=000000ad ecx=80042000 edi=7c90e514
kd> p
8053d6df c1ef08          shr     edi,8

kd> r eax, ecx, edi
eax=000000ad ecx=80042000 edi=000000ad
kd> p
8053d6e2 83e730          and     edi,30h

kd> r eax, ecx, edi
eax=000000ad ecx=80042000 edi=00000000
kd> p
8053d6e5 8bcf            mov     ecx,edi

The first number in EAX register is 0xad (10101101), which we’re shifting to the right for 8 bits. This makes a number 0x0, which is then later AND-ed with the 0x30. The transformations result in the number 0x000000.

Since we’ve just calculated the value stored in the ECX register, it’s advisable that we continue the code observation by looking at the instructions that use the value in the ECX register. We won’t do that now since the article might get too long, but you get the picture.


In this article we’ve seen the internals of what happens when the sysenter instruction is called. We could go a lot deeper, but I didn’t want to make the article too long. The most important things to remember are how the system service number is calculated and how the service is called.



[1] Shifting yourself to space, accessible at

[2] Manual inspection of service dispatch table (SSDT) for hook detection, accessible at

[3] Hunting rootkits with Windbg, accessible at…/Hunting%20rootkits%20with%20Windbg.pdf.

[4] BlackEnergy Version 2 Rootkit Analysis – SecuraBit, accessible at…/Rootkit-Analysis-Hiding-SSDT-Hooks1.pdf.

[5] ntoskrnl.exe, accessible at


[7] x86 Instruction Set Reference,

[8] System Call Optimization with the SYSENTER Instruction,