Sharing Kernel Objects by Name – Perks and Perils

Author

Pavel Yosifovich
25+ years as Software developer, trainer, consultant, author, and speaker. Co-author of “Windows Internals”. Author of “Windows Kernel Programming”, “Windows 10 System Programming, as well as System and kernel programming courses and “Windows Internals” series.

Some Windows kernel object types can have string-based names, which is one way such objects can be relatively easily shared between processes. However, it’s not all rainbows and unicorns.

There are fundamentally three ways to share kernel objects between processes:

  • Share by name (the focus of this post)
  • Handle inheritance – applies when a process creates a child process and wants to duplicate some of its handles to the newly created process.
  • Handle duplication – this is the most generic mechanism, employing the DuplicateHandle API, but the most difficult in practice for reasons I’ll discuss in a future post.

To share by name, we need to create the object with a name so that it can be used in other processes. For example, suppose we have a bunch of worker processes that are controlled by a “main” process. At some point, the controller (“main”) process may want to notify the worker processes to shut down gracefully. A kernel event object could be used for that purpose. The controller would create the object with a well-known name, and the worker processes will open their own handles to the same object. We can even have the same executable to be used by the controller and worker processes. We’ll assume that the first process creating the event will serve as the “controller”, and subsequent processes will serve as “workers”. Here is the code to create the event:

HANDLE hEvent = CreateEvent(nullptr, TRUE, FALSE, L"ShutdownEvent");
if (!hEvent)
	return 1;

The event is created as manual-reset (second argument TRUE) so that signaling the event can wake any number of threads waiting on it. The nice thing about the CreateEvent API and similar Create* functions when creating named objects is that the API behaves as “Create or Open” – if the object with that name already exists, it receives a handle to the existing object.

Gain Insider Knowledge

Subscribe to updates from the TrainSec trainers

How can we tell if the object was just created, or a handle was opened to an existing object? The GetLastError API is here to help. Normally, it reports the last error that occurred because of the latest API call. However, for a successful creation or opening of a named object, it returns a special value (ERROR_ALREADY_EXISTS) if the object existed prior to the Create call. If the object exists, the process assumes the role of a worker, and waits for the event to be signaled by the controller process:

if (GetLastError() == ERROR_ALREADY_EXISTS) {
	printf("Worker PID: %u\n", GetCurrentProcessId());

	WaitForSingleObject(hEvent, INFINITE);

	printf("Shutting down...\n");
}

If GetLastError returns zero, it means the object was just created, so the process assumes the role of the controller, waits for the user to press ENTER, and sends the signal (by setting the event with SetEvent) to the waiting workers (this is just for demonstration purposes, of course. Pressing ENTER is not likely to be used in a real scenario, but the mechanism is the same):

else {
	printf("Controller PID: %u\n", GetCurrentProcessId());

	printf("Press ENTER to set event for graceful shutdown...\n");
	char s[2];
	gets_s(s);

	SetEvent(hEvent);
}

While the user does not press ENTER we have multiple processes, where one is a controller, and the rest are workers. Here are some output from 4 processes:

Handle: 0x00000000000000A4
Controller PID: 57524
Press ENTER to set event for graceful shutdown...

Handle: 0x00000000000000A4
Worker PID: 59980

Handle: 0x00000000000000A8
Worker PID: 62228

Handle: 0x00000000000000B4
Worker PID: 56228

If we run Process Explorer and check the properties of any of the handles (before pressing ENTER in the controller), we see this:

image

There are 4 open handles to that same event. Note the name of the event – it’s not actually “ShutdownEvent”, but has a prefix which is session relative – “\Sessions\1\BaseNamedObjects”, which implies that when working with named objects in the Windows API, the created name is stored under a session-relative directory object in the Object Manager’s namespace. We can see this object from a named-object perspective in Sysinternals WinObj tool:

image 1

Once we press ENTER in the controller’s console, the event is set, the worker processes wait is satisfied, and they cleanly exit.

Windows Internal master badge

$1300

$1040 or $104 X 10 payments

Windows Internals Master

Broadens and deepens your understanding of the inner workings of Windows.

So, What’s Wrong with Named Objects?

Using named objects for sharing is easy and convenient. However, there could be a couple of issues. First, the name of the object is “public”, i.e. visible using tools like Process Explorer, which uses an API to enumerate all handles in the system. Although that API is not officially documented (NtQuerySystemInformation with SystemExtendedHandleInformation), it’s known, and I have written about it in my latest book, “Windows Native API Programming“. This means anyone can find the object and could potentially tamper with it.

Here is a simple example using the Windows classic media player application (wmplayer.exe). This application makes sure it’s running with a single instance by creating a named Mutex object and checking if such an object exists. If it does, it terminates, so that the first instance is the only remaining. Here is the Mutex shown in Process Explorer:

image 2

One thing we could do to create an additional media player instance is to close the existing handle using Process Explorer (right-click and select Close Handle), and then a new instance can be launched. A malicious entity could do the opposite: create a named mutex with exactly the same name before a media player is launched, and then you’ll find that media player fails to launch entirely! It thinks there is another instance already running, and immediately shuts down. That’s a deny of service attack.

The bottom line is that named objects are visible. Can we do something about it? Fortunately, we can. Windows Vista added the ability to use Private Object Namespaces, where a cooperating set of processes know about a private namespace for the named objects they want to share. That private namespace is not visible from user mode at all – there is no API to get it. You can find examples of using this API online or in my book, “Windows 10 System Programming, Part 1“.

The second issue with named objects is simpler – some object types cannot have names, so cannot be shared by name. Examples include tokens, processes, and threads (processes and threads have IDs, not names).

Sharing Across Sessions

As we’ve seen The Windows API creates named objects under a session-relative directory object. What if we need to share kernel objects between processes running in different sessions? For example, a service running in session 0, and a GUI client application running in session 1. The Windows API supports using the “Global” prefix to store the object in session 0 namespace, accessible by all sessions as a “shared” named objects repository. A name like “Global\ShutdownEvent” will create the object in the “\BaseNamedObjects” directory (directly under the root). Try it out!

blue depth

About the author

Pavel Yosifovich
25+ years as Software developer, trainer, consultant, author, and speaker. Co-author of “Windows Internals”. Author of “Windows Kernel Programming”, “Windows 10 System Programming, as well as System and kernel programming courses and “Windows Internals” series.

Black Friday & Cyber Monday Sale Started!

For a limited time, enjoy 25% off ALL available courses for the next month. Whether you’re looking to sharpen your skills or explore new areas of cybersecurity, now’s the perfect time to invest in your growth.

Use code BFRIDAY24 at checkout to claim your discount.

*Excluding bundles and monthly plans.