MaliciousContentRules.psm1
[System.Collections.Generic.List[String]] $suspiciousTokenElements = @( ## Code compilation "Add-Type", "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", "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 ) ## Get all of the sub-ASTs, and sort by length (so that we can get the specific matches). $allAsts = $ScriptBlockAst.FindAll( { $true }, $true) | Sort { $_.Extent.Text.Length } ## A list of ASTs that we've reported on, so that we don't need to report on its ## parent. $reportedAsts = @() foreach($ast in $allAsts) { $foundSuspiciousContent = $false $suspiciousContent = "" ## Quick test to see if the token text has an exact match if($suspiciousTokens.Contains($ast.Extent.Text)) { $foundSuspiciousContent = $true $suspiciousContent = $ast.Extent.Text } else { ## Otherwise, substring search foreach($suspiciousToken in $suspiciousTokens) { if($ast.Extent.Text.IndexOf($suspiciousToken, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) { $foundSuspiciousContent = $true $suspiciousContent = $suspiciousToken } } } if($foundSuspiciousContent) { ## If this AST is a parent of an already reported AST, ## don't report the parent. foreach($reportedAst in $reportedAsts) { $reportedAstParent = $reportedAst do { if($reportedAstParent -eq $ast) { $foundSuspiciousContent = $false } $reportedAstParent = $reportedAstParent.Parent } while($reportedAstParent -and $foundSuspiciousContent) if(-not $foundSuspiciousContent) { break } } } if($foundSuspiciousContent) { $reportedAsts += $ast [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord] @{ "Message" = "Possible malicious script content: " + $suspiciousContent "Extent" = $ast.Extent "RuleName" = "MaliciousContent.SuspiciousContentText" "Severity" = "Warning" } } } } |