The last blog post I wrote got way more recognition than I expected and because of that, I was inspired to continue writing and sharing my experiences/research. This blog will be about the short journey I took to hone bypasses relating to Constrained Language Mode in PowerShell and AppLocker Policies. My goal was to create payloads that ran under these conditions while also bypassing every AV engine on antiscan.me.
TL;DR: Creating bypasses for CLM using administrative privileges or using custom runspaces while bypassing EDR using obfuscation, encryption, and multi-staged payloads. Creating AppLocker bypasses using default AppLocker policies and finally using MSBuild with an arbitrary csproj file.
What is Constrained Language Mode? According to this Microsoft blog post, "PowerShell Constrained Language is a language mode of PowerShell designed to support day-to-day administrative tasks, yet restrict access to sensitive language elements that can be used to invoke arbitrary Windows APIs."
The goal of Constrained Language Mode in PowerShell is to constrain the user’s access to certain features that may allow a malicious user to execute arbitrary code. For a full list of the restrictions please refer to this blog post . However, this is not a perfect solution, PowerShell still leaves full shell access with all native commands intact. Instead, Constrained Language Mode is used in tandem with application control solutions such as AppLocker. Hence the purpose of this blog post.
Constrained Language Mode
We’ll start with Constrained Language Mode bypasses and build off that. To determine if we’re in a CLM PowerShell prompt simply type the following:
We can also determine if we’re in constrained language mode by attempting to use common .NET modules such as the .NET web client.
However, the user should be cautious when attempting the previous method. Whenever an error is generated in PowerShell a log is created in Event Viewer. It’s common for mature organizations to have monitoring software that checks for these types of errors. The reasoning behind this is that a normal office user has no business using PowerShell in the first place, so a PowerShell error from their machine can be deemed suspicious at a minimum.
As you can see from the previous screenshot the same error that was printed to our PowerShell prompt was logged and recorded in Event Viewer.
PSLockDownPolicy: CLM Bypass 1
With that being said, the first bypass is relatively simple. If you’ve somehow found a way to escalate privileges to administrator, you can simply set an environment variable to turn off Constrained Language Mode. To change these environment variables, type "Advanced System Settings" in the taskbar run box. Next select "Environment Variables," and then under System variables select "New." In the Variable Name box input: "__PsLockDownPolicy" (two underscores). In the Variable Value box input: 8. Then hit OK.
This is what it should look like when you’re finished.
Once you’ve completed this task hit OK once more until you’re back to the original System Properties Pane. Next, close your current PowerShell process and open a new one. The new process will be in full language mode.
Although simple, this bypass requires a few prerequisites, which is not ideal. The first is that you must already be an administrative user on the machine before you can perform this action. The second is that this environment variable’s true purpose is for debugging. It’s not supposed to be an actual security precaution and is not supported by Microsoft. There are many available bypasses for domains that implement CLM using this environment variable such as the bypass showcased here. Instead, CLM should be implemented using AppLocker or DeviceGuard.
Runspaces: CLM Bypass 2
Since version 5, PowerShell recognizes whether it starts in Constrained Language mode via script rules. PowerShell will create a module and a script, with a name following the pattern __PSSCRIPTPOLICYTEST_LQU1DAME.3DD.PS1, under $env:temp and tries to execute them. If AppLocker or another tool blocks this attempt, PowerShell starts in Constrained Language mode.
In the case where CLM is implemented correctly, we need to come up with a new bypass. That is where .NET runspaces comes into play. Each PowerShell session is considered a runspace and since the PowerShell CLI is an interpreter for .NET assembly, we can create a runspace using .NET in C#. This means it should be possible to create a new runspace using a C# assembly and execute arbitrary PowerShell code without the infringements of CLM.
First, create a new .NET framework C# project in Visual Studio. Next, we will start with our imports. You’ll need to import "System.Management.Automation," and "System.Management.Runspaces." However, the Automation DLL is not a reference DLL in Visual Studio by default. Therefore, we need to add it ourselves. Right-click the name of the project under "Solution Explorer," hit "add," then "reference." Next under the "Browse" tab hit the "Browse" button on the right-hand side of the pane. The DLL we need is under the following path:
Our imports should look like the following:
Next, we can create our simple C# code under our main function that will create the runspace and execute the PowerShell commands. This code is a derivative of the code found in this blog post.
The code above you is fairly straightforward. First, we instantiate a runspace object named "rs" using the "CreateRunspace" method from the "RunspaceFactory" namespace. Next, we use the runspace object's "Open" method to open our runspace. Then we create a PowerShell object and name it "ps." We set our PowerShell object’s runspace property to the runspace we just created and opened. Next, we instantiate a string object and set it to our arbitrary PowerShell code. In this instance I’ve sent mine to the following PowerShell payload to start with:
This payload is a PowerShell script that dynamically loads Windows APIs into memory, then downloads shellcode from a targeted web server, injects the shellcode into the currently running process, and finally executes it. All operations are done in memory and leave no trace to disk. If you’d like a more detailed explanation, please read my previous blog here. The shellcode is a C2 implant generated by the Sliver C2 server, which can be found here.
Next, we feed our PowerShell object the payload using the "AddScript" method. Finally, all we need to do is use the "Invoke" method, close the runspace, and we’re finished.
However, the PowerShell payload needs some massaging to run and execute properly. Our first prerequisite is to make it in such a format that it can be executed in a one-liner. The easiest way to do this is to base64 encode the PowerShell shellcode running script. That way we can base64 decode the script at run time and execute it. The process for this looks like the following:
Then to execute the script we just encoded at runtime we’ll use the following command in our C# code:
Now we need to build our executable, run it with CLM enabled, and see if a sliver session calls back.
Great! We built our executable, confirmed it works to bypass CLM in PowerShell, and we’ve received a call back. The next question is, how will this fare against antivirus solutions? Intriguingly enough, 0 of 26 antivirus solutions on antiscan.me find this executable malicious (use antiscan.me for all payload development, they do not submit payloads samples for signature creation).
Obfuscating | Encrypting Payloads
Although this payload may not be detected by many common antivirus engines, it is quite lackluster when it comes to OpSec. If a human analyst were to get their hands on this payload, they would be able to easily determine this is malicious by seeing the cmd payload base64 decoding it and seeing the shellcode runner script in cleartext. We’ll need to develop some more intricate OffSec tradecraft to make it more difficult for any analyst who ends up with it. Thankfully many tools exist to help us with this task. First, we’ll use Out-EncryptedScript from PowerShellMafia’s PowerSploit repo.
You can find more details regarding this tool in the PowerSploit documents. This tool will take our PowerShell shellcode runner script, encrypt it, and save it to a file, using a password and salt of our choosing. The process for performing this task looks like the following:
First, we start by bypassing AMSI because many PowerSploit scripts have had signatures developed for them by antivirus engines and Defender will prevent them from running. For easy AMSI bypasses you can use this utility. Then you can test if AMSI is enabled in your PowerShell process by using the following string test:
If "amsiscanstring" doesn’t return an AMSI error, then AMSI is disabled in the current PowerShell process.
Next, we import our Out-EncryptedScript module using the ..\ notation. Next, we create a "SecureString" object in our PowerShell context using our password of "DepthSecurity!". Finally, we use our imported function with the path to our shellcode runner script, our secure string object, the file path to output our script to (by default will output to .\evil.ps1), and a random salt of our choosing. In my case I used "BreadMan" as the salt. The finished product will look like this when done:
The encrypted script contains a decryption function named "de" that accepts two parameters to do its job. The first is the key and the second is the salt we chose. It’s important to remember what key / salt we used when encrypting the script because it will not be outputted with our final product. To maximize evasiveness, we’ll move our encrypted script to our attacking web server to be downloaded and execute