Geoff Chappell, Software Analyst
It is well known that a handle for any of the numerous objects of the windowing system, e.g., windows, hooks and cursors—let’s call them user objects and user handles—is not a formal HANDLE such as used for access to kernel-mode objects such as files, processes, threads (or even window stations and desktops). Though the windowing system’s Handle Manager is nowadays in kernel mode, it does not use the kernel’s Object Manager. That this separate design and implementation is well known may be mostly because it comes with advantages and disadvantages that programmers arguably do need to understand if they want their Windows programs to use the windowing system efficiently. Were it not well known for that, however, it would have become so because the openness of the windowing system’s Handle Manager has in its time caused much concern for computer security.
The openness is very much by design. User handles and objects are nowadays created by WIN32K.SYS in kernel mode. Before version 4.0, they were creations of WINSRV.DLL in the CSRSS.EXE process. But whether it is the kernel or CSRSS that acts as server to potentially numerous clients, much of the point to the windowing system is that its handles are directly transportable between client processes and its objects are simultaneously visible to the clients.
Each handle indexes a HANDLEENTRY structure in an array that acts as the handle table. The table is in shared memory, pointed to by the aheList member of the SHAREDINFO structure that each process gets an instance of when its USER32 connects to WIN32K. The current size of the table, as a count of entries, is held separately as the cHandleEntries member of the SERVERINFO structure that is also in shared memory.
The HANDLEENTRY is not documented. Though symbol files for WIN32K.SYS in Windows 8 and higher name the HANDLEENTRY in the C++ decorations of internal routines, type information for the structure is present in symbol files for Windows 7 only—not before and not since.
That said, these names (for both the structure and its members) that are known with certainty from symbol-file type information in version 6.1 are just those that were long known in public as names that Microsoft likely does use in the source code. These show in the plain-text output of meta-commands that are implemented in debugger extensions as supplied by Microsoft with its debuggers in various kits for both kernel-mode and user-mode programming. See, for instance, USEREXTS.DLL from as far back as the Device Driver Kit (DDK) for Windows NT 3.51. Though the HANDLEENTRY structure is not formally documented, Microsoft certainly has understood that knowledge of it may help programmers in the depths of debugging what they’re doing with Windows!
If only in retail builds of Windows, the HANDLEENTRY is 0x0C or 0x18 bytes in 32-bit and 64-bit Windows, respectively, except that before its one known change—for version 3.51—it was only eight bytes.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x00 | 0x00 |
HEAD *phead; |
all | |
0x04 | 0x08 |
PVOID pOwner; |
3.51 and higher | previously in HEAD |
0x04 (3.10); 0x08 |
0x10 |
UCHAR bType; |
all | |
0x05 (3.10); 0x09 |
0x11 |
UCHAR bFlags; |
all | |
0x06 (3.10); 0x0A |
0x12 |
USHORT wUniq; |
all |
The phead is the kernel-mode address of the object that corresponds to the handle. All objects that can be referred to by a handle begin with either a HEAD or a compatible elaboration such as a THRDESKHEAD or PROCDESKHEAD (depending, in these examples, on whether ownership of the object is by thread or process). The object may be intended to have user-mode visibility through some user-mode address that can be computed from the kernel-mode address and other input, notably by subtracting a per-process delta such as may exist in the process’s instance of the SHAREDINFO.
Not shown in the formal definition is that when a HANDLEENTRY is free, the phead does not point to any type of object but instead holds the index of another free HANDLEENTRY.
What pOwner points to, if anything, is either a PROCESSINFO or a THREADINFO, depending on the nature of ownership for the type of object. Again, this is a kernel-mode address—but it is a leaked kernel-mode address in the sense that what it points to is not mapped to any user-mode address.
Numerical interpretation of the bType varies greatly in the early history and is subject to misinformation (apparently from lazy analysis of output from debugger extensions). An enumeration of known possibilities is attempted in a separate section, below.
Though bits have been added to the bFlags, none are known to have changed. Again, an enumeration of known possibilities is attempted in a separate section, below.
In general, the high 16 bits of a handle must match the wUniq member in the HANDLEENTRY that is selected by the handle’s low 16 bits. The wUniq is incremented each time a handle is freed. Should the freed handle somehow get presented again for use, it will be stale in the sense of not selecting any valid HANDLEENTRY—well, not until the corresponding HANDLEENTRY is allocated and freed 64K times. This safeguard provided by the wUniq member does not apply in the exception to the generality, which is when the high 16 bits of a handle are all clear or all set (which cases occur when the handle has come from 16-bit code).
No formal enumeration of defined object types is known. The WINSRV executable in version 3.10 helpfully has descriptive strings pointed to from an array in increasing order of object type. No use is known to be made of the array, however, and it is here supposed that the strings and array were intended for debug output and are retained in the executable only by oversight when compiling for release. They indeed do not survive into the executable in version 3.51, but plainly are present in the source code since the .DBG file for that version has symbols for the strings and the array but with an OMAP entry that shows they were eliminated. Where these names do survive is the USEREXTS.DLL debugger extension, notably to support the !dhe command.
Of course, each user object is accessed in the code not by name but as a structure. Microsoft’s names for almost all these structures can nowadays be known with certainty because public symbol files for WIN32K.SYS in Windows 8 and higher provide the C++ decorations of the names of routines that work with the structures or of data that points to instances of the structures, e.g., for a list. For many of these structures, Microsoft’s names were known sooner with as much certainty by matching executable code with type information in the public symbol files for WIN32K.SYS from Windows 7 and with less certainty by matching against names and other details learnt from debugger extensions (that Microsoft seems to have last made public for Windows 2000).
The great change, and complication of description, was that a renumbering for version 3.51 shifted nearly half the defined object types towards the end. Ignore version 3.10, and the table looks much more orderly.
Type | Object | Name | Versions | Remarks |
---|---|---|---|---|
0x00 (3.10) | Callback | 3.10 only | ||
0x01 (3.10); 0x00 |
none | Free | all | |
0x02 (3.10) | Zombie | 3.10 only | next as 0x0F | |
0x03 (3.10) | WINDOWSTATION | WindowStation | 3.10 only | next as 0x0A |
0x04 (3.10) | DESKTOP | Desktop | 3.10 only | next as 0x0B |
0x05 (3.10); 0x01 |
WND | Window | all | |
0x06 (3.10); 0x02 |
MENU | Menu | all | |
0x07 (3.10) | SVR_INSTANCE_INFO | DDE access | 3.10 only | next as 0x0C |
0x08 (3.10) | DDECONV | DDE conv | 3.10 only | next as 0x0D |
0x09 (3.10); 0x03 |
CURSOR | Icon/Cursor | all | |
0x0A (3.10) | ACCELTABLE | Accelerator | 3.10 only | next as 0x09 |
0x0B (3.10) | HOOK | Hook | 3.10 only | next as 0x05 |
0x0C (3.10); 0x04 |
SMWP | WPI(SWP) structure | all | |
0x05 | HOOK | Hook | 3.51 and higher | |
0x0D (3.10) | XSTATE | DDE Transaction | 3.10 only | next as 0x0E |
0x0E (3.10); 0x06 (3.51 to 4.0) |
THREADINFO | ThreadInfo | 3.10 to 4.0 | |
0x0F (3.10); 0x07 (3.51) |
Q | Input Queue | 3.10 to 3.51 | |
0x07 (4.0); 0x06 |
Clipboard Data | 4.0 and higher | ||
0x10 (3.10); 0x08 (3.51 to 4.0); 0x07 |
CALLPROCDATA | CallProcData | all | |
0x09 (3.51 to 4.0); 0x08 |
ACCELTABLE | Accelerator | 3.51 and higher | previously 0x0A |
0x0A | WINDOWSTATION | WindowStation | 3.51 only | previously 0x03 |
0x0B | DESKTOP | Desktop | 3.51 only | previously 0x04 |
0x0C (3.51); 0x0A (4.0); 0x09 |
SVR_INSTANCE_INFO | DDE access | 3.51 and higher | previously 0x07 |
0x0D (3.51); 0x0B (4.0); 0x0A |
DDECONV | DDE conv | 3.51 and higher | previously 0x08 |
0x0E (3.51); 0x0C (4.0); 0x0B |
XSTATE | DDE Transaction | 3.51 and higher | previously 0x0D |
0x0F (3.51); 0x0D (4.0); 0x0C |
Zombie | 3.51 to 4.0 | previously 0x02 | |
MONITOR | Monitor | 5.0 and higher | ||
0x0E (4.0); 0x0D |
KL | Keyboard Layout | 4.0 and higher | |
0x0F (4.0); 0x0E |
KBDFILE | Keyboard File | 4.0 and higher | |
0x0F | EVENTHOOK | WinEvent Hook | 5.0 and higher | |
0x10 | TIMER | Timer | 5.0 and higher | |
0x11 | IMC | Input Context | 5.0 and higher | |
0x12 | HIDDATA | 5.1 and higher | ||
0x13 | DEVICEINFO | 5.1 and higher | ||
0x14 | TOUCHINPUTINFO | 6.1 and higher | ||
0x15 | GESTUREINFO | 6.1 and higher | ||
0x16 | HID_POINTER_DEVICE_INFO | 6.2 and higher |
Some types of user objects were discontinued by the windowing system’s migration to kernel mode in version 4.0. The WINDOWSTATION and DESKTOP became kernel objects in the sense of having handles that are subject to the Object Manager’s notions of security. The THREADINFO and Q became kernel-mode structures that are exposed through no sort of handle.
Version 4.0 is here thought to have retained an object type for the THREADINFO despite making the latter into a kernel-mode creation with no user-mode access. Be aware that support for this conjecture just from what’s in the executable is slim: an array of pool allocation tags in increasing order of object type has “Usti” in just the right place for the supposed retention.
In no version is any use known of what version 3.10 names as Zombie. That it was renumbered as 0x0F for version 3.51 is certain. That it survives to version 4.0 is based on nothing more than having no idea what else might have been defined in its place only to be left unused in version 4.0 for someone to reassign, out of sequence, for version 5.0.
No name is known for the structure that supports the Clipboard Data. The structure is apparently too simple to survive inlining and get its name into symbol files. The object is used for passing arbitrary data, especially but not only for the clipboard. The structure is a HEAD and then a dword that holds the size in bytes of the opaque data that follows.
No name is yet found in any symbol files, neither in type information nor C++ decorations, for the structure that supports the Accelerator object. The name ACCELTABLE is instead known from tables of what might be termed structure offsets that debugger extensions from DDKs for early Windows versions use for their !dso command.
Not shown above for the object names that are learnt from the !dhe command is that USEREXTS version 4.0 has no name for the Keyboard File and version 5.0 cuts “WPI(SWP) structure” to “WPI(SWP) struct”.
For completeness, it’s as well to note that other Microsoft sources of information about the HANDLEENTRY have other names for some object types.
Though the version 3.51 executable loses the names that show in debugger output, it has a different set of names which it puts to use for tagging the desktop heap. This tagging doesn’t survive the move to kernel mode in version 4.0—see that the RtlCreateTagHeap function, which is exported from NTDLL.DLL in version 3.51 and higher, never has been a kernel-mode export—but the names correspond to a separate set of names that does survive to later versions, just not in the executable. This other set shows in the output of the !du command and is notable because it continues for some object types that never have been valid to WINSRV or WIN32K and anyway has plainly incorrect names for some, even most, numerical values.
Type | Name (Tag Heap) | Name (!du) | USEREXTS Versions | Remarks |
---|---|---|---|---|
0x00 | FREE | Free | 3.51 to 5.0 | |
0x01 | WINDOW | Window | 3.51 to 5.0 | |
0x02 | MENU | Menu | 3.51 to 5.0 | |
0x03 | CURSOR | Cursor | 3.51 to 5.0 | |
0x04 | SETWINDOWPOS | SetWindowPos | 3.51 to 5.0 | |
0x05 | HOOK | Hook | 3.51 to 5.0 | |
0x06 | THREADINFO | Thread Info | 3.51 to 5.0 | incorrect in 5.0 |
0x07 | INPUTQUEUE | Input Queue | 3.51 | |
Clip Data | 4.0 to 5.0 | incorrect in 5.0 | ||
0x08 | CALLPROC | Call Proc | 4.0 to 5.0 | incorrect in 5.0 |
0x09 | ACCELTABLE | Accel Table | 4.0 to 5.0 | incorrect in 5.0 |
0x0A | WINSTATION | WindowStation | 4.0 to 5.0 | incorrect |
0x0B | DESKTOP | DeskTop | 4.0 to 5.0 | incorrect |
0x0C | DDEACCESS | DdeAccess | 4.0 to 5.0 | incorrect |
0x0D | DDECONV | DdeConv | 4.0 to 5.0 | incorrect |
0x0E | DDEXACT | DdeExact | 4.0 to 5.0 | incorrect |
0x0F | ZOMBIE | Zombie | 3.51 to 4.0 | incorrect in 4.0 |
Monitor | 5.0 | incorrect | ||
0x10 | CTYPES | Ctypes | 3.51 to 5.0 | |
0x11 | CONSOLE | Console | 3.51 to 5.0 | |
0x12 | GENERIC | Generic | 3.51 to 5.0 | |
0x13 | HM | |||
0x14 | LOCK |
The names for tagging the desktop heap exist in the executable as a null-terminated sequence of null-terminated Unicode strings. Except towards the end, these strings do correspond reliably to the object types, but no reason is known that any of them must correspond at all.
Debugger extensions from early development kits helpfully have descriptive strings for the bits within the bFlags. It seems highly plausible that these are the macros that are used for the bits in the source code. They are not, however, all the defined bits.
Mask | Symbol | Versions |
---|---|---|
0x01 | HANDLEF_DESTROY | all |
0x02 | HANDLEF_INDESTROY | all |
0x04 | HANDLEF_INWAITFORDEATH | |
0x08 | HANDLEF_FINALDESTROY | all |
0x10 | HANDLEF_MARKED_OK | |
0x20 | HANDLEF_GRANTED | 5.0 and higher |
0x40 | 5.0 and higher |
The 0x01 bit is set when the object is marked for destruction, which cannot happen while the cLockObj in the HEAD is non-zero. The 0x02 bit is set if destruction ever begins.
The 0x40 bit dates from version 5.0 but is not named by the USEREXTS from that version. It is set when the object is not on the desktop heap despite being a type that ordinarily would be. One known case is the window that is its terminal’s desktop owner.