├── ScriptBlockLoggingAnalyzer ├── 1.2 │ ├── ScriptBlockLoggingAnalyzer.psd1 │ ├── init.ps1 │ ├── loader.psm1 │ ├── Get-SBLLogSize.ps1 │ ├── Convert-SIDToUser.ps1 │ ├── Clear-SBLLog.ps1 │ ├── Enable-SBL.ps1 │ ├── Get-SBLStatus.ps1 │ ├── Set-SBLLogSize.ps1 │ ├── Disable-SBL.ps1 │ ├── Get-SBLCodeStatistic.ps1 │ └── Get-SBLEvent.ps1 └── 2.0 │ ├── init.ps1 │ ├── loader.psm1 │ ├── Get-SBLLogSize.ps1 │ ├── Convert-SIDToUser.ps1 │ ├── Clear-SBLLog.ps1 │ ├── ScriptBlockLoggingAnalyzer.psd1 │ ├── Enable-SBL.ps1 │ ├── Get-SBLStatus.ps1 │ ├── Set-SBLLogSize.ps1 │ ├── Disable-SBL.ps1 │ ├── Get-SBLCodeStatistic.ps1 │ └── Get-SBLEvent.ps1 ├── LICENSE └── README.md /ScriptBlockLoggingAnalyzer/1.2/ScriptBlockLoggingAnalyzer.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobiasPSP/Modules.ScriptBlockLoggingAnalyzer/HEAD/ScriptBlockLoggingAnalyzer/1.2/ScriptBlockLoggingAnalyzer.psd1 -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/init.ps1: -------------------------------------------------------------------------------- 1 | 2 | # use this file to define global variables on module scope 3 | # or perform other initialization procedures. 4 | # this file will not be touched when new functions are exported to 5 | # this module. 6 | 7 | 8 | -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/init.ps1: -------------------------------------------------------------------------------- 1 | 2 | # use this file to define global variables on module scope 3 | # or perform other initialization procedures. 4 | # this file will not be touched when new functions are exported to 5 | # this module. 6 | 7 | 8 | -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/loader.psm1: -------------------------------------------------------------------------------- 1 | 2 | 3 | # LOADING ALL FUNCTION DEFINITIONS: 4 | 5 | . $PSScriptRoot\Convert-SIDToUser.ps1 6 | . $PSScriptRoot\Disable-SBL.ps1 7 | . $PSScriptRoot\Enable-SBL.ps1 8 | . $PSScriptRoot\Get-SBLCodeStatistic.ps1 9 | . $PSScriptRoot\Get-SBLEvent.ps1 10 | . $PSScriptRoot\Get-SBLLogSize.ps1 11 | . $PSScriptRoot\Get-SBLStatus.ps1 12 | . $PSScriptRoot\Set-SBLLogSize.ps1 13 | . $PSScriptRoot\Clear-SBLLog.ps1 14 | 15 | -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/loader.psm1: -------------------------------------------------------------------------------- 1 | 2 | 3 | # LOADING ALL FUNCTION DEFINITIONS: 4 | 5 | . $PSScriptRoot\Convert-SIDToUser.ps1 6 | . $PSScriptRoot\Disable-SBL.ps1 7 | . $PSScriptRoot\Enable-SBL.ps1 8 | . $PSScriptRoot\Get-SBLCodeStatistic.ps1 9 | . $PSScriptRoot\Get-SBLEvent.ps1 10 | . $PSScriptRoot\Get-SBLLogSize.ps1 11 | . $PSScriptRoot\Get-SBLStatus.ps1 12 | . $PSScriptRoot\Set-SBLLogSize.ps1 13 | . $PSScriptRoot\Clear-SBLLog.ps1 14 | 15 | -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Get-SBLLogSize.ps1: -------------------------------------------------------------------------------- 1 | function Get-SBLLogSize 2 | { 3 | <# 4 | .SYNOPSIS 5 | Reports the current script block log size. 6 | 7 | .DESCRIPTION 8 | Returns the current size of the script block log. 9 | 10 | .EXAMPLE 11 | Get-SBLLogSize 12 | Returns the current size of the script block log. 13 | #> 14 | 15 | 16 | $Path = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Channels\Microsoft-Windows-PowerShell/Operational" 17 | $Key = Get-ItemProperty -Path $Path 18 | [PSCustomObject]@{ 19 | Enabled = $key.Enabled -eq 1 20 | MaxSize = $key.MaxSize 21 | MaxSizeMB = '{0:n1} MB' -f ($key.MaxSize / 1MB) 22 | } 23 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Get-SBLLogSize.ps1: -------------------------------------------------------------------------------- 1 | function Get-SBLLogSize 2 | { 3 | <# 4 | .SYNOPSIS 5 | Reports the current script block log size. 6 | 7 | .DESCRIPTION 8 | Returns the current size of the script block log. 9 | 10 | .EXAMPLE 11 | Get-SBLLogSize 12 | Returns the current size of the script block log. 13 | #> 14 | 15 | 16 | $Path = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Channels\Microsoft-Windows-PowerShell/Operational" 17 | $Key = Get-ItemProperty -Path $Path 18 | [PSCustomObject]@{ 19 | Enabled = $key.Enabled -eq 1 20 | MaxSize = $key.MaxSize 21 | MaxSizeMB = '{0:n1} MB' -f ($key.MaxSize / 1MB) 22 | } 23 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Convert-SIDToUser.ps1: -------------------------------------------------------------------------------- 1 | function Convert-SIDToUser 2 | { 3 | <# 4 | .SYNOPSIS 5 | Converts SID to user name 6 | 7 | .DESCRIPTION 8 | Converts a security identifier (SID) to a real user name 9 | 10 | .PARAMETER SID 11 | The security identifier to convert 12 | 13 | .EXAMPLE 14 | "S-1-5-32-544" | Convert-SIDToUser 15 | Converts the sid to a user name 16 | 17 | .EXAMPLE 18 | Convert-SIDToUser -SID "S-1-5-32-544" 19 | Converts the sid to a user name 20 | #> 21 | 22 | 23 | param 24 | ( 25 | [string] 26 | [Parameter(Mandatory,ValueFromPipeline)] 27 | $SID 28 | ) 29 | process 30 | { 31 | (New-Object System.Security.Principal.SecurityIdentifier($SID)).Translate( [System.Security.Principal.NTAccount]).Value 32 | } 33 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Convert-SIDToUser.ps1: -------------------------------------------------------------------------------- 1 | function Convert-SIDToUser 2 | { 3 | <# 4 | .SYNOPSIS 5 | Converts SID to user name 6 | 7 | .DESCRIPTION 8 | Converts a security identifier (SID) to a real user name 9 | 10 | .PARAMETER SID 11 | The security identifier to convert 12 | 13 | .EXAMPLE 14 | "S-1-5-32-544" | Convert-SIDToUser 15 | Converts the sid to a user name 16 | 17 | .EXAMPLE 18 | Convert-SIDToUser -SID "S-1-5-32-544" 19 | Converts the sid to a user name 20 | #> 21 | 22 | 23 | param 24 | ( 25 | [string] 26 | [Parameter(Mandatory,ValueFromPipeline)] 27 | $SID 28 | ) 29 | process 30 | { 31 | (New-Object System.Security.Principal.SecurityIdentifier($SID)).Translate( [System.Security.Principal.NTAccount]).Value 32 | } 33 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Clear-SBLLog.ps1: -------------------------------------------------------------------------------- 1 | function Clear-SBLLog 2 | { 3 | <# 4 | .SYNOPSIS 5 | Ckears the entire PowerShell operational log including script blog logging entries. Administrator privileges required. 6 | 7 | .DESCRIPTION 8 | Clears the complete content of the log Microsoft-Windows-PowerShell/Operational. This includes all logged script block code. 9 | 10 | .EXAMPLE 11 | Clear-SBLLog 12 | Clears the entire log Microsoft-Windows-PowerShell/Operational. 13 | #> 14 | [CmdletBinding(ConfirmImpact='High')] 15 | param() 16 | 17 | try 18 | { 19 | $ErrorActionPreference = 'Stop' 20 | wevtutil cl Microsoft-Windows-PowerShell/Operational 21 | } 22 | catch 23 | { 24 | Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell." 25 | } 26 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Clear-SBLLog.ps1: -------------------------------------------------------------------------------- 1 | function Clear-SBLLog 2 | { 3 | <# 4 | .SYNOPSIS 5 | Ckears the entire PowerShell operational log including script blog logging entries. Administrator privileges required. 6 | 7 | .DESCRIPTION 8 | Clears the complete content of the log Microsoft-Windows-PowerShell/Operational. This includes all logged script block code. 9 | 10 | .EXAMPLE 11 | Clear-SBLLog 12 | Clears the entire log Microsoft-Windows-PowerShell/Operational. 13 | #> 14 | [CmdletBinding(ConfirmImpact='High')] 15 | param() 16 | 17 | try 18 | { 19 | $ErrorActionPreference = 'Stop' 20 | wevtutil cl Microsoft-Windows-PowerShell/Operational 21 | } 22 | catch 23 | { 24 | Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell." 25 | } 26 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/ScriptBlockLoggingAnalyzer.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | RootModule = 'loader.psm1' 3 | ModuleVersion = '2.0' 4 | GUID = 'e9677f71-31b6-4167-923e-f2a6d96330e9' 5 | Author = 'Dr. Tobias Weltner' 6 | CompanyName = 'powershell.one' 7 | Copyright = '2020 Dr. Tobias Weltner (MIT-License)' 8 | Description = 'tools to manage scriptblock logging' 9 | PowerShellVersion = '5.0' 10 | FunctionsToExport = 'Clear-SBLLog', 'Convert-SidToUser', 'Disable-SBL', 'Enable-SBL', 11 | 'Get-SBLCodeStatistics', 'Get-SBLEvent', 'Get-SBLLogSize', 12 | 'Get-SBLStatus', 'Set-SBLLogSize' 13 | CmdletsToExport = '*' 14 | VariablesToExport = '*' 15 | AliasesToExport = '*' 16 | PrivateData = @{ 17 | PSData = @{ 18 | Tags = 'Security', 'ScriptBlockLogging', 'Logging' 19 | LicenseUri = 'https://en.wikipedia.org/wiki/MIT_License' 20 | ProjectUri = 'https://github.com/TobiasPSP/Modules.ScriptBlockLoggingAnalyzer' 21 | ReleaseNotes = 'cleaned up the module' 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Enable-SBL.ps1: -------------------------------------------------------------------------------- 1 | function Enable-SBL 2 | { 3 | <# 4 | .SYNOPSIS 5 | Enables script block logging. Requires Administrator privileges. 6 | 7 | .DESCRIPTION 8 | Turns script block logging on. Any code that is sent to PowerShell will be logged. 9 | 10 | .EXAMPLE 11 | Enable-SBL 12 | Enables script block logging. Administrator privileges required. 13 | #> 14 | 15 | 16 | $path = "Registry::HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" 17 | $exists = Test-Path -Path $path 18 | try 19 | { 20 | $ErrorActionPreference = 'Stop' 21 | if (!$exists) { $null = New-Item -Path $path -Force } 22 | 23 | Set-ItemProperty -Path $path -Name EnableScriptBlockLogging -Type DWord -Value 1 24 | Set-ItemProperty -Path $path -Name EnableScriptBlockInvocationLogging -Type DWord -Value 1 25 | } 26 | catch 27 | { 28 | Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell." 29 | } 30 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Enable-SBL.ps1: -------------------------------------------------------------------------------- 1 | function Enable-SBL 2 | { 3 | <# 4 | .SYNOPSIS 5 | Enables script block logging. Requires Administrator privileges. 6 | 7 | .DESCRIPTION 8 | Turns script block logging on. Any code that is sent to PowerShell will be logged. 9 | 10 | .EXAMPLE 11 | Enable-SBL 12 | Enables script block logging. Administrator privileges required. 13 | #> 14 | 15 | 16 | $path = "Registry::HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" 17 | $exists = Test-Path -Path $path 18 | try 19 | { 20 | $ErrorActionPreference = 'Stop' 21 | if (!$exists) { $null = New-Item -Path $path -Force } 22 | 23 | Set-ItemProperty -Path $path -Name EnableScriptBlockLogging -Type DWord -Value 1 24 | Set-ItemProperty -Path $path -Name EnableScriptBlockInvocationLogging -Type DWord -Value 1 25 | } 26 | catch 27 | { 28 | Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell." 29 | } 30 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Get-SBLStatus.ps1: -------------------------------------------------------------------------------- 1 | function Get-SBLStatus 2 | { 3 | <# 4 | .SYNOPSIS 5 | Returns the current status for script block logging. 6 | 7 | .DESCRIPTION 8 | Examines the registry keys that control script block logging and reports whether the keys exist, and what their state is. 9 | 10 | .EXAMPLE 11 | Get-SBLStatus 12 | Returns whether script block logging is explicitly turned on or off via registry keys. 13 | #> 14 | 15 | 16 | $path = "Registry::HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" 17 | $settings = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue 18 | $keyExists = Test-Path -Path $path 19 | [PSCustomObject]@{ 20 | EnableScriptBlockLogging = $settings.EnableScriptBlockLogging -eq 1 21 | EnableScriptBlockInvocationLogging = $settings.EnableScriptBlockInvocationLogging -eq 1 22 | SettingsKeyExists = $keyExists 23 | ScriptBlockLoggingActive = !$keyExists -or $settings.EnableScriptBlockLogging -eq 1 24 | } 25 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Get-SBLStatus.ps1: -------------------------------------------------------------------------------- 1 | function Get-SBLStatus 2 | { 3 | <# 4 | .SYNOPSIS 5 | Returns the current status for script block logging. 6 | 7 | .DESCRIPTION 8 | Examines the registry keys that control script block logging and reports whether the keys exist, and what their state is. 9 | 10 | .EXAMPLE 11 | Get-SBLStatus 12 | Returns whether script block logging is explicitly turned on or off via registry keys. 13 | #> 14 | 15 | 16 | $path = "Registry::HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" 17 | $settings = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue 18 | $keyExists = Test-Path -Path $path 19 | [PSCustomObject]@{ 20 | EnableScriptBlockLogging = $settings.EnableScriptBlockLogging -eq 1 21 | EnableScriptBlockInvocationLogging = $settings.EnableScriptBlockInvocationLogging -eq 1 22 | SettingsKeyExists = $keyExists 23 | ScriptBlockLoggingActive = !$keyExists -or $settings.EnableScriptBlockLogging -eq 1 24 | } 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dr. Tobias Weltner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Set-SBLLogSize.ps1: -------------------------------------------------------------------------------- 1 | function Set-SBLLogSize 2 | { 3 | <# 4 | .SYNOPSIS 5 | Sets a new size for the script block logging log. Administrator privileges required. 6 | 7 | .DESCRIPTION 8 | By default, the script block log has a maximum size of 15MB which may be too small to capture and log PowerShell activity over a given period of time. With this command, you can assign more memory to the log. 9 | 10 | .PARAMETER MaxSizeMB 11 | New log size in Megabyte 12 | 13 | .EXAMPLE 14 | Set-SBLLogSize -MaxSizeMB 100 15 | Sets the maximum log size to 100MB. Administrator privileges required. 16 | #> 17 | 18 | 19 | param 20 | ( 21 | [Parameter(Mandatory)] 22 | [ValidateRange(15,3000)] 23 | [int] 24 | $MaxSizeMB 25 | ) 26 | 27 | $Path = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Channels\Microsoft-Windows-PowerShell/Operational" 28 | try 29 | { 30 | $ErrorActionPreference = 'Stop' 31 | Set-ItemProperty -Path $Path -Name MaxSize -Value ($MaxSizeMB * 1MB) 32 | } 33 | catch 34 | { 35 | Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell." 36 | } 37 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Set-SBLLogSize.ps1: -------------------------------------------------------------------------------- 1 | function Set-SBLLogSize 2 | { 3 | <# 4 | .SYNOPSIS 5 | Sets a new size for the script block logging log. Administrator privileges required. 6 | 7 | .DESCRIPTION 8 | By default, the script block log has a maximum size of 15MB which may be too small to capture and log PowerShell activity over a given period of time. With this command, you can assign more memory to the log. 9 | 10 | .PARAMETER MaxSizeMB 11 | New log size in Megabyte 12 | 13 | .EXAMPLE 14 | Set-SBLLogSize -MaxSizeMB 100 15 | Sets the maximum log size to 100MB. Administrator privileges required. 16 | #> 17 | 18 | 19 | param 20 | ( 21 | [Parameter(Mandatory)] 22 | [ValidateRange(15,3000)] 23 | [int] 24 | $MaxSizeMB 25 | ) 26 | 27 | $Path = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Channels\Microsoft-Windows-PowerShell/Operational" 28 | try 29 | { 30 | $ErrorActionPreference = 'Stop' 31 | Set-ItemProperty -Path $Path -Name MaxSize -Value ($MaxSizeMB * 1MB) 32 | } 33 | catch 34 | { 35 | Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell." 36 | } 37 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Disable-SBL.ps1: -------------------------------------------------------------------------------- 1 | function Disable-SBL 2 | { 3 | <# 4 | .SYNOPSIS 5 | Disables script block logging. Requires Administrator privileges. 6 | 7 | .DESCRIPTION 8 | Turns off script block logging. 9 | 10 | .EXAMPLE 11 | Disable-SBL 12 | Turns off script block logging. 13 | #> 14 | 15 | 16 | $path = "Registry::HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" 17 | $exists = Test-Path -Path $path 18 | if (!$exists) { 19 | Write-Warning 'Script block logging was not enabled. No action taken.' 20 | return 21 | } 22 | 23 | try 24 | { 25 | $ErrorActionPreference = 'Stop' 26 | Remove-ItemProperty -Path $path -Name EnableScriptBlockLogging -ErrorAction SilentlyContinue 27 | Remove-ItemProperty -Path $path -Name EnableScriptBlockInvocationLogging -ErrorAction SilentlyContinue 28 | 29 | $key = Get-Item -Path $path 30 | $remainingValues = $key.GetValueNames().Count 31 | $remainingKeys = @(Get-ChildItem -Path $path).Count 32 | if ($remainingValues -eq 0 -and $remainingKeys -eq 0) { Remove-Item -Path $path } 33 | } 34 | catch 35 | { 36 | Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell." 37 | } 38 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Disable-SBL.ps1: -------------------------------------------------------------------------------- 1 | function Disable-SBL 2 | { 3 | <# 4 | .SYNOPSIS 5 | Disables script block logging. Requires Administrator privileges. 6 | 7 | .DESCRIPTION 8 | Turns off script block logging. 9 | 10 | .EXAMPLE 11 | Disable-SBL 12 | Turns off script block logging. 13 | #> 14 | 15 | 16 | $path = "Registry::HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" 17 | $exists = Test-Path -Path $path 18 | if (!$exists) { 19 | Write-Warning 'Script block logging was not enabled. No action taken.' 20 | return 21 | } 22 | 23 | try 24 | { 25 | $ErrorActionPreference = 'Stop' 26 | Remove-ItemProperty -Path $path -Name EnableScriptBlockLogging -ErrorAction SilentlyContinue 27 | Remove-ItemProperty -Path $path -Name EnableScriptBlockInvocationLogging -ErrorAction SilentlyContinue 28 | 29 | $key = Get-Item -Path $path 30 | $remainingValues = $key.GetValueNames().Count 31 | $remainingKeys = @(Get-ChildItem -Path $path).Count 32 | if ($remainingValues -eq 0 -and $remainingKeys -eq 0) { Remove-Item -Path $path } 33 | } 34 | catch 35 | { 36 | Write-Warning "Administrator privileges required. Run this command from an elevated PowerShell." 37 | } 38 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Get-SBLCodeStatistic.ps1: -------------------------------------------------------------------------------- 1 | function Get-SBLCodeStatistic 2 | { 3 | <# 4 | .SYNOPSIS 5 | Analyzes PowerShell code and returns statistics 6 | 7 | .DESCRIPTION 8 | Analyzes PowerShell code and returns syntax errors, commands, and member invocations. 9 | 10 | .PARAMETER code 11 | The PowerShell code to be analyzed. 12 | 13 | .EXAMPLE 14 | Get-SBLCodeStatistic -code 'Get-Process' 15 | Analyzes the submitted code. 16 | #> 17 | 18 | 19 | param 20 | ( 21 | [Parameter(Mandatory,ValueFromPipeline)] 22 | [AllowEmptyString()] 23 | [string] 24 | $code 25 | ) 26 | 27 | process 28 | { 29 | $token = $errors = @() 30 | $ast = [System.Management.Automation.Language.Parser]::ParseInput($code, [ref]$token, [ref]$errors) 31 | $commands = $ast.FindAll( { param($el) $el -is [System.Management.Automation.Language.CommandAst] }, $true ) | 32 | Foreach-Object { 33 | if ($_.CommandElements -ne $null -and $_.CommandELements.count -gt 0) 34 | { $_.CommandElements[0].Value} 35 | } 36 | $members = $ast.FindAll( { param($el) $el -is [System.Management.Automation.Language.InvokeMemberExpressionAst] }, $true ).Member.Value 37 | [PSCustomObject]@{ 38 | HasSyntaxError = $errors.Count -gt 0 39 | Commands = $commands 40 | MemberInvocation = $members 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Get-SBLCodeStatistic.ps1: -------------------------------------------------------------------------------- 1 | function Get-SBLCodeStatistic 2 | { 3 | <# 4 | .SYNOPSIS 5 | Analyzes PowerShell code and returns statistics 6 | 7 | .DESCRIPTION 8 | Analyzes PowerShell code and returns syntax errors, commands, and member invocations. 9 | 10 | .PARAMETER code 11 | The PowerShell code to be analyzed. 12 | 13 | .EXAMPLE 14 | Get-SBLCodeStatistic -code 'Get-Process' 15 | Analyzes the submitted code. 16 | #> 17 | 18 | 19 | param 20 | ( 21 | [Parameter(Mandatory,ValueFromPipeline)] 22 | [AllowEmptyString()] 23 | [string] 24 | $code 25 | ) 26 | 27 | process 28 | { 29 | $token = $errors = @() 30 | $ast = [System.Management.Automation.Language.Parser]::ParseInput($code, [ref]$token, [ref]$errors) 31 | $commands = $ast.FindAll( { param($el) $el -is [System.Management.Automation.Language.CommandAst] }, $true ) | 32 | Foreach-Object { 33 | if ($_.CommandElements -ne $null -and $_.CommandELements.count -gt 0) 34 | { $_.CommandElements[0].Value} 35 | } 36 | $members = $ast.FindAll( { param($el) $el -is [System.Management.Automation.Language.InvokeMemberExpressionAst] }, $true ).Member.Value 37 | [PSCustomObject]@{ 38 | HasSyntaxError = $errors.Count -gt 0 39 | Commands = $commands 40 | MemberInvocation = $members 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/1.2/Get-SBLEvent.ps1: -------------------------------------------------------------------------------- 1 | function Get-SBLEvent 2 | { 3 | <# 4 | .SYNOPSIS 5 | Dumps the content of the script block logging log. 6 | 7 | .DESCRIPTION 8 | Returns any logged PowerShell code. The function combines multiple code parts. It returns one object per executed code. 9 | 10 | .EXAMPLE 11 | Get-SBLEvent 12 | Dumps all logged PowerShell code. 13 | #> 14 | 15 | 16 | $hash = @{} 17 | 18 | try 19 | { 20 | $ErrorActionPreference = 'Stop' 21 | Get-WinEvent -FilterHashtable @{ ProviderName="Microsoft-Windows-PowerShell"; Id = 4104 } | 22 | ForEach-Object { 23 | $eventData = $_ 24 | $path = $eventData.Properties[4].Value 25 | if ($path.Trim().Length -eq 0) { $Path = "[from memory]" } 26 | $part = $eventData.Properties[0].Value 27 | $parts = $eventData.Properties[1].Value 28 | $id = $eventData.Properties[3].Value 29 | $code = $eventData.Properties[2].Value 30 | 31 | $hasHashKey = $hash.ContainsKey($id) 32 | 33 | # if this is not a part 1 event, collect the part and use it later 34 | if ($part -ne 1) 35 | { 36 | if (!$hasHashkey) 37 | { 38 | $hash[$id] = ,'' * ($parts) 39 | } 40 | $hash[$id][$part-1] = $code 41 | } 42 | else 43 | { 44 | if ($hasHashKey) 45 | { 46 | $hash[$id][0] = $code 47 | $code = $hash[$id] -join "`r`n" 48 | $null = $hash.Remove($id) 49 | } 50 | 51 | 52 | [PSCustomObject]@{ 53 | TimeCreated = $eventData.TimeCreated 54 | Name = Split-Path $Path -Leaf 55 | Code = $code 56 | Path = $Path 57 | UserName = $eventData.UserId | Convert-SIDToUser 58 | ComputerName = $eventData.MachineName 59 | ProcessId = $eventData.ProcessId 60 | ThreadId = $eventData.ThreadId 61 | Sid = $eventData.UserId 62 | TotalParts = $parts 63 | CodeId = $id 64 | } 65 | } 66 | } 67 | } 68 | catch 69 | { 70 | Write-Warning "No events found." 71 | } 72 | } -------------------------------------------------------------------------------- /ScriptBlockLoggingAnalyzer/2.0/Get-SBLEvent.ps1: -------------------------------------------------------------------------------- 1 | function Get-SBLEvent 2 | { 3 | <# 4 | .SYNOPSIS 5 | Dumps the content of the script block logging log. 6 | 7 | .DESCRIPTION 8 | Returns any logged PowerShell code. The function combines multiple code parts. It returns one object per executed code. 9 | 10 | .EXAMPLE 11 | Get-SBLEvent 12 | Dumps all logged PowerShell code. 13 | #> 14 | 15 | 16 | $hash = @{} 17 | 18 | try 19 | { 20 | $ErrorActionPreference = 'Stop' 21 | Get-WinEvent -FilterHashtable @{ ProviderName="Microsoft-Windows-PowerShell"; Id = 4104 } | 22 | ForEach-Object { 23 | $eventData = $_ 24 | $path = $eventData.Properties[4].Value 25 | if ($path.Trim().Length -eq 0) { $Path = "[from memory]" } 26 | $part = $eventData.Properties[0].Value 27 | $parts = $eventData.Properties[1].Value 28 | $id = $eventData.Properties[3].Value 29 | $code = $eventData.Properties[2].Value 30 | 31 | $hasHashKey = $hash.ContainsKey($id) 32 | 33 | # if this is not a part 1 event, collect the part and use it later 34 | if ($part -ne 1) 35 | { 36 | if (!$hasHashkey) 37 | { 38 | $hash[$id] = ,'' * ($parts) 39 | } 40 | $hash[$id][$part-1] = $code 41 | } 42 | else 43 | { 44 | if ($hasHashKey) 45 | { 46 | $hash[$id][0] = $code 47 | $code = $hash[$id] -join "`r`n" 48 | $null = $hash.Remove($id) 49 | } 50 | 51 | 52 | [PSCustomObject]@{ 53 | TimeCreated = $eventData.TimeCreated 54 | Name = Split-Path $Path -Leaf 55 | Code = $code 56 | Path = $Path 57 | UserName = $eventData.UserId | Convert-SIDToUser 58 | ComputerName = $eventData.MachineName 59 | ProcessId = $eventData.ProcessId 60 | ThreadId = $eventData.ThreadId 61 | Sid = $eventData.UserId 62 | TotalParts = $parts 63 | CodeId = $id 64 | } 65 | } 66 | } 67 | } 68 | catch 69 | { 70 | Write-Warning "No events found." 71 | } 72 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | **PowerShell** can log *all executed source code*, and serve as a *honey-pot* for malicious scripts. No matter how good an attacker has hidden a **PowerShell** script, scriptblock logging *will* expose it. 4 | 5 | This won't help much, though, if no one looks at the logged data. That's why this module adds two new cmdlets that help you control scriptblock logging and read the logged data. 6 | 7 | Scriptblock logging is a great tool: 8 | 9 | - it helps companies establish security workflows that identifies the **PowerShell** code that runs in their environments, plus identify *who* ran the code. 10 | - it can also be used to raise awareness of how vulnerable sensitive data stored inside scripts is (i.e. by actually exposing clear-text passwords found in scripts). 11 | - Blue teamers can use the techniques to expose **PowerShell** source code that is running inside of applications. 12 | - you can even do statistics and identify what the commands are that scripts in your company typically use, analyze code quality, etc. 13 | 14 | By default, **PowerShell** logs only selected (suspicious) scripts. `Enable-SBL` turns on full scriptblock logging and log all **PowerShell** code executing *anywhere* on the machine. This is just setting a registry key so you could control scriptblock logging via Group Policies as well. 15 | 16 | The source code is logged to the eventlog system, and when scripts are large, the source code is separated into many chunks of eventlog data. `Get-SBLEvent` reads the logged source code and recomposes the full script source code. 17 | 18 | 19 | ## Areas of Improvement 20 | This module is currently a *proof-of-concept*: it works perfectly well but there are a couple of areas that need more love: 21 | 22 | - **Windows PowerShell**: currently the module is tailored towards *Windows PowerShell*. *PowerShell 7* logs the data in a different eventlog. It should be fairly easy though to add these eventlog queries as well. 23 | - **Performance**: `Get-SBLEvent` does a simple eventlog query based on`Get-WinEvent`. It won't expose advanced filtering (yet) so you can only dump *all* logged source codes and then filter the results client-side with `Where-Object`. A much faster approach would be to expose a `-Filter` parameter that uses the native *XPath* filters found in `Get-WinEvent` to quickly search for i.e. *.exe*-files that contain **PowerShell** code or do specific queries for suspicious commands. 24 | - **Clean-Up**: scriptblock logging logs any **PowerShell** code including custom *prompt* functions etc. It would be nice to have the option to exclude such data from the results provided by `Get-SBLEvent`. 25 | 26 | Since this module definitely has the potential to become a very useful analytic tool for blue teamers and basically any security-aware **PowerShell** admin, you are cordially invited to help evolve this module (see end of this file). 27 | 28 | ## Install 29 | 30 | To install the module from the *PowerShell Gallery*, run this: 31 | 32 | ```powershell 33 | Install-Module -Name ScriptBlockLoggingAnalyzer -Scope CurrentUser 34 | ``` 35 | 36 | ## Enable ScriptBlock Logging 37 | 38 | To enable scriptblock logging to the eventlog, with *Administrator* privileges, run this: 39 | 40 | ```powershell 41 | Enable-SBL 42 | ``` 43 | 44 | Note: it may take few minutes until scriptblock logging is fully enabled. The most relaxed approach is to enable scriptblock logging and check back after lunch or the next day. 45 | 46 | ### Hardening 47 | 48 | When enabled, all scriptblocks anywhere (manually executed **PowerShell** code, running scripts, or **PowerShell** code executing inside an application) will be logged. Obviously, source code is sensitive and would be a great find for any attacker that wants to understand your IT. 49 | 50 | By default, the eventlog access is not restricted so any user can see the logged source code. In production environments (or anywhere else for that matter), you want to restrict read access to the log and make sure only *Administrators* can view the logged source code. 51 | 52 | The script below is *one* way to restict access: it copies the access restrictions from the *Security* eventlog to the eventlog that stores the scriptblock source code: 53 | 54 | ```powershell 55 | # read current access control for eventlog 'Security': 56 | $sddlSecurity = ((wevtutil gl security) -like 'channelAccess*').Split(' ')[-1] 57 | 58 | # apply this to the eventlog that logs the scriptblocks: 59 | $Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\winevt\Channels\Microsoft-Windows-PowerShell/Operational" 60 | Set-ItemProperty -Path $Path -Name ChannelAccess -Value $sddlSecurity 61 | 62 | # restart service to apply settings: 63 | Restart-Service -Name EventLog -Force 64 | ``` 65 | 66 | Microsoft supports other hardening strategies as well. You can protect the source code with certificates as well. This however isn't trivial to set up. 67 | 68 | ### Log Size 69 | 70 | By default, the eventlog logging the source code is limited to 15MB. To log more source code, you may want to adjust the eventlog (requires *Administrator* privileges). This expands the maximum eventlog size to 100MB: 71 | 72 | ```powershell 73 | Set-SBLLogSize -MaxSizeMB 100 74 | ``` 75 | 76 | 77 | ### Known Limitations 78 | 79 | Currently, only code executed by *Windows PowerShell* will be logged and retrieved. *PowerShell 7* saves the logged scriptblock code to a different eventlog. You are welcome to adapt this code to *PowerShell 7*. If you do, please let me know or issue a pull request. 80 | 81 | Note: ScriptBlock logging was introduced to *Windows PowerShell* in version 4 and fully implemented in version 5. That's yet another good reason why on *Windows* you should check your *Windows PowerShell* version and make sure you are not using an outdated version. Plus you should make sure that in your *Windows Features* the old optional *PowerShell 2* is *not available*. Hackers *love* that old version exactly because *it does not log*: 82 | 83 | ``` 84 | PS> Get-WindowsOptionalFeature -FeatureName *powershellv2* -Online 85 | 86 | 87 | FeatureName : MicrosoftWindowsPowerShellV2Root 88 | DisplayName : Windows PowerShell 2.0 89 | Description : Adds Windows PowerShell 2.0 or removes this component. 90 | RestartRequired : Possible 91 | State : Disabled 92 | CustomProperties : 93 | 94 | 95 | FeatureName : MicrosoftWindowsPowerShellV2 96 | DisplayName : Windows PowerShell 2.0 Engine 97 | Description : Adds Windows PowerShell 2.0 Engine or removes this component. 98 | RestartRequired : Possible 99 | State : Disabled 100 | CustomProperties : 101 | ``` 102 | 103 | Due to a long-standing bug in all versions of **PowerShell** (including *PowerShell 7*), when scriptblock logging is enabled, pipeline operations are slowed down. This can affect scripts that process a large number of objects. More details and workarounds can be found here: https://powershell.one/tricks/performance/pipeline 104 | 105 | ## Reading Logged Source Code 106 | 107 | To read the logged source code, use `Get-SBLEvent`. By default, reading the logged source code is not restricted. If you hardened the access to the eventlog with the example above, *Administrator* privileges are required. In this case, non-Admins always receive the warning: *No events found.*. 108 | 109 | This pulls the newest 100 **PowerShell** source codes captured: 110 | 111 | ``` 112 | PS> Get-SBLEvent | Select-Object -First 100 113 | 114 | TimeCreated : 15.01.2021 10:33:31 115 | Name : [from memory] 116 | Code : cls 117 | Path : [from memory] 118 | UserName : DELL7390\tobia 119 | ComputerName : DELL7390 120 | ProcessId : 10000 121 | ThreadId : 8536 122 | Sid : S-1-5-21-2770831484-2260150476-2133527644-1001 123 | TotalParts : 1 124 | CodeId : 27f07d4d-ed9d-410c-b6eb-a569f8ddac6c 125 | 126 | TimeCreated : 15.01.2021 10:33:25 127 | Name : Get-SBLEvent.ps1 128 | Code : { 129 | $eventData = $_ 130 | $path = $eventData.Properties[4].Value 131 | if ($path.Trim().Length -eq 0) { $Path = "[from memory]" } 132 | $part = $eventData.Properties[0].Value 133 | $parts = $eventData.Properties[1].Value 134 | $id = $eventData.Properties[3].Value 135 | $code = $eventData.Properties[2].Value 136 | (...) 137 | ``` 138 | 139 | *Name* returns the name of the logged script. Interactive commands show *\[from memory\]* instead. The source code is returned in *Code*. 140 | 141 | Since `Get-SBLEvent` currently has no built-in way of filtering the logged scriptblocks, dumping everything can take a long time. That's why the example above uses `Select-Object -First x` which is the second-best approach: once `Get-SBLEvent` has returned the requested number of logged scriptblocks, `Select-Object` aborts the pipeline. 142 | 143 | In future releases, obviously `Get-SBLEvent` should expose its own set of filtering parameters like `-Newest`, `-FileType`, `-Filter`, etc. 144 | 145 | Note: When scriptblock logging is *not* enabled, this returns only very few *suspicious* scripts. When you do enable scriptblock logging via `Enable-SBL`, *all* code is logged, including interactive code. However, it may take some time before the scriptblock logging system is fully operationalt: 146 | 147 | - On some systems, logging starts momentarily. 148 | - On other systems, you may have to wait an hour. 149 | 150 | If you know more about this initial delay, and why it happens, please share. Once scriptblock logging is running, it then logs code in real-time and does so immediately after reboots. So it's just the initial turning on that may be delayed. 151 | 152 | ### Identifying Suspicious Activity 153 | 154 | You can automate scanning logged source code and for example routinely search for suspicious commands. 155 | 156 | You could also check to see which file types have been executed in the past, and for example identify whether unknown *.exe*-Applications executed **PowerShell** code. If so, you can even see and examine the **PowerShell** code that executed inside such an application. 157 | 158 | #### Identifying Suspicious Applications 159 | 160 | This example reads all logged scripts and returns the file extensions found that executed **PowerShell** code: 161 | 162 | ``` 163 | PS> Get-SBLEvent | 164 | Foreach-Object { [System.IO.Path]::GetExtension($_.Name) } | 165 | Group-Object -NoElement 166 | 167 | Count Name 168 | ----- ---- 169 | 41227 170 | 885 .ps1 171 | 496 .psm1 172 | 734 .psd1 173 | 5 .exe 174 | ``` 175 | Note: based on the number of logged scripts, this can take a long time to run. 176 | Blank file extensions represent interactively entered **PowerShell** code. 177 | 178 | #### Exposing PowerShell Code Inside Applications 179 | 180 | The next example would search for *.exe*-Applications and return file path and **PowerShell** source code content: 181 | 182 | ```powershell 183 | Get-SBLEvent | Where-Object { $_.Name -like '*.exe' } | Select-Object -Property Path, Code 184 | ``` 185 | 186 | Obviously, this script yields nothing if there was no *.exe* application on your system that ran **PowerShell** code. 187 | 188 | ### Conclusions 189 | 190 | The previous examples prove that it is *unacceptable* to ever save sensitive data such as passwords in **PowerShell** scripts, no matter how you wrap them into applications. Even if you launched a script with sensitive passwords *only once*, it *will* end up in the log and compromises all secrets hard-coded inside of it. 191 | 192 | Obviously this is not a *limitation* of **PowerShell** but a huge *benefit*: scriptblock logging exposes to you what attackers can do (with a plethora of other means). 193 | 194 | Use the examples here to raise sensitivity among your co-workers. 195 | 196 | ### Default Logging 197 | 198 | Note: Even if scriptblock logging is not enabled, **PowerShell** will log selected code based on hard-coded trigger words. 199 | 200 | ## Contribute 201 | 202 | If you have questions or suggestions, please join our [Discussions](https://github.com/TobiasPSP/Modules.ScriptBlockLoggingAnalyzer/discussions). 203 | 204 | A much more preferred way is to submit [issues](https://github.com/TobiasPSP/Modules.ScriptBlockLoggingAnalyzer/issues/new/choose) and pull requests: if you identify areas of improvement, i.e. expanding it to *PowerShell 7* and adding eventlog filters for better performance, I'd greatly appreciate if you not just *asked* for the improvements but actually *helped code the improvements* and send the *actual code* to me via pull requests. 205 | 206 | This way we can share the load of development, and I could review your code suggestions and quickly integrate them to the module. Many thanks! 207 | --------------------------------------------------------------------------------