AppLocker CLM Bypass via COM

Constrained Language Mode is a method of restricting PowerShell’s access to functionality such as Add-Type, or many of the reflective methods which can be used to leverage the PowerShell runtime as a launchbed for post-exploitation tooling.

Despite what Microsoft may claim, this feature is very much being used as a security control, providing defenders with the ability to stop tools such as “Invoke-Mimikatz” from executing due to the heavy reliance on reflection techniques .

As I was getting ready to complete an engagement in an environment enforcing Constrained Language Mode, I wanted to take a quick look at any potential ways around this protection should it be needed. I spun up a Windows 10 instance and configured CLM via the default rule set. In this post I will show the results of this research and a possible way of bypassing this protection as a non-admin user.

Let’s start…

The first thing that we need to do within our test environment is to enable AppLocker. For this post we will use the default rules deployed by Windows when enforcing script restrictions. After firing up the Application Identity service, we can use the following command to ensure that CLM has been enabled:


Here we should see that a value of ConstrainedLanguage is returned, indicating that we are now within a restricted environment. This can be further confirmed by attempting to perform a trivial task utilising a restricted command within PowerShell:

[code]Add-Type “namespace test { }”[/code]

OK, now we have CLM enabled, what are our options to bypass it?

New-Object within AppLocker CLM… and this works??

Surprisingly, when I started looking at the attack surface of CLM, I found that New-Object works (albeit with some restrictions) when CLM has been enabled via AppLocker. This seemed at odds with what is trying to be achieved, but sure enough, we find that the following command will execute just fine:

[code]New-Object -ComObject WScript.Shell[/code]

This of course gives us a perfect way of manipulating the PowerShell process from within PowerShell, as COM objects are exposed via DLL’s which can be loaded into the calling process. So how can we create a COM object ready for loading? Well if we take a look at ProcMon during an attempt to call New-Object -ComObject xpntest, we see that there are a number of requests to the HKEY_CURRENT_USER hive:

After some playing around, we see that we can create the required registry keys within HKCU with the following script:

And now if we attempt to load our COM object, we see that our custom DLL is loaded into the PowerShell process space:

OK, so that’s pretty cool, and we now have a way of loading an arbitrary DLL into PowerShell without resorting to the noisy CreateRemoteThread or WriteProcessMemory calls, all while being in a restrictive context. But we set out to disable Constrained Language Mode, how can we achieve this with our unmanaged DLL loading? Well here we leverage the .NET CLR, or to be exact, we load the .NET CLR from our unmanaged DLL to help invoke a .NET assembly…

Unmanaged DLL to Managed DLL to Reflection

The process of loading the CLR into an unmanaged process is now taken for granted with tools like Cobalt Strike offering Execute-Assembly to facilitate the process. I have previously shared a GIST on how to achieve this same technique outside of Cobalt Strike:

I won’t cover the internals of this code here (I recommend you read through Microsoft’s post here if you are interested), but the end-result is that the DLL will load the .NET CLR, followed by a .NET assembly, and pass execution to the specified method.

With this completed, we now have access to .NET, and more importantly, .NET’s reflective capability. Next we need to figure out just where Constrained Language Mode’s on/off switch is.

Disassembling the .NET assembly which makes up PowerShell, System.Management.Automation.dll, we see that one of the places used identify the language mode is within the property System.Management.Automation.Runspaces.RunspaceBase.LanguageMode. As we will be using reflection, we need to find a reference to a Runspace via a variable which we can manipulate during runtime. The best way I found to do this is via Runspaces.Runspace.DefaultRunspace.SessionStateProxy.LanguageMode, for example:

Compiling this into a .NET assembly, we now have a way to disable CLM via reflection. All that is left to do is to create a PowerShell script to kick off the process:

And there we have it, let’s see a video of this in action:

Why Does This Work?

So why exactly is COM being allowed to float past this protection, and just how is PowerShell handling COM loading?

The answer can be found within the SystemPolicy.IsClassInApprovedList method, which is used to check if the CLSID we provide to New-Object should be permitted or not. As we dig into this method, we actually see that the heavy lifting of is done by the following code:

[code]if (SystemPolicy.WldpNativeMethods.WldpIsClassInApprovedList(ref clsid, ref wldp_HOST_INFORMATION, ref num, 0u) >= 0 && num == 1) { … }[/code]

This call is simply a wrapper around the WldpIsClassInApprovedList function exposed by wldp.dll, which is used to check a CLSID against a DeviceGuard policy (or Windows Defender Application Control as it is now known). As this method does not work for AppLocker, it means that any CLSID passed will come through as approved.

That’s Weird??!!

So while testing this technique, I ran into a weird scenario, in that this technique will not work if CLM is set via the following method:

[code]$ExecutionContext.SessionState.LanguageMode = “ConstrainedLanguage”[/code]

This bugged me for a while, as I have used the above for testing payloads in the past, so what was the difference? Returning to our disassembly, we actually find the answer in the assembly Microsoft.Powershell.Commands.Utility.dll, specifically the BeginProcessing method of the NewObjectCommand class:

Here we can see that there are essentially 2 code paths depending on how CLM has been enabled. The first code path is taken if SystemPolicy.GetSystemLockdownPolicy returns Enforce, which is the case when AppLocker or DeviceGuard has been enabled, but not the case when we simply set the ExecutionContext.SessionState.LanguageMode property. By setting this property directly, we go straight into the if (!flag)… code block, which throws an exception. The takeaway from this, is that CLM will actually react slightly differently depending on if it has been enabled via AppLocker, DeviceGuard, or via the LanguageMode property.

This is by no means the only way to bypass CLM, as even a cursory look at PowerShell reveals a number of potential routes to achieve a similar result. If you want to check out some more techniques, we have it on good authority that Oddvar Moe has some pretty special techniques to demo during his Derbycon talk!

written by

MDSec Research

Ready to engage
with MDSec?

Copyright 2021 MDSec