Process enumeration
TL;DR
List or find running processes by name across Windows + Linux.
Pure Go on top of CreateToolhelp32Snapshot / /proc. Used by
credentials/lsassdump to find lsass, by process/tamper/phant0m
to find the EventLog svchost, and by every "find this process by
name" workflow that doesn't want a Windows-specific dependency.
Primer
Process enumeration is the "hello world" of post-exploitation
discovery. Every implant eventually wants to find lsass.exe,
explorer.exe, the EventLog svchost, the user's browser. The
package wraps the platform's standard listing API and surfaces
the same Process struct shape on both OSes.
The technique itself is universally invisible — every Task
Manager, every ps, every container runtime calls these
APIs. EDRs do not flag the enumeration; they correlate it
against subsequent suspicious actions (lsass open, token
theft, process hollowing).
How It Works
flowchart LR
subgraph win [Windows]
TH[CreateToolhelp32Snapshot<br>TH32CS_SNAPPROCESS]
TH --> P32[Process32First/Next walk]
P32 --> WIN[Process<br>PID/PPID/Name]
end
subgraph linux [Linux]
PROC[walk /proc/<pid>/]
PROC --> COMM[read comm + status]
COMM --> LIN[Process<br>PID/PPID/Name]
end
WIN --> OUT[List or FindByName or FindProcess]
LIN --> OUT
The comm file on Linux carries the truncated 16-byte process
name; for the full executable path use process/session.ImagePath
(Windows-only) or read /proc/<pid>/exe.
API Reference
type Process
| Field | Type | Description |
|---|---|---|
PID | uint32 | Process ID |
PPID | uint32 | Parent process ID |
Name | string | Image base name (e.g., notepad.exe) |
Functions
| Symbol | Description |
|---|---|
List() ([]Process, error) | Snapshot of every running process |
FindByName(name string) ([]Process, error) | Filter by image name (case-insensitive on Windows) |
FindProcess(pred) (*Process, error) | First match where pred(name, pid, ppid) returns true |
Examples
Simple — list everything
import "github.com/oioio-space/maldev/process/enum"
procs, _ := enum.List()
for _, p := range procs {
fmt.Printf("%5d %5d %s\n", p.PID, p.PPID, p.Name)
}
Composed — find lsass + open it
import (
"github.com/oioio-space/maldev/credentials/lsassdump"
"github.com/oioio-space/maldev/process/enum"
wsyscall "github.com/oioio-space/maldev/win/syscall"
)
procs, _ := enum.FindByName("lsass.exe")
if len(procs) == 0 {
return
}
caller := wsyscall.New(wsyscall.MethodIndirect, nil)
h, err := lsassdump.OpenLSASS(caller)
defer lsassdump.CloseLSASS(h)
Advanced — predicate-based search
Find a child of explorer.exe that runs from a user-writable path — typical pattern for finding an injection target.
explPID := uint32(0)
procs, _ := enum.FindByName("explorer.exe")
if len(procs) > 0 {
explPID = procs[0].PID
}
target, _ := enum.FindProcess(func(name string, pid, ppid uint32) bool {
return ppid == explPID && strings.HasSuffix(name, ".exe")
})
OPSEC & Detection
| Artefact | Where defenders look |
|---|---|
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS) calls | Universal API — every Task Manager, AV, EDR uses it. Not a useful signal. |
| Sustained polling of process list | Behavioural EDR may flag a process that calls Process32Next thousands of times per second |
Enumeration immediately followed by OpenProcess(lsass, VM_READ) | EDR rule correlation — credential dumping pattern |
/proc walks from non-shell processes | Linux EDR rules; rare unless the implant polls aggressively |
D3FEND counters:
- D3-PA — behavioural correlation of enumeration + subsequent open/inject calls.
Hardening for the operator:
- Enumerate once, cache, reuse — sustained polling stands out.
- Scope
FindProcesspredicates tightly so the package short-circuits on the first match (avoid full snapshot walks when you only need one PID). - Pair with
process/session.Activeinstead of full enum when you only need logged-in interactive sessions.
MITRE ATT&CK
| T-ID | Name | Sub-coverage | D3FEND counter |
|---|---|---|---|
| T1057 | Process Discovery | full — name + predicate search across Windows + Linux | D3-PA |
Limitations
Process32Nextrace conditions. Processes can exit between snapshot and read; Windows handles this gracefully butFindProcessmay miss a process that existed at the start of the walk.- No image-path resolution.
Processcarries name only. For full path useprocess/session.ImagePath(Windows) or read/proc/<pid>/exe(Linux). - No command-line. PEB-based command-line surfacing is out of scope; use a separate ETW / WMI query when the command line matters.
- Linux
commis truncated. 15 chars + nul. Process names longer than that need/proc/<pid>/cmdline[0].
See also
process/session— Windows-specific session + threads + modules + image-path enumeration.credentials/lsassdump— primary consumer (find lsass).process/tamper/phant0m— consumer (find EventLog svchost).- Operator path.
- Detection eng path.