Thursday, September 8, 2016

Using Device Guard to Mitigate Against Device Guard Bypasses

In my last post, I presented an introduction to Device Guard and described how to go about developing a fairly locked down code integrity policy - a policy that consisted entirely of implicit allow rules. In this post, I’m going to describe how to deny execution of code that would otherwise be whitelisted according to policy. Why would you want to do this? Well, as I blogged about previously, one of the easiest methods of circumventing user-mode code integrity (UMCI) is to take advantage of signed applications that can be used to execute arbitrary, unsigned code. In the blog post, I achieved this using one of Microsoft’s debuggers, cdb.exe. Unfortunately, cdb.exe isn’t the only signed Microsoft binary that can circumvent a locked down code integrity policy. In the coming months, Casey Smith (@subtee) and I will gradually unveil additional signed binaries that circumvent UMCI. In the spirit of transparency, Casey and I will release bypasses as we find them but we will only publicize bypasses for which we can produce an effective mitigation. Any other bypass would be reported to Microsoft through the process of coordinated disclosure.

While the existence of bypasses may cause some to question the effectiveness of Device Guard, consider that the technique I will describe will block all previous, current, and future versions of binaries that circumvent UMCI. The only requirement being that the binaries be signed with a code signing certificate that is in the same chain as the PCA certificate used when we created a deny rule - a realistic scenario. What I’m describing is the FilePublisher file rule level.

In the example that follows, I will create a new code integrity policy with explicit deny rules for all signed versions of the binaries I’m targeting up to the highest supported version number (65535.65535.65535.65535) – cdb.exe, windbg.exe, and kd.exe – three user-mode and kernel-mode debuggers signed by Microsoft. You can then merge the denial CI policy with that of your reference policy. I confirmed with the Device Guard team at Microsoft that what I’m about to describe is most likely the ideal method (at time of writing) of blocking the execution of individual binaries that bypass your code integrity policy.

# The directory that contains the binaries that circumvent our Device Guard policy
$Scanpath = 'C:\Program Files\Windows Kits\10\Debuggers\x64'

# The binaries that circumvent our Device Guard policy
$DeviceGuardBypassApps = 'cdb.exe', 'windbg.exe', 'kd.exe'

$DenialPolicyFilePath = 'BypassMitigationPolicy.xml'

# Get file and signature information for every file in the scan directory
$Files = Get-SystemDriver -ScanPath $Scanpath -UserPEs -NoShadowCopy

# We'll use this to filter out the binaries we want to block
$TargetFilePaths = $DeviceGuardBypassApps | ForEach-Object { Join-Path $Scanpath $_ }

# Filter out the user-mode binaries we want to block
# This would just as easily apply to drivers. Just change UserMode to $False
# If you’re wanting this to apply to drivers though, you might consider using
# the WHQLFilePublisher rule.
$FilesToBlock = $Files | Where-Object {
    $TargetFilePaths -contains $_.FriendlyName -and $_.UserMode -eq $True
}

# Generate a dedicated device guard bypass policy that contains explicit deny rules for the binaries we want to block.
New-CIPolicy -FilePath $DenialPolicyFilePath -DriverFiles $FilesToBlock -Level FilePublisher -Deny -UserPEs

# Set the MinimumFileVersion to 65535.65535.65535 - an arbitrarily high number.
# Setting this value to an arbitraily high version number will ensure that any signed bypass binary prior to version 65535.65535.65535.65535
# will be blocked. This logic allows us to theoretically block all previous, current, and future versions of binaries assuming
# they were signed with a certificate signed by the specified PCA certificate
$DenyPolicyRules = Get-CIPolicy -FilePath $DenialPolicyFilePath
$DenyPolicyRules | Where-Object { $_.TypeId -eq 'FileAttrib' } | ForEach-Object {
    # For some reason, the docs for Edit-CIPolicyRule say not to use it...
    Edit-CIPolicyRule -FilePath $DenialPolicyFilePath -Id $_.Id -Version '65535.65535.65535.65535'

}


# The remaining portion is optional. They are here to demonstrate
# policy merging with a reference policy and deployment.


<#
$ReferencePolicyFilePath = 'FinalPolicy.xml'
$MergedPolicyFilePath = 'Merged.xml'
$DeployedPolicyPath = 'C:\DGPolicyFiles\SIPolicy.bin'
#>

# Extract just the file rules from the denial policy. We do this because I don't want to merge
# and possibly overwrite any policy rules from the reference policy.
<#
$Rules = Get-CIPolicy -FilePath $DenialPolicyFilePath
Merge-CIPolicy -OutputFilePath $MergedPolicyFilePath -PolicyPaths $ReferencePolicyFilePath -Rules $Rules
#>

# Deploy the new policy and reboot.
<#
ConvertFrom-CIPolicy -XmlFilePath $MergedPolicyFilePath -BinaryFilePath $DeployedPolicyPath
#>

So in the code above, to generate the policy, we specified the location where the offending binaries were installed. In reality, they can be in any directory and you can generate this deny policy on any machine. In other words, you’re not required to generate it on the machine that will have the code integrity policy deployed. That directory is then scanned. You need to filter out the specific binaries that you want to deny and merge the deny policy with a reference policy and redeploy. Once you’ve redeployed the policy, you will want to validate its efficacy. To validate it, I would ensure the following:
  1. Both the x86 and x64 version of the binary are blocked.
  2. At least two versions of each binary (for each architecture) are blocked.

So, for example, to validate that the signed cdb.exe can no longer execute, be sure to obtain two versions of cdb.exe and have a 32-bit and 64-bit build of each version.

It is unfortunately kind of a hack to have to manually modify the policy XML to specify an arbitrarily large version number. Ideally, in a future version of Device Guard, Microsoft would allow you to specify a wildcard that would imply that the deny rule would apply to all versions of the binary. In the meantime, this hack seems to get the job done. What’s great about this simple workflow is that as new bypasses come out, you can just keep adding deny rules to an all-encompassing Device Guard bypass code integrity policy! In fact, I plan on maintaining such a bypass-specific CI policy on GitHub in the near future.

Now, I’ve done a decent amount of testing of this mitigation, which I consider to be effective and not difficult to implement. I encourage everyone out there to poke holes in my theory, though. And if you discover a bypass for my mitigation, please be a good citizen and let the world know! I hope these posts are continuing to pique your interest in this important technology!

For reference, here is the policy that was generated based on the code above. Note that while there are explicit file paths in the generated policy, the deny rules apply regardless of where the binaries are located on disk.


<?xml version="1.0" encoding="utf-8"?>
<SiPolicy xmlns="urn:schemas-microsoft-com:sipolicy">
  <VersionEx>10.0.0.0</VersionEx>
  <PolicyTypeID>{A244370E-44C9-4C06-B551-F6016E563076}</PolicyTypeID>
  <PlatformID>{2E07F7E4-194C-4D20-B7C9-6F44A6C5A234}</PlatformID>
  <Rules>
    <Rule>
      <Option>Enabled:Unsigned System Integrity Policy</Option>
    </Rule>
    <Rule>
      <Option>Enabled:Audit Mode</Option>
    </Rule>
    <Rule>
      <Option>Enabled:Advanced Boot Options Menu</Option>
    </Rule>
    <Rule>
      <Option>Required:Enforce Store Applications</Option>
    </Rule>
    <Rule>
      <Option>Enabled:UMCI</Option>
    </Rule>
  </Rules>
  <!--EKUS-->
  <EKUs />
  <!--File Rules-->
  <FileRules>
    <FileAttrib ID="ID_FILEATTRIB_F_1" FriendlyName="C:\Program Files\Windows Kits\10\Debuggers\x64\cdb.exe FileAttribute" FileName="CDB.Exe" MinimumFileVersion="65535.65535.65535.65535" />
    <FileAttrib ID="ID_FILEATTRIB_F_2" FriendlyName="C:\Program Files\Windows Kits\10\Debuggers\x64\kd.exe FileAttribute" FileName="kd.exe" MinimumFileVersion="65535.65535.65535.65535" />
    <FileAttrib ID="ID_FILEATTRIB_F_3" FriendlyName="C:\Program Files\Windows Kits\10\Debuggers\x64\windbg.exe FileAttribute" FileName="windbg.exe" MinimumFileVersion="65535.65535.65535.65535" />
  </FileRules>
  <!--Signers-->
  <Signers>
    <Signer ID="ID_SIGNER_F_1" Name="Microsoft Code Signing PCA">
      <CertRoot Type="TBS" Value="27543A3F7612DE2261C7228321722402F63A07DE" />
      <CertPublisher Value="Microsoft Corporation" />
      <FileAttribRef RuleID="ID_FILEATTRIB_F_1" />
      <FileAttribRef RuleID="ID_FILEATTRIB_F_2" />
      <FileAttribRef RuleID="ID_FILEATTRIB_F_3" />
    </Signer>
    <Signer ID="ID_SIGNER_F_2" Name="Microsoft Code Signing PCA 2010">
      <CertRoot Type="TBS" Value="121AF4B922A74247EA49DF50DE37609CC1451A1FE06B2CB7E1E079B492BD8195" />
      <CertPublisher Value="Microsoft Corporation" />
      <FileAttribRef RuleID="ID_FILEATTRIB_F_1" />
      <FileAttribRef RuleID="ID_FILEATTRIB_F_2" />
      <FileAttribRef RuleID="ID_FILEATTRIB_F_3" />
    </Signer>
  </Signers>
  <!--Driver Signing Scenarios-->
  <SigningScenarios>
    <SigningScenario Value="131" ID="ID_SIGNINGSCENARIO_DRIVERS_1" FriendlyName="Auto generated policy on 09-07-2016">
      <ProductSigners />
    </SigningScenario>
    <SigningScenario Value="12" ID="ID_SIGNINGSCENARIO_WINDOWS" FriendlyName="Auto generated policy on 09-07-2016">
      <ProductSigners>
        <DeniedSigners>
          <DeniedSigner SignerId="ID_SIGNER_F_1" />
          <DeniedSigner SignerId="ID_SIGNER_F_2" />
        </DeniedSigners>
      </ProductSigners>
    </SigningScenario>
  </SigningScenarios>
  <UpdatePolicySigners />
  <CiSigners>
    <CiSigner SignerId="ID_SIGNER_F_1" />
    <CiSigner SignerId="ID_SIGNER_F_2" />
  </CiSigners>
  <HvciOptions>0</HvciOptions>
</SiPolicy>

Tuesday, September 6, 2016

Introduction to Windows Device Guard: Introduction and Configuration Strategy

Introduction

Welcome to the first in a series a Device Guard blog posts. This post is going to cover some introductory concepts about Device Guard and it will detail the relatively aggressive strategy that I used to configure it on my Surface Pro 4 tablet running a fresh install of Windows 10 Enterprise Anniversary Update (1607). The goal of this introductory post is to start getting you comfortable with Device Guard and experimenting with it yourselves. In subsequent posts, I will begin to describe various bypasses and will describe methods to effectively mitigate against each bypass. The ultimate goal of this series of posts is to educate readers about the strengths and current weaknesses of what I consider to be an essential technology in preventing a massive class of malware infections in a post-compromise scenario (i.e. exploit mitigation is another subject altogether).

Device Guard Basics

Device Guard is a powerful set of hardware and software security features available in Windows 10 Enterprise and Server 2016 (including Nano Server with caveats that I won’t explain in this post) that aim to block the loading of drivers, user-mode binaries (including DLLs), MSIs, and scripts (PowerShell and Windows Script Host - vbs, js, wsf, wsc) that are not explicitly authorized per policy. In other words, it’s a whitelisting solution. The idea, in theory, being a means to prevent arbitrary unsigned code execution (excluding remote exploits). Right off the bat, you may already be asking, “why not just use AppLocker and why is Microsoft recreating the wheel?” I certainly had those questions and I will attempt to address them later in the post.

Device Guard can be broken down into two primary components:

1 - Code integrity (CI)

The code integrity component of Device Guard enforces both kernel mode code integrity (KMCI) and user mode code integrity (UMCI). The rules enforced by KMCI and UMCI are dictated by a code integrity policy - a configurable list of whitelist rules that can apply to drivers, user-mode binaries, MSIs, and scripts. Now, technically, PowerShell scripts can still execute, but unless the script or module is explicitly allowed via the code integrity policy, it will be forced to execute in constrained language mode, which prevents a user from calling Add-Type, instantiating .NET objects, and invoking .NET methods, effectively precluding PowerShell from being used to gain any form of arbitrary unsigned code execution. Additionally, WSH scripts will still execute if they don't comply with the deployed code integrity policy, but they will fail to instantiate any COM objects which is reasonable considering unsigned PowerShell will still execute but in a very limited fashion. As of this writing, the script-based protections of Device Guard are not documented by Microsoft.

So with a code integrity policy, for example, if I wanted my system to only load drivers or user-mode code signed by Microsoft, such rules would be stated in my policy. Code integrity policies are created using the cmdlets present in the ConfigCI PowerShell module. CI policies are configured as a plaintext XML document then converted to a binary-encoded XML format when they are deployed. For additional protections, CI policies can also be signed with a valid code-signing certificate.

2 - Virtualization-based Security (VBS)

Virtualization-based Security is comprised of several hypervisor and modern hardware-based security features are used to protect the enforcement of a code integrity policy, Credential Guard, and shielded VMs. While it is not mandatory to have hardware that supports VBS features, without it, the effectiveness of Device Guard will be severely hampered. Without delving into too much detail, VBS will improve the enforcement of Device Guard by attempting to prevent disabling code integrity enforcement even as an elevated user who doesn’t have physical access to the target. It can also prevent DMA-based attacks and also restrict any kernel code from creating executable memory that isn’t explicitly conformant to the code integrity policy. The following resources elaborate on VBS:


Microsoft provides a Device Guard and Credential Guard hardware readiness tool that you should use to assess which hardware-specific components of Device Guard and/or Credential Guard can be enabled. This post does not cover Credential Guard.

Configuration Steps and Strategy

Before we get started, I highly recommend that you read the official Microsoft documentation on Device Guard and also watch the Ignite 2015 talk detailing Device Guard design and configuration - Dropping the Hammer Down on Malware Threats with Windows 10’s Device Guard (PPTX, configuration script). The Ignite talk covers some aspects of Device Guard that are not officially documented.

You can download the fully documented code I used to generate my code integrity policy. You can also download the finalized code integrity policy that the code below generated for my personal Surface Pro 4. Now, absolutely do not just deploy that to your system. I’m only providing it as a reference for comparison to the code integrity policy that you create for your system. Do not complain that it might be overly permissive (because I know it is in some respects) and please do not ask why this policy doesn’t work on your system. You also probably wouldn't want to trust code signed by my personal code-signing certificate. ;)

In the Ignite talk linked to above, Scott and Jeffrey describe creating a code integrity policy for a golden system by scanning the computer for all binaries present on it and allowing any driver, script, MSI, application, or DLL to execute based on the certificate used to sign those binaries/scripts. While this is in my opinion, a relatively simple way to establish an initial policy, in practice, I consider this approach to be overly permissive. When I used this methodology on my fresh install of Windows 10 Enterprise Anniversary Update with Chrome installed, the code integrity policy generated consisted of what would be considered normal certificates mixed in with several test signing certificates. Personally, I don’t want to grant anything permission to run that was signed with a test certificate. Notable certificates present in the generated policy were the following:

  • Microsoft Windows Phone Production PCA 2012
  • MSIT Test CodeSign CA 6
  • OEMTest OS Root CA
  • WDKTestCert wdclab,130885612892544312

Upon finding such certificate oddities, I decided to tackle development of a code integrity policy another way – create an empty policy (i.e. deny everything), configure Device Guard in audit mode, and then craft my policy based on what was loaded and denied in the CodeIntegrity event log.

So now let’s dive into how I configured my Surface Pro 4. For starters, I only wanted signed Microsoft code to execute (with a couple third party hardware driver exceptions). Is this a realistic configuration? It depends but probably not. You’re probably going to want non-Microsoft code to run as well. That’s fine. We can configure that later but my personal goal is to only allow Microsoft code to run since everyone using Device Guard will need to do that at a minimum. I will then have a pristine, locked down system which I can then use to research ways of gaining unsigned code execution with signed Microsoft binaries. Now, just to be clear, if you want your system be able to boot and apply updates, you’ll obviously need to allow code signed by Microsoft to run. So to establish my “golden system,” I did the following:

  1. Performed a fresh install of Windows 10 Enterprise Anniversary Update.
  2. Ensured that it was fully updated via Windows Update.

In the empty, template policy, I have the following policy rules enabled:

  1 - Unsigned System Integrity Policy (during policy configuration/testing phases)

Signing your code integrity policy makes it so that deployed policies cannot be removed (assuming they are locked in UEFI using VBS protections) and that they can only be updated using approved code signing certificates as specified in the policy.

  2 - Audit Mode (during policy configuration/testing phases)

I want to simulate denying execution of everything on the system that attempts to load. After I perform normal computing tasks on my computer for a while, I will then develop a new code integrity policy based upon the certificates used to sign everything that would have been denied in the Microsoft-Windows-CodeIntegrity/Operational and Microsoft-Windows-AppLocker (it is not documented that Device Guard pulls from the AppLocker log) logs.

  3 - Advanced Boot Options Menu (during policy configuration/testing phases)

If I somehow misconfigure my policy, deploy it, and my Surface no longer boots, I’ll need a fallback option to recover. This option would allow you to reboot and hold down F8 to access a recovery prompt where I could delete the deployed code integrity policy if I had to. Note: you might be thinking that this would be an obvious Device Guard bypass for someone with physical access. Well, if your policy is not in audit mode and it is required to be signed, you can delete the deployed code integrity policy from disk but it will return unharmed after a reboot. Configuring Bitlocker would prevent an attacker with physical access from viewing and deleting files from disk though via the recovery prompt.

  4 - UMCI

We want Device Guard to not only apply to drivers but to user-mode binaries, MSIs, and scripts as well.

  5 - WHQL


Only load driver that are Windows Hardware Quality Labs (WHQL) signed. This is supposed to be a mandate for all new Windows 10-compatible drivers so we’ll want to make sure we enforce this.

  6 - EV Signers

We want to only load drivers that are not only WHQL signed but also signed with an extended validation certificate. This is supposed to be a requirement for all drivers in Windows 10 Anniversary update. Unfortunately, as we will later discover, this is not the case; not even for all Microsoft drivers (specifically, my Surface Pro 4-specific hardware drivers).

Several others policy rules will be described in subsequent steps. For details on all the available, configurable policy rule options, read the official documentation.

What will follow will be the code and rationale I used to develop my personal code integrity policy. This is a good time to mention that there is never going to be a one size fits all solution for code integrity policy development. I am choosing a relatively locked down, semi-unrealistic policy that will most likely form a minimal basis for pretty much any other code integrity policy out there.

Configuration Phase #1 - Deny-all audit policy deployment

In this configuration phase, I’m going to create an empty, template policy placed in audit mode that will simulate denying execution of every driver, user-mode binary, MSI, and script. After running my system for a few days and getting a good baseline for the programs I’m going to execute (excluding third party binaries since I only want MS binaries to run), I can generate a new policy based on what would have been denied execution in the event log.

There is no standard method of generating an empty policy so what I did was call New-CIPolicy and have it generate a policy from a completely empty directory.

It is worth noting at this point that I will be deploying all subsequent policies directly to %SystemRoot%\System32\CodeIntegrity\SIPolicy.p7b. You can, however configure via Group Policy an alternate file path where CI policies should be pulled from and I believe you have to set this location via Group Policy if you’re using a signed policy file (at least from my experimentation). This procedure is documented here. You had damn well better make sure that any user doesn’t have write access to the directory where the policy file is contained if an alternate path is specified with Group Policy.

What follows is the code I used to generate and deploy the initial deny-all audit policy. I created a C:\DGPolicyFiles directory to contain all my policy related files. You can use any directory you want though.

# The staging directory I'm using for my Device Guard setup
$PolicyDirectory = 'C:\DGPolicyFiles'

# Path to the empty template policy that will place Device Guard
# into audit mode and simulate denying execution of everything.
$EmptyPolicyXml = Join-Path -Path $PolicyDirectory -ChildPath 'EmptyPolicy.xml'

# Generate an empty, deny-all policy
# There is no intuitive way to generate an empty policy so we will
# go about doing it by generating a policy based on an empty directory.
$EmptyDir = Join-Path -Path $PolicyDirectory -ChildPath 'EmptyDir'
mkdir -Path $EmptyDir
New-CIPolicy -FilePath $EmptyPolicyXml -Level PcaCertificate -ScanPath $EmptyDir -NoShadowCopy
Remove-Item $EmptyDir

# Only load drivers that are WHQL signed
Set-RuleOption -FilePath $EmptyPolicyXml -Option 2
# Enable UMCI enforcement
Set-RuleOption -FilePath $EmptyPolicyXml -Option 0

# Only allow drivers to load that are WHQL signed by trusted MS partners
# who sign their drivers with an extended validation certificate.
# Note: this is an idealistic setting that will probably prevent some of your
# drivers from loading. Enforcing this in audit mode however will at least
# inform you as to what the problematic drivers are.
Set-RuleOption -FilePath $EmptyPolicyXml -Option 8

# A generated policy will also have the following policy options set by default
# * Unsigned System Integrity Policy
# * Audit Mode
# * Advanced Boot Options Menu
# * Enforce Store Applications

# In order to deploy the policy, the XML policy has to be converted
# to p7b format with the ConvertFrom-CIPolicy cmdlet.
$EmptyPolicyBin = Join-Path -Path $PolicyDirectory -ChildPath 'EmptyPolicy.bin'

ConvertFrom-CIPolicy -XmlFilePath $EmptyPolicyXml -BinaryFilePath $EmptyPolicyBin

# We're going to copy the policy file in binary format to here. By simply copying
# the policy file to this destination, we're deploying our policy and enabling it
# upon reboot.
$CIPolicyDeployPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\CodeIntegrity\SIPolicy.p7b'

Copy-Item -Path $EmptyPolicyBin -Destination $CIPolicyDeployPath -Force

# At this point, you may want to clear the Microsoft-Windows-CodeIntegrity/Operational
# event log and increase the size of the log to accommodate the large amount of
# entries that will populate the event log as a result of an event log entry being
# created upon code being loaded.

# Optional: Clear Device Guard related logs
# wevtutil clear-log Microsoft-Windows-CodeIntegrity/Operational
# wevtutil clear-log "Microsoft-Windows-AppLocker/MSI and Script"

# Reboot the computer and the deny-all audit policy will be in place.

Configuration Phase #2 - Code integrity policy creation based on audit logs

Hopefully, you’ve run your system for a while and established a good baseline of all the drivers, user-mode binaries (including DLLs) and scripts that are necessary for you to do your job. If that’s the case, then you are ready to build generate the next code integrity policy based solely on what was reported as denied in the event log.

When generating this new code integrity policy, I will specify the PcaCertificate file rule level which is probably the best file rule level for this round of CI policy generation as it is the highest in the code signing cert signer chain and it has a longer validity time frame than a leaf certificate (i.e. lowest in the signing chain). You could use more restrictive file rules (e.g. LeafCertificate, Hash, FilePublisher, etc.) but you would be weighing updatability with increased security. For example, you should be careful when whitelisting third party PCA certificates as a malicious actor would just need to be issued a code signing certificate from that third party vendor as a means of bypassing your policy. Also, consider a scenario where a vulnerable older version of a signed Microsoft binary was used to gain code execution. If this is a concern, consider using a file rule like FilePublisher or WHQLFilePublisher for WHQL-signed drivers.

Now, when we call New-CIPolicy to generate the policy based on the audit log, you may notice a lot of warning messages claiming that it is unable to locate a bunch of drivers on disk. This apperas to be an unfortunate path parsing bug that will become a problem that we will address in the next configuration phase.

Driver path parsing bug

# Hopefully, you've spent a few days using your system for its intended purpose and didn't
# install any software that would compromise the "gold image" that you're aiming for.
# Now we're going to craft a CI policy based on what would have been denied from loading.
# Obviously, these are the kinds of applications, scripts, and drivers that will need to
# execute in order for your system to work as intended.

# The staging directory I'm using for my Device Guard setup
$PolicyDirectory = 'C:\DGPolicyFiles'

# Path to the CI policy that will be generated based on the entries present
# in the CodeIntegrity event log.
$AuditPolicyXml = Join-Path -Path $PolicyDirectory -ChildPath 'AuditLogPolicy.xml'

# Generate the CI policy based on what would have been denied in the event logs
# (i.e. Microsoft-Windows-CodeIntegrity/Operational and Microsoft-Windows-AppLocker/MSI and Script)
# PcaCertificate is probably the best file rule level for this round of CI policy generation
# as it is the highest in the code signing cert signer chain and it has a longer validity time frame
# than a leaf certificate (i.e. lowest in the signing chain).
# This may take a few minutes to generate the policy.
# The resulting policy will result in a rather concise list of whitelisted PCA certificate signers.
New-CIPolicy -FilePath $AuditPolicyXml -Level PcaCertificate -Audit -UserPEs

# Note: This policy, when deployed will still remain in audit mode as we should not be confident
# at this point that we've gotten everything right.

# Now let's deploy the new policy
$AuditPolicyBin = Join-Path -Path $PolicyDirectory -ChildPath 'AuditLogPolicy.bin'

ConvertFrom-CIPolicy -XmlFilePath $AuditPolicyXml -BinaryFilePath $AuditPolicyBin

# We're going to copy the policy file in binary format to here. By simply copying
# the policy file to this destination, we're deploying our policy and enabling it
# upon reboot.
$CIPolicyDeployPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\CodeIntegrity\SIPolicy.p7b'

Copy-Item -Path $AuditPolicyBin -Destination $CIPolicyDeployPath -Force

# Optional: Clear Device Guard related logs
# wevtutil clear-log Microsoft-Windows-CodeIntegrity/Operational
# wevtutil clear-log "Microsoft-Windows-AppLocker/MSI and Script"

# Reboot the computer and the audit policy will be in place.

Configuration Phase #3 - Code integrity policy final tweaks while still in audit mode

In this phase, we’ve rebooted and noticed that there are a bunch of drivers that wouldn’t have loaded if we actually enforced the policy. This is due to the driver path parsing issue I described in the last section. Until this bug is fixed, I believe there are two realistic methods of handling this:

  •  Manually copy the paths of the drivers from the event log with a PowerShell script and copy the drivers to a dedicated directory and generate a new policy based on the drivers in that directory and then merge that policy with the policy we generated in phase #2. I personally had some serious issues with this strategy in practice.
  • Generate a policy by scanning %SystemRoot%\System32\drivers and then merge that policy with the policy we generated in phase #2. For this blog post, that’s what we will be doing out of simplicity. The only reason I hesitate to use this strategy is that I don’t want to be overly permissive necessarily and whitelist certificates for drivers I don’t use that might be issued by a non-Microsoft public certification authority.

Additionally, one of the side effects of this bug is that the generated policy from phase #2 only has rules for user-mode code and not drivers. We obviously need driver rules.

# My goal in this phase is to see what remaining CodeItegrity log entries
# exist and to try to rectify them while still in audit mode before placing
# code integrity into enforcement mode.

# For me, I had about 30 event log entries that indicated the following:
#
# Code Integrity determined that a process (Winload) attempted to load
# System32\Drivers\mup.sys that did not meet the Authenticode signing
# level requirements or violated code integrity policy. However, due to
# code integrity auditing policy, the image was allowed to load.

# Upon trying to create a new policy based on these event log entries via the following command
# New-CIPolicy -FilePath Audit2.xml -Level PcaCertificate -Audit
# I got a bunch of the following warnings:
#
# File at path \\?\GLOBALROOTSystem32\Drivers\Wof.sys in the audit log was not found.
# It has likely been deleted since it was last run
#
# Ugh. No it wasn't deleted. This looks like a path parsing bug. Personally, I'm
# comfortable trusting all drivers in %SystemRoot%\System32\Drivers so I'm going
# to create a policy from that directory and merge it with my prior. Afterall,
# my system would not boot if I didn't whitelist them.

$PolicyDirectory = 'C:\DGPolicyFiles'

# Path to the CI policy that will be generated based on the entries present
# in the CodeIntegrity event log.
$DriverPolicyXml = Join-Path -Path $PolicyDirectory -ChildPath 'SystemDriversPolicy.xml'

# Create a whitelisted policy for all drivers in System32\drivers to account for
# the New-CIPolicy audit log scanning path parsing bug...

# Note: this really annoying bug prevented and rules in the previous phase from being created
# for drivers - only user-mode binaries and scripts. If I were to deploy and enforce a policy without
# driver whitelist rules, I'd have an unbootable system.
New-CIPolicy -FilePath $DriverPolicyXml -Level PcaCertificate -ScanPath 'C:\Windows\System32\drivers\'

# Some may consider this strategy to be too permissive (myself partially included). The ideal strategy
# here probably would have been to pull out the individual driver paths, copy them to a dedicated
# directory and generate a policy for just those drivers. For the ultra paranoid, this is left as an
# exercise to the reader.

# Now we have to merge this policy with the last one as a means of consolidating whitelist rules.
$AuditPolicyXml = Join-Path -Path $PolicyDirectory -ChildPath 'AuditLogPolicy.xml'
$MergedAuditPolicyXml = Join-Path -Path $PolicyDirectory -ChildPath 'MergedAuditPolicy.xml'
Merge-CIPolicy -OutputFilePath $MergedAuditPolicyXml -PolicyPaths $DriverPolicyXml, $AuditPolicyXml

# Now let's deploy the new policy
$MergedAuditPolicyBin = Join-Path -Path $PolicyDirectory -ChildPath 'MergedAuditPolicy.bin'

ConvertFrom-CIPolicy -XmlFilePath $MergedAuditPolicyXml -BinaryFilePath $MergedAuditPolicyBin

# We're going to copy the policy file in binary format to here. By simply copying
# the policy file to this destination, we're deploying our policy and enabling it
# upon reboot.
$CIPolicyDeployPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\CodeIntegrity\SIPolicy.p7b'

Copy-Item -Path $MergedAuditPolicyBin -Destination $CIPolicyDeployPath -Force

# Optional: Clear Device Guard related logs
# wevtutil clear-log Microsoft-Windows-CodeIntegrity/Operational
# wevtutil clear-log "Microsoft-Windows-AppLocker/MSI and Script"

# Reboot the computer and the merged policy will be in place.

Configuration Phase #4 - Deployment of the CI policy in enforcement mode

Alright, we’ve rebooted and the CodeIntegrity log no longer presents the entries for drivers that would not have been loaded. Now we’re going to simply remove audit mode from the policy, redeploy, reboot, and cross our fingers that we have a working system upon reboot.

# This is the point where I feel comfortable enforcing my policy. The CodeIntegrity log
# is now only populated with a few anomalies - e.g. primarily entries related to NGEN
# native image generation. I'm okay with blocking these but hopefully, the Device Guard
# team can address how to handle NGEN generated images properly since this is not documented.

$PolicyDirectory = 'C:\DGPolicyFiles'
$MergedAuditPolicyXml = Join-Path -Path $PolicyDirectory -ChildPath 'MergedAuditPolicy.xml'

# Now all we need to do is remove audit mode from the policy, redeploy, reboot, and cross our
# fingers that the system is useable. Note that the "Advanced Boot Options Menu" option is still
# enabled so we have a way to delete the deployed policy from a recovery console if things break.

Set-RuleOption -FilePath $MergedAuditPolicyXml -Delete -Option 3

$MergedAuditPolicyBin = Join-Path -Path $PolicyDirectory -ChildPath 'MergedAuditPolicy.bin'

ConvertFrom-CIPolicy -XmlFilePath $MergedAuditPolicyXml -BinaryFilePath $MergedAuditPolicyBin

$CIPolicyDeployPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\CodeIntegrity\SIPolicy.p7b'

Copy-Item -Path $MergedAuditPolicyBin -Destination $CIPolicyDeployPath -Force

# Optional: Clear Device Guard related logs
# wevtutil clear-log Microsoft-Windows-CodeIntegrity/Operational
# wevtutil clear-log "Microsoft-Windows-AppLocker/MSI and Script"

# Reboot the computer and the enforced policy will be in place. This is the moment of truth!

Configuration Phase #5 - Updating policy to no longer enforce EV signers

So it turns out that I was a little overambitious in forcing EV signer enforcement on my Surface tablet as pretty much all of my Surface hardware drivers didn't load. This is kind of a shame considering I would expect MS hardware drivers to be held to the highest standards imposed by MS. So I'm going to remove EV signer enforcement and while I'm at it, I'm going to enforce blocking of flight-signed drivers. These are drivers signed by an MS test certificate used in Windows Insider Preview builds. So obviously, you won't want to be running WIP builds of Windows if you're enforcing this.

FYI, I was fortunate enough for the system to boot to discover that EV signature enforcement was the issue.

$PolicyDirectory = 'C:\DGPolicyFiles'
$MergedAuditPolicyXml = Join-Path -Path $PolicyDirectory -ChildPath 'MergedAuditPolicy.xml'

# No longer enforce EV signers
Set-RuleOption -FilePath $MergedAuditPolicyXml -Delete -Option 8

# Enforce blocking of flight signed code.
Set-RuleOption -FilePath $MergedAuditPolicyXml -Option 4

$MergedAuditPolicyBin = Join-Path -Path $PolicyDirectory -ChildPath 'MergedAuditPolicy.bin'

ConvertFrom-CIPolicy -XmlFilePath $MergedAuditPolicyXml -BinaryFilePath $MergedAuditPolicyBin

$CIPolicyDeployPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\CodeIntegrity\SIPolicy.p7b'

Copy-Item -Path $MergedAuditPolicyBin -Destination $CIPolicyDeployPath -Force

# Optional: Clear Device Guard related logs
# wevtutil clear-log Microsoft-Windows-CodeIntegrity/Operational
# wevtutil clear-log "Microsoft-Windows-AppLocker/MSI and Script"

# Reboot the computer and the modified, enforced policy will be in place.

# In retrospect, it would have been smart to have enabled "Boot Audit on Failure"
# with Set-RuleOption as it would have placed device guard into audit mode in order to allow
# boot drivers to boot that would have otherwise been blocked by policy.

Configuration Phase #6 - Monitoring and continued hardening

At this point we have a decent starting point and I'll leave it up to you as to how you'd like to proceed in terms of CI policy configuration and deployment.

Me personally, I performed the following:

  1. Used Add-SignerRule to add an Update and User signer rule with my personal code signing certificate. This grants me permission to sign my policy and execute user-mode binaries and scripts signed by me. I need to sign some of my PowerShell code that I use often since it is incompatible in constrained language mode. Signed scripts authorized by CI policy execute in full language mode. Obviously, I personally need to sign my own code sparingly. For example, it would be dumb for me to sign Invoke-Shellcode since that would explicitly circumvent user-mode code integrity.
  2. Remove "Unsigned System Integrity Policy" from the configuration. This forces me to sign the policy. It also prevents modification and removal of a deployed policy and it can only be updated by signing an updated policy.
  3. I removed the "Boot Menu Protection" option from the CI policy. This is a potential vulnerability to an attacker with physical access.
  4. I also enabled virtualization-based security via group policy to achieve the hardware supported Device Guard enforcement/improvements.

What follows is the code I used to allow my code signing cert to sign the policy and sign user-mode binaries. Obviously, this is specific to my personal code-signing certificate.

# I don't plan on using my code signing cert to sign drivers so I won't allow that right now.

# Note: I'm performing these steps on an isolated system that contains my imported code signing
# certificate. I don't have my code signing cert on the system that I'm protecting with
# Device Guard hopefully for obvious reasons.

$PolicyDirectory = 'C:\DGPolicyFiles'
$CodeSigningSertPath = Join-Path $PolicyDirectory 'codesigning.cer'
$MergedAuditPolicyXml = Join-Path -Path $PolicyDirectory -ChildPath 'MergedAuditPolicy.xml'

Add-SignerRule -FilePath $MergedAuditPolicyXml -CertificatePath $CodeSigningSertPath -User -Update

$MergedAuditPolicyBin = Join-Path -Path $PolicyDirectory -ChildPath 'MergedAuditPolicy.bin'

ConvertFrom-CIPolicy -XmlFilePath $MergedAuditPolicyXml -BinaryFilePath $MergedAuditPolicyBin

# I'm signing my code integrity policy now.
signtool.exe sign -v /n "Matthew Graeber" -p7 . -p7co 1.3.6.1.4.1.311.79.1 -fd sha256 $MergedAuditPolicyBin

# Now, once I deploy this policy, I will only be able to make updates to the policy by
# signing an updated policy with the same signing certificate.

Virtualization-based Security Enforcement

My Surface Pro 4 has the hardware to support these features so I would be silly not to employ them. This is easy enough to do in Group Policy. After configuring these settings, reboot and validate that all Device Guard features are actually set. The easiest way to do this in my opinion is to use the System Information application.

Enabling Virtualization Based Security Features

Confirmation of Device Guard enforcement

Conclusion

If you’ve made it this far, congratulations! Considering there’s no push-button solution to configuring Device Guard according to your requirements, it can take a lot of experimentation and practice. That said, I don’t think there should ever be a push-button solution to the development of a strong whitelisting policy catered to your specific environment. It takes a lot of work just like how competently defending your enterprise should take a lot of work versus just throwing money at "turnkey solutions".

Examples of blocked applications and scripts

Now at this point, you may be asking the following questions (I know I did):

  • How much of a pain will it be to update the policy to permit new applications? Well, this would in essence require a reference machine in which you can place it into audit mode during a test period of the new software installation. You would then need to generate a new policy based on the audit logs and hope that all loaded binaries are signed. If not, you’d have to fall back to file hash rules which would force you to update the policy again as soon as a new update comes out. This process is complicated by installer applications whereas configuring portable binaries should be much easier since the footprint is much smaller.
  • What if there’s a signed Microsoft binary that permits unsigned code execution? Oh these certainly exist and I will cover these in future blog posts along with realistic code integrity policy deny rule mitigations.
  • What if a certificate I whitelist is revoked? I honestly don’t think Device Guard currently covers this scenario.
  • What are the ways in which an admin (local or remote) might be able to modify or disable Device Guard? I will attempt to enumerate some of these possibilities in future blog posts.
  • What is the fate of AppLocker? That will need to be left to Microsoft to answer that question.
  • I personally have many more questions but this blog post may not be the appropriate forum to air all possible grievances. I have been in direct contact with the Device Guard team at Microsoft and they have been very receptive to my feedback.

Finally, despite the existence of bypasses, in many cases code integrity policies can be supplemented to mitigate many known bypasses. In the end though, Device Guard will significantly raise the cost to an attacker and block most forms of malware that don't specifically take Device Guard bypasses into consideration. I commend Microsoft for putting some serious thought and engineering into Device Guard and I sincerely hope that they will continue to improve it, document it more thoroughly, and evangelize it. Now, I may be being overly optimistic, but I would hope that they would consider any vulnerabilities to the Device Guard implementation and possibly even unsigned code execution from signed Microsoft binaries to be a security boundary. But hey, a kid can dream, right?

I hope you enjoyed this post! Look forward to more Device Guard posts (primarily with an offensive twist) coming up!