Windows analysis

OS queries

Domain

Get Fully Qualified Domain Name (FQDN):

([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname

Get just domain name:

(Get-WmiObject -Class win32_computersystem).domain

OS

Print hostname, OS build info, and powershell version:

$Bit = (get-wmiobject Win32_OperatingSystem).OSArchitecture ; 
$V = $host | select-object -property "Version" ; 
$Build = (Get-WmiObject -class Win32_OperatingSystem).Caption ; 
write-host "$env:computername is a $Bit $Build with Pwsh $V"

Hardware

Get processor info:

gcim -ClassName Win32_Processor | fl caption, Name, SocketDesignation;

Computer Model:

gcim -ClassName Win32_ComputerSystem | fl Manufacturer, Systemfamily, Model, SystemType

Disk space in Gigs:

gcim  -ClassName Win32_LogicalDisk | Select -Property DeviceID, DriveType, @{L='FreeSpaceGB';E={"{0:N2}" -f ($_.FreeSpace /1GB)}}, @{L="Capacity";E={"{0:N2}" -f ($_.Size/1GB)}} | fl

Time

Human-readable time:

Get-Date -UFormat "%T %a %d-%b-%Y UTC:%Z"

Comparisons between two strings of time:

[Xml.XmlConvert]::ToString((Get-Date).ToUniversalTime(), [System.Xml.XmlDateTimeSerializationMode]::Utc)

Compare UTC time with local time:

$Local = get-date;$UTC = (get-date).ToUniversalTime();
write-host "LocalTime is: $Local";write-host "UTC is: $UTC"

Updates

Show all patch IDs with installation date:

get-hotfix|
select-object HotFixID,InstalledOn|
Sort-Object  -Descending -property InstalledOn|
format-table -autosize

Why an update failed:

$Failures = gwmi -Class Win32_ReliabilityRecords;
$Failures | ? message -match 'failure'  | Select -ExpandProperty message 

Recursively look for SomeFile.dll:

$file = 'SomeFile.dll'; $directory = 'C:\windows' ;
gci -Path $directory -Filter $file -Recurse -force|
sort-object  -descending -property LastWriteTimeUtc | fl *

Account queries

Enumerating

Enabled local user accounts:

 Get-LocalUser | ? Enabled -eq "True"

All users currently logged in:

qwinsta

To find all users logged in across the entire AD, use YossiSassi’s Get-UserSession.ps1 and Get-RemotePSSession.ps1.

Logging out

To force user logout, target their session id:

logoff <session id> /v

Changing passwords

Force a new user password for AD:

$user = "username" ; 
$newPass = "Hard to guess new password";
Set-ADAccountPassword -Identity $user -Reset -NewPassword (ConvertTo-SecureString -AsPlainText "$newPass" -Force) -verbose

For local non-domain joined machines:

net user username "Hard to guess new password"

Disabling

To disable an AD Account, use the SAMAccountName:

$user = "username"; 
Disable-ADAccount -Identity "$user"

Check it is disabled:

(Get-ADUser -Identity $user).enabled

To re-enable the account:

Enable-ADAccount -Identity "$user" -verbose

To disable a local account, list accounts with Get-LocalUser.

Disable-LocalUser -name "name"

To quickly eject an account from a specific group, like administrators or remote management:

$user = "username"
remove-adgroupmember -identity Administrators -members $User -verbose -confirm:$false

Device accounts

Adversaries like to use accounts that have a $.

To show machine accounts which are a part of interesting groups:

Get-ADComputer -Filter * -Properties MemberOf | ? {$_.MemberOf}

Reset password for a machine account:

Reset-ComputerMachinePassword

Service queries

Enumerating

All the services:

get-service|Select Name,DisplayName,Status|
sort status -descending | ft -Property * -AutoSize|
Out-String -Width 4096

Show the executable supporting a service:

Get-WmiObject win32_service |? State -match "running" |
select Name, DisplayName, PathName, User | sort Name |
ft -wrap -autosize

Info on a specific service (eventlog):

$Name = "eventlog"; 
gwmi -Class Win32_Service -Filter "Name = '$Name' " | fl *

Responses

Kill a service:

Get-Service -DisplayName "service" | Stop-Service -Force -Confirm:$false -verbose

To identify a service install which is hiding Windows services:

sc sdset evilsvc "D:(D;;DCLCWPDTSD;;;IU)(D;;DCLCWPDTSD;;;SU)(D;;DCLCWPDTSD;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"

Deploy:

Get-ItemProperty -Path "HKLM:\System\CurrentControlSet\services\*" | 
ft PSChildName, ImagePath -autosize | out-string -width 800

Grep results from System32:

Get-ItemProperty -Path "HKLM:\System\CurrentControlSet\services\*" | 
where ImagePath -notlike "*System32*" | 
ft PSChildName, ImagePath -autosize | out-string -width 800

Network queries

TCP connections

Get-NetTCPConnection |
select LocalAddress,localport,remoteaddress,remoteport,state,@{name="process";Expression={(get-process -id $_.OwningProcess).ProcessName}}, @{Name="cmdline";Expression={(Get-WmiObject Win32_Process -filter "ProcessId = $($_.OwningProcess)").commandline}} | 
sort Remoteaddress -Descending | ft -wrap -autosize

Search for/filter anypattern:

Get-NetTCPConnection |
select LocalAddress,localport,remoteaddress,remoteport,state,@{name="process";Expression={(get-process -id $_.OwningProcess).ProcessName}}, @{Name="cmdline";Expression={(Get-WmiObject Win32_Process -filter "ProcessId = $($_.OwningProcess)").commandline}} |
Select-String -Pattern 'anypattern';

Established connections

Established connections, sorted by creationtime (change to whatever sorting parameter):

Get-NetTCPConnection -AppliedSetting Internet |
select-object -property remoteaddress, remoteport, creationtime |
Sort-Object -Property creationtime |
format-table -autosize

Unique remote IP connections:

(Get-NetTCPConnection).remoteaddress | Sort-Object -Unique 

Investigating a suspicious IP:

Get-NetTCPConnection |
? {($_.RemoteAddress -eq "1.2.3.4")} |
select-object -property state, creationtime, localport,remoteport | ft -autosize

UDP connections

Get-NetUDPEndpoint | select local*,creationtime, remote* | ft -autosize

Kill a connection

stop-process -verbose -force -Confirm:$false (Get-Process -Id (Get-NetTCPConnection -RemoteAddress "1.2.3.4" ).OwningProcess)

Check Hosts file

gci "C:\Windows\System32\Drivers\etc\hosts"

To check whether timestamps were recently altered:

gci "C:\Windows\System32\Drivers\etc\hosts" | fl *Time*

DNS Cache

Collect the DNS cache on an endpoint:

Get-DnsClientCache | out-string -width 1000

Using regex (second line):

Get-DnsClientCache | 
? Entry -NotMatch "workstation|server|kerb|ocsp" |
out-string -width 1000 

IPv6

Windows OS prioritises IPv6 over IPv4 making on-path-attacks easier.

Get IPv6 addresses and networks:

Get-NetIPAddress -AddressFamily IPv6  | ft Interfacealias, IPv6Address

Check if a machine prioritises IPv6:

ping $env:COMPUTERNAME -n 4 

Changing reg to de-prioritise IPv6:

New-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\" -Name "DisabledComponents" -Value 0x20 -PropertyType "DWord"

If this reg already exists and has values, change the value:

Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\" -Name "DisabledComponents" -Value 0x20

Restart the computer for this to take effect

BITS

Get-BitsTransfer| 
fl DisplayName,JobState,TransferType,FileList, OwnerAccount,BytesTransferred,CreationTime,TransferCompletionTime

Filter out common bits jobs in the local environment (below is just an example):

Get-BitsTransfer|
? displayname -notmatch "WU|Office|MachineName|configjson" |
fl DisplayName,JobState,TransferType,FileList, OwnerAccount,BytesTransferred,CreationTime,TransferCompletionTime

Hunt down BITS transfers that are UPLOADING (indicator of data exfiltration):

Get-BitsTransfer| 
? TransferType -match "Upload" | 
fl DisplayName,JobState,TransferType,FileList, OwnerAccount,BytesTransferred,CreationTime,TransferCompletionTime

Remoting queries

Enumerating

Created Powershell sessions:

Get-PSSession

WinRM sessions:

get-wsmaninstance -resourceuri shell -enumerate | 
select Name, State, Owner, ClientIP, ProcessID, MemoryUsed, 
@{Name = "ShellRunTime"; Expression = {[System.Xml.XmlConvert]::ToTimeSpan($_.ShellRunTime)}},
@{Name = "ShellInactivity"; Expression = {[System.Xml.XmlConvert]::ToTimeSpan($_.ShellInactivity)}}

Remoting permissions:

Get-PSSessionConfiguration | 
fl Name, PSVersion, Permission

Check constrained language:

$ExecutionContext.SessionState.LanguageMode

RDP settings

To check if RDP is allowed on an endpoint:

if ((Get-ItemProperty "hklm:\System\CurrentControlSet\Control\Terminal Server").fDenyTSConnections -eq 0){write-host "RDP Enabled" } 
else { echo "RDP Disabled" }

To block RDP:

Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -name "fDenyTSConnections" -value 1

Firewall:

Disable-NetFirewallRule -DisplayGroup "Remote Desktop"

Firewall queries

Enumerating

To list firewall profile names:

(Get-NetFirewallProfile).name

Get enabled rules of a specific profile:

Get-NetFirewallProfile -Name Public | Get-NetFirewallRule | ? Enabled -eq "true"

Firewall rules

Show enabled firewall rules:

Get-NetFirewallRule | ? Enabled -eq "true"

Firewall rules for inbound traffic:

Get-NetFirewallRule | ? direction -eq "inbound"

Firewall rules for outbound traffic:

Get-NetFirewallRule | ? direction -eq "outbound"

Stack filters:

Get-NetFirewallRule -Enabled True -Direction Inbound

Isolate endpoint

New-NetFirewallRule -DisplayName "Block all outbound traffic" -Direction Outbound -Action Block | out-null; 
New-NetFirewallRule -DisplayName "Block all inbound traffic" -Direction Inbound -Action Block | out-null; 
$adapter = Get-NetAdapter|foreach { $_.Name } ; Disable-NetAdapter -Name "$adapter" -Confirm:$false; 
Add-Type -AssemblyName PresentationCore,PresentationFramework; 
[System.Windows.MessageBox]::Show('Your Computer has been Disconnected from the Internet for Security Reasons. Please do not try to re-connect to the internet. Contact Security. ',' Security Alert',[System.Windows.MessageBoxButton]::OK,[System.Windows.MessageBoxImage]::Information)

SMB queries

Enumerating

List shares:

Get-SMBShare

List SMB connections:

Get-SmbConnection |
select Dialect, Servername, Sharename | sort Dialect 

Response

Remove an SMB share:

Remove-SmbShare -Name ShareToBeRemoved -Confirm:$false -verbose

Process queries

Enumerating

Processes and TCP connections:

Get-NetTCPConnection |
select LocalAddress,localport,remoteaddress,remoteport,state,@{name="process";Expression={(get-process -id $_.OwningProcess).ProcessName}}, @{Name="cmdline";Expression={(Get-WmiObject Win32_Process -filter "ProcessId = $($_.OwningProcess)").commandline}} | 
sort Remoteaddress -Descending | ft -wrap -autosize

Show all processes and their associated user:

get-process * -Includeusername

Hunting

Suspicious processes from users:

gwmi win32_process | 
Select Name,@{n='Owner';e={$_.GetOwner().User}},CommandLine | 
sort Name -unique -descending | Sort Owner | ft -wrap -autosize

Get specific info about the full path binary that a process is running:

gwmi win32_process | 
Select Name,ProcessID,@{n='Owner';e={$_.GetOwner().User}},CommandLine | 
sort name | ft -wrap -autosize | out-string

Get specific info a process is running:

get-process -name "nc" | ft Name, Id, Path,StartTime,Includeusername -autosize 

Check whether a specific process is running or not:

$process = "danger";
if (ps |  where-object ProcessName -Match "$process") {Write-Host "$process successfully installed on " -NoNewline ; hostname} else {write-host "$process absent from " -NoNewline ; hostname}

Get hash of a process:

foreach ($proc in Get-Process | select path -Unique){try
{ Get-FileHash $proc.path -Algorithm sha256 -ErrorAction stop |
ft hash, path -autosize -HideTableHeaders | out-string -width 800 }catch{}}

List all DLLs loaded with a process:

get-process -name "memestask" -module | fl 

Identify process CPU usage:

(Get-Process -name "someupdate").CPU | fl 

Sort by least CPU-intensive processes:

gps | Sort CPU |
Select -Property ProcessName, CPU, ID, StartTime | 
ft -autosize -wrap | out-string -width 800

Response

Stop a process:

Get-Process -Name "dangerousprocess" | Stop-Process -Force -Confirm:$false -verbose

Recurring task queries

Scheduled tasks

schtasks /query /FO CSV /v | convertfrom-csv |
where { $_.TaskName -ne "TaskName" } |
select "TaskName","Run As User", Author, "Task to Run"| 
fl | out-string

A specific schtask:

Get-ScheduledTask -Taskname "wifi*" | fl *

The commands a task is running:

$task = Get-ScheduledTask | where TaskName -EQ "meme task"; 
$task.Actions

Stop a task:

Get-ScheduledTask "dangeroustask" | Stop-ScheduledTask -Force -Confirm:$false -verbose

All schtask locations:

HKLM\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tree
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tasks
C:\Windows\System32\Tasks
C:\Windows\Tasks
C:\windows\SysWOW64\Tasks\

Check for tasks missing from the C:\Windows directories, but present in the Registry:

$Reg=(Get-ItemProperty -path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\tree\*").PsChildName
$XMLs = (ls C:\windows\System32\Tasks\).Name
Compare-Object $Reg $XMLs

Threat actors can manipulate scheduled tasks such that Task Scheduler no longer has visibility of the recuring task. Querying the Registry locations HKLM\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tree and HKLM\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tasks, can reveal some of these sneaky tasks.

Loop and parse \Taskcache\Tasks Registry location for scheduled tasks, and parse Actions to show the underlying binary/commands for the schtask. Note: the Unicode string reduces the chances of getting invalid characters, and the regex will assist in stripping each string of junk.

(Get-ItemProperty "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tasks\*").PSChildName | 
Foreach-Object {
  write-host "----Schtask ID is $_---" -ForegroundColor Magenta ;
  $hexstring = Get-ItemProperty "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tasks\$_" | Select -ExpandProperty Actions;
  $fixedstring = [System.Text.Encoding]::Unicode.GetString($hexstring) -replace '[^a-zA-Z0-9\\._\-\:\%\/\$ ]', ' ';
  write-host $fixedstring
}

Then, with a binary/one-liner that seems suspicious, query it in the other Registry location:

$ID = "{XYZ}" ; 
get-itemproperty -path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tree\*" | 
? Id -Match "$ID" | fl *Name,Id,PsPath

Then kill these Registry schtask entriesvia Regedit’s GUI.

Programs running at startup

Get-CimInstance Win32_StartupCommand | Select-Object Name, command, Location, User | Format-List 

List the files in each user’s startup folder:

(gci "c:\Users\*\appdata\roaming\microsoft\windows\start menu\programs\startup\*").fullname

Extract from the path User, Exe, and print machine name:

(gci "c:\Users\*\appdata\roaming\microsoft\windows\start menu\programs\startup\*").fullname | 
foreach-object {$data = $_.split("\\");write-output "$($data[2]), $($data[10]), $(hostname)"}

Check the first couple lines of files’ contents:

(gci "c:\Users\*\appdata\roaming\microsoft\windows\start menu\programs\startup\*").fullname | 
foreach-object {write-host `n$_`n; gc $_ -encoding byte| fhx |select -first 5}

Programs at login

Create HKU drive:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS

List all user’s environments:

(gp "HKU:\*\Environment").UserInitMprLogonScript

Collect SID of target user with related logon task:

gp "HKU:\*\Environment" | FL PSParentPath,UserInitMprLogonScript

Insert SID and convert it into username

gwmi win32_useraccount | 
select Name, SID | 
? SID -match "" #insert SID between quotes 

Confirm via whatif flag this is the right key:

remove-itemproperty "HKU:\SID-\Environment\" -name "UserInitMprLogonScript" -whatif

Delete it:

remove-itemproperty "HKU:\SID-\Environment\" -name "UserInitMprLogonScript" -verbose

Programs at PowerShell

Adversaries can link their persistence mechanisms to a PowerShell profile, executing their code every time PowerShell is started.

Confirm the profile you are querying:

echo $Profile

Show PowerShell profile contents:

type $Profile

edit the profile and remove the persistence (notepad $Profile), or:

(gci C:\Users\*\Documents\WindowsPowerShell\*profile.ps1, C:\Windows\System32\WindowsPowerShell\v1.0\*profile.ps1).FullName|
Foreach-Object {
  write-host "----$_---" -ForegroundColor Red ; 
  gc $_ | select-string -notmatch function
}

Jobs

Find out what scheduled jobs are on the machine:

Get-ScheduledJob | fl * 

Get details:

Get-ScheduledJob | Get-JobTrigger | 
Ft -Property @{Label="ScheduledJob";Expression={$_.JobDefinition.Name}},ID,Enabled, At, frequency, DaysOfWeek

Kill job:

Disable-ScheduledJob -Name evil_sched
Unregister-ScheduledJob -Name eviler_sched
Remove-Job -id <id>

Check with Get-ScheduledJob it was removed. If it persists, tack on -Force -Confirm:$false -verbose to Unregister-ScheduledJob or Remove-Job.

WMI Persistence

Get-CimInstance -Namespace root\Subscription -Class __FilterToConsumerBinding
Get-CimInstance -Namespace root\Subscription -Class __EventFilter
Get-CimInstance -Namespace root\Subscription -Class __EventConsumer

Removing it:

gcim -Namespace root\Subscription -Class __EventFilter | 
? Name -eq "EVIL" | Remove-CimInstance -verbose
gcim -Namespace root\Subscription -Class __EventConsumer| 
? Name -eq "EVIL" | Remove-CimInstance -verbose

It is easier to use gwmi here instead of gcim:

gwmi -Namespace root\Subscription -Class __FilterToConsumerBinding | 
? Consumer -match "EVIL" | Remove-WmiObject -verbose

Run Keys

Finding Run Evil with a pwsh for-loop can collect the contents of the four registry locations.

Create HKU drive:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS
(gci HKLM:\Software\Microsoft\Windows\CurrentVersion\Run, HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce, HKU:\*\Software\Microsoft\Windows\CurrentVersion\Run, HKU:\*\Software\Microsoft\Windows\CurrentVersion\RunOnce ).Pspath |
Foreach-Object {
  write-host "----Reg location is $_---" -ForegroundColor Magenta ; 
  gp $_ | 
  select -property * -exclude PS*, One*, vm* | #exclude results here
  FL
}

Removing Run Evil:


Create HKU drive:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS

List the malicious reg by path:

get-itemproperty "HKU:\SID\Software\Microsoft\Windows\CurrentVersion\RunOnce" | select -property * -exclude PS* | fl

Pick the EXACT name of the Run entry to remove. Copy-paste it, include any * or !:

Remove-ItemProperty -Path "HKU:\SID-\Software\Microsoft\Windows\CurrentVersion\RunOnce" -Name "*EvilerRunOnce" -verbose

Check:

get-itemproperty "HKU:\*\Software\Microsoft\Windows\CurrentVersion\RunOnce" | select -property * -exclude PS* | fl

Other malicious run locations

Folders can be the locations of persistence:

Create HKU drive:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS
$folders = @("HKU:\*\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders","HKU:\*\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders","HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders","HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders")
foreach ($folder in $folders) {
  write-host "----Reg key is $folder--- -ForegroundColor Magenta "; 
  get-itemproperty -path "$folder"  | 
  select -property * -exclude PS* | fl
}

Svchost startup persistence:

get-itemproperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost"

Winlogon startup persistence:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS
(gci "HKU:\*\Software\Microsoft\Windows NT\CurrentVersion\Winlogon").PSPath | 
Foreach-Object {
  write-host "----Reg location is $_---" -ForegroundColor Magenta ; 
  gp $_ | 
  select -property * -exclude PS* |
  FL
}

For evidence of Run Key execution, query the Microsoft-Windows-Shell-Core/Operational log to find evidence if a registry run key was successful in executing:

get-winevent -filterhashtable @{ logname = "Microsoft-Windows-Shell-Core/Operational" ; ID = 9707} |
select TimeCreated, Message, 
@{Name="UserName";Expression = {$_.UserId.translate([System.Security.Principal.NTAccount]).value}}  | 
sort TimeCreated -desc| fl

Screensaver persistence:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS
gp "HKU:\*\Control Panel\Desktop\" | select SCR* | fl

Then collect the .scr listed in the full path, and reverse engineer the binary.

To collect wallpaper info:

gp "HKU:\*\Control Panel\Desktop\" | select wall* | fl

Query Group Policy

Group policy can be leveraged and weaponised to propogate malware and even ransomware across the entire domain.

Query the changes made in the last X days:

# collect the domain name as a variable to use later
$domain = (Get-WmiObject -Class win32_computersystem).domain; 
Get-GPO -All -Domain $domain | 
?{ ([datetime]::today - ($_.ModificationTime)).Days -le 10 } | sort
# Change the digit after -le to the number of days you want to go back for

Query GPO Scripts

List all policies, and see where a policy contains a script or executable (change the include at the end to whatever is needed):

$domain = (Get-WmiObject -Class win32_computersystem).domain;
gci -recurse \\$domain\\sysvol\$domain\Policies\ -file -include *.exe, *.ps1

To discover where GPO scripts live:

$domain = (Get-WmiObject -Class win32_computersystem).domain;
gci -recurse \\$domain\\sysvol\*\scripts

File queries

Enumerating

Use the tree command to list the directories and files underneath the current working directory.

Use wildcard paths and files. For example, to list the pwsh scripts in their \temp directory:

gci "C:\Users\*\AppData\Local\Temp\*" -Recurse -Force -File  -Include *.ps1, *.psm1, *.txt | 
ft lastwritetime, name -autosize | 
out-string -width 800

Check if a specific file or path is alive to quickly check for specific vulnerabilities. For example, for CVE-2021-21551:

test-path -path "C:\windows\temp\DBUtil_2_3.Sys"

Test if files and directories are present or absent after for example, cleaning up:

$Paths = "C:\windows" , "C:\temp", "C:\windows\system32", "C:\WhateverDir" ; 
if (Get-Process | where-object Processname -eq "explorer") {write "process working"} else {
foreach ($Item in $Paths){if (test-path $Item) {write "$Item present"}else{write "$Item absent"}}}

Query file contents:

Get-item C:\Temp\Some.csv |
select-object -property @{N='Owner';E={$_.GetAccessControl().Owner}}, *time, versioninfo | fl 

Alternate data streams

Show streams that aren’t the normal $DATA:

get-item evil.ps1 -stream "*" | where stream -ne ":$DATA"

Details:

get-content evil.ps1 -steam "evil_stream"

Read hex of file:

gc .\evil.ps1 -encoding byte | 
Format-Hex

File types

Recursively look for particular file types, and once you find the files get their hashes:

Get-ChildItem -path "C:\windows\temp" -Recurse -Force -File -Include *.aspx, *.js, *.zip|
Get-FileHash |
format-table hash, path -autosize | out-string -width 800

Compare two files’ hashes:

get-filehash "C:\windows\sysmondrv.sys" , "C:\Windows\HelpPane.exe"

File manipulation

Copy multiple files to new location:

copy-item "C:\windows\System32\winevt\Logs\Security.evtx", "C:\windows\System32\winevt\Logs\Windows PowerShell.evtx" -destination C:\temp

Grep in Powershell

Change the string in the second line:

ls C:\Windows\System32\* -include '*.exe', '*.dll' | 
select-string 'RunHTMLApplication' -Encoding unicode | 
select-object -expandproperty path -unique

With ascii:

ls C:\Windows\System32\* -include '*.exe', '*.dll' | 
select-string 'RunHTMLApplication' -Encoding Ascii | 
select-object -expandproperty path -unique

Registry queries

HKCU signifies Current User and results will be limited to the user you are. To see more results, you should change HKCU to HKU.

To set up HKU:

New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS;
(Gci -Path HKU:\).name

Enumerating

Show all registry keys:

(Gci -Path Registry::).name

Show HK users:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS;(Gci -Path HKU:\).name

List the entries in the HKEY_CURRENT_USER subkey:

(Gci -Path HKCU:\).name

Read a registry entry:

Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\SysmonDrv"

Useful registry keys

Query timezone on an endpoint. Look for the TimeZoneKeyName value:

HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation

Query the drives on the endpoint:

HKLM\SYSTEM\MountedDevices

Query the services on this machine, and if you want to see more about one of the results just add it to the path:

HKLM\SYSTEM\CurrentControlSet\Services
HKLM\SYSTEM\CurrentControlSet\Services\ACPI

Query software on this machine:

HKLM\Software
HKLM\Software\PickOne

Query SIDs:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\[Long-SID-Number-HERE]

Query user’s wallpaper. Once we know a user’s SID, we can go and look at these things:

HKU\S-1-5-18\Control Panel\Desktop\

Query if credentials on a machine are being cached maliciously (network-wide):

if ((Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest").UseLogonCredential -eq 1){write-host "Plain text credentials forced, likely malicious, on host: " -nonewline ;hostname } else { echo "/" }

Response

Remediate:

reg add "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" /v UseLogonCredential /t REG_DWORD /d 0

To remove a registry entry, create HKU drive:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS

Read the registry to make sure this is the entry to be removed:

get-itemproperty -Path 'HKU:\*\Keyboard Layout\Preload\'

Remove it by piping it to Remove-Item:

get-itemproperty -Path 'HKU:\*\Keyboard Layout\Preload\' | Remove-Item -Force -Confirm:$false -verbose

Check by trying to re-read it:

get-itemproperty -Path 'HKU:\*\Keyboard Layout\Preload\'

If a Registry is under HKCU, it’s not clear exactly WHO it can belong to. Get the SID of the user, and traverse to that as the path as HKU. For conversion:

mount -PSProvider Registry -Name HKU -Root HKEY_USERS

Driver queries

Bootkits and rootkits have been known to weaponise drivers.

You can utilise Winbindex to investigate drivers, and compare a local copy with the indexed info. Malicious copies may have a hash that doesn’t match, or a file size that doesn’t quite match.

Printer drivers

Some legitimate drivers are not signed ; some malicious drivers sneak a signature.

Get-PrinterDriver | fl Name, *path*, *file* 

System drivers

Unsigned drivers:

gci C:\Windows\*\DriverStore\FileRepository\ -recurse -include *.inf|
Get-AuthenticodeSignature | 
? Status -ne "Valid" | ft -autosize

gci -path C:\Windows\System32\drivers -include *.sys -recurse -ea SilentlyContinue | 
Get-AuthenticodeSignature | 
? Status -ne "Valid" | ft -autosize

Signed drivers:

Get-WmiObject Win32_PnPSignedDriver | 
fl DeviceName, FriendlyName, DriverProviderName, Manufacturer, InfName, IsSigned, DriverVersion

Alternatives:

gci -path C:\Windows\System32\drivers -include *.sys -recurse -ea SilentlyContinue | 
Get-AuthenticodeSignature | 
? Status -eq "Valid" | ft -autosize 
gci C:\Windows\*\DriverStore\FileRepository\ -recurse -include *.inf|
Get-AuthenticodeSignature | 
? Status -eq "Valid" | ft -autosize

Other Drivers

All 3rd party drivers:

Get-WindowsDriver -Online -All | 
fl Driver, ProviderName, ClassName, ClassDescription, Date, OriginalFileName, DriverSignature 

Drivers by Registry

Leverage the Registry to look at drivers. Knowing the driver, give the full path and wildcard the end:

get-itemproperty -path "HKLM:\System\CurrentControlSet\Services\DBUtil*"

Not knowing the path, filter for drivers that have \drivers\ in their ImagePath:

get-itemproperty -path "HKLM:\System\CurrentControlSet\Services\*"  | 
? ImagePath -like "*drivers*" | 
fl ImagePath, DisplayName

Drivers by time

To look for the drivers that exist via directory diving, focus on .INF and .SYS files, and sort by the time last written:

gci C:\Windows\*\DriverStore\FileRepository\ -recurse -include *.inf | 
sort-object LastWriteTime -Descending |
ft FullName,LastWriteTime | out-string -width 850
gci -path C:\Windows\System32\drivers -include *.sys -recurse -ea SilentlyContinue | 
sort-object LastWriteTime -Descending |
ft FullName,LastWriteTime | out-string -width 850

DLL queries

Enumerating

Get the DLLs involved with a specific process, their file location, their size, and if they have a company name:

get-process -name "google*" | 
Fl @{l="Modules";e={$_.Modules | fl FileName, Size, Company | out-string}}

Details on the DLLs that a process may call on:

(gps -name "google").Modules.FileName | Get-AuthenticodeSignature

As with drivers, if a DLL is signed or unsigned, it doesn’t immediately signal malicious. There are plenty of official files on a Windows machine that are unsigned. Equally, malicious actors can get signatures for their malicious files too.

Get invalid DLL’s:

gci -path C:\Windows\*, C:\Windows\System32\*  -file -force -include *.dll |
Get-AuthenticodeSignature | ? Status -ne "Valid" 

Collect valid DLL’s:

gci -path C:\Windows\*, C:\Windows\System32\*  -file -force -include *.dll |
Get-AuthenticodeSignature | ? Status -eq "Valid" 

Details on a specific DLL:

gci -path C:\Windows\twain_32.dll | get-filehash
gci -path C:\Windows\twain_32.dll | Get-AuthenticodeSignature 

AV queries

If Defender is active, you can leverage PowerShell to query what threats the AV is facing:

Get-MpThreatDetection | Format-List threatID, *time, ActionSuccess

Take the ThreatID and drill down further:

Get-MpThreat -ThreatID

Defender scan

Trigger Defender scan:

Update-MpSignature; Start-MpScan

Full scan:

Start-MpScan -ScanType FullScan

Specify path:

Start-MpScan -ScanPath "C:\temp"

Check if Defender has been manipulated

Adversaries enjoy simply turning off / disabling the AV. Query the status of Defender’s detections:

Get-MpComputerStatus | fl *enable*

And enjoy adding exclusions to AV (Note that some legitimate tooling and vendors ask that some directories and executables are placed on the exclusion list):

Get-MpPreference | fl *Exclu*

Responses

If some values have been disabled, re-enable with:

Set-MpPreference -DisableRealtimeMonitoring $false -verbose

Get rid of the exclusions made by the adversary:

Remove-MpPreference -ExclusionProcess 'velociraptor' -ExclusionPath 'C:\Users\IEUser\Pictures' -ExclusionExtension '.pif' -force -verbose

Log queries

From a security perspective, you probably don’t want to query logs on the endpoint itself. Endpoints after a malicious event can not be trusted. You’re better to focus on the logs that have been forwarded from endpoints and centralised in your SIEM.

Enumerating

Show logs that are enabled and whose contents are not empty:

Get-WinEvent -ListLog *|
where-object {$_.IsEnabled -eq "True" -and $_.RecordCount -gt "0"} | 
sort-object -property LogName | 
format-table LogName -autosize -wrap

Details on a log:

Get-WinEvent -ListLog Microsoft-Windows-Sysmon/Operational | Format-List -Property * 

Last time a log was written to:

(Get-WinEvent -ListLog Microsoft-Windows-Sysmon/Operational).lastwritetime 

Response

An adversary can evade endpoint logging. It’s better to use logs that have been taken to a central point, to trust EVENT IDs from Sysmon, or trust network traffic if you have it.

Powershell

WhatIf

-WhatIf is quite a cool flag, as it will tell you what will happen if you run a command. So before you kill a vital process for example, if you include -WhatIf you’ll gain some insight into the irreversible future!

Clip

You can pipe straight to your clipboard. Then all you have to do is paste.

Stop truncation

Powershell truncates stuff, even when it’s really unhelpful and pointless for it to do so. To fix this, use out-string at the very end of whatever you’re running and is getting truncated:

| outstring -width 250

Transcripts

Trying to report back what you ran, when you ran, and the results of your commands can become a chore. If you forget a pivotal screenshot, … Instead, ask PowerShell to create a log of everything we run and see on the command line.

Start-Transcript -path "C:\Malware_Analysis\$(Get-Date -UFormat "%Y_%b_%d_%a_UTC%Z")\PwSh_transcript.log" -noclobber -IncludeInvocationHeader

At the end of the analysis, stop all transcripts:

Stop-transcript

You can open up your Powershell transcript with notepad.

Resources