بسم الله الرحمن الرحيم
Imagine you’re a detective walking into a busy office building. So your first move is to scope out who’s working there, like their names, roles, and what they’re doing. Process enumeration is the digital version of that, it’s the technique of listing every active process (programs, apps, services) running on a computer, along with details. Process enumeration is a versatile technique used by attackers, pentesters, administrators, and security systems alike to gather critical system information on local machines and networked environments. Performed before, during, and after attacks or as part of routine maintenance, it serves purposes ranging from exploitation and evasion to monitoring and troubleshooting. Understanding its role and recognizing unusual activity is essential for both leveraging it responsibly and defending against its misuse
Intel We Collect
- Process names
chrome.exe, avp.exe - Unique identifiers (PID)
- Memory & CPU footprint
- Parent ⇄ child lineage
- Session, integrity, security context
Why It Matters
- Tag crown‑jewel targets like
lsass.exe - Camouflage inside legitimate traffic to skirt EDR/AV watchlists
- Stage vectors for process injection, hollowing, or token theft
Quick Recon Commands
- Windows CLI:
tasklist /v - PowerShell:
Get-Process | ft -AutoSize - Unix/Linux:
ps aux
Why Malware Developers Enumerate Processes
Hunt Security Tools
Malware scans for processes like msmpeng.exe (Windows Defender) or egui.exe (ESET) or CrowdStrike. Spot one? It’ll try to kill the process, evade detection, or disable the service.
Find Injection Hosts
To hide, malware often injects malicious code into legitimate processes (Ex: explorer.exe). Enumeration identifies trusted, long-running targets—making the attack invisible.
Privilege Escalation
Processes running as SYSTEM or Administrator are jackpots. Malware hijacks them to gain high-level access, install rootkits, or steal credentials.
Avoiding Analysis Environments (Sandboxes)
Malware checks for processes tied to sandboxes (vmtoolsd.exe) or debuggers (x32dbg.exe). If detected, it shuts down to avoid analysis.
Data-Theft Recon
Targeting specific apps? Banking trojans hunt for chrome.exe (to steal logins) or cryptocurrency wallets like Electrum.exe.
Thread Enumeration
A malware may enumerate threads within a process to hijack execution flow or inject shellcode into a running thread.
Enumeration is the compass that guides every modern attack.
Before or After the Breach?
Before the Attack (Recon Phase)
- Environment Profiling: What AV/EDR is installed? or what other security features are enabled.
- Identify weak spots: Which process runs as
SYSTEMprocesses. - Sandbox/VM Detection: Malware may enumerate processes early to check for analysis environments (
vmtoolsd.exe). - Example: Ransomware like LockBit enumerates processes first to terminate backup services
sqlagent.exebefore encryption.
During the Attack (Execution Phase)
- Inject into newly launched processes like
calc.exe. - Check if security tools were/are restarted..
- Finding Privilege Escalation vulnerable processes
spoolsv.exe(PrintNightmare). - Injecting into a long-running process
svchost.exe; dumplsass.exe.
After the Attack (Persistence/Cleanup/Lateral Movement)
Enumerate across the network with PsExec, WMI, or similar tools, then pivot into remote processes for long-term access.
Enumerate early, adapt mid-attack, maintain footholds after the fact.
The Bottom Line
Process enumeration is the Swiss Army knife in a hacker’s toolkit and it is used before, during, and after attacks to gather intel, exploit weaknesses, and evade consequences.
For defenders, spotting suspicious enumeration (Ex: mass OpenProcess calls) is a red flag.
Understanding this cat-and-mouse game isn’t just technical, it’s critical for building resilient systems. Moreover, process enumeration is not inherently malicious, sysadmins and EDRs use it too
Why Defenders Must Watch the Process List
Attack chains almost always surface in process telemetry before they touch disk or siphon data. Rogue cmd.exe spawning
rundll32.exe under the wrong parent? A silent driver installer kicked off by svchost.exe? Spotting these anomalies
in real time turns response from cleanup to prevention.
Catch Precursors
Lateral-movement frameworks enumerate and inject long before ransomware detonates. Early alerts shut doors before the blast.
Verify Controls
If EDR blocks unsigned injections, NtCreateThreadEx → lsass.exe should never appear. Process logs keep policy honest.
Baseline Normal
Finance servers run daylight batch jobs; identity servers hum 24/7. Process-mix drift flags misconfigurations as fast as intrusions.
Tooling that makes Monitoring Practical
Native OS Hooks
ETW (Microsoft-Windows-Kernel-Process, Threat-Intelligence), Sysmon (1, 10, 11), auditd, systemd-journald, and macOS Unified Logging provide the raw feed.
Query Engines
osquery, PSReadProcess, WMIC/WMI, PowerShell’s Get-CimInstance craft ad-hoc views without extra agents.
Detection Stacks
Sigma, Splunk/ELK, Elastic Endpoint, Wazuh, CrowdStrike, Defender for Endpoint, SentinelOne transform events into enriched alerts.
Container & Cloud Sensors
Falco (eBPF), GKE Audit Policy, AWS GuardDuty EKS, Azure Defender for Containers keep pods honest across clusters.
Memory Forensics & IR Kits
Velociraptor, Rekall, Volatility pull process tables straight from RAM or dumps when malware tampers with APIs.
High-Value Analytics in Action
Parent-Child Lineage Scoring
Flag execution chains like Word → wscript → certutil.
Token & Integrity Shifts
Alert when a medium-IL user suddenly spawns a SYSTEM helper.
Module Whitelisting
Detect unsigned or rarely-seen DLLs loaded inside critical hosts.
Burst Frequency Detection
Catch brute-force loops reading every PID in milliseconds.
Drift & Absence Monitoring
Silence from an EDR sensor can warn as loudly as a new binary.
Methods for Process Enumeration
Using: Enum Processes
// Project Name: Process Enumeration via EnumProcesses
// By: Iron Hulk
using System;
using System.Runtime.InteropServices;
using System.Text;
class Program
{
[DllImport("psapi.dll", SetLastError = true)]
static extern bool EnumProcesses(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] uint[] processIds,
uint arraySizeBytes,
out uint bytesCopied
);
[DllImport("psapi.dll", SetLastError = true)]
static extern uint GetModuleFileNameEx(
IntPtr hProcess,
IntPtr hModule,
[Out] StringBuilder lpFilename,
uint nSize
);
static void Main()
{
uint[] processIds = new uint[1024];
uint bytesCopied;
if (!EnumProcesses(processIds, (uint)(processIds.Length * sizeof(uint)), out bytesCopied))
{
Console.WriteLine($"EnumProcesses failed: {Marshal.GetLastWin32Error()}");
return;
}
int numProcesses = (int)(bytesCopied / sizeof(uint));
Console.WriteLine("PID\tProcess Name\tExecutable Path");
Console.WriteLine("-----------------------------------------------");
for (int i = 0; i < numProcesses; i++)
{
uint pid = processIds[i];
if (pid == 0) continue; // Skip Idle process
try
{
IntPtr hProcess = System.Diagnostics.Process.GetProcessById((int)pid).Handle;
var sb = new StringBuilder(1024);
if (GetModuleFileNameEx(hProcess, IntPtr.Zero, sb, (uint)sb.Capacity) != 0)
{
Console.WriteLine($"{pid}\t{System.Diagnostics.Process.GetProcessById((int)pid).ProcessName}\t{sb}");
}
}
catch { } // Skip inaccessible processes
}
}
}
Using: WTS Enumerate Processes
// Project Name: Process Enumeration via WTSEnumerateProcesses
// By: Iron Hulk
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern bool WTSEnumerateProcesses(
IntPtr hServer,
int Reserved,
int Version,
ref IntPtr ppProcessInfo,
ref int pCount
);
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern void WTSFreeMemory(IntPtr pMemory);
[StructLayout(LayoutKind.Sequential)]
struct WTS_PROCESS_INFO
{
public int SessionId;
public int ProcessId;
public IntPtr pProcessName;
public IntPtr pUserSid;
}
static void Main()
{
IntPtr ppProcessInfo = IntPtr.Zero;
int pCount = 0;
if (!WTSEnumerateProcesses(IntPtr.Zero, 0, 1, ref ppProcessInfo, ref pCount))
{
Console.WriteLine($"WTSEnumerateProcesses failed: {Marshal.GetLastWin32Error()}");
return;
}
Console.WriteLine("PID\tProcess Name");
Console.WriteLine("----------------------");
for (int i = 0; i < pCount; i++)
{
IntPtr ptr = (IntPtr)((long)ppProcessInfo + i * Marshal.SizeOf(typeof(WTS_PROCESS_INFO)));
var info = (WTS_PROCESS_INFO)Marshal.PtrToStructure(ptr, typeof(WTS_PROCESS_INFO));
string processName = Marshal.PtrToStringAnsi(info.pProcessName) ?? "(Unknown)";
Console.WriteLine($"{info.ProcessId}\t{processName}");
}
WTSFreeMemory(ppProcessInfo);
}
}
Using: Nt Query System Information
// Project Name: Process Enumeration via NtQuerySystemInformation
// By: Iron Hulk
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
internal static class Program
{
static void Main()
{
foreach (var p in NtEnum())
Console.WriteLine($"{p.pid,6} → {p.name}");
}
private static IEnumerable<(int pid, string name)> NtEnum()
{
const int SystemProcessInformation = 5;
const int STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004);
int len = 0x100000;
IntPtr buf = Marshal.AllocHGlobal(len);
try
{
int status;
int need;
while ((status = NtQuerySystemInformation(
SystemProcessInformation, buf, len, out need))
== STATUS_INFO_LENGTH_MISMATCH)
{
len = need;
Marshal.FreeHGlobal(buf);
buf = Marshal.AllocHGlobal(len);
}
if (status < 0) yield break; // call failed
IntPtr ptr = buf;
for (; ; )
{
var spi = (SYSTEM_PROCESS_INFORMATION)
Marshal.PtrToStructure(ptr,
typeof(SYSTEM_PROCESS_INFORMATION));
string image;
if (spi.ImageName.Length > 0 && spi.ImageName.Buffer != IntPtr.Zero)
image = Marshal.PtrToStringUni(
spi.ImageName.Buffer, spi.ImageName.Length / 2);
else
image = "System Idle Process";
yield return ((int)spi.UniqueProcessId, image);
if (spi.NextEntryOffset == 0) break;
ptr = IntPtr.Add(ptr, (int)spi.NextEntryOffset);
}
}
finally { Marshal.FreeHGlobal(buf); }
}
// ---------------- P/Invoke & structs ----------------
[DllImport("ntdll.dll")]
private static extern int NtQuerySystemInformation(
int systemInformationClass,
IntPtr systemInformation,
int systemInformationLength,
out int returnLength);
[StructLayout(LayoutKind.Sequential)]
private struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_PROCESS_INFORMATION
{
public uint NextEntryOffset;
public uint NumberOfThreads;
private long Reserved1, Reserved2, Reserved3;
public long CreateTime, UserTime, KernelTime;
public UNICODE_STRING ImageName;
public int BasePriority;
public IntPtr UniqueProcessId;
public IntPtr InheritedFromUniqueProcessId;
}
}
Using: Powershell Command
Get-CimInstance Win32_Process | Select @{n='PID';e={$_.ProcessId}}, @{n='PPID';e={$_.ParentProcessId}}, @{n='Threads';e={$_.ThreadCount}}, @{n='Name';e={$_.Name}} | ft -a