Nahashon

Windows Object Handles

What are handles ?

 

Handles are pointers to system resources such as memory addresses, processes, threads, files, registry keys and device drivers.

You can think of them as C/C++ void pointers that can be cast to pointers of other data types.

In this case, the handle, void*, can be casted to a process, thread or an open file.

 

 

Handles are context specific

 

The system interprets handles differently depending on the context in which they are being used.

In the context of processes: handle 0xA, might point to an open file in process 1 but point to a registry key in process 2.

It is also possible for a handle to exist in one context but not in another.

 

Handles must be used within the context in which they are obtained.

 

 

Handles are not pointers

Yes and no.

Handles are an abstraction of the underlying resources.

The system handles the complexities of representing and translating the object pointers transparently.

 

Therefore:

    - Yes: they can be real pointers depending on context and implementation

    - No: Again the context and impelementation defines.

 

Generally, handles as not the real pointers to the resources. 

 

 

Windows handles

 

Windows defines a handle as a C/C++ union, HANDLE_TABLE_ENTRY.

The context in which the handle is used determines the definition that applies.

 

Note: The HANDLE_TABLE_ENTRY is an undocumented windows structure.

 

windows 11 23H2 Handle_Table_Entry x64

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 Context Handles

In the context of a windows process, the Handle_Table_Entry is as follows:

 

windows 11 23H2 Process Context Handle_Table_Entry x64

struct _HANDLE_TABLE_ENTRY
{
    unsigned long long ObjectPointerBits:44;
  	unsigned long GrantedAccessBits:25;
  	unsigned long NoRightsUpgrade:1;
    unsigned long Spare1:6;
};

 

ObjectPointerBits

pointer to the opened object.

The object can be any of the system objects, drivers, files, registry keys, processes...

 

GrantedAccessBits

Permissions granted to the process on the object

 

 

Windows Process handles

 

Each windows process is represented in the kernel by a data structure called the _EPROCESS.

One of the _EPROCESS's member fields is the ObjectPointerBits that points to another structure called the HANDLE_TABLE.

 

The handle_table has all the information necessary to access all handles that belong to the target process context.

This includes handles opened while the process was in kernel mode, such as when invoking a system call or a driver IOCTL.

 

NoteThe HANDLE_TABLE is an undocumented windows structure and its definition varies between windows releases.

 

windows 11 23H2 Handle_Table x64

struct HANDLE_TABLE
{
    unsigned long NextHandleNeedingPool;
    long ExtraInfoPages;
    volatile unsigned long long TableCode;
    struct _EPROCESS* QuotaProcess;
    struct _LIST_ENTRY HandleTableList;
    unsigned long UniqueProcessId;
    union
    {
        unsigned long Flags;
        struct
        {
            unsigned char StrictFIFO:1;
            unsigned char EnableHandleExceptions:1;
            unsigned char Rundown:1;
            unsigned char Duplicated:1;
            unsigned char RaiseUMExceptionOnInvalidHandleClose:1;
        };
    };
    struct _EX_PUSH_LOCK HandleContentionEvent;
    struct _EX_PUSH_LOCK HandleTableLock;
    union
    {
        struct _HANDLE_TABLE_FREE_LIST FreeLists[1];
        struct
        {
            unsigned char ActualEntry[32];
            struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo;
        };
    };
};

 

 

HandleTableList

 

Windows maintains a circular doubly linked list of all HANDLE_TABLES.

The linked list includes handle tables from all windows processes including the kernel itself.

 

The HandleTableList field of the HANDLE_TABLE is a pointer to the next entry in the circular doubly linked lists.

 

The HandleTableList points to the HandleTableList member of the next HANDLE_TABLE structure in the circular doubly linked list. A pointer to the HANDLE_TABLE can be calculated by subtracting the offset of the HandleTableList member from the pointer.

 

HANDLE_TABLE* nextTable = (HANDLE_TABLE*)((unsigned long long)currentTable->HandleTableList - offsetof(HANDLE_TABLE, HandleTableList) );
 

 

TableCode

The TableCode field of the handle table structure holds two key information:

 

1. Layout

The layout is the value of bits 0 and 1 of the tableCode.

int layout = HandleTable.TableCode & 3;
 

2. A page pointer

Bits 2 to 63 for x64 and bits 2 to 31 for x86.

The page pointers are aligned to a 8 byte page boundary.

 
HANDLE_TABLE_ENTRIES* entries = HandleTable.TableCode ^ 3;

 

 

TableCode Layout

The TableCode layout section gives more information about the page pointer value of the table code.

The layout can be either, 0, 1 or 2.

For each case, the number of handle entries varies as well as the interpretation of the pointer.

 

Case 0

In this case, the TableCode page pointer points to a page containing the handle_table_entries.

HANDLE_TABLE -> [Object_Handles_Page]

 

Each page is 4096 bytes and each Handle_Table_Entry is typically 16 bytes.

The first entry in the page is normally invalid.

 

with this the maximum number of handles that can be stored in the page is:

x64 : max_handles = (4096 / 16) - 1 = 255

x86 : max_handles = (4096 / 8) - 1 = 511

 

If a process opens more handles than can't fit in the current page, the process is automatically elevated to case 1.

 

Case 1

The TableCode page pointer points to a page containing a list of page pointers.

Each of the nested pointers point to a page containing the handle_table_entries.

 

HANDLE_TABLE -> [pointers_page] -> [Object_Handles_Page]

 

Case 2

The nesting level increases

 

HANDLE_TABLE -> [pointers_page] -> [pointers_page] -> [Object_Handles_Page]

 

 

 

 

Share this article