Handles
Handles are tokens used to identify and reference system resource such as memory blocks, processes, threads, files, registry keys and device drivers. Handles are usually integers or pointer values.
Handles are abstractions.
They abstract the underlying complexities of representing system resources. For example, a process does not need to know where or how a file is loaded into memory. As long as it has a handle to the file, it can use it to perform file manipulations and the operations will be applied to the right file.
Handles are context specific.
Each handle is context specific and identifies only a single unique resource within that context. Taking a process view context, a handle with value 0x8 in process A is different from a handle with the same value in process B. The handle my be a file reference in process A but point to device driver in process B.
Taking this into account, each handle is valid and can only be used within the context it was obtained.
Each handle carries its own set of attributes such as permissions.
Windows Object Handles
Windows object handles are issued and managed by the kernel.
Internally, the kernel defines a handle as a C/C++ union called HANDLE_TABLE_ENTRY.
The context in which the handle is being used determines the union definition that applies.
NOTEThe
HANDLE_TABLE_ENTRYis an undocumented Windows structure and may vary between Windows major and minor releases.
union _HANDLE_TABLE_ENTRY { volatile long long VolatileLowValue; long long LowValue; struct { struct _HANDLE_TABLE_ENTRY_INFO* volatile InfoTable; long long HighValue; union _HANDLE_TABLE_ENTRY* NextFreeHandleEntry; struct _EXHANDLE LeafHandleValue; }; long long RefCountField; unsigned long long Unlocked:1; unsigned long long RefCnt:16; unsigned long long Attributes:3; struct { unsigned long long ObjectPointerBits:44; unsigned long GrantedAccessBits:25; unsigned long NoRightsUpgrade:1; unsigned long Spare1:6; }; unsigned long Spare2;};Process Handles
In a process context, the HANDLE_TABLE_ENTRY is interpreted as follows:
struct _HANDLE_TABLE_ENTRY { unsigned long long ObjectPointerBits:44; unsigned long GrantedAccessBits:25; unsigned long NoRightsUpgrade:1; unsigned long Spare1:6;};| Member | Description |
|---|---|
| ObjectPointerBits | Pointer to the system resource, driver, file, registry key, process. |
| GrantedAccessBits | Permissions granted to the handle on the system resource. |
To obtain a resource handle, a process typically opens the resource using Win32 API calls, OpenProcess, OpenFile.
A process can open multiple handles to the same resource. In this scenario, the ObjectPointerBits are similar but the GrantedAccessBits might defer based on the requested and granted access permissions.
NOTEThe Win32 API is well documented and provides definitions for API calls to work with resource handles.
TIPHandles obtained when a process is in kernel mode are normally granted higher access rights to resources. To prevent handle leaks, it is important for processes to close handles when done working with the resources. This also helps in preventing malicious applications from hijacking handles to protected system resources.
The Windows kernel also closes all handles obtained by a process when it terminates or is killed.
Locating Process Handles
Each process is represented in the kernel by a C/C++ structure called the EPROCESS.
NOTEThe
EPROCESSstructure is enormous and undocumented. For brevity, the definition is shortened to the interesting part.
struct _EPROCESS { // ... // 0x0-0x560 struct _EPROCESS_QUOTA_BLOCK* QuotaBlock; // 0x568 struct _HANDLE_TABLE* ObjectTable; // 0x570 VOID* DebugPort; // 0x578 struct _EWOW64PROCESS* WoW64Process; // 0x580 // ... // 0x588-0x70}The ObjectTable member of the EPROCESS points to another C/C++ structure called the HANDLE_TABLE.
The HANDLE_TABLE provides valuable information needed to enumerate all handles opened by a process as well as all handles opened by other processes in the system.
Basically, if you locate the HANDLE_TABLE for one process, you can locate for any other process in the system including the kernel and protected Windows processes.
NOTEThe definition of the
HANDLE_TABLEis again shortened for brevity. This is also an undocumented structure.
struct HANDLE_TABLE { unsigned long NextHandleNeedingPool; long ExtraInfoPages; volatile unsigned long long TableCode; struct _EPROCESS* QuotaProcess; struct _LIST_ENTRY HandleTableList; ULONG UniqueProcessId; //....};The NextHandleNeedingPool, TableCode, and HandleTableList are very important members of the HANDLE_TABLE.
HandleTableList
Windows maintains a circular doubly linked list of all HANDLE_TABLES in the system.
This is why if you can find one HANDLE_TABLE, you can find any other HANDLE_TABLE in the system.
The HandleTableList is a member of this circular doubly linked lists of HANDLE_TABLEs.
This is the definition of LIST_ENTRY structure.
struct _LIST_ENTRY { struct _LIST_ENTRY* Flink; //0x0 struct _LIST_ENTRY* Blink; //0x8};The Flink is the forward list in the circular doubly linked list while the Blink is the backward list.
The Flink and Blink members do not point directly to the start of the next HANDLE_TABLE.
Instead, they point to the HandleTableList member of the HANDLE_TABLE.
To get the actual HANDLE_TABLE pointer, you can subtract the offset of the HandleTableList member from the Flink or Blink value.
HANDLE_TABLE* pNextTable = (HANDLE_TABLE*)((unsigned long long)pHandleTable->HandleTableList - offsetof(HANDLE_TABLE, HandleTableList) );TableCode
The TableCode field of the handle table structure holds two key information:
-
Page pointer:
A pointer to a 4KB aligned memory page. -
Layout:
Format of the information in the page pointed to by the page pointer.
The layout is obtained from bits 0-1 of the TableCode.
The page pointer on the other hand is obtained by masking bits 0-1 of the TableCode.
The page pointer is thus always aligned to a 4KB page boundary.
# Get the layout from the TableCodeint layout = HandleTable.TableCode & 3;
# Get page pointer from the TableCodevoid* page_pointer = (void*)(HandleTable.TableCode & ~3);TableCode Layout
The TableCode layout, is bits 0-1 of the TableCode.
It can be either of the 3 decimal values, 0, 1, or 2.
In binary, that is 00, 01, or 10.
Each value determines how the page pointed by the page pointer is interprated.
Case 0 (Level 0 page)
In this case the page pointer points to a page containing the handle table entries.
Each entry in the page is an instance of the HANDLE_TABLE_ENTRY struct.
This is the simplest form of the handle table. Just a single page containing an array of handle table entries.
The first entry in the page is normally invalid.
A page is 4096 bytes and the HANDLE_TABLE_ENTRY struct is 16 bytes in x64 and 8 bytes in x86.
That means the maximum handle table entries that can fit in the page is 255 in x64 and 511 in x86.
# in x64max_entries = (4096 / 16) - 1 = 255
# in x86max_entries = (4096 / 8) - 1 = 511If a process opens more handles than can fit in the page, the process is automatically elevated to case 1.
page_pointer├── handle_table_entry_00├── handle_table_entry_01└── handle_table_entry_02Case 1 (Level 1 page)
In this case the page pointer points to a page that points to other Level 0 pages.
Each entry in the page pointed by the page pointer is basically a Level 0 page pointer.
page_pointer├── page_pointer_A| ├── handle_table_entry_A0| ├── handle_table_entry_A1| └── ...└── page_pointer_B ├── handle_table_entry_Bx ├── handle_table_entry_Bx └── ...Case 2 (Level 2 page)
The nesting increases again with a case 2 layout.
Each entry in the page pointed by the page pointer is a Level 1 page pointer.
page_pointer├── page_pointer_A| ├── page_pointer_A0| | ├── handle_table_entry_A00| | ├── handle_table_entry_A01| | └── ...| ├── page_pointer_A1| | ├── handle_table_entry_A10| | ├── handle_table_entry_A11| | └── ...| └── ....└── page_pointer_B ├── page_pointer_B0 | ├── handle_table_entry_B00 | ├── handle_table_entry_B01 | └── ... ├── page_pointer_B1 | ├── handle_table_entry_B10 | ├── handle_table_entry_B11 | └── ... └── ...NextHandleNeedingPool
Now that you have understood the TableCode, it is easier to understand the NextHandleNeedingPool member.
This field is used by the kernel when allocating pages for a process handles.
It indicates the next handle entry which will require a new page to be allocated when it is used.
This saves the kernel time since it no longer has to traverse the handle tables and non existent memory structures just to find out the next handle that will trigger new pages to be created.
If a handle is bigger than or equal to the NextHandleNeedingPool value, it is most likely invalid or uninitialized.
Summary
- Each process is represented by an
EPROCESSstructure in the kernel. - The
ObjectTablemember or theEPROCESSpoints to the process’s handle table,HANDLE_TABLE. - The
HandleTableListmember of the handle table is an entry in a circular doubly linked list of all handle tables in the system. - The
TableCodemember of the handle table, points to a page whose interpretation depends on the lower 2 bits of its value known as thelayout. - Each handle is unique with its own set of attributes.
Conclusion
You now have a much better understanding of how object handles work in Windows. Given a debugger like livekd or windbg, you are now armed with enough information to manually traverse your way around the kernel to find the various object handles.
WARNINGThe post makes several references to undocumented internal Windows structures. These structures are subject to change with changes in Windows releases and updates. Make sure to get updated structures for your specific Windows build.
WARNINGThe information given herein is for educational purposes only.
This gives a deaper understanding of the playing field leading to even more secure software solutions.