Known-Good Processes — Baselines for Windows, Linux & macOS
You can't spot evil if you don't know what good looks like. This is the "know normal, find evil" reference — the legitimate core processes on each OS, their expected parent, path, user, and count, and the red flags that mean something is impersonating or abusing them. Use it during enumeration, triage, and threat hunting.
Last verified2026-06 See also: Endpoint Detection · Digital Forensics · Windows Registry
The Core Principle
Malware constantly masquerades as legitimate system processes (MITRE T1036) — naming itself svchost.exe, hiding in [kworker], or pretending to be mdworker. Signature-based detection misses novel malware, but anomaly-against-baseline catches the masquerade, because the impostor almost always gets one of these wrong:
┌─ Is it the RIGHT NAME? (svchost.exe vs scvhost.exe / svhost.exe — typosquats) ├─ in the RIGHT PATH? (C:\Windows\System32\svchost.exe — not %TEMP%\svchost.exe) ├─ with the RIGHT PARENT? (svchost's parent must be services.exe — not winword.exe) ├─ as the RIGHT USER? (lsass runs as SYSTEM — not as 'bob') ├─ the RIGHT COUNT? (exactly ONE lsass.exe — not three) └─ doing the RIGHT THING? (lsass should have NO children; explorer shouldn't beacon)
Memory hook"right name, path, parent, user, count, behavior." Impersonation is hard to get perfect. A malicious
svchost.exemight nail the name but run from the wrong folder, lack the-kflag, or be spawned by Word instead of services.exe. Detection is just checking those six attributes against the known baseline — get fluent in the baseline and the anomalies jump out. This is the entire premise of the SANS Hunt Evil process-tree approach.
Windows — Core Processes & The Boot Tree
Windows has a rigid, predictable process tree at boot. Memorizing it is the single highest-value Windows detection skill.
System Idle Process (PID 0)
└─ System (PID 4) kernel; no real parent, no disk image
└─ smss.exe Session Manager — System32, parent=System(4)
├─ csrss.exe one per session (usually 2); parent (smss) EXITS
├─ wininit.exe (session 0) parent (smss) exits
│ ├─ services.exe the service control manager
│ │ ├─ svchost.exe (many) ALWAYS launched with -k <group>
│ │ ├─ spoolsv.exe print spooler
│ │ └─ (other service hosts)
│ ├─ lsass.exe ★ ONE instance, NO children, SYSTEM
│ └─ lsm.exe / fontdrvhost.exe
└─ winlogon.exe (session 1) handles interactive logon
└─ userinit.exe launches the shell, then EXITS
└─ explorer.exe ★ runs as the USER, from C:\Windows\ (not System32)
└─ (user apps: browsers, Office, etc.)| Process | Expected path | Expected parent | Runs as | Normal count | Red flags |
|---|---|---|---|---|---|
| System | (none — kernel) | — (PID 0) | SYSTEM | 1 (PID 4) | A "System" not at PID 4; an image path |
| smss.exe | System32 | System (4) | SYSTEM | 1 master | Wrong path; wrong parent; multiple persistent |
| csrss.exe | System32 | smss (exited) | SYSTEM | 1 per session (~2) | Misspelled (csrsss); from %TEMP%; has odd children |
| wininit.exe | System32 | smss (exited) | SYSTEM | 1 (session 0) | More than one; wrong path |
| services.exe | System32 | wininit | SYSTEM | 1 | Not parented by wininit; wrong path |
| lsass.exe | System32 | wininit | SYSTEM | 1 | Multiple; ANY child process; not from wininit; wrong path; misspelled (lsas/lsass1) — classic credential-dump masquerade |
| svchost.exe | System32 | services.exe | SYSTEM / NETWORK / LOCAL SERVICE | many | No -k flag; parent NOT services.exe; wrong path; running as a normal user |
| winlogon.exe | System32 | smss (exited) | SYSTEM | 1 per interactive session | Wrong path; spawning cmd/powershell |
| explorer.exe | C:\Windows\ | userinit (exited) | the logged-on user | 1 per logged-on user | In System32 or %TEMP%; running as SYSTEM; making network beacons |
| taskhostw.exe | System32 | svchost | user/SYSTEM | varies | Wrong parent/path |
Memory hookthe four facts that catch most Windows masquerade:
lsass.exeis a lone wolfexactly one, parented by wininit, and it spawns no children.
lsass.exe → cmd.exeor twolsass.exe= credential theft / masquerade. (And watch forlsasss.exe,lsas.exe.)svchost.exealways has-kand its parent is always
services.exe. A svchost with no-k, or spawned by anything else, is malware wearing a costume.explorer.exeruns as the user, fromC:\Windows\not System32, not as SYSTEM.
Most Win32 processes live inSystem32a system-named process running from
%TEMP%,%APPDATA%,C:\Users\Public, or a user profile is the loudest signal there is.
Also classic-suspicious regardless of nameOffice apps (winword.exe, excel.exe) or w3wp.exe/mssql spawning cmd.exe/powershell.exe/wscript.exe (macro/webshell), and powershell.exe -enc <base64> or -nop -w hidden.
:: Quick Windows triage — verify the six attributes
tasklist /v :: processes + user
wmic process get Name,ProcessId,ParentProcessId,ExecutablePath,CommandLine
:: PowerShell — show parent + path together
Get-CimInstance Win32_Process | Select Name,ProcessId,ParentProcessId,Path,CommandLine
Get-Process lsass | Select-Object Path :: should be C:\Windows\System32\lsass.exe, count = 1
:: Sysinternals — the go-to, verifies signatures + shows the tree
:: procexp.exe (Process Explorer) ; pslist ; autorunsc -sLinux — Core Processes & Kernel Threads
Linux is more varied (distro/role-dependent), but the skeleton is consistent.
PID 1 systemd (or init on older systems) ← ancestor of all userland; reaps orphans
PID 2 kthreadd ← parent of ALL kernel threads
└─ [kworker/*], [ksoftirqd/*], [migration/*], [rcu_sched], [kswapd0] ...
kernel threads: shown in [brackets], PPID = 2, NO on-disk binary| Process | What it is | Notes / where it lives |
|---|---|---|
| systemd (PID 1) | init + service manager | Parent of daemons; /usr/lib/systemd/systemd |
| kthreadd (PID 2) | kernel thread spawner | Parent of every [bracketed] kernel thread |
| [kworker], [ksoftirqd], [migration], [rcu_*], [kswapd0] | kernel threads | In [brackets], PPID 2, no /proc/PID/exe target |
| systemd-journald / -logind / -udevd / -resolved | systemd subsystems | /usr/lib/systemd/ |
| sshd | SSH daemon | parent systemd; spawns a child per login session |
| cron / crond | scheduled jobs | a classic persistence + privesc spot |
| dbus-daemon, polkitd, NetworkManager, rsyslogd, chronyd, agetty | standard daemons | parent systemd |
Memory hook"kernel threads wear [brackets], have PPID 2, and have no file on disk." The favorite Linux masquerade is a malicious process named like a kernel thread —
[kworker/0:2]— to blend intops. Three tells expose it: (1) a real kernel thread's parent is kthreadd (PID 2) — a fake one isn't; (2) a kernel thread has no executable —ls -l /proc/<pid>/exeis empty/errors, whereas the fake points at a real (often deleted or/tmp) binary; (3) kernel threads use no user memory (RSS ~0). So: bracketed name + non-kthreadd parent + a real exe = malware in a costume.
Linux red flags regardless of name
- Execution from
/tmp,/dev/shm,/var/tmp,/run—ls -l /proc/<pid>/exe - Deleted binary still running —
/proc/<pid>/exe → ...(deleted)(dropped, then unlinked to hide) - A process with a space/odd char appended to look benign (
sshdwith trailing space), high-entropy names, or base64 in the cmdline - Listeners on odd ports / outbound to mining pools (
ss -tunap)
# Quick Linux triage
ps auxf # full process TREE — see parent/child at a glance
ss -tunap # listening + established sockets → owning process
ls -l /proc/<pid>/exe # real path; "(deleted)" or /tmp = suspicious
ls -l /proc/<pid>/cwd # working dir
cat /proc/<pid>/cmdline | tr '\0' ' ' # full command line
# Hunt processes running from temp dirs:
ls -l /proc/*/exe 2>/dev/null | grep -E '/tmp|/dev/shm|/var/tmp|\(deleted\)'macOS — Core Processes
Everything descends from launchd (PID 1) — macOS's combined init and service manager.
PID 0 kernel_task ← the kernel
PID 1 launchd ← init + launchd service manager; ancestor of ALL
├─ LaunchDaemons (system services, root)
└─ per-user launchd → LaunchAgents (user GUI/login services)| Process | What it is | Notes |
|---|---|---|
| kernel_task (PID 0) | the kernel | Also manages thermals |
| launchd (PID 1) | init + service manager | Parent of essentially everything; legit persistence = LaunchAgents/Daemons |
| WindowServer | the GUI/display server | runs as _windowserver |
| loginwindow | login session manager | |
| mds / mds_stores / mdworker | Spotlight indexing | high disk/CPU here is normal indexing — and a common masquerade target |
| cfprefsd | preferences daemon | |
| securityd / trustd | security & cert trust | |
| configd, coreaudiod, bluetoothd, distnoted, UserEventAgent | core daemons | parent launchd |
| Finder, Dock, SystemUIServer | user GUI shell | run as the user |
Memory hookon macOS, "everything's parent is launchd (PID 1), and persistence = LaunchAgents/Daemons." Unlike Windows' rigid tree, macOS is flat under launchd, so the tells shift to code signature + path + plist. The common masquerade is naming malware after a Spotlight worker like
mdworkerormdswhile running it from/tmp,/Users/Shared, or a hidden dir instead of/System/Library/.... Three checks: (1) is the binary code-signed and notarized (codesign -dv --verbose,spctl -a)? — system daemons are signed by Apple; (2) is it in the expected system path or somewhere user-writable? (3) what LaunchAgent/LaunchDaemon plist launched it (~/Library/LaunchAgents,/Library/LaunchDaemons), and is that plist legit? (See macOS hardening.)
# Quick macOS triage
ps auxww # all processes + args
ps -axo pid,ppid,user,comm # parent/child + user
codesign -dv --verbose=4 /path/to/binary # signing identity (Apple? unsigned? ad-hoc?)
spctl --assess --verbose /path/to/app # Gatekeeper assessment
launchctl list # loaded launchd jobs
ls -la ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons # persistence
lsof -i -nP # network connections → processCross-Platform Triage Checklist
For any suspicious process, ask the six baseline questions:
[ ] NAME — exact spelling? (svchost vs scvhost, mdworker vs mdworkers)
[ ] PATH — expected system dir, or %TEMP%/AppData/Public / /tmp / /dev/shm / /Users/Shared?
[ ] PARENT — does the parent make sense? (svchost←services, lsass←wininit, all←launchd/PID1/2)
[ ] USER — right account? (lsass=SYSTEM, explorer=user, kernel thread=root w/ no exe)
[ ] COUNT — expected number? (exactly ONE lsass / wininit / services)
[ ] BEHAVIOR— right children & network? (lsass has NO kids; Office shouldn't spawn powershell)
PLUS:
[ ] Is the binary SIGNED/notarized? (Authenticode on Windows, codesign on macOS, pkg on Linux)
[ ] Is the on-disk file present, or DELETED-but-running? (/proc/pid/exe → (deleted))
[ ] What's it connected to? (ss/netstat/lsof → unexpected egress, beaconing, mining pools)Memory hookthe process tree beats the process list. A flat list of process names hides the masquerade; the parent-child relationship exposes it.
lsass.exelooks fine in a list — butlsass.exespawned bycmd.exe, or spawningcmd.exe, is damning. Always pull the tree (ps auxf, Process Explorer,pstree), because the relationship is the evidence — which is the same reason endpoint detection is built on process lineage.
Interview Questions
Because malware routinely masquerades as legitimate system processes — naming itself svchost.exe, hiding as a bracketed kernel thread, or impersonating a Spotlight worker — and signature detection misses novel samples. What it can't easily fake is the full context: the right name, in the right path, with the right parent, as the right user, in the right count, behaving correctly. So detection becomes anomaly-against-baseline: a process called svchost.exe running from %TEMP%, lacking the -k flag, or parented by Word instead of services.exe is obviously wrong even if I've never seen that specific malware. You can't spot the impostor without knowing what the real one looks like, which is why "know normal, find evil" is the foundation of triage and hunting.
On a healthy Windows host there is exactly one lsass.exe, it lives in System32, it's parented by wininit.exe, it runs as SYSTEM, and critically it spawns no child processes. lsass holds credential material in memory, so it's a prime target. Multiple lsass processes usually means something is masquerading as it — often a credential dumper or malware using the trusted name. lsass spawning a child like cmd.exe or rundll32 is a strong sign of code execution within or injection into lsass, again typically credential theft. And a near-miss spelling like lsas.exe or lsass1.exe, or an lsass running from a non-System32 path, is the same masquerade. Any of these is high-severity because they cluster around credential access.
Real kernel threads have three signatures: their name appears in square brackets in ps like [kworker/0:1], their parent is kthreadd at PID 2, and they have no on-disk executable and essentially no user memory because they live in kernel space. Malware sometimes names itself like a kernel thread to blend into ps output. I expose it by checking those invariants: is its parent actually PID 2, and does /proc/
I verify the six baseline attributes. The path must be C:\Windows\System32\svchost.exe — not a user-writable folder. Its parent must be services.exe; svchost spawned by Office, a browser, or PowerShell is malware. Its command line should include the -k flag naming a service group — legitimate svchost is always launched with -k, so a bare svchost.exe with no -k is a red flag. It should run as SYSTEM, NETWORK SERVICE, or LOCAL SERVICE, not a regular user. The name spelling must be exact, since scvhost or svhost are typosquats. And I'd check its children and network connections for anything out of character, and verify the Authenticode signature. Process Explorer shows the parent, path, command line, and signature together, which makes this a quick check.
Windows has a rigid boot tree, so a lot of detection is verifying the expected parent-child relationships — lsass under wininit, svchost under services. macOS is much flatter: essentially everything descends from launchd at PID 1, so parent-child lineage tells you less. Instead the tells shift to code signature, path, and the launch configuration. System daemons are code-signed and notarized by Apple and live under /System/Library, so I lean on codesign and spctl to check whether a binary is properly signed or unsigned/ad-hoc, and on whether it's running from a user-writable location like /tmp or /Users/Shared. And because persistence on macOS is via LaunchAgents and LaunchDaemons plists, I inspect those plists and launchctl to see what launched a suspicious process. The common masquerade is naming malware after a Spotlight worker like mdworker but running it unsigned from the wrong path.
Because the masquerade hides in a flat list but is exposed by relationships. A process called lsass.exe or svchost.exe looks perfectly normal in a list of names — the malicious context only appears when you see who its parent is and what children it spawned. lsass.exe is fine until you see it was launched by cmd.exe, or that it launched powershell; svchost is fine until you see its parent is winword.exe. The parent-child edge is the actual evidence, which is exactly why endpoint detection is built on process lineage and why I always pull the tree with ps auxf, pstree, or Process Explorer rather than reading a name list. The relationship turns an innocent-looking name into a smoking gun.