Thursday, 20 July 2017

Device Guard on Windows 10 S

This blog is about Device Guard (DG) on Windows 10 S (Win10S). I’ll go through extracting the policy and finding out what you can and cannot run on a default Win10S system. Perhaps in a future blog post, I’ll describe some ways of getting arbitrary code execution without installing any additional software such as Office or upgrading to Windows 10 Pro.

Win10S is the first Windows operating system released to consumers which comes pre-locked down with DG. DG builds upon Kernel Mode Code Integrity (KMCI) introduced in Windows Vista and User Mode Code Integrity (UMCI) which was introduced in Windows 8 RT. It contains many features to restrict code execution by limiting what types of executable files/scripts, including DLLs can be loaded based on a set of policy rules. A good first step towards trying to find ways to run arbitrary code on a system with DG is to extract the DG policy and inspect it for weaknesses.

Before we start, I'd like to thank Matt Graeber for reviewing this post before it went out. His DG knowledge is far better than anyone I know.

Extracting the DG System Integrity Policy

The enforcement of DG is configured through a System Integrity (SI) policy. The SI policy is stored as a binary file on disk. When the operating system boots either WINLOAD or the kernel CI driver loads the policy into memory and begins enforcement based on the various rules configured.

The location of the file varies depending on how the policy was deployed. On the Surface Laptop I have, which comes with Win10S pre-installed the policy is located inside the C:\Windows\Boot\EFI folder with the name winsipolicy.p7b. There’s no restrictions on reading this file to extract its contents to determine what policy is enforced. Unfortunately, there’s no official documentation I know of which describes the binary policy file format. There is the ConfigCI Powershell module which will convert an XML file to a binary policy. Unfortunately, there’s no corresponding command to perform the reverse.

Fortunately, the ever amazing Matt Graeber put in the effort and wrote a Powershell script which can convert the binary format back to the XML format. Unfortunately, there was some issues with the original script, such as missing some new additions to the format that Microsoft have added as well as a couple of bugs. Therefore, I’ve tweaked the original to fully support the policy format used on Win10S as well as fixing some bugs. Matt updated his copy on github with my fixes, you can get it from here. Load the script into Powershell then run the following command:

ConvertTo-CIPolicy winsipolicy.p7b output.xml

The result of the conversion is an XML file we can read. As with the binary file, the XML file is poorly documented, the best resource is almost certainly Matt Graeber’s blog, specific this post. Let’s break it down into small sections.

System Integrity Policy Rules

The first important section are the Rules which define a set of boolean options to enable in the System Integrity policy.

<Rules>
 <Rule>
   <Option>Enabled:UMCI</Option>
 </Rule>
 <Rule>
   <Option>Enabled:Advanced Boot Options Menu</Option>
 </Rule>
 <Rule>
   <Option>Required:Enforce Store Applications</Option>
 </Rule>
 <Rule>
   <Option>Enabled:Conditional Windows Lockdown Policy</Option>
 </Rule>
</Rules>

The first option enables UMCI. By default, DG doesn’t enforce UMCI, although it does enforce KMCI. The second option enables the Advanced Boot Options Menu, this is interesting as by default the menu would be disabled and this policy allows the user of the system to have more control over the boot process. This configuration will become important in a later blog post. The third option Enforces Store Applications. This ensures you can’t disable UMCI for store applications. Without this setting, it’d be possible to configure a side-loading policy to allow you deploy your own UWP applications. As the purpose of Win10S is “security,” they only allow UWP applications which are store signed, I’ll explain what that means in the section on allowed signers. Finally, Conditional Windows Lockdown Policy seems to be related to the Windows 10 S SKU and the potential that the lockdown policy can ultimately be disabled. This is related to the license values and a system environment variable “Kernel_CI_SKU_UNLOCKED”. This probably needs further investigation.

File Rules

Next up are the set of file rules. These are usually used to blacklist specific executable files which are known DG bypasses and would allow you to trivially run arbitrary code. This is close to the same list provided by Microsoft in their DG deployment guide here. However, they also block things like registry editing tools and Windows Scripting Host.

<FileRules>
 <Deny FileName="bash.exe" MinimumFileVersion="65535.65535.65535.65535" />
 <Deny FileName="CDB.Exe" MinimumFileVersion="..." />
 <Deny FileName="cmd.Exe" MinimumFileVersion="..." />
 <Deny FileName="cscript.exe" MinimumFileVersion="..." />
 <Deny FileName="csi.Exe" MinimumFileVersion="..." />
 <Deny FileName="dnx.Exe" MinimumFileVersion="..." />
 <Deny FileName="fsi.exe" MinimumFileVersion="..." />
 <Deny FileName="kd.Exe" MinimumFileVersion="..." />
 <Deny FileName="MSBuild.Exe" MinimumFileVersion="..." />
 <Deny FileName="mshta.exe" MinimumFileVersion="..." />
 <Deny FileName="ntsd.Exe" MinimumFileVersion="..." />
 <Deny FileName="powershell.exe" MinimumFileVersion="..." />
 <Deny FileName="powershell_ise.exe" MinimumFileVersion="..." />
 <Deny FileName="rcsi.Exe" MinimumFileVersion="..." />
 <Deny FileName="reg.exe" MinimumFileVersion="..." />
 <Deny FileName="regedit.exe" MinimumFileVersion="..." />
 <Deny FileName="regedt32.exe" MinimumFileVersion="..." />
 <Deny FileName="wbemtest.exe" MinimumFileVersion="..." />
 <Deny FileName="windbg.Exe" MinimumFileVersion="..." />
 <Deny FileName="wmic.exe" MinimumFileVersion="..." />
 <Deny FileName="wscript.exe" MinimumFileVersion="..." />
</FileRules>

For each deny rule, the policy specifies a filename and a minimum file version. Note that the minimum version is really the maximum when it comes to a deny rule. In the sense that the rule only applies to files with a version number less than that specified. As every rule has the version set to 65535.65535.65535.65535 which is the absolute maximum it ensures that no version of these executables can ever execute. The filename and version are extracted from the executable’s version resources, this means you can’t just rename cmd.exe to badger.exe, the policy will see the Original Filename inside the version resource and block execution. If you try and modify the version resource then the file’s signature no longer matches and you won’t pass the signing policy.

It’s not really clear why Microsoft went all out and blocked things like CMD other than to annoy users. Sure, you could use it to run commands but you’re still somewhat limited by what executables you can run based on the signing policy. PowerShell and WScript I can perhaps more understand, but as we’ll see later, these file policy rules only serve as a speed bump to prevent us getting arbitrary code execution.

Allowed Signers

Now we get on to what signers the DG policy will allow (assuming of course they’re not blocked by the file rules). First, the DG policy defines the list of allowed signers, this list is then referred to later in the policy configuration. The list of allowed signers is as follows:
<Signer Name="MincryptKnownRootMicrosoftTestRoot2010" ID="ID_SIGNER_TEST2010">
 <CertRoot Type="Wellknown" Value="0A" />
</Signer>
<Signer Name="MincryptKnownRootMicrosoftDMDRoot2005" ID="ID_SIGNER_DRM">
 <CertRoot Type="Wellknown" Value="0C" />
</Signer>
<Signer Name="MincryptKnownRootMicrosoftProductRoot2010" ID="ID_SIGNER_DCODEGEN">
 <CertRoot Type="Wellknown" Value="06" />
 <CertEKU ID="ID_EKU_DCODEGEN" />
</Signer>
<Signer Name="MincryptKnownRootMicrosoftStandardRoot2011" ID="ID_SIGNER_AM">
 <CertRoot Type="Wellknown" Value="07" />
 <CertEKU ID="ID_EKU_AM" />
</Signer>
<Signer Name="Microsoft Product Root 2010 Windows EKU" ID="ID_SIGNER_WINDOWS_PRODUCTION_USER">
 <CertRoot Type="Wellknown" Value="06" />
 <CertEKU ID="ID_EKU_WINDOWS" />
</Signer>
<Signer Name="Microsoft Product Root 2011 Windows EKU" ID="ID_SIGNER_WINDOWS_PRODUCTION_USER_2011">
 <CertRoot Type="TBS" Value="4E80BE107C860DE896384B3EFF50504DC2D76AC7151DF3102A4450637A032146" />
 <CertEKU ID="ID_EKU_WINDOWS" />
</Signer>
<Signer Name="Microsoft Product Root 2010 ELAM EKU" ID="ID_SIGNER_ELAM_PRODUCTION_USER">
 <CertRoot Type="Wellknown" Value="06" />
 <CertEKU ID="ID_EKU_ELAM" />
</Signer>
<Signer Name="Microsoft Product Root 2010 HAL EKU" ID="ID_SIGNER_HAL_PRODUCTION_USER">
 <CertRoot Type="Wellknown" Value="06" />
 <CertEKU ID="ID_EKU_HAL_EXT" />
</Signer>
<Signer Name="Microsoft Product Root 2010 WHQL EKU" ID="ID_SIGNER_WHQL_SHA2_USER">
 <CertRoot Type="Wellknown" Value="06" />
 <CertEKU ID="ID_EKU_WHQL" />
</Signer>
<Signer Name="Microsoft Product Root WHQL EKU SHA1" ID="ID_SIGNER_WHQL_SHA1">
 <CertRoot Type="Wellknown" Value="05" />
 <CertEKU ID="ID_EKU_WHQL" />
</Signer>
<Signer Name="Microsoft Product Root WHQL EKU MD5" ID="ID_SIGNER_WHQL_MD5">
 <CertRoot Type="Wellknown" Value="04" />
 <CertEKU ID="ID_EKU_WHQL" />
</Signer>
<Signer Name="Microsoft Flighting Root 2014 Windows EKU" ID="ID_SIGNER_WINDOWS_FLIGHT_ROOT">
 <CertRoot Type="Wellknown" Value="0E" />
 <CertEKU ID="ID_EKU_WINDOWS" />
</Signer>
<Signer Name="Microsoft Flighting Root 2014 ELAM EKU" ID="ID_SIGNER_ELAM_FLIGHT">
 <CertRoot Type="Wellknown" Value="0E" />
 <CertEKU ID="ID_EKU_ELAM" />
</Signer>
<Signer Name="Microsoft Flighting Root 2014 HAL EKU" ID="ID_SIGNER_HAL_FLIGHT">
 <CertRoot Type="Wellknown" Value="0E" />
 <CertEKU ID="ID_EKU_HAL_EXT" />
</Signer>
<Signer Name="Microsoft Flighting Root 2014 WHQL EKU" ID="ID_SIGNER_WHQL_FLIGHT_SHA2">
 <CertRoot Type="Wellknown" Value="0E" />
 <CertEKU ID="ID_EKU_WHQL" />
</Signer>
<Signer Name="Microsoft MarketPlace PCA 2011" ID="ID_SIGNER_STORE">
 <CertRoot Type="TBS" Value="FC9EDE3DCCA09186B2D3BF9B738A2050CB1A554DA2DCADB55F3F72EE17721378" />
 <CertEKU ID="ID_EKU_STORE" />
</Signer>
<Signer Name="Microsoft Product Root 2010 RT EKU" ID="ID_SIGNER_RT_PRODUCTION">
 <CertRoot Type="Wellknown" Value="06" />
 <CertEKU ID="ID_EKU_RT_EXT" />
</Signer>
<Signer Name="Microsoft Flighting Root 2014 RT EKU" ID="ID_SIGNER_RT_FLIGHT">
 <CertRoot Type="Wellknown" Value="0E" />
 <CertEKU ID="ID_EKU_RT_EXT" />
</Signer>
<Signer Name="Microsoft Standard Root 2001 RT EUK" ID="ID_SIGNER_RT_STANDARD">
 <CertRoot Type="Wellknown" Value="07" />
 <CertEKU ID="ID_EKU_RT_EXT" />
</Signer>

The majority of the signing certificates use a special “Wellknown” format with just a single numeric value which identifies the certificate. Finding out what certificates these correspond to can be tricky, again poor documentation. Fortunately, the Powershell ConfigCI module on Win10S has example policy files such as Default_WindowsEnforced.xml which at least gives them names if not spelling out the explicit certificate used (they could be multiple Microsoft Product Root 2010 certificates after all). It’s likely for example that “Microsoft Product Root 2010” is the following root which is the root certificate of pretty much all the signed files on Win10S:

root_2010.PNG

However, it’s not enough to be signed by whitelisted signer that’d be too easy. You must also have in the certificate chain a specific Enhanced Key Usage (EKU). So for example signer ID_SIGNER_WINDOWS_PRODUCTION_USER must have the EKU ID_EKU_WINDOWS which has the OID value 1.3.6.1.4.1.311.10.3.6. The Windows binaries have this EKU set, but something which is also Microsoft signed such as WinDBG is signed by the same root but doesn’t have this EKU set meaning it doesn’t load. From this information we can understand what it means to be store signed. It’s a combination of a specific certificate chain and a specific Store EKU. This is reflected in the ID_SIGNER_STORE signing rule.

cert_ekus.png

For kernel code, the following signers are allowed:

<AllowedSigner SignerId="ID_SIGNER_WINDOWS_PRODUCTION_USER" />
<AllowedSigner SignerId="ID_SIGNER_ELAM_PRODUCTION_USER" />
<AllowedSigner SignerId="ID_SIGNER_HAL_PRODUCTION_USER" />
<AllowedSigner SignerId="ID_SIGNER_WHQL_SHA2_USER" />
<AllowedSigner SignerId="ID_SIGNER_WHQL_SHA1" />
<AllowedSigner SignerId="ID_SIGNER_WHQL_MD5" />
<AllowedSigner SignerId="ID_SIGNER_WINDOWS_FLIGHT_ROOT" />
<AllowedSigner SignerId="ID_SIGNER_ELAM_FLIGHT" />
<AllowedSigner SignerId="ID_SIGNER_HAL_FLIGHT" />
<AllowedSigner SignerId="ID_SIGNER_WHQL_FLIGHT_SHA2" />
<AllowedSigner SignerId="ID_SIGNER_TEST2010" />

For user mode, the following are allowed:

<AllowedSigner SignerId="ID_SIGNER_WINDOWS_PRODUCTION_USER" />
<AllowedSigner SignerId="ID_SIGNER_ELAM_PRODUCTION_USER" />
<AllowedSigner SignerId="ID_SIGNER_HAL_PRODUCTION_USER" />
<AllowedSigner SignerId="ID_SIGNER_WHQL_SHA2_USER" />
<AllowedSigner SignerId="ID_SIGNER_WHQL_SHA1" />
<AllowedSigner SignerId="ID_SIGNER_WHQL_MD5" />
<AllowedSigner SignerId="ID_SIGNER_WINDOWS_FLIGHT_ROOT" />
<AllowedSigner SignerId="ID_SIGNER_ELAM_FLIGHT" />
<AllowedSigner SignerId="ID_SIGNER_HAL_FLIGHT" />
<AllowedSigner SignerId="ID_SIGNER_WHQL_FLIGHT_SHA2" />
<AllowedSigner SignerId="ID_SIGNER_STORE" />
<AllowedSigner SignerId="ID_SIGNER_RT_PRODUCTION" />
<AllowedSigner SignerId="ID_SIGNER_DRM" />
<AllowedSigner SignerId="ID_SIGNER_DCODEGEN" />
<AllowedSigner SignerId="ID_SIGNER_AM" />
<AllowedSigner SignerId="ID_SIGNER_RT_FLIGHT" />
<AllowedSigner SignerId="ID_SIGNER_RT_STANDARD" />
<AllowedSigner SignerId="ID_SIGNER_TEST2010" />

The only thing which stands out here is the user mode signing for ID_SIGNER_DRM which is because it’s a pre-trusted root key for DRM. And as I’ve blogged about before, it’s almost certainly possible to get a private key for a certificate which chains to this root from many graphics drivers (see my blog post here). I’ve not tested it but while you could chain to this root by grabbing the private key from the kernel driver (assuming it’s in software), the chain you could build probably isn’t suitable for code signing anyway. But again it’s something worth looking at.

The final use for signers is specifying who’s allowed to sign and update the policy. In order for a policy to be used unsigned there must be the “Enabled:Unsigned System Integrity Policy” set, however as we saw earlier in this blog that wasn’t the case. You can see which signer is allowed to sign the policy in the following snippet.

<UpdatePolicySigners>
<UpdatePolicySigner SignerId="ID_SIGNER_WINDOWS_PRODUCTION_USER_2011" />
</UpdatePolicySigners>

This policy is using ID_SIGNER_WINDOWS_PRODUCTION_USER_2011 which if you look back at the signers is a To-Be-Signed certificate rather than a well known one. So we’d need to find the actual certificate which matches this hash value. However we can guess, it’s almost certainly just one of the roots for signing the existing winsipolicy.p7b file. We can use the Get-CIBinaryPolicyCertificate cmdlet from Matt’s script to dump the certificates and then use the ConfigCI Powershell module to generate the TBS value. Which we can see matches up with the TBS value from before:

update_signer.PNG

Conclusions

Overall, from a basic DG policy perspective, Win10S seems reasonable. Effectively, only Microsoft signed code can run and then only ones with either WHQL or Windows EKUs in the certificates which would make it tricky to the find anything useful outside of what’s installed with the operating system to exploit. Of course with Desktop Bridge applications, which are effectively store signed Win32 applications and the general quality of Windows driver developers no doubt there’s some additional code which can be exploited by installing it onto the system. You just have to look at Office, which is allowed to be installed from the Store which has its VBA macro functionality intact.

What is missing though is any use of Hyper-V based enforcement, either to restrict removing the policy or ensure kernel mode integrity through HyperGuard or HVCI code integrity enforcement. This is a severe weakness, it’s not like Win10S doesn’t support Hyper-V, you can even install the full Hyper-V and configuration tools. This allows you to run a normal version on Windows in a VM on top of the locked down platform which is actually kind of nice. But it means that the System Integrity policy is not very well protected. This is something which we’ll come back to at a later date in a future blog post.