MaliciousContentRules.psm1

[System.Collections.Generic.List[String]] $suspiciousTokenElements = @(

    ## Code compilation
    "DllImport",

    ## Doing dynamic assembly building / method indirection
    "DefineDynamicAssembly", "DefineDynamicModule", "DefineType", "DefineConstructor", "CreateType",
    "DefineLiteral", "DefineEnum", "DefineField", "ILGenerator", "Emit", "UnverifiableCodeAttribute",
    "DefinePInvokeMethod", "GetTypes", "GetAssemblies", "Methods", "Properties",

    ## Suspicious methods / properties on "Type"
    "GetConstructor", "GetConstructors", "GetDefaultMembers", "GetEvent", "GetEvents", "GetField",
    "GetFields", "GetInterface", "GetInterfaceMap", "GetInterfaces", "GetMember", "GetMembers",
    "GetMethod", "GetMethods", "GetNestedType", "GetNestedTypes", "GetProperties", "GetProperty",
    "InvokeMember", "MakeArrayType", "MakeByRefType", "MakeGenericType", "MakePointerType",
    "DeclaringMethod", "DeclaringType", "ReflectedType", "TypeHandle", "TypeInitializer",
    "UnderlyingSystemType",

    ## Doing things with System.Runtime.InteropServices
    "InteropServices", "Marshal", "AllocHGlobal", "PtrToStructure", "StructureToPtr",
    "FreeHGlobal", "IntPtr",

    ## General Obfuscation
    "MemoryStream", "DeflateStream", "FromBase64String", "EncodedCommand", "Bypass", "ToBase64String",
    "ExpandString", "NewScriptBlock", "InvokeScript", "GetPowerShell",

    ## Suspicious Win32 API calls
    "OpenProcess", "VirtualAlloc", "VirtualFree", "WriteProcessMemory", "CreateUserThread", "CloseHandle",
    "GetDelegateForFunctionPointer", "kernel32", "CreateThread", "memcpy", "LoadLibrary", "GetModuleHandle",
    "GetProcAddress", "VirtualProtect", "FreeLibrary", "ReadProcessMemory", "CreateRemoteThread", 
    "AdjustTokenPrivileges", "WriteByte", "WriteInt32", "OpenThreadToken", "PtrToString",
    "FreeHGlobal", "ZeroFreeGlobalAllocUnicode", "OpenProcessToken", "GetTokenInformation", "SetThreadToken",
    "ImpersonateLoggedOnUser", "RevertToSelf", "GetLogonSessionData", "CreateProcessWithToken",
    "DuplicateTokenEx", "OpenWindowStation", "OpenDesktop", "MiniDumpWriteDump", "AddSecurityPackage",
    "EnumerateSecurityPackages", "GetProcessHandle", "DangerousGetHandle",

    ## Crypto - ransomware, etc.
    "CryptoServiceProvider", "Cryptography", "RijndaelManaged", "SHA1Managed", "CryptoStream",
    "CreateEncryptor", "CreateDecryptor", "TransformFinalBlock", "DeviceIoControl", "SetInformationProcess",
    "PasswordDeriveBytes", 

    ## Keylogging
    "GetAsyncKeyState", "GetKeyboardState", "GetForegroundWindow",

    ## Using internal types
    "BindingFlags", "NonPublic",

    ## Changing logging settings
    "ScriptBlockLogging", "LogPipelineExecutionDetails", "ProtectedEventLogging"
)
[System.Collections.Generic.HashSet[String]] $suspiciousTokens = 
    New-Object 'System.Collections.Generic.HashSet[String]' $suspiciousTokenElements, ([StringComparer]::OrdinalIgnoreCase)


######################################################################
##
## Rules
##
######################################################################

<#
.DESCRIPTION
    Finds instances of dynamic method invocation, which can be used to hide
    or obscure the name of a .NET method being invoked.
#>

function Measure-DynamicMethodInvocation
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    ## Finds MemberExpressionAst nodes that don't invoke a constant expression
    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.InvokeMemberExpressionAst]
        if($targetAst)
        {
            if(-not ($targetAst.Member -is [System.Management.Automation.Language.ConstantExpressionAst]))
            {
                return $true
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible method obfuscation found via dynamic method invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicMethodInvocation"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of access to use of the [Type] class, which can be used to hide
    or obscure access to unsafe types.
#>

function Measure-DynamicTypeUsage
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    ## Finds TypeExpressionAst that accesses the [Type] class
    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.TypeExpressionAst]
        if($targetAst)
        {
            if([Type] -eq $targetAst.TypeName.GetReflectionType())
            {
                return $true
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible type obfuscation found via type cast: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicTypeCast"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of conversions to the [Type] class, which can be used to hide
    or obscure access to unsafe types.
#>

function Measure-DynamicTypeConversion
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    ## Finds ConvertExpressionAst that converts to the [Type] class
    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.ConvertExpressionAst]
        if($targetAst)
        {
            if([Type] -eq $targetAst.Type.TypeName.GetReflectionType())
            {
                return $true
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible type obfuscation found via type conversion: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicTypeConversion"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of dynamic member access, which can be used to hide
    or obscure the name of a .NET member (i.e.: property) being accessed.
#>

function Measure-DynamicMemberAccess
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    ## Finds MemberExpressionAst that uses a non-constant member
    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.MemberExpressionAst]
        $methodAst = $Ast -as [System.Management.Automation.Language.InvokeMemberExpressionAst]
        if($targetAst -and (-not $methodAst))
        {
            if($targetAst.Member -is [System.Management.Automation.Language.ConstantExpressionAst])
            {
                ## See if this is static member access on a variable
                $typeAst = $targetAst.Expression -as [System.Management.Automation.Language.TypeExpressionAst]
                if($targetAst.Static -and (-not $typeAst))
                {
                    return $true
                }

                ## Otherwise, this is just constant access and will be validated by
                ## content matching
                return $false
            }
            else
            {
                ## This is not constant access, therefore suspicious
                return $true
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible member obfuscation found via dynamic member access: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicMemberAccess"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of suspicious strings or tokens.member access, which can be used to hide
    or obscure the name of a .NET member (i.e.: property) being accessed.
#>

function Measure-SuspiciousContent
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    $asts = $ScriptBlockAst.FindAll( { $true }, $true ) | Sort-Object { $_.Extent.Text.Length }

    foreach($actualAst in $asts)
    {
        foreach($SuspiciousToken in $suspiciousTokenElements)
        {
            $foundIndex = $actualAst.Extent.Text.IndexOf($suspiciousToken, [System.StringComparison]::OrdinalIgnoreCase)
            if($foundIndex -ge 0)
            {
                [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
                    "Message"  = "Possible malicious script content: " + $SuspiciousToken
                    "Extent"   = $actualAst.Parent.Extent
                    "RuleName" = "MaliciousContent.SuspiciousContentText"
                    "Severity" = "Warning" }
            }
        }
    }
}

<#
.DESCRIPTION
    Finds instances of dynamic command invocation, which can be used to hide
    or obscure the name of a command name being invoked.
#>

function Measure-DynamicCommandInvocation
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    ## Finds CommandAst nodes that don't invoke a constant expression
    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.CommandAst]
        if($targetAst)
        {
            if(-not ($targetAst.CommandElements[0] -is [System.Management.Automation.Language.ConstantExpressionAst]))
            {
                return $true
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible command obfuscation found via dynamic command invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicCommandInvocation"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of dynamic alias creation, which can be used to hide
    or obscure the name of a command name being invoked.
#>

function Measure-DynamicAliasDefinitionNewAlias
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.CommandAst]
        if($targetAst)
        {       
            if($targetAst.CommandElements[0].Extent.Text -in ("New-Alias", "Set-Alias", "nal", "sal"))
            {                      
                for($commandElement = 1; $commandElement -lt $targetAst.CommandElements.Count; $commandElement++)
                {
                    if(-not ($targetAst.CommandElements[$commandElement] -is [System.Management.Automation.Language.ConstantExpressionAst]))
                    {
                        return $true;
                    }
                }
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible command obfuscation found via dynamic command invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicCommandInvocationNewAlias"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of alias importing, which can be used to hide
    or obscure the name of a command name being invoked.
#>

function Measure-DynamicAliasDefinitionImportAlias
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.CommandAst]
        if($targetAst)
        {       
            if($targetAst.CommandElements[0].Extent.Text -in ("Import-Alias", "ipal"))
            {                      
                return $true;
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible command obfuscation found via dynamic command invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicCommandInvocationImportAlias"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of Invoke-Expression, which can be used to hide
    or obscure the script being invoked.
#>

function Measure-InvokeExpression
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.CommandAst]
        if($targetAst)
        {       
            if($targetAst.CommandElements[0].Extent.Text -in ("Invoke-Expression", "iex", "Add-Type"))
            {                      
                return $true;
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible command obfuscation found via dynamic command invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DangerousCommand"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of dynamic alias definition through the provider Set-Content interface.
#>

function Measure-DynamicAliasDefinitionNewAliasProviderSyntax
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.AssignmentStatementAst]
        if($targetAst)
        {       
            if(($targetAst.Left.VariablePath.DriveName -eq "Alias") -and
               (-not ($targetAst.Right -is [System.Management.Automation.Language.ConstantExpressionAst])))
            {                      
                return $true;
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible command obfuscation found via dynamic command invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicCommandInvocationNewAliasProviderSyntax"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of dynamic alias creation via the provider cmdlets, which can be used to hide
    or obscure the name of a command name being invoked.
#>

function Measure-DynamicAliasDefinitionProviderCmdlet
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.CommandAst]
        if($targetAst)
        {
            $elementsTargetingAliasDrive = $targetAst.CommandElements | Where-Object { $_.Extent.Text -like "*alias:*" }
            
            if($elementsTargetingAliasDrive)
            {
                $elementsWithNonConstantExpression = $targetAst.CommandElements | Where-Object { -not ($_ -is [System.Management.Automation.Language.ConstantExpressionAst]) }
                if($elementsWithNonConstantExpression)
                {
                    return $true
                }
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible command obfuscation found via dynamic command invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicCommandInvocationNewAliasProviderCmdlet"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of dynamic alias creation via creating a new PSDrive for aliases,
    which can be used to hide or obscure the name of a command name being invoked.
#>

function Measure-DynamicAliasDefinitionNewPSDriveForAliases
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.CommandAst]
        if($targetAst)
        {       
            if($targetAst.CommandElements[0].Extent.Text -in ("New-PSDrive", "mount", "ndr"))
            {                      
                for($commandElement = 1; $commandElement -lt $targetAst.CommandElements.Count; $commandElement++)
                {
                    if($targetAst.CommandElements[$commandElement].Extent.Text -match "Alias")
                    {
                        return $true;
                    }
                }
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible command obfuscation found via dynamic command invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicCommandInvocationNewAliasNewDrive"
            "Severity" = "Warning" }
    }
}

<#
.DESCRIPTION
    Finds instances of dynamic alias creation via creating a new PSDrive for aliases,
    which can be used to hide or obscure the name of a command name being invoked.
#>

function Measure-DynamicAliasDefinitionNewPSDriveForAliasesNonConstant
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

    [ScriptBlock] $predicate = {
        param ([System.Management.Automation.Language.Ast] $Ast)

        $targetAst = $Ast -as [System.Management.Automation.Language.CommandAst]
        if($targetAst)
        {       
            if($targetAst.CommandElements[0].Extent.Text -in ("New-PSDrive", "mount", "ndr"))
            {
                $staticBound = [System.Management.Automation.Language.StaticParameterBinder]::BindCommand($targetAst)
                $providerParameter = $staticBound.BoundParameters["PSProvider"]
                
                if(-not $providerParameter.ConstantValue)
                {
                    return $true;
                }
            }
        }
    }

    $foundNode = $ScriptBlockAst.Find($predicate, $true)
    if($foundNode)
    {
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{
            "Message"  = "Possible command obfuscation found via dynamic command invocation: " + $foundNode.Extent
            "Extent"   = $ScriptBlockAst.Extent
            "RuleName" = "MaliciousContent.DynamicCommandInvocationNewAliasNewDriveVariable"
            "Severity" = "Warning" }
    }
}