Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Kernel Look-up

0.00/5 (No votes)
4 Apr 2006 2  
How tro display kernel information in a C# application.

Introduction

The Sony debacle last year is why everybody knows the word root kit. But how does the root kit technology work? And more important these days: how is it possible to detect such software? This article will explain one method, and the enclosed source code will show how to present it in a C# Windows application.

Background

What is a root kit? A root kit describes mostly malicious software with one main characteristic: the process tries to hide itself from the user by manipulating file, registry, and other important access queries. Changing entries in the KiServiceTable is the most used method to achieve this.

Short explanation: a normal Windows application can’t directly access kernel functions like “open file” or “open register key”. WinNT, therefore, offers the ntdll.dll layer. Most exports in ntdll.dll are direct kernel calls. A little example: we need access to a Windows registry key. Therefore, we will use the RegOpenKey API (advapi32.dll). RegOpenKey will do the internal work and then forward the call to NtOpenKey in ntdll.dll. You will be surprised because NtOpenKey is a very short function:

public NtOpenKey        ; WinXP SP2
mov     eax, 77h        ; NtOpenKey service number
mov     edx, 7FFE0300h
call    dword ptr [edx] ; Kernel call
retn    0Ch

The function moves the service number 0x77 (NtOpenKey) into eax and then jumps directly into kernel mode. A dispatching routine in the kernel address space will redirect the code flow depending on the value in the eax register. Why such a complicated method? It ensures that the user can access the selected kernel functions because direct calls are not possible. The service numbers are OS version dependent.

One weak point here is the KiServiceTable. The KiServiceTable is a large area with addresses for each service. For our example, the dispatcher will look at the position 0x77. At this position, there should be the affiliated address for NtOpenKey. A root kit simply changes this address to its own code. Every time a user application tries to open a registry key, the dispatcher will forward the call to the root kit and not to the real NtOpenKey service.

What will we do to detect such behavior? We can’t access the KiServiceTable directly because it is located in the kernel address space. Therefore, our little program has two parts. The first is a C# GUI and the second is a C kernel driver.

The kernel driver responds to three different IO requests:

  • IOCTL_GET_MODULE_NAME: the driver tries to find a module name for a specific address.
  • IOCTL_GET_SERVICE_TABLE: the driver copies the complete KiServiceTable into the output buffer.
  • IOCTL_GET_INT_TABLE: the driver copies the complete Interrupt Descriptor Table into the output buffer.

The C# program verifies if each service points to the ntoskrnl.exe module, otherwise it declares it as hooked. You can see an example above in the screenshot. NtCreateKey is redirected by Daemon Tools, and NtCreateFile by a SoftIce extension. To assign each service with a name, we use the ntdll.dll export table. The program iterates through the exports, and connects the exported name with a service number under the condition that the first instruction in the function is mov eax, serviceNumber.

About the code

Class MainForm: This class handles the UI, calls the kernel service with the help of the Driver class, and fills the list views with the received and evaluated data.

//load driver as a service
LookupDriver.Load();
            
//create the kiServiceTable and get it from the kernel service
kiServiceTable = LookupDriver.GetKiServiceTable();

//resolve every service number into its name from ntdll.dll
LookupDriver.GetKiServiceNames(kiServiceTable);

//put the entries into the list field
FillServiceTableList();

Class ServiceInstaller: It is a wrapper class for the Windows Service APIs. The class contains two methods: InstallService and UnInstallService. ServiceInstaller is responsible to load and unload our little kernel driver. The code is taken from an article at www.c-sharpcorner.com.

Class Driver: This is the core of the program. It interacts with the driver and evaluates the received data. I will explain the main methods and how they work, in a few words:

public bool Open(string name)

This method simply opens a device to our driver with the CreateFile import from the kernel32.dll.

private unsafe int Interact(
    uint IoControlCode,        //control code for the driver
    byte[] inBuffer,           //input buffer, send to the driver
    byte[] outBuffer)          //output buffer, receive from the driver

The Interact method communicates with the driver. It sends the inBuffer and the IoControlCode, and receives information in the outBuffer. The API used for that is DeviceIoControl.

public unsafe KiServiceTable GetKiServiceTable()

This method sends the IOCTL_GET_SERVICE_TABLE control code to the driver and receives the complete KiServiceTable.

public unsafe string GetModuleName(uint addr)

This method sends the IOCTL_GET_MODULE_NAME control code to the driver. The kernel driver tries to find the module for the specific address. The return value is the module name as a string or "unkown".

public unsafe void GetKiServiceNames(KiServiceTable kiServiceTable)

The above method iterates through the ntdll.dll export table and tries to assign service numbers with the exported functions.

The second part is the kernel driver located in the sys directory, lookup.c. The driver responds to three different IO requests. IO requests are handled in the DrvDeviceControl function. DrvDeviceControl is called over the DeviceIoControl API. The code is really self explaining. You will find the assignment in the DriverEntry function. DriverEntry is the entry point, and is called by Windows to initialize the driver.

case IOCTL_GET_MODULE_NAME:
//get a module name for a address
{
  //address from the input buffer, first dword
  addr = ((DWORD *)inBuf)[0];
  //call getmodulename
  name = GetModuleName(addr);

  if(name != 0)
  //module name found
    memcpy(outBuf, name, strlen(name));
  else
    memcpy(outBuf, "unkown\0", 7);

  Irp->IoStatus.Information = outBufLength;
  ntStatus = STATUS_SUCCESS;
  break;
}

IOCTL_GET_MODULE_NAME is the first IO request code handled by the driver. The input buffer contains the memory address for which the user needs the module name. The function GetModuleName iterates with the kernel function ZwQuerySystemInformation through every module, and when the address is between the start and the end, the function returns the pointer to its name.

case IOCTL_GET_SERVICE_TABLE:  //get complete service table
{
  //number of services avaible in the current os
  ((DWORD *)outBuf)[0] = 
    KeServiceDescriptorTable->ServiceTable[0].NumberOfServices;

  //first dword in the output buffer = number of services
  ptr = (char *)KeServiceDescriptorTable->ServiceTable[0].TableBase;
  outBuf += 4;

  //copy each entry into the output buffer
  for(i = 0;i < KeServiceDescriptorTable->
                ServiceTable[0].NumberOfServices;i++)            
    ((DWORD *)outBuf)[i] = ((DWORD *)ptr)[i];
                  
  Irp->IoStatus.Information = outBufLength;
  ntStatus = STATUS_SUCCESS;
  break;
}

IOCTL_GET_SERVICE_TABLE is the second and the most important IO request for our little program. The KiServiceTable address is not directly accessible. But we will find it in the ntoskrnl.exe export KeServiceDescriptorTable. The same applies for the number of services available in the current OS version. With both information, we simply copy each entry into the output buffer.

case IOCTL_GET_INT_TABLE:
//get complete interrupt descriptor table
{
  dwIdt = GetIntTable();      //base address from the idt

  ((DWORD *)outBuf)[0] = 255; //hardcoded 255 interrupt entries
  outBuf += 4;
  for(i = 0;i < 255;i++)      //copy each entry into the output buffer
    ((DWORD *)outBuf)[i] = GetIntAddress(dwIdt, i);                        
                  
  Irp->IoStatus.Information = outBufLength;
  ntStatus = STATUS_SUCCESS;       
  break;
}

IOCTL_GET_INT_TABLE is the third and the last IO request. It simply copies the whole Interrupt Descriptor Table into the output buffer.

Points of Interest

You will need the Windows DDK and VC# to build the project. The demo program itself is tested under Windows XP SP2, Windows Server 2003, and built in VC#2005 / .NET 2.0.

This is my first article for CodeProject. I hope you enjoyed it.

History

  • 1.0 - initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here