Showing posts with label application whitelisting. Show all posts
Showing posts with label application whitelisting. Show all posts

Monday, July 10, 2017

Bypassing Device Guard with .NET Assembly Compilation Methods

Tl;dr

This post will describe a Device Guard user mode code integrity (UMCI) bypass (or any other application whitelisting solution for that matter) that takes advantage of the fact the code integrity checks are not performed on any code that compiles C# dynamically with csc.exe. This issue was reported to Microsoft on November 14, 2016. Despite all other Device Guard bypasses being serviced, a decision was made to not service this bypass. This bypass can be mitigated by blocking csc.exe but that may not be realistic in your environment considering the frequency in which legitimate code makes use of these methods - e.g. msbuild.exe and many PowerShell modules that call Add-Type.

Introduction

When Device Guard enforces user mode code integrity (UMCI), aside from blocking non-whitelisted binaries, it also only permits the execution of signed scripts (PowerShell and WSH) approved per policy. The UMCI enforcement mechanism in PowerShell is constrained language mode. One of the features of constrained language mode is that unsigned/unapproved scripts are prevented from calling Add-Type as this would permit arbitrary code execution via the compilation and loading of supplied C#. Scripts that are approved per Device Guard code integrity (CI) policy, however, are under no such restrictions, execute in full language mode, and are permitted to call Add-Type. While investigating Device Guard bypasses, I considered targeting legitimate, approved calls to Add-Type. I knew that the act of calling Add-Type caused csc.exe – the C# compiler to drop a .cs file to %TEMP%, compile it, and load it. A procmon trace of PowerShell calling Add-Type confirms this:

Process Name Operation  Path
------------ ---------  ----
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\bfuswtq5.cmdline
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\bfuswtq5.0.cs
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\CSC3FBE068FE0A4C00B4A74B718FAE2E57.TMP
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\CSC3FBE068FE0A4C00B4A74B718FAE2E57.TMP
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\RES1A69.tmp
cvtres.exe   CreateFile C:\Users\TestUser\AppData\Local\Temp\CSC3FBE068FE0A4C00B4A74B718FAE2E57.TMP
cvtres.exe   CreateFile C:\Users\TestUser\AppData\Local\Temp\RES1A69.tmp
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\RES1A69.tmp
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\RES1A69.tmp
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\bfuswtq5.dll
csc.exe      CreateFile C:\Users\TestUser\AppData\Local\Temp\CSC3FBE068FE0A4C00B4A74B718FAE2E57.TMP

Upon seeing these files created, I asked myself the following questions:
  1. Considering an approved (i.e. whitelisted per policy) PowerShell function is permitted to call Add-Type (as many Microsoft-signed module functions do), could I possibly replace the dropped .cs file with my own? Could I do so quickly enough to win that race?
  2. How is the .DLL that’s created loaded? Is it subject to code integrity (CI) checks?

Research methodology
Let’s start with the second question since exploitation would be impossible if CI would prevent the loading of a hijacked, unsigned DLL. To answer this question, I needed to determine what .NET methods were called upon Add-Type being called. This determination was relatively easy by tracing method calls in dnSpy. I quickly traced execution of the following .NET methods:
Once the Microsoft.CSharp.CSharpCodeGenerator.Compile method is called, this is where csc.exe is ultimately invoked. After the Compile method returns, FromFileBatch takes the compiled artifacts, reads them in as a byte array, and then loads them using System.Reflection.Assembly.Load(byte[], byte[], Evidence). This is the same method called by msbuild.exe when compiling inline tasks – a known Device Guard UMCI bypassed discovered by Casey Smith. Knowing this, I gained the confidence that if I could hijack the dropped .cs file, I would end up having a constrained language mode bypass, allowing arbitrary unsigned code execution. What we’re referring to here is known as a “time of check time of use” (TOCTOU) attack. If I could manage to replace the dropped .cs file with my own prior to csc.exe consuming it, then I would win that race and perform the bypass. The only constraints imposed on me, however, would be that I would need to write a hijack payload within the constraints of constrained language mode. As it turns out, I was successful.

Exploitation

I wrote a function called Add-TypeRaceCondition that will accept attacker-supplied C# and get an allowed call to Add-Type to compile it and load it within the constraints of constrained language mode. The weaponized bypass is roughly broken down as follows:
  1. Spawn a child process of PowerShell that constantly tries to drop the malicious .cs file to %TEMP%.
  2. Maximize the process priority of the child PowerShell process to increase the likelihood of winning the race.
  3. In the parent PowerShell process, import a Microsoft-signed PowerShell module that calls Add-Type – I chose the PSDiagnostics process for this.
  4. Kill the child PowerShell process.
  5. At this point, you will have likely won the race and your type will be loaded in place of the legitimate one expected by PSDiagnostics.
In reality, the payload wins the race a little more than 50% of the time. If Add-TypeRaceCondition doesn’t work on the first try, it will almost always work on the second try.

Do note that while I weaponized this bypass for PowerShell, this can be weaponized using anything that would allow you to overwrite the dropped .cs file quickly enough. I've weaponized the bypass using a batch script, VBScript, and with WMI. I'll leave it up to the reader to implement a bypass using their language of choice.

Operational Considerations

It's worth noting that while an application whitelisting bypass is just that, it also serves as a method of code execution that is likely to evade defenses. In this bypass, an attacker need only drop a C# file to disk which results in the temporary creation of a DLL on disk which is quickly deleted. Depending upon the payload used, some anti-virus solutions with real-time scanning enabled could potentially have the ability to quarantine the dropped DLL before it's consumed by System.Reflection.Assembly.Load.

Prevention

Let me first emphasize that this is a .NET issue, not a PowerShell issue. PowerShell was simply chosen as a convenient means to weaponize the bypass. As I’ve already stated, this issue doesn’t just apply to when PowerShell calls Add-Type, but when any application calls any of the CodeDomProvider.CompileAssemblyFrom methods. Researchers will continue to target signed applications that make such method calls until this issue is mitigated.

A possible user mitigation for this bypass would be to block csc.exe with a Device Guard rule. I would personally advise against this, however, since there are many legitimate Add-Type calls in PowerShell and presumably in other legitimate applications. I’ve provided a sample Device Guard CI rule that you can merge into your policy if you like though. I created the rule with the following code:

# Copy csc.exe into the following directory
# csc.exe should be the only file in this directory.
$CSCTestPath = '.\Desktop\ToBlock\'
$PEInfo = Get-SystemDriver -ScanPath $CSCTestPath -UserPEs -NoShadowCopy

$DenyRule = New-CIPolicyRule -Level FileName -DriverFiles $PEInfo -Deny
$DenyRule[0].SetAttribute('MinimumFileVersion', '65535.65535.65535.65535')

$CIArgs = @{
    FilePath = "$($CSCTestPath)block_csc.xml"
    Rules = $DenyRule
    UserPEs = $True
}

New-CIPolicy @CIArgs

Detection

Unfortunately, detection using free, off-the-shelf tools will be difficult due to the fact that the disk artifacts are created and subsequently deleted and by the nature of System.Reflection.Assembly.Load(byte[]) not generating a traditional module load event that something like Sysmon would be able to detect.

Vendors with the ability to hash files on the spot should consider assessing the prevalence of DLLs created by csc.exe. Files with low prevalence should be treated as suspicious. Also, unfortunately, since dynamically created DLLs by their nature will not be signed, there will be no code signing heuristics to key off of.

It's worth noting that I intentionally didn't mention PowerShell v5 ScriptBlock logging as a detection option since PowerShell isn't actually required to achieve this bypass.

Conclusion

I remain optimistic of Device Guard’s ability to enforce user mode code integrity. It is a difficult problem to tackle, however, and there is plenty of attack surface. In most cases, Device Guard UMCI bypasses can be mitigated by a user in the form of CI blacklist rules. Unfortunately, in my opinion, no realistic user mitigation of this particular bypass is possible. Microsoft not servicing such a bypass is the exception and not the norm. Please don’t let this discourage you from reporting any bypasses that you may find to secure@microsoft.com. It is my hope that by releasing this bypass that it will eventually be addressed and it will provide other vendors with the opportunity to mitigate.

Previously serviced bypasses for reference:

Friday, December 30, 2016

Updating Device Guard Code Integrity Policies

In previous posts about Device Guard, I spent a lot of time talking about initial code integrity (CI) configurations and bypasses. What I haven't covered until now however is an extremely important topic: how does one effectively install software and update CI policies according? In this post, I will walk you through how I got Chrome installed on my Surface Book running on an enforced Device Guard code integrity policy.

The first questions I posed to myself were:
  1. Should I place my system into audit mode, install the software, and base an updated policy on CodeIntegrity event log entries?
  2. Or should I install the software on a separate, non Device Guard protected system, analyze the file footprint, develop a policy based on the installed files, deploy, and test?
My preference is option #2 as I would prefer to not place a system back into audit mode if I can avoid it. That said, audit mode would yield the most accurate results as it would tell you exactly which binaries would have been blocked that you would want to base whitelist rules off of. In this case, there's no right or wrong answer. My decision to go with option #2 was to base my rules solely off binaries that execute post-installation, not during installation. My mantra with whitelisting is to be as restrictive as is reasonable.

So how did I go about beginning to enumerate the file footprint of Chrome?
  1. I opened Chrome, ran it as I usually would, and used PowerShell to enumerate loaded modules.
  2. I also happened to know that the Google updater runs as a scheduled task so I wanted to obtain the binaries executed via scheduled tasks as well.
I executed the following to get a rough sense of where Chrome files were installed:

(Get-Process -Name *Chrome*).Modules.FileName | Sort-Object -Unique
(Get-ScheduledTask -TaskName *Google*).Actions.Execute | Sort-Object -Unique

To my surprise and satisfaction, Google manages to house nearly all of its binaries in C:\Program Files (x86)\Google. This allows for a great starting point for building Chrome whitelist rules.

Next, I had to ask myself the following:
  1. Am I okay with whitelisting anything signed by Google?
  2. Do I only want to whitelist Chrome? i.e. All Chrome-related EXEs and all DLLs they rely upon.
  3. I will probably want want Chrome to be able to update itself without Device Guard getting in the way, right?
While I like the idea of whitelisting just Chrome, there are going to be some potential pitfalls. By whitelisting just Chrome, I would need to be aware of every EXE and DLL that Chrome requires to function. I can certainly do that but it would be a relatively work-intensive effort. With that list, I would then create whitelist rules using the FilePublisher file rule level. This would be great initially and it would potentially be the most restrictive strategy while allowing Chrome to update itself. The issue is that what happens when Google decides to include one or more additional DLLs in the software installation? Device Guard will block them and I will be forced to update my policy again. I'm all about applying a paranoid mindset to my policy but at the end of the day, I need to get work done other than constantly updating CI policies.

So the whitelist strategy I choose in this instance is to allow code signed by Google and to allow Chrome to update itself. This strategy equates to using the "Publisher" file rule level - "a combination of the PcaCertificate level (typically one certificate below the root) and the common name (CN) of the leaf certificate. This rule level allows organizations to trust a certificate from a major CA (such as Symantec), but only if the leaf certificate is from a specific company (such as Intel, for device drivers)."

I like the "Publisher" file rule level because it offers the most flexibility, longevity for a specific vendor's code signing certificate. If you look at the certificate chain for chrome.exe, you will see that the issuing PCA (i.e. the issuer above the leaf certificate) is Symantec. Obviously, we wouldn't want to whitelist all code signed by certs issued by Symantec but I'm okay allowing code signed by Google who received their certificate from Symantec.

Certificate chain for chrome.exe
So now I'm ready to create the first draft of my code integrity rules for Chrome.

I always start by creating a FilePublisher rule set for the binaries I want to whitelist because it allows me to associate what binaries are tied to their respective certificates.

$GooglePEs = Get-SystemDriver -ScanPath 'C:\Program Files (x86)\Google' -UserPEs
New-CIPolicy -FilePath Google_FilePub.xml -DriverFiles $GooglePEs -Level FilePublisher -UserPEs

What resulted was the following ruleset. Everything looked fine except for a single Microsoft rule generated which was associated with d3dcompiler_47.dll. I looked in my master rule policy and I already had this rule. Me being obsessive compulsive wanted a pristine ruleset including only Google rules. This is good practice anyway once you get in the habit of managing large whitelist rulesets. You'll want to keep separate policy XMLs for each whitelisting scenario you run into and then merge accordingly. After removing the MS binary from the list, what resulted was a much cleaner ruleset (Publisher applied this time) consisting of only two signer rules.

$OnlyGooglePEs = $GooglePEs | ? { -not $_.FriendlyName.EndsWith('d3dcompiler_47.dll') }
New-CIPolicy -FilePath Google_Publisher.xml -DriverFiles $OnlyGooglePEs -Level Publisher -UserPEs

So now, all I should need to do is merge the new rules into my master ruleset, redeploy, reboot, and if all works well, Chrome should install and execute without issue.

$MasterRuleXml = 'FinalPolicy.xml'
$ChromeRules = New-CIPolicyRule -DriverFiles $OnlyGooglePEs -Level Publisher
Merge-CIPolicy -OutputFilePath FinalPolicy_Merged.xml -PolicyPaths $MasterRuleXml -Rules $ChromeRules
ConvertFrom-CIPolicy -XmlFilePath .\FinalPolicy_Merged.xml -BinaryFilePath SIPolicy.p7b
# Finally, on the Device Guard system, replace the existing
# SIPolicy.p7b with the one that was just generated and reboot.

One thing I neglected to account for was the initial Chrome installer binary. I could have incorporated the binary into this process but I wanted to try my luck that Google used the same certificates to sign the installer binary. To my luck, they did and everything installed and executed perfectly. I would consider myself lucky in this case because I selected a software publisher (Google) who employs decent code signing practices.

Conclusion

In future blog posts, I will document my experiences deploying software that doesn't adhere to proper signing practices or doesn't even sign their code. Hopefully, the Google Chrome case study will, at a minimum, ease you into the process of updating code integrity policies for new software deployments.

The bottom line is that this isn't an easy process. Are there ways in which Microsoft could improve the code integrity policy generation/update/deployment/auditing experience? Absolutely! Even if they did though, the responsibility ultimately lies on you to make informed decisions about what software you trust and how you choose to enforce that trust!

Tuesday, November 22, 2016

On the Effectiveness of Device Guard User Mode Code Integrity

Is a security feature with known bypasses pointless?

I felt compelled to answer to this question after seeing several tweets recently claiming that Device Guard User Mode Code Integrity (UMCI) is a pointless security mechanism considering all of the recently reported bypasses. Before specifically diving into UMCI and its merits (or lack thereof), let’s use an analogy in the physical world to put things into perspective - a door.

Consider the door at the front of your home or business. This door helps serve as the primary mechanism to prevent intruders from breaking, entering, and stealing your assets. Let's say it's a solid wood door for the sake of the analogy. How might an attacker go about bypassing it?

  • They could pick the lock
  • They could compromise the latch with a shimming device
  • They could chop the door down with an ax
  • They could compromise the door and the hinges with a battering ram

Now, there are certainly better doors out there. You could purchase a blast door and have it be monitored with a 24/7 armed guard. Is that measure realistic? Maybe. It depends on the value of the assets you want to protect. Realistically, it's probably not worth your money since you suspect that a full frontal assault of enemy tanks is not a part of your threat model.

Does a determined attacker ultimately view the door as a means of preventing them from gaining access to your valuable assets? Of course not. Does the attacker even need to bypass the door? Probably not. They could also:

  • Go through the window
  • Break through a wall
  • Hide in the store during business hours and wait for everyone to leave
  • Submit their resume, get a job, develop trust, and slowly, surreptitiously steal your assets

So, will a door prevent breaches in all cases? Absolutely not. Will it prevent or deter an attacker lacking a certain amount of skill from breaking and entering? Sure. Other than preventing the elements from entering your store, does the locked door serve a purpose? Of course. It is a preventative mechanism suitable for the threat model that you choose to accept or mitigate against. The door is a baseline preventative mechanism employed in conjunction with a layered defense consisting of other preventative (reinforced, locked windows) and detective (motion sensors, video cameras, etc.) measures.




Now let's get back to the comfortable world of computers. Is a preventative security technology completely pointless if there are known bypasses? Specifically, let’s focus on Device Guard user mode code integrity (UMCI) as it’s received a fair amount of attention as of late. Considering all of the public bypasses posted, does it still serve a purpose? I won't answer that question using absolutes. Let me make a few proposals and let you, the reader decide. Consider the following:

1) A bypass that applies to Device Guard UMCI is extremely likely to apply to all application whitelisting solutions. I would argue that Device Guard UMCI goes above and beyond other offerings. For example, UMCI places PowerShell (one of the largest user-mode attack surfaces) into constrained language mode, preventing PowerShell from being used to execute arbitrary, unsigned code. Other whitelisting solutions don’t even consider the attack surface posed by PowerShell. Device Guard UMCI also applies code integrity rules to DLLs. There is no way around this. Other solutions allow for DLL whitelisting but not by default.

2) Device Guard UMCI, as with any whitelisting solution, is extremely effective against post-exploitation activities that are not aware of UMCI bypasses. The sheer amount of attacks that app-whitelisting prevents without any fancy machine learning is astonishing. I can say first hand that every piece of “APT” malware I reversed in a previous gig would almost always drop an unsigned binary to disk. Even in the cases where PowerShell was used, .NET methods were used heavily - something that constrained language mode would have outright prevented.

3) The majority of the "misplaced trust" binaries (e.g. MSBuild.exe, cdb.exe, dnx.exe, rcsi.exe, etc.) can be blocked with Device Guard code integrity policy updates. Will there be more bypass binaries? Of course. Again, these binaries will also likely circumvent all app-whitelisting solutions as well. Does it require an active effort to stay on top of all the bypasses as a defender? Yes. Deal with it.

Now, I along with awesome people like Casey Smith (@subtee) and Matt Nelson (@enigma0x3) have reported our share of UMCI bypasses to Microsoft for which there is no code integrity policy mitigation. We have been in the trenches and have seen first hand just how bad some of the bypasses are. We are desperately holding out hope that Microsoft will come through, issue CVEs, and apply fixes for all of the issues we’ve reported. If they do, that will set a precedent and serve as proof that they are taking UMCI seriously. If not, I will start to empathize a bit more with those who claim that Device Guard is pointless. After all, we’re starting to see more attackers “live off the land” and leverage built-in tools to host their malware. Vendors need to be taking that seriously.

Ultimately, Device Guard UMCI is just another security feature that a defender should consider from a cost/benefit analysis based on threats faced and the assets they need to defend. It will always be vulnerable to bypasses, but raises the baseline bar of security. Going back to the analogy above, a door can always be bypassed but you should be able to detect an attacker breaking in and laying their hands on your valuable assets. So obviously, you would want to use additional security solutions along with Device Guard - e.g. Windows Event Forwarding, an anti-malware solution, and to perform periodic compromise/hunt assessments.

What I’m about to say might be scandalous but I sincerely think that application whitelisting should the new norm. You probably won’t encounter any organizations that don’t employ an anti-malware solution despite the innumerable bypasses. These days, anti-malware solutions are assumed to be a security baseline as I think whitelisting should be despite the innumerable bypasses that will surface over time. Personally, I would ask any defender to seriously consider it and I would encourage all defenders to hold whitelisting solution vendors' feet to the fire and hold them accountable when there are bypasses for which there is no obvious mitigation.


I look forward to your comments here or in a lively debate on Twitter!

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!