[转载]Hooking Windows NT System Services
信息来源:[url]http://www.windowsitlibrary.com/Content/356/06/1.html[/url]Abstract
This chapter explores system services under DOS, Windows 3.x, Windows 95/98, and Windows NT. The authors discuss the need for hooking these system services.
THIS CHAPTER DISCUSSES hooking Windows NT system services. Before we begin, let’s first review what we mean by a system service. A system service refers to a set of functions (primitive or elaborate) provided by the operating system. Application programming interfaces (APIs) enable developers to call several system services, directly or indirectly. The operating system provides APIs in the form of a dynamic link library (DLL) or a static compiler library. These APIs are often based on system services provided by the operating system. Some of the API calls are directly based on a corresponding system service, and some depend on making multiple system service calls. Also, some of the API calls may not make any calls to system services. In short, you do not need a one-to-one mapping between API functions and system services. Figure 6-1 demonstrates this in context of Windows NT.
SYSTEM SERVICES: THE LONG VIEW
System services and the APIs calling these system services have come a long way from DOS to Windows NT.
System Services under DOS
Under DOS, system services comprise part of the MS-DOS kernel (including MSDOS.SYS and IO.SYS). These system services are available to users in the form of Interrupt Service Routines (ISRs). ISRs can be invoked by calling the appropriate interrupt handlers using the INT instruction. API functions, provided by compiler libraries, call the interrupt handler for system services (the INT 21h interrupt). For example, to open a file, MS-DOS provides a system service for which you have to specify the function number 0x3D in the AH register, attribute mask in the CL register, filename in the DS:DX register, as well as issue the INT 21h instruction. Compilers typically provide wrappers around this and provide a nice API function for this purpose.
System Services under Windows 3.x and Windows 95/98
Under Windows 3.x or Windows 95/98, the core system services take the form of VXDs and DLLs and some real-mode DOS code. The APIs are provided in the form of dynamic link libraries. These dynamic link libraries call the system services to implement the APIs. For example, to open a file, applications call an API function from KERNEL32.DLL such as OpenFile() or CreateFile(). These APIs, in turn, call a system service.
System Services under Windows NT
Under Windows NT, the NT executive (part of NTOSKRNL.EXE) provides core system services. These services are rather generic and primitive. Various APIs such as Win32, OS/2, and POSIX are provided in the form of DLLs. These APIs, in turn, call services provided by the NT executive. The name of the API function to call differs for users calling from different subsystems even though the same system service is invoked. For example, to open a file from the Win32 API, applications call CreateFile() and to open a file from the POSIX API, applications call the open() function. Both of these applications ultimately call the NtCreateFile() system service from the NT executive.
Note: Under Windows NT 3.51, the system services are provided by a kernel-mode component called NTOSKRNL.EXE. Most of the KERNEL32.DLL calls—such as those related to memory management and kernel objects management—are handled by these system services. The USER32 and GDI32 calls are handled by a separate subsystem process called CSRSS. Starting with Windows NT 4.0, Microsoft moved most of the functionality of CSRSS into a kernel-mode driver called WIN32K.SYS. The functionality moved into WIN32K.SYS is made available to the applications in the form of system services. These system services are not truly part of native system services since they are specific to the user interface and not used by all subsystems. This chapter and the next chapter focus only on the system services provided by NTOSKRNL.EXE.
NEED FOR HOOKING SYSTEM SERVICES
Hooking represents a very common mechanism of intercepting a particular section of executing code. Hooking provides a useful way of modifying the behavior of the operating system. Hooking can help the developer in several ways. Often developers are concerned more with how to hook a system service or an API call rather than why to hook. Nevertheless, we examine the various possible situations in which the need to hook a system service arises. How hooking can help the developer is explained in the following sections.
Trapping Events at Occurrence
Developers trap events such as the creation of a file (CreateFile()), creation of a mutex (CreateMutex()), or Registry accesses (RegCreateKey()) for specific purposes. Hooking a particular event-related API or system service call, synchronously, can help trap those events. Applications doing system monitoring will find these kinds of hooking invaluable. These hooks could act as interrupts triggered by the occurrence of these events. A developer could write a routine to handle the occurrence of these events and take appropriate action.
Modifying System Behavior to Suit User Needs
Diverting the normal flow of control by introducing the hooks can modify operating system behavior. This enables the developer to change data structures and context at the time of hooking–enough to induce new behavior. For example, you can protect the opening of a sensitive file by hooking the NtCreateFile() system service. Although NTFS provides user-level security for files, this security is not available on FAT partitions. You should ensure that hooking does not have any undesirable side effects on the operating system. Protecting modifications to Registry keys is something easily doable when you hook the Registry system services. This has several applications, since little protection is provided for Registry settings created by applications.
Studying the Behavior of the System
In order to get a better idea of the internal workings of the operating system, studying the behavior of the system is something most debuggers or system hackers will relate to. Understanding of undocumented operating system functionality requires a lot of hacking, which goes hand in hand with hooking.
Debugging
Complex programs could make use of system-service hooking to debug the stickiest problems. For example, a few days back, we had a problem with the installation of a piece of software. We had difficulty creating folders and shortcuts for this application. Using a systemwide hook, we quickly figured that the installation program was looking for a Registry value that indicated where to install the folders (which happened to be the Start menu). We hooked the NtQueryValueKey() call, then obtained the value the installation program was looking for. We created that value and solved our problem.
Getting Performance Data for Specific Tasks and Generating Statistics
These tasks can prove very useful to those writing benchmarks and applications to critically measure system performance under specific conditions. Even measuring the frequency of certain system services becomes very easy with this type of hooking. Measuring file system performance by hooking the file system-related system services exemplify this procedure.
Life without hooking is unthinkable for most Windows developers in today’s Microsoft-dominated world of operating systems. Windows NT system services lie at the center of the NT universe, and having the ability to hook these can prove extremely handy.
TYPES OF HOOKS
The following sections explore two types of hooking.
Kernel-Level Hooking
You can achieve kernel-level hooking by writing a VXD or device driver. In this method, essential functions provided by the kernel are hooked. The advantage of this type of hooking is that you get one central place from which you can monitor the events occurring as a result of a user-mode call or a kernel-mode call. The disadvantage of this method is that you need to decipher the parameters of the call passed from kernel mode, since many times these services are undocumented. Also, the data passed to the kernel-mode call might differ from the data passed in a user-mode call. Also, a user-level API call might be implemented using multiple calls to the kernel. In this case, hooking becomes far more difficult. In general, this type of hooking is more difficult to achieve, but it can produce more rewarding results.
User-Level Hooking
You can perform this type of hooking with some help from a VXD or device driver. In this method, the functions provided by the user-mode DLLs are hooked. The advantage of this method is that these functions are usually well documented. Therefore, you know the parameters to expect. This makes it easy to write the hook function. This type of hooking limits your field of vision to user mode only and does not extend to kernel mode.
IMPLEMENTATIONS OF HOOKS
The following sections detail the implementation of hooks under various Microsoft platforms.
DOS
In the DOS world, system services are implemented as an interrupt handler routine (INT 21h). The compiler library routines typically call this interrupt handler to provide an API function to the programmer. It is trivial to hook this handler using the GetVect (INT 21h, AX=25h) and SetVect (Int 21h, AX=35h) services. Hence, hooking system services are fairly straightforward. DOS does not contain separate user and kernel modes.
Windows 3.x
In the Windows 3.x world, system services are implemented in DLLs. The compiler library routines represent stubs that jump to the DLL code (this is called dynamic linking of DLLs). Also, because the address space is common to all applications, hooking amounts to getting the address of that particular system service and changing a few bytes at that address. Changing of these bytes sometimes requires the simple aliasing of selectors.
XREF: Refer to the MSDN article in Microsoft Systems Journal (Vol. 9, No. 1) entitled, “Hook and Monitor Any 16-bit Windows(tm) Function With Our ProcHook DLL,” by James Finnegan.
Windows 95 and 98
In the Windows 95/98 world, system services are implemented in a DLL as in Windows 3.1. However, under Windows 95/98, all 32-bit applications run in separate address spaces. Because of this, you cannot easily hook any unshared DLL. It is fairly easy to hook a shared DLL such as KERNEL32.DLL. You simply modify a few code bytes at the start of the system service you want to hook and write your hook function in a DLL that is loaded in shared memory. Modifying the code bytes may involve writing a VXD, because KERNEL32.DLL is loaded in the upper 2GB of the address space and protected by the operating system.
Windows NT
In the Windows NT world, system services are implemented in the kernel component of NT (NTOSKRNL.EXE). The APIs supported by various subsystems (Win32, OS/2, and POSIX) are implemented by using these system services. There is no documented way of hooking these system services from kernel mode. There are several documented ways for hooking user-level API calls.
XREF: Refer to the MSDN articles in Microsoft Systems Journal entitled, “Learn System-Level Win32(r) Coding Techniques by Writing and API Spy Program,” by Matt Pietrek (Vol.9, No.12), and “Load Your 32-bit DLL into Another Process’s Address Space Using INJLIB,” by Jeffrey Richter (Vol.9, No.5).
Refer to CyberSensor on [url]http://www.cybermedia.co.in[/url]
We will present one way of achieving hooking of NT system services in kernel mode in this chapter. We also provide the code for this on the CD-ROM accompanying this book.
WINDOWS NT SYSTEM SERVICES
Windows NT has been designed with several design goals in mind. Support for multiple (popular) APIs, extensibility, isolation of various APIs from each other, and security are some of the most important ones. The present design incorporates several protected subsystems (for example, the Win32 subsystem, the POSIX subsystem, and others) that reside in the user space isolated from each other. The NT executive runs in the kernel mode and provides native support to all the subsystems. All subsystems use the NT system services provided by the NT executive to implement most of their core functionality.
Windows programmers, when they link with the KERNEL32, USER32, and GDI32 DLLs, are completely unaware of the existence of the NT system services supporting the various Win32 calls they make. Similarly, POSIX clients using the POSIX API end up using more or less the same set of NT system services to get what they want from the kernel. Thus, NT system services represent the fundamental interface for any user-mode application or subsystem to the kernel.
For example, when a Win32 application calls CreateProcess() or when a POSIX application calls the fork() call, both ultimately call the NtCreateProcess() system service from the NT executive.
NT system services represent routines, which run entirely in the kernel mode. For those familiar with the Unix world, NT system services can be considered the equivalent of system calls in Unix.
Figure 6-2 A caller program invoking an NT system service
Currently, Windows NT system services are not completely documented. The only place where you can find some documentation regarding the NT system services is on Windows NT DDK CD-ROMs from Microsoft. The DDK discusses about 25 different system services and covers the parameters passed to them in some detail. You’ll see from Appendix A that this is only the tip of the iceberg. In Windows NT 3.51, 0xC4 different system services exist, in Windows NT 4.0, 0xD3 different system services exist, and in Windows 2000 Beta-2, 0xF4 different system services exist.
We deciphered the parameters of 90% of the system services. Prototypes for all these system services can be found in UNDOCNT.H on the CD-ROM included with this book. We also provide detailed documentation of some of the system services in Appendix A.
In the following section, you will learn how to hook these system services.
HOOKING NT SYSTEM SERVICES
Let’s first look at how NT System Services are implemented in the Windows NT operating system. We also will discuss the exact mechanics of hooking an NT system service. In addition, we’ll explore the kernel data structures involved and provide sample code to aid hooking of system services.
On the CD: Check out hookdrv.c on the accompanying CD-ROM.
Implementation of a System Service in Windows NT
The user mode interface to the system services of NTOSKRNL is provided in the form of wrapper functions. These wrapper functions are present in a DLL called NTDLL.DLL. These wrappers use the INT 2E instruction to switch to the kernel mode and execute the requested system service. The Win32 API functions (mainly in KERNEL32.DLL and ADVAPI32.DLL) use these wrappers for calling a system service. The Win32 API functions performs validations on the parameters passed to the API functions, and translates everything to Unicode. After this, the Win32 API function calls an appropriate wrapper function in NTDLL corresponding to the required service. Each system service in NTOSKRNL is identified by the Service ID. The wrapper function in NTDLL fills in the service id of the requested system service in the EAX register, fills in the pointer to stack frame of the parameters in EDX register, and issues the INT 2E instruction. This instruction changes the processor to the kernel mode, and the processor starts executing the handler specified for the INT 2E in the Interrupt Descriptor Table (IDT). The Windows NT executive sets up this handler. The INT 2E handler copies the parameters from user-mode stack to kernel-mode stack. The base of the stack frame is identified by the contents of the EDX register. The INT 2E handler provided by NT Executive is internally called as KiSystemService().
During the initialization of NTOSKRNL, it creates a function table, hereafter referred to as the System Service Dispatch Table (SSDT), for different services provided by NTOSKRNL (see Figure 6-3). Each entry in the table contains the address of the function to be executed for a given service ID. The INT 2Eh handler looks up this table based on the service ID passed in EAX register and calls the corresponding system service. The code for each function resides in the kernel. Similarly, another table called the ParamTable (hereafter referred to as System Service Parameter Table [SSPT]) provides the handler with the number of parameter bytes to expect from a particular service.
Hooking NT System Services
The easiest way to put a hook into the system services is to locate the System Service Dispatch Table used by the operating system and change the function pointers to point to some other function inserted by the developer. You can do this only from a kernel-mode device driver because this table is protected by the operating system at the page table level. The page attribute for these pages is set so that only kernel-mode components can read from and write to this table. User-level applications cannot read or write these memory locations.
LOCATING THE SYSTEM SERVICE DISPATCH TABLE IN THE NTOSKRNL
There is one undocumented entry in the export list of NTOSKRNL called KeServiceDescriptorTable(). This entry is the key to accessing the System Service Dispatch Table. The structure of this entry looks like this:
typedef struct ServiceDescriptorTable {
PVOID ServiceTableBase;
PVOID ServiceCounterTable(0);
unsigned int NumberOfServices;
PVOID ParamTableBase;
}
where
ServiceTableBase Base address of the System Service Dispatch Table.
NumberOfServices Number of services described by ServiceTableBase.
ServiceCounterTable This field is used only in checked builds of the operating system and contains the counter of how many times each service in SSDT is called. This counter is updated by INT 2Eh handler (KiSystemService).
ParamTableBase Base address of the table containing the number of parameter bytes for each of the system services.
ServiceTableBase and ParamTableBase contain NumberOfServices entries. Each entry represents a pointer to a function implementing the corresponding system service.
The following program provides an example of hooking system services, under Windows NT. The system service NtCreateFile() hooks and the name of the file created prints when the hook gets invoked. We encourage you to insert code for hooking any other system service of choice. Note the proper places for inserting new hooks in the following code.
Here are the steps to try out the sample (assuming that the sample binaries are copied in C:\SAMPLES directory):
1. Run “instdrv hooksys c:\samples\hooksys.sys.” This will install the hooksys.sys driver. The driver will hook the NtCreateFile system service.
2. Try to access the files on your hard disk. For each accessed file, the hooksys.sys will trap the call and display the name of the file accessed in the debugger window. These messages can be seen in SoftICE or using the debug message-capturing tool.
#include "ntddk.h"
#include "stdarg.h"
#include "stdio.h"
#include "hooksys.h"
#define DRIVER_SOURCE
#include "..\..\include\wintype.h"
#include "..\..\include\undocnt.h"
typedef NTSTATUS (*NTCREATEFILE)(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocationSize OPTIONAL,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
PVOID EaBuffer OPTIONAL,
ULONG EaLength
);
#define SYSTEMSERVICE(_function)
KeServiceDescriptorTable.ServiceTableBase[
*(PULONG)((PUCHAR)_function+1)]
NTCREATEFILE OldNtCreateFile;
NTSTATUS NewNtCreateFile(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocationSize OPTIONAL,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
PVOID EaBuffer OPTIONAL,
ULONG EaLength)
{
int rc;
char ParentDirectory[1024];
PUNICODE_STRING Parent=NULL;
ParentDirectory[0]='\0';
if (ObjectAttributes->RootDirectory!=0) {
PVOID Object;
Parent=(PUNICODE_STRING)ParentDirectory;
rc=ObReferenceObjectByHandle(ObjectAttributes->RootDirectory,
0,
0,
KernelMode,
&Object,
NULL);
if (rc==STATUS_SUCCESS) {
extern NTSTATUS
ObQueryNameString(void *, void *, int size,
int *);
int BytesReturned;
rc=ObQueryNameString(Object,
ParentDirectory,
sizeof(ParentDirectory),
&BytesReturned);
ObDereferenceObject(Object);
if (rc!=STATUS_SUCCESS)
RtlInitUnicodeString(Parent,
L"Unknown\\");
} else {
RtlInitUnicodeString(Parent,
L"Unknown\\");
}
}
DbgPrint("NtCreateFile : Filename = %S%S%S\n",
Parent?Parent->Buffer:L"",
Parent?L"\\":L"", ObjectAttributes-
>ObjectName->Buffer);
rc=((NTCREATEFILE)(OldNtCreateFile)) (
FileHandle,
DesiredAccess,
ObjectAttributes,
IoStatusBlock,
AllocationSize,
FileAttributes,
ShareAccess,
CreateDisposition,
CreateOptions,
EaBuffer,
EaLength);
DbgPrint("NtCreateFile : rc = %x\n", rc);
return rc;
}
NTSTATUS HookServices()
{
OldNtCreateFile=(NTCREATEFILE)(SYSTEMSERVICE(ZwCreateFile));
_asm cli
(NTCREATEFILE)(SYSTEMSERVICE(ZwCreateFile))=NewNtCreateFile;
_asm sti
return STATUS_SUCCESS;
}
void UnHookServices()
{
_asm cli
(NTCREATEFILE)(SYSTEMSERVICE(ZwCreateFile))=OldNtCreateFile;
_asm sti
return;
}
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
MYDRIVERENTRY(DRIVER_DEVICE_NAME,
FILE_DEVICE_HOOKSYS,
HookServices());
return ntStatus;
}
NTSTATUS
DriverDispatch(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest (Irp,
IO_NO_INCREMENT
);
return Irp->IoStatus.Status;
}
VOID
DriverUnload(
IN PDRIVER_OBJECT DriverObject
)
{
WCHAR deviceLinkBuffer[] =
L"\\DosDevices\\"DRIVER_DEVICE_NAME;
UNICODE_STRING deviceLinkUnicodeString;
UnHookServices();
RtlInitUnicodeString (&deviceLinkUnicodeString,
deviceLinkBuffer
);
IoDeleteSymbolicLink (&deviceLinkUnicodeString);
IoDeleteDevice (DriverObject->DeviceObject);
}
SUMMARY
In this chapter, we explored system services under DOS, Windows 3.x, Windows 95/98, and Windows NT. We discussed the need for hooking these system services. We discussed kernel- and user-lever hooks. We discussed the data structures used during the system call and the mechanism used for hooking Windows NT system services. The chapter concluded with an example that hooked the NtCreateFile() system service.
页:
[1]