# Object Management

## Object Management

## Object Management - The Basics

In our series so far, we’ve explored the system’s architecture, the processes it runs, and the memory they occupy. Throughout, we’ve repeatedly encountered **objects** and **handles**. Everything from processes and threads to files and events is represented in the kernel as a carefully managed, protected entity.

The key to interacting with these resources lies in the **Object Manager**, a core component of the Windows Executive. In this post, we’ll unpack this essential mechanism, examining how kernel objects are organized, how handles relate to objects, and the practical ways objects are shared between processes and their lifetimes managed.

#### Kernel Objects and the Object Manager

Much of the functionality in the Windows operating system is built around the concept of **kernel objects**. These are fundamental data structures, managed by the kernel, that represent system resources. For example, when an application needs to access a file, it calls the **CreateFile API**. This function asks the OS to create a **file object** in the kernel, and in return, the application receives a **handle**—a unique identifier that allows it to interact with that file. The file object itself is an abstraction that allows the application to communicate with devices and files in a standardized way.

At the center of this architecture is the **Object Manager**, a core component of the Windows **Executive**. The Object Manager's role is to **manage all kernel objects in a generic, uniform way**. It doesn't need to understand the specific details of a file object versus a thread object; it simply knows how to perform common management tasks for all of them. Its key responsibilities include maintaining a **reference count** for each object and, for objects that can be named, managing them within a hierarchical **namespace**. This namespace allows different processes to look up and share objects by name.

The **reference counting** mechanism is crucial for managing the lifetime of an object. When an application gets a handle to an object, the object's reference count is incremented. When the application is finished and calls **CloseHandle**, the reference count is decremented. **However, closing a handle does not necessarily mean the object is immediately destroyed.** The Object Manager will only destroy the object when its reference count drops to **zero**, which happens only after the very last handle has been closed and the last kernel-mode pointer to it has been released. This ensures that an object remains valid and accessible as long as any part of the system is still using it.

Accessing these kernel objects differs significantly depending on whether the code is running in user mode or kernel mode. For a **user-mode** application, the only way to interact with a kernel object is indirectly, through a **handle**. This is a critical security boundary that prevents user applications from directly accessing or corrupting kernel memory.

In contrast, **kernel-mode** clients, such as the kernel itself or device drivers, have more privileged access. They can work with kernel objects using either **handles** or direct **pointers** to the object's location in kernel memory. The choice often depends on which API is being used, as some require handles while others require pointers. Within kernel mode, it is a straightforward process to convert between these two representations using documented functions provided by the Windows Driver Kit (WDK).

#### Object Structures

**The Windows Object Manager** is designed to handle all kernel objects in a uniform way, providing a generic management layer regardless of an object's specific type. To achieve this, every kernel object is constructed from two fundamental pieces: a standardized header and a type-specific body. The first part is **the Object Header**, which is owned and managed by the **Object Manager** itself. This header **is identical in structure for every object and contains all the generic information common to all object types.** The second part is **the Object Body**, which contains the actual data and attributes that define what the object is—the specifics that make a process a process, a file a file, or a thread a thread. This body is owned by the relevant component of **the Windows Executive** that manages that type. For some core object types, the body itself is layered. **A Process Object**, for example, is represented by the executive-level EPROCESS structure. However, the very first member of this structure is a KPROCESS structure, a lower-level object owned by the Kernel that handles more fundamental, scheduler-related details. The same pattern applies to threads, with an ETHREAD structure containing a KTHREAD structure. In contrast, higher-level objects like a Job Object are simpler; they are represented by a single EJOB structure and have no corresponding KJOB, as they are managed entirely by the Executive. The generic Object Header contains several crucial fields that enable the Object Manager to do its job. If an object is named, the header stores its **name** and a **reference** to the directory object where it resides within **the Object Manager's hierarchical namespace.** It also holds a **Security Descriptor**, which is a data structure that defines the access control for the object, specifying who can perform what actions on it. Every kernel object can be protected by a security descriptor. Furthermore, the header maintains a **Reference Count**, which is the key to managing an object's lifetime. This count is the sum of two separate values: the number of **open handles** to the object (from user or kernel mode) and the number of **direct pointers** to the object held by kernel-mode components. The Object Manager will only destroy an object when this combined reference count drops to zero. To understand what an object is, the header contains a **pointer** to a **Type Object**. You can think of the Type Object as the **class** definition in object-oriented programming. All objects of the same type, such as all process objects, point to the same single Type Object that defines the behavior and properties for that class. This Type Object itself is a singleton and contains important metadata, including a simple string for the Type Name, such as **Process** or **Job**. It also specifies **the Pool Type**, which indicates whether objects of this type should be allocated from the **Non-Paged Pool** (memory that can never be swapped to disk) or **the standard Paged Pool**. Another piece of metadata is the Synchronizable Flag, a boolean indicating whether threads can wait on objects of this type using functions like **WaitForSingleObject**. Finally, the Type Object contains a set of Object Methods, which are functions (similar to virtual methods) that define **type-specific behavior for operations like opening, closing, or parsing the object.**

![source: Windows Internals course by Pavel](https://2365385460-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjcSqiYcR3JLKrNVQi5Hh%2Fuploads%2FmuCw27XH9CPYs7IbPFhw%2Fimage.png?alt=media)

#### Object Types Exposed by The Windows API

The Windows operating system abstracts its core resources into a uniform set of **kernel objects**. An application interacts with these secure, kernel-managed structures through handles. Understanding these object types and their associated APIs is fundamental to Windows programming.

**Core Execution and Management Objects**

* **Process:** This object represents a running application, encapsulating its memory space, handles, and threads. To launch a new application, a developer calls **CreateProcess**. To interact with an already running process (for example, to read its memory or terminate it), one must first get a handle to it using **OpenProcess**.
* **Thread:** The fundamental unit of execution within a process. A new thread is created using **CreateThread**, allowing for concurrent operations within a single application. A handle to an existing thread can be obtained via **OpenThread**.
* **Job:** A powerful container object used to manage a group of processes as a single unit. After creating a job with **CreateJobObject** or opening an existing one with **OpenJobObject**, a developer can use **SetInformationJobObject** to apply resource limits, such as restricting CPU time or memory usage, to all processes within that job.

**Memory and File System Objects**

* **File Mapping (Section):** This object is the foundation for both shared memory and memory-mapped files. It is created with **CreateFileMapping** and can be shared between processes by name using **OpenFileMapping**. To actually access the memory, a process must create a view of the section in its own address space by calling **MapViewOfFile(Ex)**.
* **File:** This versatile object provides an abstraction for interacting with files, directories, and devices. The **CreateFile** API is the universal function for opening a handle to any of these resources. Once the handle is obtained, data is transferred using functions like **ReadFile** and **WriteFile**, while device-specific commands are sent using **DeviceIoControl**.

**Security and Synchronization Objects**

* **Token:** The Access Token object encapsulates the security context of a process or thread. A handle to a process's token can be retrieved with **OpenProcessToken**. A new token representing a user's credentials can be created with **LogonUser**, and existing tokens can be copied, often to impersonate a different security context, using **DuplicateTokenEx**.
* **Mutex (Mutant):** A synchronization primitive that ensures mutual exclusion, allowing only one thread at a time to own it. A mutex is created with **CreateMutex(Ex)** and can be shared across processes by name with **OpenMutex**. After a thread finishes its work in a protected section, it must call **ReleaseMutex** to allow other threads to acquire it.
* **Event:** A simple signaling object used to notify threads that a specific condition has occurred. After creating an event with **CreateEvent(Ex)** or opening a shared one with **OpenEvent**, its state is controlled with **SetEvent** (to signal it) and **ResetEvent** (to return it to the non-signaled state).
* **Semaphore:** A counting synchronization object that allows a limited number of threads to access a resource pool concurrently. It's created with **CreateSemaphore(Ex)**. When a thread successfully waits on the semaphore, its count is decremented. When the thread is done, it calls **ReleaseSemaphore** to increment the count, allowing another waiting thread to proceed.
* **Timer:** A kernel object that can be set to transition to a signaled state at a specific time or at regular intervals. A waitable timer is created with **CreateWaitableTimer(Ex)** and armed with specific timing information using **SetWaitableTimer**.

**Advanced I/O and UI Objects**

* **I/O Completion Port:** A highly scalable mechanism for managing a large number of asynchronous I/O operations. A port is created with **CreateIoCompletionPort**, and file handles are associated with it. Worker threads then call **GetQueuedCompletionStatus(Ex)** to efficiently wait for and process I/O completion packets as they arrive.
* **Window Station:** A secure object that contains a group of other user interface objects, such as desktops. A process is associated with a window station, which can be created with **CreateWindowStation** or controlled with functions like **SetProcessWindowStation**.
* **Desktop:** An object contained within a window station that holds the logical display surface for UI elements (windows, menus, etc.). While users typically interact with one primary desktop, applications can use **CreateDesktop** to create additional desktops and **SwitchDesktop** to make them visible.

All of these object types are created and reside in kernel space, making them accessible from both user mode (via handles) and kernel mode (via handles or direct pointers). They are the essential building blocks that the Windows OS provides for applications to function.

While we know kernel objects have a standardized **header** and a **type-specific body**, seeing these structures firsthand provides a much deeper understanding. Using a combination of tools like Process Explorer and the Windows Kernel Debugger (WinDbg), we can directly inspect the memory of any kernel object and see how the theoretical concepts map to real data.

#### Finding the Object in Memory

The first step is to get the kernel-space address of a specific object instance. A simple way to do this is to use Process Explorer to view the open handles for any process. By double-clicking a handle—for example, a Semaphore object—we can open its properties dialog. This dialog conveniently displays the object's address in kernel memory. With this address copied, we can switch over to a live kernel debugging session in WinDbg.

![](https://2365385460-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjcSqiYcR3JLKrNVQi5Hh%2Fuploads%2FBiIwzRyWPFrm2SLgMysH%2F1A088564%20F092%204E59%209842%20F620F2173ABE.png?alt=media)

The primary command for inspecting any kernel object is **!object**. This powerful command takes the object's address and provides a generic summary. When we run **`!object <address>`**, the debugger returns key information, including the object's type (**Semaphore**), a pointer to its corresponding **Type Object**, and a pointer to its **Object Header**. A crucial detail is that the object body's address is always at a fixed offset (**0x30** hex, or **48 bytes**) after the start of its header.

```c
3: kd> !object 0xFFFFA28691355110
Object: ffffa28691355110  Type: (ffffa2868aab7f00) Semaphore
    ObjectHeader: ffffa286913550e0 (new version)
    HandleCount: 2  PointerCount: 32771
    Directory Object: ffffc58ae83f4850  Name: CDP_CALLBACK_9D844332-97BF-7344-C1DD-1311854E80C8
```

#### Dissecting the Type Object

The Type Object acts as the **class** definition for all objects of a certain type. Using the address provided by the **`!object`** command, we can dump its contents with the **`dt _OBJECT_TYPE <address>`** command. The **\_OBJECT\_TYPE** structure, while not officially documented, is available through public symbols.

```c
3: kd> dt _OBJECT_TYPE ffffa2868aab7f00
ntdll!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY [ 0xffffa286`8aab7f00 - 0xffffa286`8aab7f00 ]
   +0x010 Name             : _UNICODE_STRING "Semaphore"
   +0x020 DefaultObject    : (null) 
   +0x028 Index            : 0x13 ''
   +0x02c TotalNumberOfObjects : 0x801
   +0x030 TotalNumberOfHandles : 0x80c
   +0x034 HighWaterNumberOfObjects : 0x8dc
   +0x038 HighWaterNumberOfHandles : 0x8e6
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : 0x616d6553
   +0x0c8 CallbackList     : _LIST_ENTRY [ 0xffffa286`8aab7fc8 - 0xffffa286`8aab7fc8 ]
```

Within this structure, we can see the type's name (here, a **Semaphore** string), its system-wide index, and the total number of objects and handles currently active for this type across the entire system. These are the exact numbers that tools like Process Explorer's **Object Types** view display, confirming that this information is read directly from these singleton Type Objects in the kernel. All semaphore objects, for instance, will point to this same, single **Type Object.**

we can verify that same information are in **object explorer** app:

![](https://2365385460-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjcSqiYcR3JLKrNVQi5Hh%2Fuploads%2FMqUQ7pfRVZr9rrIC00dH%2F629DDA9C%20DA21%2040EA%20B485%20F39F3D2A0F19.png?alt=media)

we can see the same index number: (in windbg 0x13, the same as 19)

we can also verify in windbg

```c
3: kd> ? 0x8dc
Evaluate expression: 2268 = 00000000`000008dc
3: kd> ? 0x8e6
Evaluate expression: 2278 = 00000000`000008e6
```

#### Inspecting a Specific Object's Header

To see the details for our *specific* semaphore instance, we look at its **Object Header**. Using the header address from the initial **!object** command, we can run **`dt _OBJECT_HEADER <header_address>`** This reveals instance-specific data, such as the **HandleCount** for this particular object. While the object's name is also referenced here, it is stored indirectly in a more complex way.

```c
3: kd> dt _OBJECT_HEADER ffffa286913550e0
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n32771
   +0x008 HandleCount      : 0n2
   +0x008 NextToFree       : 0x00000000`00000002 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0x27 '''
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0xa ''
   +0x01b Flags            : 0 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y0
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y0
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 1
   +0x020 ObjectCreateInfo : 0xffffa286`8cd2bcc0 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xffffa286`8cd2bcc0 Void
   +0x028 SecurityDescriptor : 0xffffc58a`e88da0aa Void
   +0x030 Body             : _QUAD
```

The header also contains a pointer to the object's **Security Descriptor**. We can inspect this using the **`!sd`** command. A key quirk is that the lower bits of this pointer are often used for flags, so the address must be **16-byte** aligned before being passed to the command (this is easily done by changing the last hexadecimal digit of the address to **zero**).

```c
3: kd> !sd 0xffffc58a`e88da0aa
1001c00020000: Unable to get MIN SID header
1001c00020000: Unable to read in Owner in SD
3: kd> !sd 0xffffc58a`e88da0a0
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8004
            SE_DACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-21-4242792061-3686935945-504144734-1001
->Group   : S-1-5-21-4242792061-3686935945-504144734-513
->Dacl    : 
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x1c
->Dacl    : ->AceCount   : 0x1
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x14
->Dacl    : ->Ace[0]: ->Mask : 0x001f0003
->Dacl    : ->Ace[0]: ->SID: S-1-1-0

->Sacl    :  is NULL
```

Running **`!sd <aligned_address>`** displays the decoded security information, including the object's **Owner** and its **Discretionary Access Control List** (**DACL**). The **DACL** contains a list of Access Control Entries (**ACEs**), each specifying **a user or group (identified by a SID)**, a set of permissions (the **access mask**), and whether access is allowed or denied. This raw data corresponds directly to the user-friendly permissions list shown in the **Security** tab of Process Explorer's handle properties, providing a clear link between the GUI representation and the underlying kernel data structures. This entire process can be repeated for any object type, from semaphores to files to Object Manager directories, revealing the consistent structure that the Object Manager imposes on all kernel resources.

![](https://2365385460-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjcSqiYcR3JLKrNVQi5Hh%2Fuploads%2F7r6wK6oRgpdhnCJXIfo1%2FB4E68147%20913F%204C14%208B2E%20E13038F782C3.png?alt=media)

### Objects and Handles

Since all kernel objects reside in protected kernel space, user-mode applications cannot access them directly. Instead, they must first obtain a **handle**, which serves as a secure, indirect reference to the object. A user-mode client can only interact with a kernel object by passing this handle to the specific set of APIs that the object type exposes; there is no other way to access kernel objects from user mode.

A crucial concept to understand is that every process has its own **private handle table**. This means that a handle value is only meaningful within the context of the process that obtained it. For example, if your application calls **CreateFile** and receives a handle with the value 0x20, you cannot simply pass that numeric value to a different process and expect it to work. That other process has its own handle table, where the entry at index 0x20 either points to a completely different object or is empty. While handles themselves are private, the underlying kernel objects are system-wide resources and are designed to be shareable between processes, though this requires specific sharing mechanisms.

To see which handles a process has open, developers and system administrators can use several well-known tools. The most popular is **Process Explorer** from the Sysinternals suite, which provides a comprehensive graphical view of all handles for a given process. Its command-line alternative, **handle.exe**, offers the same information in a console-based format.

Windows also includes a built-in tool called **Resource Monitor**. However, it has a significant limitation: it only displays handles for objects that have names. This makes it far less useful for a complete analysis than Process Explorer, which can show all handles, including those to anonymous objects. From a debugging perspective, the WinDbg \*\*`!handle**` command is an essential tool. It can display detailed information about a particular handle within the context of a process, both in user-mode and kernel-mode debugging sessions.

💡

When an application works with a kernel object, it does so through a handle—an opaque value that seems to magically represent a system resource. But what exactly is a handle, and which entity is responsible for translating this simple number into a meaningful action. This is where the **Windows Object Manager**, a core component of the kernel, plays a crucial role.

A handle is not a pointer. It is an **index** into a special, per-process **Handle Table**. A pointer to this private table is stored within each process's kernel structure (**EPROCESS**), but the table itself resides in protected kernel memory. When you call an API like **ReadFile** with a handle, your thread transitions into kernel mode. **The Object Manager** then uses your process context to locate its private handle table and uses your handle as an index to look up an entry. This table entry is the critical link, as it contains two pieces of secret information: **the actual kernel-mode pointer to the object and the access rights granted when the handle was created**. The Object Manager validates your requested operation against these rights and only then uses the real pointer to access the object. This entire mechanism acts as a robust security and stability boundary, ensuring user-mode code never touches a raw kernel pointer.

### Handle Entry Layout

What is the cost of a handle, and how many can we actually create in a single process? We know that every process has its own handle table, which is pointed to by the ObjectTable member within the EPROCESS data structure. Every entry in this table has a specific format. First, it must store the address of the object in kernel space. For this, we need 48 bits, as this is the maximum address space that can be used on modern Windows systems based on hardware processor limitations. However, since the address is always 16-byte aligned, we only need 44 bits to store the address itself. This frees up the remaining bits for several important flags. One of these is the **Audit on Close** flag, which provides a way to audit the operation of closing a handle, a feature that can be set up by administrators using Group Policy.

Another critical flag is the **Inheritance** flag, which is used for handle inheritance, one of the primary ways to share objects between processes. Finally, there is the **Protect from Close** bit. If this is set, the handle cannot be closed by calling the CloseHandle API. The purpose of this flag is to safeguard against unintentional closures, particularly in cases where a handle is passed to unfamiliar code that might not know it shouldn't close the handle. While this protection isn't meant to stop malicious code (as the flag itself could be removed), it prevents bugs or misunderstandings from causing the loss of an important handle.

Following the pointer and flags, the handle entry contains the **Access Mask**. Every handle has its own defined power, and the access mask is what indicates that power. This is a critical part of the handle entry because different handles, even if they point to the same object, can have different permissions based on their unique access mask. The entry also contains an **inverted usage count**. This field appears to be used internally by Microsoft for testing purposes; it starts with a large number and is decremented every time the handle is used. This can affect the reference count that is displayed in tools like Process Explorer, making it a value that cannot always be relied upon directly.

Due to memory alignment purposes, the rest of the data structure is unused padding. In total, each and every handle that exists in the system consumes 16 bytes of memory in kernel space. While this means that having handles is not free, as each one consumes system resources, it is also not prohibitively expensive. This still leaves the question of exactly how many handles can be created in a single process.

![Source: Windows Internals Course By Pavel](https://2365385460-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjcSqiYcR3JLKrNVQi5Hh%2Fuploads%2F0OKBkDs4zjhKY6cA7R51%2F9FB613EE%2074D0%204FD1%208A52%202A039827CC34.png?alt=media)

## Object Management - Sharing Objects

### Sharing Objects

We know that every process has its own **private handle table**, where each handle points to some object in kernel space. This privacy means we cannot simply take a handle's numeric value from one process, pass it to another, and expect it to work; the second process's handle table would have a completely different mapping. On the other hand, since kernel objects reside in a global, shared kernel space, they are, by definition, **shareable**. The question is, how do we bridge this gap? Windows provides three primary mechanisms for sharing a single kernel object between multiple processes.

The first and simplest method is **sharing by name**. If an object type supports naming, one process can create it with a **unique name**, and other processes can then open a handle to that same object by providing the same name. This is often the easiest approach, but it has limitations: not all object types can be named, making this method irrelevant in some cases.

The second option is **process handle inheritance**. This mechanism is straightforward but is restricted to a specific parent-child scenario. When a parent process calls **CreateProcess** to launch a new child process, it can specify that certain handles from its own table should be duplicated and inherited by the newly created child. This provides a direct and simple way to pass resources from a parent to its immediate offspring.

The third and most powerful option is **duplicating a handle**. Using the **DuplicateHandle** API, a process with sufficient permissions can create a new handle in a target process's handle table that points to the exact same kernel object as a handle in a source process. This is the most comprehensive and general-purpose method as it works between any two processes (given the right permissions) and for any object type. However, it also comes with its own complexities and potential downsides.

To make this concrete, let's consider a common scenario for sharing by name. Imagine a system with a **controller** process, perhaps with a graphical user interface, that manages several **worker** processes performing background tasks. At some point, the user may wish to shut down the entire system gracefully. Rather than forcefully terminating the worker processes, which could lead to data loss, the controller needs to send a **shut down** message to all workers simultaneously. One effective way to tackle this communication problem is to use a kernel object, like an Event, that is shared among all these processes. By creating the event with a well-known name, the controller can create and signal it, and all the worker processes can open a handle to that same named event and wait for it to be signaled, triggering their graceful shutdown procedure.

* **Share By Name**

  ```c
  HANDLE hEvent = CreateEventW(nullptr, TRUE, FALSE, L"ShutDownEvent"); 
  if (!hEvent) {
  	printf("Error : %u \n ", GetLastError());
  	return 0; 
  }

  printf("hEvent : 0x%p \n ", hEvent);
  if (GetLastError() == ERROR_ALREADY_EXISTS) {
  	printf(" Worker Process : %u. Waiting for Shut down !", GetCurrentProcessId());
  	WaitForSingleObject(hEvent, INFINITE);
  	printf("[+] Shutting down ... \n");
  }
  else {
  	printf("Controller process : %u . Press ENTER to send shut down notification ...", GetCurrentProcessId());
  	char dummy[3];
  	gets_s(dummy);
  	SetEvent(hEvent);
  	printf("[+] Notification Sent ! \n");

  }
  CloseHandle(hEvent);
  return 0; 
  ```

  The simplest yet most effective ways to facilitate communication between multiple processes: **sharing a kernel object by name**. The scenario involves a **controller** process that needs to signal several **worker** processes to shut down gracefully. Instead of using inefficient methods like polling a file on disk, we can leverage a named kernel object. In this case, an **Event object** acts as a system-wide flag that all processes can see.

  The logic is implemented within a single executable. The very first instance to run will become the controller, while all subsequent instances will act as workers. This distinction is made possible by a clever feature of the **CreateEvent** API. The **CreateEvent** function serves a dual purpose: it acts as a **create or open** operation. When it's called with a specific name, like **ShutDownEvent**, one of two things happens:

  1. If no object with that name exists, the function creates a brand new event object and returns a handle to it. In this case, **`GetLastError`** will return **0 (ERROR\_SUCCESS).**
  2. If an object with that name *already* exists, the function does not create a new one. Instead, it simply opens a handle to the existing object. In this case, **`GetLastError`** will return the special code **ERROR\_ALREADY\_EXISTS**.

  Our application uses this behavior to determine its role. If **GetLastError** is **0**, the process knows it's the first one and designates itself as the **controller**. It then waits for the user to press Enter. If **GetLastError** is **ERROR\_ALREADY\_EXISTS**, the process knows it's a **worker**. It immediately calls **WaitForSingleObject** on the handle it received, which causes its thread to block and efficiently wait until the event is signaled.

  When the user presses Enter in the controller's console window, the controller process calls **SetEvent(hEvent)**. This single action changes the state of the shared kernel object from **non-signaled** to **signaled**. Because all the **worker processes** are waiting on a handle to this *exact same object*, they are all simultaneously released from their wait state. They then print a shutdown message and terminate gracefully.

  Using a tool like Process Explorer confirms the underlying mechanics. When the first instance (the controller) is launched, we can see it has a handle to an Event object named **\Sessions\2\BaseNamedObjects\ShutDownEvent.** (The prefix is added by the system to scope named objects to a user's session). When we launch a second instance (a worker), it gets its own, different handle value, but crucially, that handle points to the exact same kernel object. We can verify this by inspecting the object's properties in Process Explorer and seeing its handle count increase from one to two. Each new worker process gets another handle to the same shared event.

  This sharing-by-name technique is extremely easy to implement; the only requirement is that all participating processes agree on a common name. It is not limited to Event objects and works equally well for other nameable object types, including Mutexes, Semaphores, Timers, and File Mapping (Section) objects.
* **Share By Inhertience**

  Beyond sharing objects by name, Windows provides a second powerful mechanism known as **handle inheritance**. This technique is elegant and efficient, but it comes with a specific constraint: it only works in a parent-child relationship, at the exact moment a new process is created. If the target process is already running, this method cannot be used.

  The core idea is that a parent process can **gift** some of its open handles to a child process as it's being launched. This is a two-step process that gives the parent explicit control over which resources are shared.

  First, the parent process must create a handle to a kernel object. In this example, we use CreateEvent to create an **anonymous** (**unnamed**) event object. By default, handles are created as non-inheritable for security reasons. To make it shareable, the parent must explicitly mark it as inheritable by calling the **SetHandleInformation** API with the **HANDLE\_FLAG\_INHERIT** flag.

  Second, the parent calls the **CreateProcess** API. The crucial parameter is **bInheritHandles**, which must be set to **TRUE**. When this flag is true, the kernel performs a special action during process creation: it scans the parent's handle table for any handles marked with the **inherit** attribute. For each one it finds, it duplicates that handle into the newly created child's handle table.

  ```c
  int main() {
      PROCESS_INFORMATION pi;
      STARTUPINFO si = { sizeof(si) };

      // The command line to execute
      std::wstring name = L"mspaint ";

      // 1. Create an anonymous kernel object.
      HANDLE hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);

      // 2. Mark the handle as inheritable. Without this, it will not be shared.
      SetHandleInformation(hEvent, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);

      // Append the handle value to the command line for the child to use.
      name += std::to_wstring((ULONG_PTR)hEvent);

      // 3. Create the child process, enabling handle inheritance.
      CreateProcess(nullptr, name.data(), nullptr, nullptr, TRUE,
          0, nullptr, nullptr, &si, &pi);

      // Clean up handles in the parent process.
      CloseHandle(pi.hProcess);
      CloseHandle(pi.hThread);
      CloseHandle(hEvent);

      return 0;
  }
  ```

  A walkthrough with a tool like Process Explorer clearly demonstrates this. After the parent creates the event, we can see the handle in its table (e.g., with value 0xC4). Initially, it has no special attributes. After SetHandleInformation is called, Process Explorer shows an "Inherit" attribute on the handle. When CreateProcess launches the child (mspaint.exe), we can inspect the child's handle table and find that it now *also* has a handle with the **exact same value** (0xC4), pointing to the exact same anonymous event object. The object's handle count in the kernel is now two.

  The fact that the handle value is **guaranteed to be identical** in the child process is the most important feature of this mechanism. It solves the critical problem of how to communicate the handle's value to the child. Since the parent knows the handle value *before* the child process even starts, it can simply pass that number as a **command-line argument**. A child process that is part of the same software system would be designed to parse its command line, retrieve this number, cast it to a HANDLE, and immediately begin using the shared resource. This provides a direct and reliable way to bootstrap communication and resource sharing between a parent and its immediate children.

  💡

  A common point of confusion in the handle inheritance example is the manipulation of the **`name`** string variable. It's crucial to understand that this code is **not** creating a named kernel object. The event object itself remains anonymous. Instead, the code is constructing the **command-line string** that will be used to launch the child process. This command line becomes the primary communication channel for telling the child which inherited handle it should use.

  The process is straightforward: first, a string is initialized with the executable's path (e.g., "notepad"). After an anonymous event object is created, the numeric value of the handle returned by the kernel (e.g., 123) is converted into a string and appended to the command line. The final command-line string becomes "notepad 123". When **CreateProcess** is called with this string, the operating system launches Notepad and passes **123** as a command-line argument. While Notepad misinterprets this as a file name, a custom-built worker process would be designed to parse this argument, converting it back into a number and using it as a valid handle.

  #### Why the Handle Value Must Be Identical

  This leads to the most critical aspect of handle inheritance: the **guarantee that the inherited handle will have the exact same numeric value in the child process as it does in the parent.** This feature is the elegant solution to a fundamental communication problem: How does the parent tell the child which handle to use?

  Because the parent process knows the handle's value *before* the child process is even created, it can reliably pass this value as a message, most commonly via the command line. The child process, which is part of the same software system, is programmed to expect this value. When it starts, it reads its command-line arguments, finds the number, and knows with certainty that this is the value of the shared handle in its own private handle table. It can then cast this number to a HANDLE and begin using the shared resource immediately.

  Without this guarantee, there would be no simple way to bridge this communication gap. The parent would have a handle, and the child would have a handle to the same object, but neither would know the value of the other's handle. By ensuring the values are identical, the Windows kernel provides a direct and reliable mechanism to bootstrap resource sharing and communication between a parent and its immediate child processes.

### Object Names

One of the primary ways to share kernel objects between processes is by assigning them a string-based name. Many common object types, such as events, mutexes, semaphores, and file mappings, support this capability. When a creation function like CreateEvent or CreateMutex is called with a name, the Object Manager creates the object and registers it within a global, hierarchical namespace. Any other process can then open a handle to that same object instance by using the same name.

It's important to understand that a name is simply a lookup mechanism; it does not change the object's fundamental behavior or capabilities. A named event works in exactly the same way as an anonymous one. Furthermore, not all object types can be named. Process and thread objects, for example, do not have string-based names; they are identified by their system-wide unique IDs. All named objects are stored within the Object Manager's namespace, which can be visually explored with tools like WinObj.

While naming is a simple and convenient sharing mechanism, it comes with two significant potential issues. The first is the possibility of **name collisions**. If your application tries to create an event named "ShutDownEvent," it might fail if another unrelated application on the system has already created an object with that exact name. To mitigate this, developers often use long, unique names, sometimes incorporating GUIDs, to make collisions highly unlikely.

The second, more serious issue is **visibility**. Because these names are stored in a public namespace, they are easily discoverable. Anyone can use tools like Process Explorer or write code using Native APIs to enumerate named objects. This visibility can create a security risk. A malicious actor could discover the name of a critical synchronization object, get a handle to it, and interfere with the application's logic—for example, by signaling an event prematurely to trigger an unintended action.

To address this security concern, Windows provides an advanced feature called a **private object namespace**. A process can create its own isolated namespace that is not publicly visible from the global namespace. Objects created within this private space can still be shared by name among a select group of trusted processes but are completely hidden from all others. It is even possible to secure a private namespace with a security descriptor, further restricting access to only processes running with specific security identities (SIDs). This allows developers to get the convenience of naming without the security risks of public visibility.

### Names and Sessions

When a standard user-mode application creates a kernel object with a name, the Windows Object Manager performs an important, transparent redirection. The object is not placed in the root of the global namespace. Instead, to prevent name collisions between different users logged into the same system, the object is automatically placed within a directory private to that user's **Session**. This is why tools like Process Explorer show object names with a prefix like **\Sessions\<SessionID>\BaseNamedObjects\<YourObjectName>**. This session-based isolation is a crucial security and stability feature, ensuring that an application run by one user cannot unintentionally (or maliciously) interfere with a identically-named object created by another user.

This automatic redirection is handled by the high-level Win32 APIs. For developers needing more control, such as creating objects in other parts of the namespace, it is possible to use the lower-level Native APIs (like NtCreateEvent), which can accept full object paths, though this often requires elevated privileges.

However, this session isolation creates a challenge: how can a process running in one session communicate with a process in a different session using a named object? A classic example is a system service running in the non-interactive Session 0 that needs to share an object with a user-facing GUI application running in an interactive session (e.g., Session 1 or 2).

The solution is the \**Global\** prefix. When an application creates a named object and prefixes its name with Global\ (for example, Global\MyAppSharedMemory), the Object Manager knows not to place it in the current session's private directory. Instead, it creates the object in the global namespace, which is physically located in the BaseNamedObjects directory of **Session 0**. By using this prefix, both the service in Session 0 and the client application in Session 1 can create or open a handle to the exact same object instance, enabling robust communication across the session boundary. This is the standard mechanism for sharing resources like shared memory, mutexes, or events between system services and user applications.

## Object Management - Odds and Ends

### Handles : A Turn Back

To work with a kernel object, a program must first obtain a handle to it. This is typically done through one of two families of APIs. The *Create functions*\* (e.g., **CreateEvent**, **CreateMutex**, **CreateProcess**) are used to instantiate a new object. If a name is provided to one of these functions and an object with that name already exists, the function will act as an **open** operation and return a handle to the existing object. In contrast, the *Open functions*\* (e.g., **OpenProcess**, **OpenEvent**) are used exclusively to get an additional handle to an object that already exists; if the specified object cannot be found, the open operation will **fail**.

Once a process has a **valid** handle, it can use it to interact with the object for as long as needed. When it is finished, it is crucial that the process calls the **CloseHandle** API. This function removes the entry from the process's **private handle table**, making that specific handle value invalid. Calling **CloseHandle** also decrements the object's reference count in the kernel. However, this does not necessarily mean the object will be destroyed. Because kernel objects are **reference-counted**, the object will only be deleted by the Object Manager when the very last handle and the last kernel-mode pointer to it are released.

While user-mode code is strictly limited to accessing objects via these process-private handles, code running in **kernel mode** has more power and flexibility. Kernel components, like device drivers, can also use handles. A special type of handle, known as a **kernel handle**, can be created that is process-agnostic, meaning it remains valid regardless of which process context the CPU is currently executing in.

More commonly, however, kernel code works directly with **pointers** to objects. This is often faster because it bypasses the handle-to-pointer translation step. The decision to use a handle or a pointer in kernel mode is usually dictated by the specific API one needs to call. To get a direct pointer from an existing handle, a driver can call **ObReferenceObjectByHandle**. This function not only returns a pointer but also increments the object's reference count. This is a critical step that ensures the object will not be destroyed while the driver is using the pointer. When the driver is finished, it **must** call **ObDereferenceObject** to decrement the count. Failing to do so will create a resource leak, as the object will be kept alive indefinitely. Ultimately, an object's total reference count is the sum of all its open handles and all these explicit kernel-mode pointer references.

* A classic example of inter-process communication can be observed in the behavior of **Windows Media Player**. If you launch one instance of the application and then try to launch a second one, a new window doesn't appear. Instead, the existing window simply gains focus. The second instance launches, detects that another is already running, and then quickly shuts itself down. The question is: how does it know?

  The answer lies in the use of a named **kernel object**. Specifically, Windows Media Player uses a **Mutex** (known as a **Mutant** in kernel terminology) with a very distinctive name: **Microsoft\_WMP\_70\_CheckForOtherInstanceMutex**. When the first instance of Media Player starts, it calls an API like **CreateMutex** with this specific name. Since no object with that name exists, the kernel creates a new one, and the application continues running.

  ![](https://2365385460-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjcSqiYcR3JLKrNVQi5Hh%2Fuploads%2F5uDqh0gcctkkD4AdUMqO%2F7B5E0D0F%20F1AF%204B97%20A7AC%20A6B7737D5703.png?alt=media)

  When you attempt to launch a second instance, it performs the exact same action: it calls **CreateMutex** with the name **Microsoft\_WMP\_70\_CheckForOtherInstanceMutex**. This time, however, the kernel sees that an object with this name already exists. The API call still succeeds and returns a handle to the existing object, but it also sets the last error code to **ERROR\_ALREADY\_EXISTS**. The new Media Player instance checks for this specific code, and upon seeing it, knows that another instance is already active. It then sends a message to the existing window to bring it to the foreground and promptly terminates itself.

  This mechanism demonstrates a common pattern for creating **singleton** applications that only allow one instance to run at a time. However, it also perfectly illustrates the primary vulnerability of using named objects: **visibility**. Because the object's name is public, it can be easily discovered with a tool like Process Explorer. An advanced user or another application can interfere with this mechanism.

  For example, by using Process Explorer to find and forcibly close the handle to this specific mutex, the original Media Player process loses its claim on the object. The named mutex is destroyed. If you then try to launch a new instance of Media Player, it will successfully create a *new* mutex with that name and run, allowing multiple instances to exist simultaneously.

  ![](https://2365385460-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjcSqiYcR3JLKrNVQi5Hh%2Fuploads%2FO4UM3bAGVjdipuN4K3BT%2F19D852BA%20FCB9%204CE8%2083F7%20C123A5EB087A.png?alt=media)

  And try to open another instance

  Even more disruptively, a different application can preemptively create a mutex with that exact name *before* Media Player is ever launched. When you then try to run Media Player, it will detect the existing mutex, incorrectly assume another instance of itself is running, and refuse to start at all. This highlights a significant downside of using publicly visible names for object sharing and coordination, as they can be discovered and manipulated by other processes on the system.

  ```c
  HANDLE hMutex = CreateMutex(nullptr, FALSE , L"**Microsoft_WMP_70_CheckForOtherInstanceMutex");
  return 0 ;** 
  ```

### Closing Handle in Another Process

A common question that arises when using tools like Process Explorer is how it's able to perform actions that seem impossible for a normal application, such as closing a handle in a completely different process. While the standard **CloseHandle** API can only operate on handles within the calling process, Process Explorer uses a clever, well-defined mechanism that can be leveraged by any application with sufficient permissions.

The first assumption one might make is that Process Explorer uses some special, undocumented function within its kernel driver to perform the close. While plausible, this is not the case. The technique is actually based on a standard user-mode API: **DuplicateHandle**.

The key to this operation is a special flag. The process is as follows:

* First, the controlling application (like Process Explorer) must obtain a handle to the target process itself. Crucially, this handle must be opened with at least the **PROCESS\_DUP\_HANDLE** access right, which grants permission to manipulate the target's handle table.
* With this powerful process handle, the application then calls **DuplicateHandle**.
* The magic happens in the final parameter: a special flag, **DUPLICATE\_CLOSE\_SOURCE**, is specified. This flag instructs the kernel to close the source handle in the target process as part of the duplication operation.
* The destination for the **duplicated** handle can simply be NULL, as the goal isn't to receive a new handle but only to trigger the closure of the original one in the remote process.

Of course, this technique is subject to security restrictions. Your application typically needs to be running with administrative privileges to get the necessary **PROCESS\_DUP\_HANDLE** access right on another process. Furthermore, this method will fail against modern Protected Processes, which are shielded from this kind of interference.

This is where Process Explorer's kernel driver comes into play. It does not perform a special "kernel close." Instead, when running elevated, it uses its kernel-level privileges to successfully obtain the powerful handle *to the protected process*—a handle that a normal user-mode application could not get. Once Process Explorer has this privileged process handle, it then proceeds to use the very same user-mode DuplicateHandle technique with the **DUPLICATE\_CLOSE\_SOURCE** flag to perform the final action. The driver's power is used to open the door, but the tool still walks through it using a standard, documented mechanism.

### Private Object Namespace

Sharing kernel objects by name is one of the easiest ways for processes to communicate, but it comes with a significant security drawback: the names are publicly visible. This visibility creates a vulnerability, as a malicious process can discover the name of a critical object, obtain a handle to it, and interfere with an application's intended behavior, as demonstrated with the Windows Media Player example. This raises an important question: can we get the convenience of sharing by name without exposing our objects to the entire system?

The answer is yes, using a feature introduced in Windows Vista called a **Private Object Namespace**. This powerful mechanism allows a group of trusted processes to create their own isolated, secure namespace for hosting named objects. The key benefit is that objects created within this private namespace are essentially **invisible** to any process outside of it. From a user-mode perspective, there is no standard way to enumerate or discover the names of objects residing in another process's private namespace.

When viewed with tools like Process Explorer, handles to objects in a private namespace will appear with obfuscated or unusual names that cannot be used by other processes for a lookup. This effectively solves the visibility problem. Furthermore, a private namespace can be secured even more tightly. It's possible to associate it with a security descriptor, restricting access to only processes running with specific Security IDs (SIDs) or at a certain integrity level. This provides a robust solution that combines the ease of sharing objects by name with a strong, layered security model, ensuring that only designated applications can participate in the communication.

### Zombie Processes and Threads

When a process terminates, it ceases to execute any code. However, this does not mean its corresponding kernel object is immediately destroyed. Since all kernel objects are reference-counted, the EPROCESS structure and all the data it points to will be kept alive as long as there is at least one open handle pointing to it. A process in this state—no longer running code but with its kernel object persisting due to an open handle—is known as a **zombie process**.

The existence of zombie processes can have a tangible impact on system resources. Process objects are fairly large, and keeping them alive unnecessarily consumes valuable kernel memory. Furthermore, as long as the zombie process object exists, its Process ID (PID) cannot be reused by the system for a new process. Applications that frequently create child processes but fail to promptly close the handles to them can "leak" these zombie processes, gradually degrading system performance. It is therefore a critical best practice to always call CloseHandle on a process handle once you are finished with it. While it's common to keep the handle open briefly after termination to query final information, such as the process's exit code or total runtime, it should be closed soon after.

This same principle applies to threads. When a thread finishes its work and exits, its ETHREAD kernel object can be held in a "zombie" state if another part of the system still holds an open handle to it. While thread objects are much smaller than process objects, this is still a form of resource leak and should be avoided by ensuring all thread handles are properly closed. Fortunately, specialized tools exist that can scan the system and identify these lingering zombie processes and threads, helping developers diagnose and fix handle leaks in their applications.

### User and GDI Objects

While the standard kernel objects managed by the Object Manager are central to Windows, there is another important class of objects related to the graphical user interface. These **USER and GDI objects** are managed by a different kernel component, **win32k.sys**, which is the kernel-mode driver for the Windows subsystem. These objects and their corresponding handles have a unique set of rules that differ significantly from their standard kernel object counterparts. The system calls for these objects are not routed through ntdll.dll but through a separate library, **win32u.dll**, which handles the transition to **win32k.sys**.

#### USER Objects: A Shared Namespace

**USER objects** are the fundamental components of the user interface, including windows, menus, and hooks. The handles to these objects behave very differently from standard kernel handles. Instead of being private to a process, USER object handles are **private to a Window Station**. This means that any process running within the same Window Station can share a handle value directly. If one process creates a window and gets a handle, it can simply pass that numeric handle value to another process in the same Window Station, and the second process can immediately use it to interact with the window. No duplication is necessary or even supported.

However, this ease of sharing comes with a major caveat: there is **no reference counting** for USER objects. If one process creates a window and shares its handle with another, and then the first process calls **DestroyWindow**, the window is destroyed immediately. The second process is left holding a **garbage** handle that is now invalid and useless. This lack of reference counting places the burden of lifetime management directly on the application developer.

#### GDI Objects: Private and Strictly Limited

**GDI (Graphics Device Interface) objects** are the classic drawing primitives that have been in Windows since its earliest days. This includes objects like pens, brushes, bitmaps, and palettes. Handles to GDI objects are the most restrictive of all. They are **strictly private to the process that created them** and cannot be shared with other processes in any way. If you need another process to draw with a red brush, for example, you cannot pass it a handle to your brush. You must instead pass it the logical properties (i.e., the color red), and the second process must create its own, separate brush object.

Furthermore, GDI objects operate under surprisingly strict system-wide limits that persist even in modern Windows. There is a hard limit of 65,536 (2^16) total GDI objects that can exist **per session**. While a per-process limit of 10,000 objects also exists (which can be raised via the registry), it can never exceed the session-wide maximum. This creates a potential vulnerability: a single malicious or buggy application that leaks GDI objects can exhaust the entire session's supply, which can cause other well-behaved applications, including critical system tools like Task Manager, to fail or misbehave because they are unable to create the GDI objects they need to draw their user interface.

## Conclusion

The Object Manager is the unsung hero of the Windows architecture. By providing a uniform, secure, and reference-counted mechanism for all system resources, it creates the foundation for the entire OS. We've seen that every resource, from a file to a thread, is a kernel object composed of a standard header and a type-specific body. User-mode code interacts with these objects safely through private handles, which are indexes into a per-process handle table.

Understanding this object-oriented design is the key to mastering Windows. It explains how processes can share resources securely, how the system prevents resource leaks through reference counting, and why the boundary between user mode and kernel mode is so robust.

Now, we will shift our focus from the **what** (objects) to the **when**, exploring the fast-paced world of **Interrupts, Exceptions, IRQLs, and DPCs**—the core mechanisms that allow the OS to respond to events from hardware and software.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://usta0x001.gitbook.io/posts/fundamentals/windows-internals/object-management.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
