Although the Windows NT API is not object-oriented, NT uses objects internally to provide support for certain features uniformly across a variety of different entities. Internally, files, events, mutexes, pipes, and so on, are all implemented as objects. This allows NT to perform some operations, such as security checks, in a generic fashion for different types of objects. The object manager is a part of the NT executive that provides services that allow other NT subsystems to operate on objects. Most parts of these services are not currently documented, but this article describes how you can access some of them to discover information such as how many partitions a disk has.
The Object Manager Database
To allow symbolic access to NT objects, NT allows them to have names. Rather than have a single global namespace and require all object names to be unique, the NT object manager organizes object names into a hierarchy, just like the hierarchical names you give to DOS files (in fact, the file system is conceptually just a subtree of the overall NT object namespace tree). You may have run into this hierarchical namespace when, for example, creating a pipe; named pipes have names of the form \\<server>\pipe\<pipename>, where <server> is the name of the server that created the pipe (or .. to indicate a local pipe), and <pipename> is the name of the individual pipe you want to refer to. You can think of this as though the object manager reserves a directory called pipe for holding the names of pipes created for any given server.
Just as a particular part of the object namespace is reserved for named pipes, other object directories in the namespace are reserved for other purposes. For example, the Device directory contains a list of objects that represent devices, such as Floppy0, HardDisk0, and so on. A variety of interesting information is available, if you could just traverse the object managers namespace.
The object manager also supports symbolic links. As the name implies, these are place holders that refer to other objects in the namespace tree. Symbolic links allow NT to make the same object available under different names. For example, NT may have named your floppy drive \Device\Floppy0, but by creating a symbolic link called A: that refers to it, you can use familiar DOS names to access the floppy drive.
The namespace is created dynamically by the object manager at runtime, so theres no disk file you can use to traverse it. Instead, you have to find a way to access the API that the object manager provides for other NT subsystems.
Viewing the Object Manager Namespace
There are some user-level tools available to view the namespace. ObjDir is part of the DDK and WinObj is available from www.ntinternals.com. But if you look in the DDK documentation you will notice that functions to access the namespace are not documented. Sometimes it is useful to programmatically find out which drivers are installed or how many disk partitions are available.
If you look at the exported symbols of ntdll.dll (e.g., by using DUMPBIN, which is part of MS VC 4.x) you will find lots of functions that are not documented. Of particular interest are NtOpenDirectoryObject(), NtQueryDirectoryObject(), NtOpenSymbolicLinkObject(), and NtQuerySymbolicLinkObject(). When you peek into ntdll.lib (the import library of ntdll.dll) with a hex editor, you find a hint for the number of parameters the functions expect. Visual C++ demands export libraries that use mangled names, even if the names in the corresponding DLL are not mangled, and the mangling includes an indication of the number of bytes of parameters the function takes. The number after the @ of the mangled symbol name is the total size in bytes of all parameters.
In Win32 every parameter is at least four bytes (there are 64-bit parameters, but not here). Almost all Windows NT executive functions return a status code, so the four functions here would probably do so, too. For the two NTOpenXxxx() functions, it was easy to guess that they have parameters similar to the documented functions in the DDK that open objects. Interestingly, the access flags for directory and symbolic link objects are already in ntddk.h. Discovering the arguments to NtQueryDirectoryObject() was not that easy, but you can find out what the parameters may mean by disassembling, for example, ObjDir and then playing around with the function.
NtQueryDirectoryObject() is used to get the name and type of the objects in a directory object. A specific object is referenced by a zero-based index. An error is returned if the index is too large. The type of an object is a string such as Device, Directory, SymbolicLink, and so on. For the additional parameters of this function see the comments in objlst.h (Listing 1).
In a similar way, you can find out about NtQuerySymbolicLinkObject(), which is easier because it has fewer parameters. The function resolves a symbolic link and returns the name of the object to which the link points.
The header file objlst.h shows the prototypes for the functions. The naming convention and types are similar to ntddk.h of the DDK. You may have noticed that there are similar functions in the export list of ntdll.dll that start with ZwXxxx instead of NtXxxx. These functions differ only in the way they do internal parameter checking. The ZwXxxx functions are called from device drivers and the parameters are not checked, whereas the NtXxxx functions are called from subsystems and the parameters are validated (detailed parameter checking depends on the mode from which a function is called; NtXxxx functions always set the previous mode to user mode).
The sample program objlst.c (Listing 2) is a simple tool which uses the four functions. It recursively lists the directories of the Object Manager. DumpAllObjects() is the workhorse; it traverses a directory, resolves symbolic links if necessary, and recurses into other directories if it finds one. An optional parameter for the program specifies the directory to start with (e.g., OBJLST \Device to list the device directory).
objlst.c requires ntddk.h (and all included headers) and ntdll.lib from the DDK. It shows how to write a simple Win32 console application that uses Windows NT executive functions and that can be compiled without using the standard DDK build tool. Of course, you can use the functions in a kernel-mode driver also. You can also use the ZwXxxx functions instead of the NtXxxx counterparts.
Note that a Microsoft-specific extension of the format strings for the printf() function class is used. %Z and %wZ are used to print PANSI_STRING and PUNICODE_STRING parameters. PANSI_STRING and PUNICODE_STRING do not directly point to the string buffer they point to a structure which contains the number of characters in the buffer, the maximum length of the buffer, and a pointer to the buffer itself. The string in the buffer is not guaranteed to be zero-terminated.
Sometimes you need to use undocumented functions to solve a problem. There is always the issue of whether it is safe to use them. Of course, they are not supported by Microsoft and they may change without notice in the next release or service pack (NT executive functions probably wont). But you can perform many interesting tasks which are otherwise nearly impossible. There are still a lot of things to uncover in Windows NT.
Dieter Spaar is a self-employed software developer doing high-level and low-level programming since 1990. When not working for clients, he relaxes by uncovering undocumented features in various pieces of software. Dieter can be reached at [email protected]