How to Find a Suspicious Process on Windows with Get-Process

How to Find a Suspicious Process on Windows with Get-Process

The PC is slow, the fan won't stop, and Task Manager shows a list of names that all look legitimate: svchost.exe appears fifteen times, and one of them could be an impostor. Task Manager shows the name, but not what really matters: where that executable came from, who signed it and who started it. PowerShell's Get-Process, combined with two more cmdlets, answers all three in a few minutes.

What Get-Process gives you

According to Microsoft's documentation, Get-Process lists the processes on the local computer and returns System.Diagnostics.Process objects with dozens of properties. It works on Windows PowerShell 5.1 and PowerShell 7. The ones that matter for triage:

  • Id: the process PID.
  • ProcessName: the name, without the extension.
  • Path: the full path of the executable.
  • StartTime: when the process started.
  • CPU: total processor time used, in seconds (cumulative, not the current percentage).
  • WorkingSet: memory in use, in bytes.

Open PowerShell as administrator. Without elevation, the Path of protected processes may come back empty, and the documentation notes that the -Module and -FileVersionInfo parameters require running as administrator for processes you don't own.

How do I see which processes use the most CPU in PowerShell?

Run Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 to list the ten processes with the most accumulated CPU time.

Get-Process |
  Sort-Object CPU -Descending |
  Select-Object -First 10 Name, Id, CPU, StartTime,
    @{n='MB';e={[math]::Round($_.WorkingSet/1MB)}}, Path

Because CPU is cumulative, an old system service always shows up at the top. Look closely at what is recent: compare StartTime with when the slowness began. An unknown process started in the middle of the night that already has a lot of CPU time deserves investigation.

Step 1: where is the executable running from?

Legitimate programs live in C:\Windows or C:\Program Files. Malware tends to run from folders where a regular user can write: AppData, Temp, Downloads, ProgramData. This filter shows everything outside the two expected locations:

Get-Process |
  Where-Object { $_.Path -and $_.Path -notmatch '^C:\\(Windows|Program Files)' } |
  Select-Object Name, Id, StartTime, Path |
  Sort-Object Path

The regex covers both Program Files and Program Files (x86). Plenty of legitimate software shows up here, like Chrome, Discord and Teams, which install into AppData. So the list is a starting point, not a verdict. The red flag is a name that imitates a system process (svchost.exe, explorer.exe, lsass.exe) running outside C:\Windows\System32.

Step 2: does the executable have a valid digital signature?

Get-AuthenticodeSignature reads a file's Authenticode signature. Per the documentation, it is only available on Windows, and when a file has both an embedded and a catalog signature, it uses the catalog one. Cross it with the processes:

Get-Process | Where-Object Path | Select-Object -Unique Path |
  ForEach-Object {
    $s = Get-AuthenticodeSignature -LiteralPath $_.Path
    [pscustomobject]@{
      Status = $s.Status
      Signer = $s.SignerCertificate.Subject
      Path   = $_.Path
    }
  } | Where-Object Status -ne 'Valid' | Format-Table -AutoSize

The result is the executables in memory without a valid signature (NotSigned, HashMismatch, NotTrusted). Small software and in-house company tools are often unsigned, so not everything here is malware. But a HashMismatch, which indicates a file changed after it was signed, calls for immediate attention.

One important warning: a valid signature does not mean a safe file. It proves the file was not altered after signing and that the certificate is trusted, nothing more. Attackers use stolen certificates and abuse legitimate Microsoft binaries. The signature helps rule out the obvious; it doesn't clear a process.

Step 3: who started the process?

Get-Process doesn't show the parent process or the command line. For that, use the WMI Win32_Process class:

Get-CimInstance Win32_Process |
  Select-Object ProcessId, ParentProcessId, Name, CommandLine |
  Sort-Object ParentProcessId

With ParentProcessId you can rebuild the process tree. Some relationships are normal and others are suspicious:

  • Normal: the legitimate svchost.exe is started by services.exe and runs from C:\Windows\System32.
  • Suspicious: powershell.exe or cmd.exe as a child of winword.exe, excel.exe or the browser. This is the classic pattern of a malicious macro or a document that fires a command.
  • Suspicious: a CommandLine with -EncodedCommand, -WindowStyle Hidden or a URL download.

Be careful when pasting this output into a ticket or forum: the command line can contain passwords and tokens passed as arguments.

Step 4: is the process talking to the internet?

Link the PID to network connections with Get-NetTCPConnection:

Get-NetTCPConnection -State Established |
  Select-Object RemoteAddress, RemotePort, OwningProcess,
    @{n='Processo';e={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
  Sort-Object Processo

A process you don't recognize, with an established connection to an external IP on an unusual port, completes the picture. The post on auditing open ports with Get-NetTCPConnection covers this cmdlet in detail.

I found something odd. Now what?

Before ending the process, write down the evidence: the Path, the CommandLine and the file hash, which you generate with Get-FileHash to look up in a reputation service such as VirusTotal. Then end it:

Stop-Process -Id 1234 -Force

Killing the process is only the first step. If it is malware, it probably has a way back: a scheduled task, a service or a startup key. If the process reappears after you end it, that is a sign of persistence. On a work machine, tell the security team before touching anything else, and on a personal PC consider disconnecting from the network and running a full Microsoft Defender scan.

The bigger lesson

A process name proves nothing, because anyone can call an executable svchost.exe. What identifies it is the combination: path, signature, parent and network behavior. To keep a permanent record of this kind of activity, set up the PowerShell audit logs and, if you want to go deeper into the triage routine, move on to reviewing scheduled tasks and persistence.

Comments