A Volatility 3 plugin for enumerating and analysing Windows named pipes from memory images, including reading buffered pipe data directly from kernel memory. Built to support incident responders and threat hunters investigating C2 frameworks, lateral movement, and privilege escalation via IPC.
Named pipes are one of the oldest and most abused Windows inter-process communication mechanisms. Modern C2 frameworks — Cobalt Strike, Metasploit, Sliver, Covenant, PoshC2, and Brute Ratel — rely heavily on named pipes for:
- Beacon-to-beacon lateral movement (SMB pivoting via
\\<host>\pipe\<name>) - Local privilege escalation (token impersonation over named pipe servers)
- Post-exploitation staging (injected shellcode communicating via pipe back to the implant)
Despite this, most memory forensics workflows handle named pipes as an afterthought — buried inside generic handle output with no context, no access-right decoding, and no threat-pattern matching. Crucially, no existing offline tool reads the data sitting inside a pipe's kernel buffer without requiring live system access.
ipc was built to change that. It provides three complementary views of the named pipe namespace from a single memory image, with built-in decoding of Windows access rights, pattern matching against known C2 pipe names, and — uniquely — direct extraction of buffered pipe data from NPFS kernel structures, mirroring the malfind hex-dump output format.
Move ipc.py inside the Volatility 3 Windows plugins directory:
volatility3/
framework/
plugins/
windows/
ipc.py ← PipeList, PipeScan, PipeFind
No additional dependencies beyond a working Volatility 3 installation are required.
The plugin ships three independent classes that operate on different data sources within the memory image.
Pool-scan the image for named pipe objects. Recovers both live and orphaned pipes. Includes process and PID lookup, access decoding, and C2 detection.
python vol.py -f memory.dmp windows.ipc.PipeListExample output:
Offset PipeName Process PID GrantedAccessStr Suspicious
0xfa800... \MSSE-4892-server rundll32.exe 4892 FILE_READ_DATA|FILE_WRITE_DATA|SYNCHRONIZE CobaltStrike (MSSE)
0xfa801... \lsass lsass.exe 692 FILE_READ_DATA|FILE_WRITE_DATA|READ_CONTROL N/A
PipeList Walks the physical address space searching for _FILE_OBJECT pool allocations whose DeviceObject.DeviceType equals FILE_DEVICE_NAMED_PIPE (0x0011). Because it scans directly at the pool layer, it can recover:
- Pipes that are still live but whose handles have been removed from process handle tables (e.g. after
CloseHandleon the server end while the pipe is still connected) - Pipes belonging to terminated processes whose memory has not yet been reclaimed
- Orphaned or partially freed pipe objects
For each discovered _FILE_OBJECT, the plugin walks every live process' handle table to determine which process (or processes) currently hold a handle to that pipe object, then decodes the highest GrantedAccess mask seen across all those handles.
Iterates every process returned by PsList (or a filtered subset by PID), then walks each process' handle table. Handles of type File are cast to _FILE_OBJECT and filtered by DeviceType. This gives a direct, process-centric mapping: which process, which handle, which pipe, with what access rights, and in which direction (server vs. client, inbound vs. outbound).
Walk handle tables of all (or filtered) processes to enumerate live named pipe handles. Provides handle values, pipe direction, and process creation times.
# All processes
python vol.py -f memory.dmp windows.ipc.PipeScan
# Specific PIDs
python vol.py -f memory.dmp windows.ipc.PipeScan --pid 4892 1234
# Specific process by physical offset
python vol.py -f memory.dmp windows.ipc.PipeScan --offset 0xfa8003e8b080Example output:
PID Process HandleValue GrantedAccess PipeName PipeDirection CreateTime
4892 rundll32.exe 0x3c 0x0012019f \MSSE-4892-server Duplex (Server) 2026-03-09 10:14:22
692 lsass.exe 0x1c4 0x00120089 \lsass Inbound (Server) 2026-03-09 09:58:04
Read the raw bytes currently present in each pipe's kernel-mode CCB region. Each result is rendered as a hex+ASCII dump (identical to malfind) and its printable ASCII content is extracted into the Strings column.
# All processes
python vol.py -f memory.dmp windows.ipc.PipeFind
# Filter to specific PIDs
python vol.py -f memory.dmp windows.ipc.PipeFind --pid 4892
# Limit maximum bytes read per pipe (default: 512)
python vol.py -f memory.dmp windows.ipc.PipeFind --size 1024
# Specific process by physical offset
python vol.py -f memory.dmp windows.ipc.PipeFind --offset 0xfa8003e8b080Pipes whose CCB address could not be resolved (e.g. unmapped or paged out) still appear in the output with N/A for the buffer columns, confirming the pipe was found but carries no readable resident data.
The output mirrors malfind: a Hexdump column renders the region as a hex+ASCII block, and a Strings column lists contiguous printable ASCII runs of four or more characters — often revealing C2 beacon traffic, SMB path names, credentials, or other plaintext data transmitted through the pipe without requiring any network capture.
Example output:
PID Process PipeName HandleValue GrantedAccessStr BufferOffset Hexdump Strings
4892 rundll32.exe \MSSE-4892-server 0x3c FILE_READ_DATA|FILE_WRITE_DATA|SYNCHRONIZE 0xffff... 4d 5a 90 00 ... MZ | This program
692 svchost.exe \lsass 0x1c4 FILE_READ_DATA|READ_CONTROL N/A N/A N/A
Extends PipeScan by resolving FILE_OBJECT.FsContext2 (the NPFS Client Control Block, CCB) and reading up to --size bytes from that kernel non-paged-pool region directly from the memory image:
PipeFind resolves FILE_OBJECT.FsContext2, which points to the NPFS Client Control Block (CCB) — an undocumented kernel non-paged-pool structure that owns the pipe's data queue. If FsContext2 is zero or unmapped, it falls back to FILE_OBJECT.FsContext (the File Control Block, FCB). Up to --size bytes are read from that address and rendered as both a hex+ASCII block and as extracted printable strings.
_FILE_OBJECT
├─ FsContext2 → NP_CCB (Client Control Block, opaque)
│ └─ pipe data queue bytes read as raw blob
└─ FsContext → NP_FCB (File Control Block, fallback)
└─ pipe data queue bytes read as raw blob
Because NPFS internal structure definitions are not exported through public PDB symbols, the CCB is treated as an opaque byte region rather than a parsed structure. Data written into the pipe but not yet consumed by the reader is typically present somewhere inside this region at acquisition time.
Because NPFS does not export its CCB/FCB type definitions through the public PDB symbols, FsContext2 is treated as an opaque pointer and the --size bytes at that address are presented as-is. The actual pending payload (if any) is typically located somewhere within this region in a LIST_ENTRY-linked data-queue chain. Some pipes will show no meaningful data if the buffer was empty or already paged out.
| Feature | PipeList |
PipeScan |
PipeFind |
|---|---|---|---|
| Physical-layer pool scan (recovers orphaned/closed objects) | ✅ | ❌ | ❌ |
| Handle-table walk (live process mapping) | ✅ (reverse lookup) | ✅ | ✅ |
| Pipe name | ✅ | ✅ | ✅ |
| Owning process name | ✅ | ✅ | ✅ |
| Owning process PID | ✅ | ✅ | ✅ |
| Handle value | ❌ | ✅ | ✅ |
BufferOffset (FsContext2/FsContext kernel VA) |
❌ | ❌ | ✅ |
Raw GrantedAccess (hex) |
❌ | ✅ | ❌ |
Decoded GrantedAccess (symbolic flags) |
✅ | ❌ | ✅ |
| Pipe direction (Inbound/Outbound/Duplex + Server/Client) | ❌ | ✅ | ❌ |
| Process creation time | ❌ | ✅ | ❌ |
| Kernel buffer hex-dump | ❌ | ❌ | ✅ |
| Extracted ASCII strings | ❌ | ❌ | ✅ |
| C2 suspicious-name detection | ✅ | ❌ | ❌ |
PID filter (--pid) |
❌ | ✅ | ✅ |
Process offset filter (--offset) |
❌ | ✅ | ✅ |
Buffer size limit (--size) |
❌ | ❌ | ✅ |
Rather than displaying a raw hex mask, PipeList decodes the GrantedAccess value into named constants sourced from ntifs.h and WinNT.h:
FILE_READ_DATA | FILE_WRITE_DATA | FILE_CREATE_PIPE_INSTANCE | READ_CONTROL | SYNCHRONIZE
Recognised flags include FILE_READ_DATA, FILE_WRITE_DATA, FILE_CREATE_PIPE_INSTANCE, FILE_READ_EA, FILE_WRITE_EA, FILE_EXECUTE, FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE, and ACCESS_SYSTEM_SECURITY. Any unrecognised bits are appended as a hex remainder.
PipeScan computes a direction label from the GrantedAccess mask on each handle. PipeFind uses the same label to identify which kernel queue (inbound vs. outbound) a buffer entry was read from.
| Label | Meaning |
|---|---|
Inbound (Client) |
Handle has FILE_READ_DATA; process is reading from the pipe |
Outbound (Client) |
Handle has FILE_WRITE_DATA; process is writing to the pipe |
Duplex (Client) |
Both read and write; fully bidirectional client |
Duplex (Server) |
Both read/write and FILE_CREATE_PIPE_INSTANCE; process created the pipe |
PipeList matches every recovered pipe name against a curated set of regular expressions derived from public threat intelligence and tool source code. Matches produce a short label in the Suspicious column.
| Framework | Patterns matched |
|---|---|
| Cobalt Strike | MSSE-*-server, msagent_*, postex_*, postex_ssh_*, status_*, mojo.*, wkssvc*, ntsvcs*, DserNamePipe*, SearchTextHarvester, mypipe-f*, mypipe-h*, win_svc, UIA_PIPE* |
| Metasploit / Meterpreter | meterpreter |
| Sliver C2 | 16-character lowercase hex names |
| SilverC2 | SilverPipe* |
| Covenant / Grunt | gruntsvc, negotiateservice, postexservice |
| PoshC2 | toteslegitpipe |
| Brute Ratel | GUID-format names (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) |
-
Start with
PipeScanfor a live handle-centric view. Correlate pipe names with process names and creation times. Look for:- Processes that should not be creating pipe servers (e.g. browsers, Office applications)
Duplex (Server)handles held by injected or unsigned processes- Pipes with random-looking names or known C2 patterns
-
Follow up with
PipeListto recover any pipe objects that were unlinked after the attacker cleaned up handles. TheSuspiciouscolumn will flag matches automatically. -
Run
PipeFindon pipes of interest (filter with--pid) to read the data currently buffered in kernel memory. Check theStringscolumn for plaintext C2 commands, SMB paths, credentials, or staging payloads — without capturing any network traffic. -
Cross-reference any
Suspicioushits with other plugins (windows.pslist,windows.malfind,windows.dlllist) to build a fuller picture of the injected or malicious process.
_FILE_OBJECThas no creation timestamp. TheCreateTimecolumn inPipeScanreflects the creation time of the owning process, which serves as a useful ordering proxy.- Pool scanning can produce false positives on non-paged pool artefacts. Any
_FILE_OBJECTfound whoseFileNameis unreadable is silently skipped. - Multiple processes sharing one pipe object (e.g. a server handle and a client handle in different processes pointing to the same kernel object) will appear as comma-separated names and PIDs in
PipeList, and as separate rows inPipeScanandPipeFind. - The reverse handle-table lookup in
PipeList._find_processes_with_pipewalks every process for every scanned object. On images with many processes and many pipe objects this can be slow;PipeScanis faster for targeted investigations. PipeFindonly sees data resident in kernel non-paged pool at acquisition time. Data already consumed by the receiving end, or stored in unbuffered IRP system buffers, is not accessible offline.PipeFindreads the CCB as an opaque byte blob.FsContext2(falling back toFsContext) is resolved and up to--sizebytes (default: 512) are read from that address. No NPFS internal structure parsing is performed; the actual pending payload bytes are typically present somewhere within this region.- NPFS structure definitions are not exported in public symbols. The CCB/FCB pointers are accessed via the standard
_FILE_OBJECTfields (FsContext2,FsContext), which are always available in the public PDB. The internal layout of the pointed-to structures is opaque.
- Microsoft — Named Pipes
- Microsoft — PeekNamedPipe
- Microsoft — File Access Rights Constants
- Offensive Windows IPC — Named Pipes (csandker.io)
- NamedPipeMaster — NPFS structure reverse engineering (zeze-zeze)
- SentinelOne — Detecting Cobalt Strike Named Pipes
- Named Pipe IPC — Red Canary Threat Intelligence
- Cobalt Strike Malleable C2 Profiles — default pipe names