Monday 4 February 2019

A Brief History of BaseNamedObjects on Windows NT

Recently I RE'd some new undocumented feature in Windows which I thought I should put it in a short blog post. To expand it out slightly this blog will be a brief history of BaseNamedObjects (BNO from now on) from Windows NT 3.1 to modern Windows 10.

TL;DR; New versions of Windows 10 have a BaseNamedObjects isolation feature when creating a non-sandbox process which allows an application to redirect named objects transparently to a non-shared location. I've added support for the feature in NtObjectManager v1.1.19.

Just to get you up to speed, what is BNO? The majority of Windows NT kernel objects can be assigned names. The named objects are added to the Object Manager Namespace, a hierarchical object based file system. When calling a native system call such as NtCreateEvent you can specify an OBJECT_ATTRIBUTES structure which can include a full path to the object location. However, if you're calling a Win32 API it's typical to only be provided a simple name as demonstrated by the CreateEvent lpName parameter.

Screenshot showing CreateEventW prototype with an lpName parameter which specifies the object name.
As you can't provide a full path the question might be, where does the object get created? In NT 3.1 the kernel sets up a special \BaseNamedObjects directory when it starts. When you call CreateEvent the KERNEL32 library appends the name to the object directory to get the full path*. The end result is you create your named event at the location \BaseNamedObjects\{lpName}. We can use my NtObjectManager PowerShell module to list the BaseNamedObjects directory on a modern system by listing the drive NtObject:\BaseNamedObjects, as shown below.

* This isn't strictly how the library handles redirecting the name, but it's good enough for this post.

PowerShell console showing listing of ntobject:\BaseNamedObjects.

The BNO directory was shared by all users on the system, which for NT 3.1 meant system services and the user logged into the physical console. While there's some security implications with sharing this global location for all named events this wasn't a big concern back in 1993.

This global approach hit a problem with the introduction of Terminal Services in Windows 2000 (it was available in NT4 but as an extension). Specifically now you could have multiple "normal" users logged on at the same time on a single system you run the risk of name collisions, making one app impossible to run if another user had already started it and grabbed the name of the event or similar. Not discounting the increased security risk of sharing these resources. To remedy this problem when a new user logs into a Terminal Server a new instance of CSRSS is started and creates a new directory \Session\{ID}\BaseNamedObjects where {ID} maps to the Session identifier, just an integer value.

Due to the way the original Win32 APIs were designed adding a new directory could be made transparent. Instead of mapping the name parameter to the global BNO the KERNEL32 library could look up the session ID and create the session specific name. If the session ID is 0, indicating the physical console and service session, the name is still mapped to the global BNO, anything else is mapped to the per-session directory. It would still be useful for an application to create or open entries to the global BNO, so CSRSS also creates a symbolic link, Global, which maps to the global location. Therefore if you pass the name Global\NAME it will actually create the named object inside the global BNO. There's also a corresponding Local symbolic link, which just maps back to itself. In NtObjectManager you can list the per-session BNO through the SessionNtObject: drive as shown below:

Listing SessionNtObject:\ directory in PowerShell and selecting out symbolic links with the filter "? IsSymbolicLink".

Not much changed in Windows XP, other than Terminal Services being made available in consumer facing versions of the OS. It was used to implement Fast User Switching for example. The next evolution of BNO was in Vista. First Session 0 now became the preserve of system services, instead of being shared with the physical console. All user login sessions placed their named objects in a per-session BNO whether the user connected locally or remotely. The more interesting change was the introduction of private namespaces, exposed through the CreatePrivateNamespace API.

The API allows an application to create their own private BNO. Through the use of a "Boundary Descriptor" it is also possible to share it securely with other application if it has the correct parameters. This private BNO doesn't override the application's BNO, instead the APIs provide a lpAliasPrefix parameter, which you can use to prefix your object names. For example if you create a namespace with the "Flubber" prefix, then you can create or open objects by specify "Flubber\{NAME}" and KERNEL32 will automatically resolve it to the correct location. NtObjectManager exposes private namespaces through the Get-NtDirectory and New-NtDirectory commands with the PrivateNamespaceDescriptor parameter (read the help for more information on its structure). You can also map the private namespace as a drive using New-PSDrive command and specifying a root name of "ntpriv:{BOUNDARY}" where {BOUNDARY} is the boundary descriptor string as shown below:

Creating a new Private Namespace with "New-NtDirectory -PrivateNamespaceDescriptor FLUBBER". Then mapping it as a drive with "New-PSDrive -Name flubber -PSProvider NtObjectManager -Root ntpriv:FLUBBER"

Private namespaces are used in a few locations, such as IE/Edge but on the whole they're not that popular, perhaps because they require explicit changes to code to add the new prefix.

The next step in BNO history was introduced in Windows 8 to support the AppContainer (AC) sandbox. Supporting named objects in a sandbox using built-in functionality is difficult to get right, first because you don't really want a sandboxed application manipulating more privileged application's named objects. Second you also don't want other AC applications manipulating other AC named objects as some sandboxes have more access than others. Avoiding both of these problems made a global BNO location pretty much a non-starter. Instead, MS added code to automatically detect if an application is in an AC sandbox and redirect the named objects transparently to \Sessions\{ID}\AppContainerNamedObjects\{SID}, where SID is the SDDL form of the AC's package SID. The non-sandbox process creates the directory before starting the AC process and is ACL'ed so only that package can modify it. This solves the problem neatly, and again is a testament to the original design of hiding the real underlying object naming from the Win32 API layer.

Listing the AppContainerNamedObjects directory with "ls ntobject:\Sessions\9\AppContainerNamedObjects".

Finally we get to the last part, the area I was RE'ing. Windows 10 RS3 introduced a new undocumented feature, BNO Isolation. When I say it's undocumented I can't find any public reference to it, and the expected definitions don't appear in the Windows SDK headers. Of course there seems to be some structures in the Process Hacker source code for the native format but I try and avoid asking where exactly that's come from ;-)

Anyway, I think the name of the feature pretty much gives away its purpose. It allows a process to create an isolated BNO directory without being in an AC or requiring you to use a private namespace and the accompanying prefix. It's setup by specifying the name of the isolation directory in the  ProcThreadAttributeBnoIsolation Process/Thread attribute when creating a new process. From the Win32 level you only need to specify the name. Internally CreateProcess creates the appropriate BNO directory and supporting symbolic links. At the native level the prefix name and a list handles to capture is passed to NtCreateUserProcess and this information is stored in the process token. KERNEL32 can then query for the isolation prefix using the TokenBnoIsolation information class (which is documented, sort of) when setting up its BNO directory and all named objects are redirected to this new location. I've exposed the BNO prefix in NtObjectManager with the BnoIsolationPrefix property on the token object. You can setup a new process with the BNO isolation by setting the BnoIsolationPrefix property on the Win32Config object. For example:

Creating a new process with a BnoIsolationPrefix value in Win32ProcessConfig. Then listing the new directory under Sessions\9\BaseNamedObjects\Flubber.

The isolated name gets created under the per-session BNO. If you want true isolation you probably want to name the directory with a unique random GUID. Note that the isolation prefix is not inherited across process creation, which is a bit of a shame as that makes it less useful. Still I could see it being a useful feature for running arbitrary applications in a slightly more isolated fashion, shame it's not really documented.

There ends the brief history of BNO in Windows NT. While BNO isolation doesn't immediately look interesting from a security perspective I can imagine it could find some use process isolation and containment.