PowerShell Logging: Obfuscation and Some New(ish) Bypasses Part 1

Follow me on twitter and join the Telegram channel Telegram

While giving our talk at the DEF CON Red Team Village a couple of weeks ago, I previewed a PowerShell ScriptBlock logging obfuscation technique. I have been working on it (and a few other techniques) for a while and decided to write a short blog about it. But as I started writing a blog about it, it turned out to be a lot longer than expected, so now it will be a two-part blog instead!

The first part will be some discussions on bypassing module logging and new a ScriptBlock logging bypass! Part two will go into the obfuscation technique I previewed at the Red Team and a few other obfuscation tips and tricks.

Module Logging

First, let’s discuss some updates around module logging bypasses. I haven’t really seen any discussion around disabling module logging, but it’s pretty simple. Microsoft was kind enough to tell us all about it here.

If you read that document, you will see that module logging is entirely dictated by the LogPipelineExecutionDetails property of the module. All we need to do to get the module is to use Get-Module cmdlet. Just a quick note, the module is not the cmdlet, but the encompassing code that exports all the functions and cmdlets to PowerShell. To get the module of the cmdlet, just do:

(Get-Command <command>).module

In the image above, you can see that Invoke-Expression is contained in the Microsoft.Powershell.Utility module. You’ll also notice that when trying to retrieve the module of Get-Command there is nothing returned. This is because it is in the Microsoft.Powershell.Core PowerShell snap-in, which is a legacy means of bundling commands that were largely deprecated in PowerShell v3. The Microsoft.PowerShell.Core snap-in is the only one that is still used by modern PowerShell. To disable module logging for the core PowerShell commands, we just need to run the following four commands:

$module = Get-Module Microsoft.PowerShell.Utility
$module.LogPipelineExecutionDetails = $false
$Snapin = Get-PSSnapin Microsoft.PowerShell.Core
$Snapin.LogPipelineExecutionDetails = $false

For completeness, here was what the logging looks like, prior to module logging being disabled:

Now we can run our commands and retake a look.

In the second screenshot of the logs, you can see that once the LogPipelineExecutionDetails was set to False. There were no longer any 4103 events in the logs, which indicates Module Logging events.

Before moving on to the next part, let’s revisit Cobbr’s blog post, where he published his ScriptBlock Logging bypass. In it, he mentions that the reflection method doesn’t work for Module Logging. I don’t know if he figured this out later, but he never published an update to his post. However, the reflection does (mostly) work for Module Logging. The issue is that when PowerShell starts up, it sets the LogPipelineExecutionDetails to True and when the Module Logging setting is changed to disable it, it doesn’t reset that setting. So if the modules are reimported, then Module Logging will be disabled. Here is what that would look like:

$GroupPolicy =[ReF].assembly.GetType('System.Management.Automation.Utils').GetFielD('cachedGroupPolicySettings','NonPublic,Static').GetValue($nULL);
$val=[Collections.Generic.Dictionary[String,System.Object]]::new();
$Val.Add('EnableModuleLogging',0);
$Val.add('ModuleNames', '');
$GroupPolicy['HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellModuleLogging']=$VaL
Import-Module Microsoft.Powershell.Utility -Force

The big limitation is that as far as I can tell, it’s not possible to reimport the Microsoft.Powershell.Core snap-in. In talking to a PowerShell developer, they mentioned that snap-in is “special” so some stuff can’t be accomplished with it.

Now there is one more Module Logging bypass that we are going to talk about. It’s a little more convoluted, but the methods used in it are going to open up a bunch of obfuscation options to use later on.

Module Logging Bypass

The other day I was doing some research (I don’t remember exactly what for) when I came upon this StackOverflow post. The poster was trying to figure out why the .invoke() method of their cmdlet wouldn’t work after importing the custom assembly. The answer was interesting, specifically where they recommend creating a cmdltinfo object and that object is executable directly by:

$code = ...
Add-Type $code
$WriteHello = [System.Management.Automation.CmdletInfo]::new("Write-Hello", [WriteHello])
& $WriteHello -msg "abcd"

Well, that’s just creating a referenceable object from an implementing type which we can quickly get for the commands already in PowerShell using Get-Command again:

So our cmdletinfo definition would look like this:

$Cmd = [System.Management.Automation.CmdletInfo]::new("Write-Host", [Microsoft.PowerShell.Commands.WriteHostCommand])

The interesting thing about this is that $Cmd is now a callable command that has no module or PowerShell snap-in associated with it. This means that theoretically there should be no Module Logging associated with it. During testing, we see that’s true with only a ScriptBlock log showing the line that was executed and the Out-Default module log. That’s cool, but having to use the call operator every time we want to run the command would be a pretty big indicator to track. It would be way better if we could just call it like a normal cmdlet.

Quick tip: You have to invoke it with & or Invoke-Command.

ScriptBlock Log Bypass

Well, that was a lot about module logging, but I also promised a new ScriptBlock logging bypass. Let’s get into that now.

There have been some great articles that have built a compendium of info on the existing PowerShell bypasses and how they function. To keep this article from getting too long, I am just going to refer you to MD Sec’s article. Specifically the section on ScriptBlock Logging, which gives an overview of how Cobbr’s bypass works.

The code responsible for ScriptBlock Logging is a function called LogScriptBlockCreation and the code we are concerned with is below:

internal static void LogScriptBlockCreation(ScriptBlock scriptBlock, bool force)
{
if ((force || ScriptBlock.ShouldLogScriptBlockActivity(“EnableScriptBlockLogging”)) && (!scriptBlock.HasLogged || InternalTestHooks.ForceScriptBlockLogging))
{
if (ScriptBlock.ScriptBlockLoggingExplicitlyDisabled() || scriptBlock.ScriptBlockData.IsProductCode)
{
return;
}
…
}
}

One of the exciting things about this is implied by the name of the function and logs. ScriptBlock Logging only occurs when the ScriptBlock is created, not when the ScriptBlock is executed. Whenever a command is executed from the command line, it is first converted to a ScriptBlock and then executed. This also happens when functions are called and other things that result in the nested ScriptBlock creation logs that make up what Microsoft calls Deep ScriptBlock Logging. Let’s dig a little more into what this exactly means.

We can use the [ScriptBlock] type to build ScriptBlocks from strings without executing them, unlike using Invoke-Expression.  To start things off, we are going to build a simple ScriptBlock that contains the string “Write-Host ‘Test String’” and then execute that ScriptBlock:

Now let’s take a look at the logs, and you may notice something interesting.  The Creating ScriptBlock entry doesn’t occur until after the script is invoked.

That seems a little odd considering the ScriptBlock create function was called in the previous line. Well, the logging function referenced at the beginning of this section is actually in the CompiledScriptBlock class, and it appears that PowerShell doesn’t compile the ScriptBlock until it is invoked. So that potentially allows us to mess with some stuff before the logging function is called. Looking at the nonpublic instance properties, there is one in particular that stands out.

This HasLogged property was mentioned in the code above:

if ((force || ScriptBlock.ShouldLogScriptBlockActivity(“EnableScriptBlockLogging”)) && (!scriptBlock.HasLogged || InternalTestHooks.ForceScriptBlockLogging))

From this, it appears that if the ScriptBlock has already been logged, then it just skips over the function. We can verify that by building a new ScriptBlock and getting the value of HasLogged before and after, we execute the ScriptBlock to see if it changes.

And if you go look at the logging, you will see that if you invoke the ScriptBlock again, there isn’t another entry in the log. Now just set the value of HasLogged to True before invoking the script, which can be done with this command:

[ScriptBlock].getproperty("HasLogged",@('nonpublic','instance')).setvalue($script, $true)

Granted, this isn’t quite as useful as Cobbr’s technique, as it doesn’t disable ScriptBlock Logging for the whole session, but I am a big believer in the idea that you can never have too many options. We can also combine this with some of the obfuscation stuff I am going to talk about next week, which makes this technique even more powerful.

Checkout out our Advanced Threat Emulation course if you are interested in learning more.

The post PowerShell Logging: Obfuscation and Some New(ish) Bypasses Part 1 appeared first on BC Security.

If you like the site, please consider joining the telegram channel or supporting us on Patreon using the button below.

Want to Support the Site, Become a Patron!

MANY THANKS GO TO THE

Original Source