Preventing some new(ish) logging bypasses with PowerShell Protect

Protect Security AMSI

August 27, 2020

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

Module Logging Bypass

Variant 1

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

Variant 2

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"

Preventing Variant 1

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>

Preventing Variant 2

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>

Script Block Logging Bypass

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()

Preventing Script Block Logging Bypass

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>

Conclusion

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.