Reverse engineering

The Sysenter Instruction Internals

Dejan Lukan
May 16, 2013 by
Dejan Lukan

Introduction

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.

Become a certified reverse engineer!

Become a certified reverse engineer!

Get live, hands-on malware analysis training from anywhere, and become a Certified Reverse Engineering Analyst.

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.

[cpp]

#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();

typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION {

BOOLEAN DebuggerEnabled;

BOOLEAN DebuggerNotPresent;

} SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION;

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;

SYSTEM_KERNEL_DEBUGGER_INFORMATION Info;

/* 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.");

exit(-1);

}

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 */
getchar();

return 0;

}

[/cpp]

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:

[plain]

kd> u KiFastCallEntry l100

nt!KiFastCallEntry:

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

[/plain]

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:

[plain]

8053d68d 8bf8 mov edi,eax

8053d68f c1ef08 shr edi,8

8053d692 83e730 and edi,30h

8053d695 8bcf mov ecx,edi

[/plain]

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:

[plain]

kd> bp 8053d68d "j @eax = 0x000000ad '';'gc'"

kd> bl

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

[/plain]

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:

[plain]

kd> g

nt!KiFastCallEntry+0x8d:

8053d6dd 8bf8 mov edi,eax

kd> r eax, ecx, edi

eax=000000ad ecx=80042000 edi=7c90e514

kd> p

nt!KiFastCallEntry+0x8f:

8053d6df c1ef08 shr edi,8

kd> r eax, ecx, edi

eax=000000ad ecx=80042000 edi=000000ad

kd> p

nt!KiFastCallEntry+0x92:

8053d6e2 83e730 and edi,30h

kd> r eax, ecx, edi

eax=000000ad ecx=80042000 edi=00000000

kd> p

nt!KiFastCallEntry+0x95:

8053d6e5 8bcf mov ecx,edi

[/plain]

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.

Conclusion

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.

 

References:

[1] Shifting yourself to space, accessible at http://shift32.wordpress.com/tag/sysenter/.

[2] Manual inspection of service dispatch table (SSDT) for hook detection, accessible at http://rc-labs.blogspot.com/2010/08/manual-inspection-of-service-dispatch.html.

[3] Hunting rootkits with Windbg, accessible at www.reconstructer.org/.../Hunting%20rootkits%20with%20Windbg.pdf.

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

[5] ntoskrnl.exe, accessible at http://en.wikipedia.org/wiki/Ntoskrnl.

[6] SYSENTER, http://wiki.osdev.org/SYSENTER.

[7] x86 Instruction Set Reference, http://x86.renejeschke.de/html/file_module_x86_id_313.html.

Become a certified reverse engineer!

Become a certified reverse engineer!

Get live, hands-on malware analysis training from anywhere, and become a Certified Reverse Engineering Analyst.

[8] System Call Optimization with the SYSENTER Instruction, http://www.codeguru.com/cpp/misc/misc/system/article.php/c8223/System-Call-Optimization-with-the-SYSENTER-Instruction.htm.

Dejan Lukan
Dejan Lukan

Dejan Lukan is a security researcher for InfoSec Institute and penetration tester from Slovenia. He is very interested in finding new bugs in real world software products with source code analysis, fuzzing and reverse engineering. He also has a great passion for developing his own simple scripts for security related problems and learning about new hacking techniques. He knows a great deal about programming languages, as he can write in couple of dozen of them. His passion is also Antivirus bypassing techniques, malware research and operating systems, mainly Linux, Windows and BSD. He also has his own blog available here: http://www.proteansec.com/.