At Langkjaer Cyber Defence we explore the technical aspects of enterprise security controls. Any observations, vulnerabilities, and guides resulting from this will be published here on our blog.
Note: We mostly blog in Danish, but this post is written in English to reach a wider technical audience.
In this post, I will describe a Device Guard User Mode Code Integrity (UMCI) bypass that I submitted to Microsoft in February. I submitted this bypass a few days after my last one, but since I failed to properly identify the software package I installed it with, it ended up taking quite a bit longer for it to be routed to proper team. It finally got there, and has now been added as a deny rule to the bypass policy in the Device Guard deployment guide. For more context about Device Guard refer to the documentation or some of my earlier posts.
Like my last bypass, this one was found by playing around with various Microsoft signed binaries. To get some more candidates, I installed an instance of Microsoft SQL Server and some related tools on a virtual machine. The “Microsoft SQL Server Data Tools for Visual Studio 2017” package included the following executable.
C:\Program Files (x86)\Common Files\microsoft shared\TextTemplating\14.0\TextTransform.exe
Running the executable gave me the following output:
Looking for documentation for the template format led me to an MSDN page that described the option to embed both C# and VBScript code in a template to be run during processing. This looked promising in terms of bypassing Device Guard, but if the code is generated as an external assembly, it will still be blocked on load.
The following template should result in the compilation and running of the unsigned C# code if it is not blocked by Device Guard. The C# code just runs a simple command in Powershell to show that running C# code also implies the ability to run unrestricted Powershell.
<#@ template language="C#" #> <#@ assembly name="System.Management.Automation" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Management.Automation" #> <# PowerShell PowerShellInstance = PowerShell.Create(); PowerShellInstance.AddScript("$ExecutionContext.SessionState.LanguageMode | set-content -path $env:TEMP\\output"); PowerShellInstance.Invoke(); #>
The executable TextTransform.exe has a few dependencies in the form of three DLL's that can be found in the Global Assembly Cache on the system where the tool is installed:
To test if it worked as a bypass, I copied the executable, the DLL's, and the template file to a folder on the target system, a Windows 10 with a Device Guard policy created using the official guide, and executed it. As seen below, the code ran successfully:
Looking at the decompilation of the assemblies involved in the process, I found that the executable parses and compiles the code in the template using the classes of the CodeDom namespace. Unless a debug flag is set, the application compiles and runs the code from the template in memory, thereby bypassing the Device Guard checks that occurs as code is loaded from the filesystem:
The decompiled classes also show how the flag can be set by adding debug="true" to the template. As expected, doing this gave the following result when running TextTransform on the template:
Doing a simple grep after 'GenerateInMemory' on my installed Microsoft assemblies shows quite a few assemblies that reference this variable. They could of course be setting the variable to false, and/or using the CodeDom namespace for something other than compiling and running code, but I suspect that these assemblies could provide at good starting point for looking for more bypasses.
Since Device Guard enforces its control when code is loaded from the filesystem, Microsoft signed binaries generating and executing code in memory are prime candidates for bypassing Device Guard. TextTransform.exe is one such example.