AppContainers for Windows 8: What Are They and How Can You Create Them?

Sandboxes are perfect tools for software developers to test new code, safely run programs, execute commands without affecting the application or system itself, and ensure proper security of the system under development. Furthermore, they’re helpful when you need to find malware or check suspicious programs that may contain viruses without any risk of infecting the system under test.

There are numerous types of sandboxes for various operating systems and platforms. This time, we consider the Windows sandboxing mechanism called AppContainer, which was introduced in Windows 8 to sandbox Windows Store apps.

In this article, we describe the capabilities of AppContainers and share our tips on how to create a Windows AppContainer, launch a process in it, and provide the AppContainer with access to required objects.

Written by: Egor Olehnovich, Software Developer, Apriorit Driver Development Team

What is an AppContainer?

Before we start creating AppContainers and running processes in them, let’s define what AppContainers are and why we need them.

The AppContainer environment is a restrictive process execution environment that protects applications and processes from hacking. This technology first appeared in Windows 8 and was developed for managing processes within the Universal Windows Platform (UWP).

AppContainers are sandboxes that isolate the runtime environment for a process or any code that runs in that process. Their goal is to block any possibility for malicious code to get inside the process address space.

Thanks to the isolation an AppContainer provides, an application only has access to the required resources specified by the developer. Even if malicious code manages to find and exploit a vulnerability in the launched AppContainer, it still has no chance to affect the rest of the system.

To ensure proper isolation, the AppContainer creates a security identifier (SID) that combines the identifiers of the user and the application. Therefore, credentials are unique for each user and application, meaning that an application can’t pass itself off as a user.

By isolating user data, we aim to prevent the use of such data to get access to system resources or get further into the system. AppContainers isolate credentials, devices, files, networks, and windows. Let’s explore this in detail:

  • Device isolation means that applications in an AppContainer can’t access the camera, microphone, GPS, 3G/4G/5G modules, etc. However, the keyboard and mouse are always accessible for an application in an AppContainer regardless of the specified access permissions.
  • File isolation blocks or restricts access to files and the registry, but you can grant access to certain files or a registry branch if needed. Moreover, an application launched in an AppContainer always has full access to memory resident files created specifically for that application.
  • Network isolation involves isolating the application from any network resources, the internet, and the intranet. However, you can grant full access to any resources as required.
  • Process isolation means that objects of the operating system kernel are put in the sandbox to protect other processes from interference.
  • Window isolation means the AppContainer doesn’t let an application interfere with other windowed applications.

Processes in the AppContainer run with a low priority level, which means they don’t have access to most objects, as the integrity level for objects is medium by default. Moreover, the named objects created in the AppContainer are stored in the object manager catalog based on the AppContainer SID. Therefore, the code inside the AppContainer can’t cause any severe damage to other processes.

Being isolated, an AppContainer is protected from outside influence and can’t interfere with any objects or processes that run outside of the AppContainer.

AppContainers were designed specifically for Windows Store applications. But they also can be used for running applications that aren’t built for the UWP, ensuring the same level of security and isolation.

Now that we’ve clarified the theoretical part, let’s explore how to create an AppContainer and launch a process in it.

Creating an AppContainer

First, we need to create an AppContainer and receive the AppContainer SID.

For non-UWP applications, we can choose any string — for example, c++ std::string containerName = “MyContainer”. Selecting the same string will lead to the same SID, which means we can use this string to run several processes in one container.

As the first step, let’s create an AppContainer profile:

PSID appContainerSid;::CreateAppContainerProfile(containerName, displayName, discryprion, nullptr, 0, &appContainerSid);

Knowing the containerName parameter, we can define whether the AppContainer exists or not. If the function results in an error, most likely the AppConatiner profile already exists.

In the AppContainer profile already exists, we have to extract the SID from it. Here’s an example on how to do that:

::DeriveAppContainerSidFromAppContainerName(containerName.c_str(), &appContainerSid);

Creating a process in the AppContainer

Before we create a process in the AppContainer, we have to do some preparations.

The absolute minimum we need to do to create an AppContainer and launch a process in it is initialize a process attribute list. To do this, we can use the SECURITY_CAPABILITIES structure, which defines the security capabilities of the AppContainer. Using it, we indicate that we want to create a process inside the AppContainer.

Also, we can specify the permissions for the AppContainer, such as internet access, library access, and other features defined by the Windows runtime environment.

Here’s an example of how to manage the primary initialization of the required data and memory allotment for the structures needed to launch the process:

STARTUPINFOEX si = { sizeof(si) };PROCESS_INFORMATION pi;SIZE_T size;SECURITY_CAPABILITIES sc = { 0 };sc.AppContainerSid = appContainerSid;::InitializeProcThreadAttributeList(nullptr, 1, 0, &size);BYTE buffer[size] = {0};si.lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buffer);::InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size));::UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, &sc, sizeof(sc), nullptr, nullptr));

We have specified zero permissions for now. This means that once the application is launched, it will not have access to any resources. We have done this on purpose, and a bit further, we’ll show you how new permissions can be added on the go without rebooting the application.

Now we’re ready to create a process:

::CreateProcess(nullptr, processPath, nullptr, nullptr, FALSE,EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr,(LPSTARTUPINFO)&si, &pi);

This way, we execute a process that has no access rights and is specified by the exePath command in the application container.

Granting an application access to resources

To provide our application with access to required resources (files, network, etc.), we need to follow these five steps:

1. Receive the container SID (if we don’t know it yet):

::DeriveAppContainerSidFromAppContainerName(containerName.c_str(), &sid);

2. Initialize the EXPLICIT_ACCESS_W structure, which grants the required access to the process:

EXPLICIT_ACCESS_W explicitAccess;explicitAccess.grfAccessMode = GRANT_ACCESS;explicitAccess.grfAccessPermissions = accessMask;explicitAccess.grfInheritance = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;explicitAccess.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;explicitAccess.Trustee.pMultipleTrustee = nullptr;explicitAccess.Trustee.ptstrName = reinterpret_cast<wchar_t *>(appContainerSid);explicitAccess.Trustee.TrusteeForm = TRUSTEE_IS_SID;explicitAccess.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;

3. Get the security descriptor for the specified object.

A security descriptor is a data structure that consists of security information about securable objects. To generate a security descriptor for the specified object, use the following code:

::GetNamedSecurityInfoW(objectName, objectType, DACL_SECURITY_INFORMATION, nullptr, nullptr, &originalAcl, nullptr, nullptr);

4. Create a new access control list (ACL).

An ACL is a list of permissions attached to an object. Below is an example of how you can create a new ACL:

::SetEntriesInAclW(1, &explicitAccess, originalAcl, &newAcl);

5. Set security information for the specified object:

::SetNamedSecurityInfoW(&localObjName[0], objectType, DACL_SECURITY_INFORMATION, nullptr, nullptr, newAcl, nullptr);

After completing these steps, the processes in our container will gain access to the specified resources. Also, we don’t need to reboot the application to do this, which is a big plus.

Conclusion

Creating an AppContainer for Windows and using it for sandboxing processes and applications isn’t a great challenge. We hope this blog post answered all your questions about the nature of AppContainers and their functionality.

At Apriorit, we’re always ready to share our expertise and provide professional consultations on web application development. Don’t hesitate to contact us if your project requires help from an experienced team.

--

--

Apriorit
Apriorit — Specialized Software Development Company

21+ yrs of expert software engineering services to tech companies worldwide, covering the entire software R&D cycle. Details: www.apriorit.com/about-us/company