Process Enumeration

Written by: Iron Hulk Published: Jan 15, 2025 Reading time: Iron Hulk
Back to Blogs

بسم الله الرحمن الرحيم


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?

A

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 SYSTEM processes.
  • 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.exe before encryption.
B

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; dump lsass.exe.
C

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