Every Windows application developer is familiar with the Win32 Application Programming Interface (API). On Windows NT/2000, however, the Win32 API layer is just a high-level interface to a subsystem that is built on top of another API layer, called the Native API. Although not officially supported by Microsoft's developer tools, any Win32 application can directly access this API to perform actions not available via the Win32 subsystem. This article explains the prerequisites required to operate the Windows NT/2000 Native API, and presents a sample Win32 application that displays process and thread information in a console window, using the undocumented NtQuerySystemInformation() function.

You are not always required to write a kernel-mode driver in order to talk
to the Windows 2000/NT kernel—there is a nifty system component called
ntdll.dll that allows you to call a subset of the functions exported
by the kernel module ntoskrnl.exe from a user-mode application. However,
this technique is not officially supported by Microsoft and is largely undocumented,
so neither Microsoft's Platform Software Development Kit (SDK) nor
the Device Driver Kit (DDK) will get you very far here. This article
gives you the basic knowledge needed to access the so-called Native Application
Programming Interface (API) from user-mode.

INT 2Eh Interrupt Gate

The main duty of the ntdll.dll component is to provide the Win32
kernel32.dll library with a large set of utility functions. The function set
exported by this DLL can be subdivided into two basic categories:

Runtime functions executed entirely in user-mode. This set includes parts
of the standard C runtime library.

Kernel function wrappers that perform a switch from user-mode to
kernel-mode and back.

Although the former function set is very convenient, to say the least, the
latter is a true pearl. It allows a user-mode application to call an important
subset of the Windows 2000/NT kernel-mode interface by simply linking
dynamically to a DLL—exactly as an application calls the standard Win32 API
functions. The only problem is that Microsoft doesn't provide header files
that would contain the necessary definitions required to set up the function
calls properly. Of course, some of the definitions can be found in the DDK
header files, but unfortunately, you cannot simply #include these
headers in a user-mode application project written in plain C because this
results in lots of conflicts with the Win32 SDK header files.

Before I'll show you a way out of this mess, let us first examine how
the ntdll.dll component does the magic of calling into the kernel and
returning to user-mode. If you disassemble one of the functions with the name
prefix Nt or Zw, such as NtQuerySystemInformation(), you
come across a surprisingly short implementation, shown in Listing 1. Is this
all? Just a simple interrupt invocation? Well, of course, there is more to it. I
guess this code will seem quite familiar to DOS application developers. Most DOS
operating system functions are called by setting up the CPU register AH
with a function code and DX with an additional data parameter, if any,
followed by an INT 21h instruction. Obviously, the implementations of
NtQuerySystemInformation() and its fellow Nt*() functions use
the same scheme: Register EAX contains a function code, and
EDX points to the first function argument on the caller's
stack.

Listing 1 Implementation of ntdll.NtQuerySystemInformation()

The DOS INT 21h simply forces the CPU to branch to an address stored
in slot 21h of the Interrupt Vector Table (IVT). In a
protected-mode operation system such as Windows 2000/NT, interrupts are handled
quite differently. The IVT is replaced by the Interrupt Descriptor Table
(IDT), which contains memory descriptors of the target addresses rather than
simple pointers. In the case of INT 2Eh, the IDT provides an interrupt
gate that switches the CPU from user-mode to kernel-mode before branching to the
specified address, and switches back to user-mode when the call is about to
return. The target address of the INT 2Eh gate is located in the
ntoskrnl.exe module, and is named KiSystemService(). This
function examines the function number supplied in register EAX,
retrieves the corresponding function pointer from an internal kernel structure
called Service Descriptor Table (SDT), copies the caller's argument
stack to the current kernel-mode stack, and invokes the target function. On
return, control is passed back to the caller by means of the
ntoskrnl.exe function KiServiceExit(). Actually, this
mechanism is a bit more complex than explained here. (If you want to know more,
please refer to Chapter 2 of my book Undocumented Windows 2000 Secrets
[3]).