Download Version 0.2-beta2 Traverses HandleTableList
Download Version 0.2-beta1 supports WinXP
Download Version 0.1
Note: This Proof-of-Concept code may crash the system. Use at your own risk and on test machines only. Thanks to Microsoft Online Crash Service team for pointing this out.
Introduction
Win32 Kernel Rootkits hide running processes from users using techniques like Kernel Native API Hooking, or by directly unlinking the process's EPROCESS entry from ActiveProcessLinks. Such techniques are very effective in hiding processes, and are very difficult to detect with user-mode tools.
This proof-of-concept tool demonstrates how hidden processes can be detected by directly traversing both the Kernel's ActiveProcessList and the Kernel scheduler's ETHREAD lists. This tool can also traverse the Kernel's PsLoadedModuleList to detect kernel modules/drivers that are hidden by hooking the ZwQuerySystemInformation native API.
Techniques of Process Hiding in Kernel Space
The following are some of the methods that are used by kernel rootkits to hide processes. They will be described in detail below.
In Windows, user-space applications request for system services by calling the APIs exported by the various DLLs. For example, to write data to an open file, pipe or device, the WriteFile API that is exported by kernel32.dll is usually used. Within kernel32.dll, the implementation of WriteFile API in turn calls the ZwWriteFile native API that is exported by ntdll.dll. The work done by ZwWriteFile is actually performed in kernel-space. Hence, the implementation of ZwWriteFile in ntdll.dll contains only minimal code to transit into kernel-space using interrupt 0x2E. The disassembly of ZwWriteFile is shown below.
1- MOV EAX, 0ED 2- LEA EDX, DWORD PTR SS:[ESP+4] 3- INT 2E 4- RETN 24The magic number 0ED in line 1 is the Service Number for ZwWriteFile. It will be used to offset into the ServiceTable (System Service Dispatch Table) in kernel-space to locate the address of the function that implements the writefile service. The address of the ServiceTable can be found within the Service Descriptor Table (SDT). The Service Descriptor Table can be referenced using the exported KeServiceDescriptorTable symbol. This is a structure with the following definition.
typedef struct ServiceDescriptorTable { SDE ServiceDescriptor[4]; } SDT; typedef struct ServiceDescriptorEntry { PDWORD ServiceTable; PDWORD CounterTableBase; DWORD ServiceLimit; PBYTE ArgumentTable; } SDT;
The first member of the structure, SDT.ServiceDescriptor[0].ServiceTable, is an array of function pointers to the service functions. The DWORD value at ServiceTable[0xED] is a function pointer to NtWriteFile, which contains the actual code to write to files, pipes or devices. Hence, to modify the behaviour of the user-space WriteFile API, one simply needs to write a replacement function, load it into kernel space as a driver, and modify ServiceTable[0xED] to point to the replacement function. The replacement function needs to keep the original function pointer (original value of ServiceTable[0xED]) so that it can be called to perform the original defined function.
User-space programs can use the ToolHelp APIs to obtain a list of all running processes. The ToolHelp APIs in turn calls the ZwQuerySystemInformation native API exported by ntdll.dll to obtain the list. To hide processes, a kernel-space rootkit, which is loaded as a driver, can modify the function pointer at ServiceTable[0x97] (ZwQuerySystemInformation) to redirect the call to a replacement function. The replacement function first calls the original ZwQuerySystemInformation API to obtain an array containing information of all running process. The returned array is then modified to remove the entry containing the process to be hidden. Finally, the modified result is returned to the user-space program. This effectively prevents the user-space program from "seeing" the hidden process.
This technique of process hiding can be easily defeated by directly traversing the Kernel's ActiveProcessLinks to obtain a list of all running processes.
Unlinking of Process's EPROCESS structure from ActiveProcessLinks
The kernel maintains a circular doubly linked-list of all active processes, known as the ActiveProcessLinks. The head of this list is located at PsActiveProcessHead. However, PsActiveProcessHead is not an exported symbol, and hence, cannot be used to locate the the start of the list. Despite this, it is still possible to locate this list indirectly using the exported PsInitialSystemProcess symbol, or by calling the PsGetCurrentProcess kernel API.
Each active process is represented on this linked-list with a EPROCESS structure. The exported PsInitialSystemProcess symbol points us to the EPROCESS structure of the System process. Calling PsGetCurrentProcess kernel API returns us a pointer to the EPROCESS structure of the current process. In the Win2K's kernel, offset 0xA0 from the start of the EPROCESS structure contains links to the doubly linked-list of active processes. This allows us to "get on" and traverse the list of active processes without the need to locate PsActiveProcessHead. The following diagram illustrates this.
It is possible for a kernel-space rootkit to hide processes by unlinking the process's EPROCESS structure from ActiveProcessLinks. To detect such hidden process, it is necessary to traverse the kernel scheduler's ETHREAD lists using KiWaitInListHead and KiWaitOutListHead.
Techniques of Device/Module Hiding in Kernel Space
Kernel-space rootkits are loaded into kernel-space as drivers or modules. To avoid detection, such rootkits usually use the following techniques to hide themselves. The techniques used are very similar to that of process hiding.
User-space programs can obtain a list of all loaded drivers using the ZwQuerySystemInformation native API, specifying SystemModuleInformation as its first parameter. As mentioned earlier, ZwQuerySystemInformation is exported by ntdll.dll and can be called directly by user-space programs. In kernel-space, the ZwQuerySystemInformation native API obtains the list of loaded drivers by traversing the PsLoadedModuleList.
A kernel-space rootkit can manipulate the results returned by ZwQuerySystemInformation by modifying ServiceTable[0x97] (ZwQuerySystemInformation) to point to a replacement fnuction. The replacement function will first call the original ZwQuerySystemInformation to get an array of all loaded drivers. The driver to be hidden (i.e. the rootkit) is then removed from the array. This manipulated array is returned to the user-space program.
This technique can be easily defeated by directly traversing the kernel's PsLoadedModuleList to obtain a list of all loaded drivers.
Unlinking of driver information from PsLoadedModuleList
The kernel maintains a circular doubly linked list of all loaded drivers. The head of this list can be located using the PsLoadedModuleList symbol. Unfortunately, this symbol is not exported. Nevertheless, it is possible to locate this symbol by scanning the MmGetSystemRoutineAddress kernel API, which references this symbol. A kernel-space rootkit can can hide a driver (usually itself) by unlinking the driver's information from the PsLoadedModuleList.
Usage
KProcCheck -p show kernel active process list. KProcCheck -s show scheduler thread list. KProcCheck -d show kernel module list. KprocCheck -t show hooked SDT entries. KprocCheck -g show hooked GDI SDT entries. KProcCheck -u unload support driver.
This tool consist of a kernel-space support driver (KProcCheck.sys) and a user-space program (KProcCheck.exe). The commandline parameters of the user-space program is shown above. When the user-space program is first run, it will load the kernel-space support driver. You must run KProcCheck.exe as Administrator in order for the support driver to be loaded correctly.
KProcCheck -p
When used with the -p parameter, KProcCheck will compare the list of running processes obtain by traversing the ActiveProcessLinks with that obtained using the ToolHelp APIs. Any processes that exist on the ActiveProcessLinks, but are missing from the list obtained using ToolHelp APIs will be reported as --[Hidden]--. This will detect processes that are hidden by hooking ZwQuerySystemInformation.
KProcCheck -s
With the -s parameter, KProcCheck will compare the list of running process obtained by traversing the kernel scheduler's ETHREAD lists (KiWaitInListHead and KiWaitOutListHead) with that obtained using the ZwQuerySystemInformation API. Any processes that are missing from the list returned by ZwQuerySystemInformation API will be reported as --[Hidden]--. This is effective against kernel-space rootkits that unlink the hidden process's EPROCESS structure from ActiveProcessLinks.
The two symbols KiWaitInListHead and KiWaitOutListHead are not exported, but their addresses can be obtained by scanning the KeWaitForSingleObject kernel API. These two lists can be traversed to obtain a list of ETHREAD structures that are used by the scheduler. Each ETHREAD structure contains a link back to its EPROCESS structure. This allows a list of processes that are currently being scheduled to be obtained.
KProcCheck -d
When used with the -d parameter, KProcCheck will print a list of all loaded drivers by tranversing the PsLoadedModuleList. In addition, this list will be compared against the list returned by calling ZwQuerySystemInformation. Any drivers that exist on the first list, but is missing from the second will be reported as --[Hidden]--.
KProcCheck -t
With the -t parameter, KProcCheck will check whether any of the KeServiceDescriptorTable.ServiceDescriptor[0].ServiceTable entries have been hooked. It does this by looking for any ServiceTable entries containing function pointers that point outside the memory image of ntoskrnl.exe.
KProcCheck -g
With the -g parameter, KProcCheck will check whether any of the ServiceTable entries in the Shadow Service Descriptor Table have been hooked. It does this by looking for any SeviceTable entries containing function pointers that point outside the memory image of win32k.sys.
KProcCheck -u
Using the -u parameter will instruct KProcCheck to unload the support kernel driver.
Credits