A common technique in Windows for sharing kernel objects between processes is by using a name. The cooperating processes call the appropriate Create function (e.g. CreateMutex) and specify a simple string name. The first process to make the call actually creates the object, and subsequent processes get another handle to the exact same object. Whether that’s a new object or not does not usually matter; however, that piece of information is returned with a GetLastError() code of ERROR_ALREADY_EXISTS. Another option is to call the corresponding Open (e.g. OpenMutex) function in cases where it’s known that the intended object has already been created.
This sharing mechanism is as simple it can be. One thing that makes it less than ideal is the fact that the name is visible. For example, it can be located with Process Explorer and WinObj (both from Sysinternals). It can also be discovered programmatically by a carefully crafted call to the native NtQueryInformationProcess (for a process-targeted objects information) or NtQuerySystemInformation (for a system-wide view). Although both these functions are technically undocumented, the details can be found in various internet sources.
Why is that a potential problem? Lets’ consider an example. Several processes working as part of some system want to share memory to avoid costly copying using other mechanisms. To that end, each process calls CreateFileMapping to create/open a shared memory section and than just read and write after mapping it with MapViewOfFile. To synchronize the reads and writes (so that one process is not reading while another is overwriting the data at the same time), they can use a mutex to synchronize access. Creating that mutex would be done with CreateMutex, again with the same predefined name, and then using WaitForSingleObject/ReleaseMutex for doing the actual synchronization.
The problem now is self-evident. Another process, unrelated to the implemented system, can open a handle to the same objects and create havoc. It can read (possibly sensitive) data from the shared memory, write dummy or malicious data or acquire the mutex and never release it, causing all other processes to wait forever for the mutex that would never be released.
How can we protect against such an occurrence? If the other process is not malicious, then the sharing might be just an accident. The usual solution is to select a long, non-trivial name that may involve the application’s name, version number or whatever, minimizing greatly the chance of accidental sharing. But what about a malicious agent? As we’ve seen, it can search for the actual names based on some knowledge such as the processes that would use it, its type, or perhaps some known pattern for the name, etc. and use the name, whatever it may be, to sabotage legitimate processes or steal information.
One may think of using a security descriptor to try and restrict the access to these objects, but that is problematic because the malicious agent usually would be running with the same user account, or worse – managed (by social engineering or other means) to run with elevated privileges and could not be stopped.
Is there a way to protect the names in a different way? One little known feature that existed since Windows Vista allows creating private object namespaces for kernel objects that are not visible or searchable in the usual way. The CreatePrivateNamespace is the entry point for this mechanism. Once such a namespace is created, named objects can be created within it, but will not be visible externally. Here is an example:
Let’s create a private namespace called “MyNamespace“:
auto boundaryDesc = ::CreateBoundaryDescriptor(L"PrivateBoundary", 0);
DWORD sidLen = SECURITY_MAX_SID_SIZE;
auto buffer = std::make_unique<BYTE>(sidLen);
auto sid = reinterpret_cast<PSID>(buffer.get());
::CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &sidLen);
m_hNamespace = ::CreatePrivateNamespace(nullptr, boundaryDesc, L"MyNamespace");
m_hNamespace = ::OpenPrivateNamespace(boundaryDesc, L"MyNamespace");
Creating a private namespace involves using an intermediate object called a boundary descriptor, that can be used to restrict the SIDs that are allowed access to the private namespace. In contrast to the standard Create* functions for kernel objects, if the private namespace already exists (created earlier by a cooperating process), the Create function fails and OpenPrivateNamespace must be used instead. This implies that these private namespaces are not kernel objects, and in fact have their own close function (ClosePrivateNamespace).
Now that we have a namespace, we can create an object within that namespace:
m_hSharedMem = ::CreateFileMapping(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, 1 << 20, L"MyNamespace\\MySharedMem");
With this object in place, here’s the screenshot from Process Explorer that shows the object’s details when double-clicking a handle to that section:
Notice the name: the namespace is not showing. Even calling native query functions does not reveal the full name. There are two handles to the object opened from two different processes that now transfer data through this shared memory section.