In this article we'll build on our knowledge of the Get-Process and Get-CimInstance -Class Win32_Process PowerShell features to investigate malicious code running on a Windows system. If you haven't already read Part 1, spend some time checking that out first, then come back here to see how you can apply these commands to your incident response or threat hunting investigations.
On a Windows system, I will investigate the running processes using PowerShell. I will look for suspicious indicators, including:
- Is the process a new or unrecognized name?
- Is the process name random-looking?
- Is the process running from a non-standard path or a temporary directory?
- Is the parent-child relationship suspicious?
- Is the parent process suspicious?
- Are the command-line options suspicious?
For this article I've used Metasploit to compromise and deploy malware on a Windows 10 system. Let's apply the process threat hunting techniques to evaluate this system.
Although I used Metasploit as the attack framework for demonstration purposes here, these techniques can be useful for any Windows incident response analysis, even when attackers leverage different attack frameworks or Command & Control (C2) tools.
Process Name Analysis
It's straightforward to get a list of running process names using Get-Process:
PS C:\WINDOWS\system32> Get-Process
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
189 13 2828 9212 0.06 2140 1 calc
78 5 2272 4040 0.02 6180 0 cmd
230 14 4012 22256 0.05 2540 1 conhost
102 7 6232 10252 0.00 5504 0 conhost
116 8 6344 10900 0.02 6196 0 conhost
...
0 0 60 8 0 0 Idle
539 27 10424 45648 0.28 6116 1 LockApp
1219 27 8352 20492 0.84 668 0 lsass
0 0 40 0 0.00 1704 0 Memory Compression
213 13 1972 2104 0.05 2780 0 MicrosoftEdgeUpdate
223 13 2876 10544 0.08 4600 0 msdtc
614 72 160820 93036 2.44 3468 0 MsMpEng
147 9 1520 7052 0.02 5424 0 nginx
146 10 1960 7388 0.00 5496 0 nginx
233 13 2988 14284 0.05 2640 1 notepad
115 7 1692 5840 0.03 3380 0 nssm
616 41 48136 62912 2.38 632 0 powershell
562 34 37676 47492 1.41 4484 0 powershell
596 31 60556 72248 0.58 4808 1 powershell
533 31 35368 43240 1.09 5852 0 powershell
0 8 4500 76984 0.30 92 0 Registry
...
Using this output, we can look for process names that are unusual. The problem with this kind of analysis if that you have to have a baseline of what is usual to compare to. We'll cover using PowerShell for differential baseline analysis in a later article.
Sometimes you can spot process names that appear randomly generated (e.g., iciSUnrH.exe), but it's not always obvious. It's a good idea to do a quick glance at process names to see if anything unusual jumps out, but you'll also want to investigate other process characteristics before coming to any kind of conclusion about a threat.
Processes Path Analysis
We saw in the first part of this article about how Get-Process can't tell us the system path for the process. For this, we turn to Get-CimInstance:
Name ProcessId Path
---- --------- ----
System Idle Process 0
System 4
Registry 92
smss.exe 316
csrss.exe 420
wininit.exe 524
csrss.exe 532
winlogon.exe 616 C:\WINDOWS\system32\winlogon.exe
services.exe 660
lsass.exe 668 C:\WINDOWS\system32\lsass.exe
..
RuntimeBroker.exe 5704 C:\Windows\System32\RuntimeBroker.exe
SearchIndexer.exe 5824 C:\WINDOWS\system32\SearchIndexer.exe
LockApp.exe 6116 C:\Windows\SystemApps\Microsoft.LockApp_cw5n1h2txyewy\LockApp.exe
RuntimeBroker.exe 5160 C:\Windows\System32\RuntimeBroker.exe
svchost.exe 5276 C:\WINDOWS\system32\svchost.exe
GoogleCrashHandler.exe 6068 C:\Program Files (x86)\Google\Update\1.3.36.132\GoogleCrashHandler.exe
...
Most users will run programs from the C:\Windows, C:\Program Files and C:\Program Files (x86) directories. We can uses Where-Object to quickly get a list of processes launched from a path outside of one of these directories:
PS C:\WINDOWS\system32> Get-CimInstance -Class Win32_Process | Where-Object -Property Path -NotLike "C:\Windows\*" | Where-Object -Property Path -NotLike "C:\Program Files*" | Select-Object Name, ProcessId, Path
Name ProcessId Path
---- --------- ----
System Idle Process 0
System 4
Registry 92
smss.exe 316
csrss.exe 420
wininit.exe 524
csrss.exe 532
services.exe 660
Memory Compression 1704
SecurityHealthService.exe 6252
SgrmBroker.exe 2096
svchost.exe 5788
calc.exe 2140 c:\temp\calc.exe
svchost.exe 4888
svchost.exe 4848
In this example we uses two notlike expressions with wildcards to exclude the C:\Windows and C:\Program Files/C:\Program Files (x86) directories. It's a little awkward and inefficient since we invoke Where-Object multiple times, but it gets the job done.
Alternatively, you can use a PowerShell array to specify a list elements using regular expression syntax in an array:
PS C:\WINDOWS\system32> Get-CimInstance -Class Win32_Process | Where-Object -Property Path -NotMatch @("Windows|Program Files") | Select-Object Name, ProcessId, Path
Name ProcessId Path
---- --------- ----
System Idle Process 0
System 4
Registry 92
smss.exe 316
csrss.exe 420
wininit.exe 524
csrss.exe 532
services.exe 660
Memory Compression 1704
SecurityHealthService.exe 6252
SgrmBroker.exe 2096
svchost.exe 5788
calc.exe 2140 c:\temp\calc.exe
svchost.exe 4888
svchost.exe 2932
The Windows|Program Files regular expression syntax (where | is used as an or indicator between each string) is far from perfect, but it gets the job done, is a little faster, and can be more easily extended if you identify other directories you want exclude from the results.
Result: That C:\temp\calc.exe process is sus. Users don't run programs from temporary directories normally; it's a good candidate for further investigation.
Process Parent/Child Relationship Analysis
With the C:\temp\calc process in mind, let's investigate the parent/child relationships. We know the process ID for C:\temp\calc.exe is 2140 from the previous command. Let's identify the parent process:
PS C:\WINDOWS\system32> Get-CimInstance -Class Win32_Process | Where-Object -Property name -EQ 'calc.exe' | Select-Object -Property Name, ProcessId, ParentProcessId, Path
Name ProcessId ParentProcessId Path
---- --------- --------------- ----
calc.exe 2140 6604 c:\temp\calc.exe
Here we learn the parent process ID is 6604. Let's find out what that process name and path is:
PS C:\WINDOWS\system32> Get-CimInstance -Class Win32_Process | Where-Object -Property ProcessId -EQ 6604 | Select-Object -Property Name, ProcessId, ParentProcessId, Path
PS C:\WINDOWS\system32>
This output indicates that 6604 is not an active process. There was once a process with ID 6604 that launched C:\temp\calc.exe, but that process has since exited, making this Calc process an orphan. This is not necessarily an Indicator of Compromise (IoC) though, as terminating parent processes are fairly common.
TIP: Be careful when specifying the property name that you are using for comparison in the Where-Object command. PowerShell has a bad habit of not warning you about some mistakes. For example, if you were to type Where-Object -Property PrcessId -EQ 6604 (note the missing o in PrcessId), PowerShell will not return an error, and will return no matches for the query.
Let's see if there are any child processes associated with C:\temp\calc.exe:
PS C:\WINDOWS\system32> Get-CimInstance -Class Win32_Process | Where-Object -Property ParentProcessId -EQ 2140 | Select-Object -Property Name, ProcessId, ParentProcessId, Path
Name ProcessId ParentProcessId Path
---- --------- --------------- ----
cmd.exe 5144 2140 C:\WINDOWS\SysWOW64\cmd.exe
This output tells us that C:\temp\calc.exe has launched a CMD shell. We can repeat this query, this time identifying any child processes of the CMD shell using process ID 5144:
PS C:\WINDOWS\system32> Get-CimInstance -Class Win32_Process | Where-Object -Property ParentProcessId -EQ 5144 | Select-Object -Property Name, ProcessId, ParentProcessId, Path
Name ProcessId ParentProcessId Path
---- --------- --------------- ----
conhost.exe 360 5144 C:\WINDOWS\system32\conhost.exe
powershell.exe 3776 5144 C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe
Here we learn that the CMD shell has launched a PowerShell session and the [conhost.exe](https://www.howtogeek.com/howto/4996/what-is-conhost.exe-and-why-is-it-running/) process.
We could keep digging into the child relationships, looking at the PowerShell process ID 3776 next. At this point though, this behavior of a "Calc" process launching CMD, and PowerShell is even more sus. It not outside of the possibility of normal though, so let's keep investigating.
TIP: Eelco Ligtvoet has an excellent PowerShell script to display an indented tree-like view of all processes and child processes. It uses some deprecated PowerShell functionality, but it works with the latest PowerShell at the time of this writing. Check it out as an option to quickly and visually assess all processes and their parent/child relationships.
Process Command Line Analysis
Probably my GO-TO technique for Windows process analysis is to get a list of all processes from Get-CimInstance with name, handle count, process ID, parent process ID, path, and command line details. Since this is a lot of data (some command lines can be very long), I export all of this into a CSV file and check it out in Excel:
PS C:\WINDOWS\system32> Get-CimInstance -Class Win32_Process | Select-Object -Property Name, HandleCount, ProcessId, ParentProcessId, Path, CommandLine | Export-Csv E:\TEMP\processes.csv
After turning on text wrapping, it's easy to spot very long command lines, including this example of a PowerShell script supplied as a hidden window:
c:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -W Hidden -nop -ep bypass -NoExit -E JABwAE4AYgBqAFkASQBLAHEAQQBUAGQAbQAgAD0AIABAACIADQAKAFsARABsAGwASQBtAHAAbwByAHQAKAAiAGsAZQByAG4AZQBsADMAMgAuAGQAbABsACIAKQBdAA0ACgBwAHUAYgBsAGkAYwAgAHMAdABhAHQAaQBjACAAZQB4AHQAZQByAG4AIABJAG4AdABQAHQAcgAgAFYAaQByAHQAdQBhAGwAQQBsAGwAbwBjACgA...
eAA2ADYALAAwAHgAOABiACwAMAB4AGMALAAwAHgANABiACwAMAB4ADgAYgAsADAAeAA1ADgALAAwAHgAMQBjACwAMAB4ADEALAAwAHgAZAAzACwAMAB4ADgAYgAsADAAeAA0ACwAMAB4ADgAYgAsADAAeAAxACwAMAB4AGQAMAAsADAAeAA4ADkALAAwAHgANAA0ACwAMAB4ADIANAAsADAAeAAy
Using a slightly different Where-Object clause, you can filter the Get-CimInstance results to only return command lines that have over 1000 characters:
PS C:\WINDOWS\system32> Get-CimInstance -Class Win32_Process | Where-Object { $_.CommandLine.Length -gt 1000 } | Select-Object -Property Name, HandleCount, ProcessId, ParentProcessId, Path, CommandLine, WriteTransferCount, ReadTransferCount, WorkingSetSize
Name : powershell.exe
HandleCount : 653
ProcessId : 6724
ParentProcessId : 5784
Path : C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe
CommandLine : "C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" -W Hidden -nop -ep bypass -NoExit -E JABwAE4AYgBqAFkASQBLAHEAQQBUAGQAbQAgAD0AIABAACIADQAKAFsARABsAGwASQBtAHAAbwByAHQA
...
KAAiAGsAZQByAEwASgBvAGsAVQBlADoAOgBDAHIAZQBhAHQAZQBUAGgAcgBlAGEAZAAoADAALAAwACwAJABlAEIATAB1AEsAdABvAHkAUgBEACwAMAAsADAALAAwACkADQAKAA==
WriteTransferCount : 59745
ReadTransferCount : 1003197
WorkingSetSize : 63037440
NOTE: This is similar to one of the checks we use in DeepBlueCLI to detect threats on a Windows system.
Let's break this command down, step-by-step:
- Get-CimInstance -Class Win32_Process | : Get all of the process information for the host
- Where-Object: Pipe the results to the Where-Object cmdlet
- { $_.CommandLine.Length -gt 1000 } |: Start a loop with {; for each process identified by $_, retrieve the CommandLine property Length attribute, determining if it is greater than 1000 characters; then end the loop with } and pass the processes that matched this test to the next command in the pipeline
- Select-Object -Property Name, HandleCount, ProcessId, ParentProcessId, Path, CommandLine, WriteTransferCount, ReadTransferCount, WorkingSetSize: Retrieve the selected properties
Summary
In the first article, we looked at the PowerShell Get-Process and Get-CimInstance -Class Win32_Process commands to retrieve process information. In this article, we applied those concepts to investigate a Windows system for threats.
Using these commands and the PowerShell pipeline, we looked at the steps to investigate new or unrecognized processes, random-looking names, non-standard paths, curious parent/child relationships, and command line parameters. As an incident response analyst, these are important techniques to apply when evaluating a live system, potentially revealing threats to your organization.
Until next time!
-Joshua Wright
Return to Getting Started With PowerShell
Joshua Wright is the author of SANS SEC504: Hacker Tools, Techniques, and Incident Handling, a faculty fellow for the SANS Institute, and a senior technical director at Counter Hack.