Training
Get a free hour of SANS training

Experience SANS training through course previews.

Learn More
Learning Paths
Can't find what you are looking for?

Let us help.

Contact us
Resources
Join the SANS Community

Become a member for instant access to our free resources.

Sign Up
For Organizations
Interested in developing a training plan to fit your organization’s needs?

We're here to help.

Contact Us
Talk with an expert

Month of PowerShell: Process Threat Hunting, Part 2

We'll continue our look at PowerShell threat hunting through process analysis, identifying Command & Control/C2 threats on a Windows system.

Authored byJoshua Wright
Joshua Wright

#monthofpowershell

In this article we'll build on our knowledge of the [code]Get-Process[/code] and [code]Get-CimInstance -Class Win32_Process[/code] 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.

Process Name Analysis

It's straightforward to get a list of running process names using [code]Get-Process[/code]:

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., [code]iciSUnrH.exe[/code]), 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 [code]Get-Process[/code] can't tell us the system path for the process. For this, we turn to [code]Get-CimInstance[/code]:

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 [code]C:\Windows[/code], [code]C:\Program Files[/code] and [code]C:\Program Files (x86)[/code] directories. We can uses [code]Where-Object[/code] 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 [code]C:\Windows[/code] and [code]C:\Program Files[/code]/[code]C:\Program Files (x86)[/code] directories. It's a little awkward and inefficient since we invoke [code]Where-Object[/code] 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 [code]Windows|Program Files[/code] regular expression syntax (where [code]|[/code] 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 [code]C:\temp\calc.exe[/code] 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 [code]C:\temp\calc[/code] process in mind, let's investigate the parent/child relationships. We know the process ID for [code]C:\temp\calc.exe[/code] 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 [code]C:\temp\calc.exe[/code], 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.

Let's see if there are any child processes associated with [code]C:\temp\calc.exe[/code]:

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 [code]C:\temp\calc.exe[/code] 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 [[code]conhost.exe[/code]](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.

Process Command Line Analysis

Probably my GO-TO technique for Windows process analysis is to get a list of all processes from [code]Get-CimInstance[/code] 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

Excel spreadsheet showing process name, ID, parent ID, path and command line options. The selected command line shows a long PowerShell command with Base64 characters.

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 [code]Where-Object[/code] clause, you can filter the [code]Get-CimInstance[/code] 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

Let's break this command down, step-by-step:

  • [code]Get-CimInstance -Class Win32_Process | [/code]: Get all of the process information for the host
  • [code]Where-Object[/code]: Pipe the results to the [code]Where-Object[/code] cmdlet
  • [code]{ $_.CommandLine.Length -gt 1000 } |[/code]: Start a loop with [code]{[/code]; for each process identified by [code]$_[/code], retrieve the [code]CommandLine[/code] property [code]Length[/code] attribute, determining if it is greater than 1000 characters; then end the loop with [code]}[/code] and pass the processes that matched this test to the next command in the pipeline
  • [code]Select-Object -Property Name, HandleCount, ProcessId, ParentProcessId, Path, CommandLine, WriteTransferCount, ReadTransferCount, WorkingSetSize[/code]: Retrieve the selected properties

Summary

In the first article, we looked at the PowerShell [code]Get-Process[/code] and [code]Get-CimInstance -Class Win32_Process[/code] 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.

Month of PowerShell: Process Threat Hunting, Part 2 | SANS Institute