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):
- Run “instdrv hooksys
c:\samples\hooksys.sys.” This will install the
hooksys.sys driver. The driver will hook the
NtCreateFile system service.
- 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.
|