Ironman Software Forums
Continue the conversion on the Ironman Software forums. Chat with over 1000 users about PowerShell, PowerShell Universal, and PowerShell Pro Tools.
A couple of days ago, BC Security released a blog post on how to bypass PowerShell module and script block logging using some internal methods to the PowerShell library. In this post we’ll look at how to make it much harder for someone to perform these relatively easy to execute bypasses.
To follow along with this blog post, you will need to install PowerShell Protect
The first variant of the BC Security bypass for module logging is done by setting a property on the module itself. You can turn off module logging like this.
$module = Get-Module Microsoft.PowerShell.Utility
$module.LogPipelineExecutionDetails = $false
The second variant of the BC Security bypass for module logging is done by creating a CmdletInfo
object and then issuing commands against that instead of directly calling the cmdlet.
$Cmd = [System.Management.Automation.CmdletInfo]::new("Write-Host", [Microsoft.PowerShell.Commands.WriteHostCommand])
& $Cmd "abcd"
To prevent the first variant, we can use a simply block commands that contain the use of the LogPipelineExecutionDetails
property. We could simply look for use of this value at all. This would prevent both direct execution of the property as well as reflection.
<Rule>
<Conditions>
<Condition>
<Property>script</Property>
<Operator>contains</Operator>
<Value>LogPipelineExecutionDetails</Value>
</Condition>
</Conditions>
<Actions>
<ActionRef>
<Name>Block</Name>
</ActionRef>
</Actions>
</Rule>
To prevent variant 2 we could again look in the script for the creation of the CmdletInfo
object.
This could be accomplished by first, looking for the use of the new()
operator.
<Rule>
<Conditions>
<Condition>
<Property>script</Property>
<Operator>contains</Operator>
<Value>CmdletInfo]::new(</Value>
</Condition>
</Conditions>
<Actions>
<ActionRef>
<Name>Block</Name>
</ActionRef>
</Actions>
</Rule>
It would also be possible to invoke this bypass by use New-Object
so we could add another rule to check for that as well.
<Rule>
<Conditions>
<Condition>
<Property>command</Property>
<Operator>equals</Operator>
<Value>New-Object</Value>
</Condition>
<Condition>
<Property>script</Property>
<Operator>contains</Operator>
<Value>CmdletInfo</Value>
</Condition>
</Conditions>
<Actions>
<ActionRef>
<Name>Block</Name>
</ActionRef>
</Actions>
</Rule>
The script block logging bypass that was described by BC Security utilizes an internal property of the CompiledScriptBlock
class to trick the engine into thinking the script block had already logged.
$Script = { 'Log me' }
[ScriptBlock].getproperty("HasLogged",@('nonpublic','instance')).setvalue($script, $true)
$Script.Invoke()
To prevent this bypass, we can look for several tell-tale signs of the technique. We can check for the GetProperty
class, the HasLogged
string and the SetValue
call.
<Rule>
<Conditions>
<Condition>
<Property>member</Property>
<Operator>equals</Operator>
<Value>GetProperty</Value>
</Condition>
<Condition>
<Property>member</Property>
<Operator>equals</Operator>
<Value>SetValue</Value>
</Condition>
<Condition>
<Property>string</Property>
<Operator>equals</Operator>
<Value>HasLogged</Value>
</Condition>
</Conditions>
<Actions>
<ActionRef>
<Name>Block</Name>
</ActionRef>
</Actions>
</Rule>
Being able to block in-memory bypasses like this is a handy tool to have in an arsenal against malicious PowerShell use. Note that not all security solutions are bulletproof and there are ways around the techniques I just described. It is possible to make these rules more robust and also to use auditing to flag when this type of suspicious behavior is happening on your network.
Continue the conversion on the Ironman Software forums. Chat with over 1000 users about PowerShell, PowerShell Universal, and PowerShell Pro Tools.
Receive once-a-month updates about Ironman Software. You'll learn about our product updates and blogs related to PowerShell.