Skip to content

nov3mb3r/ipc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 

Repository files navigation

ipc: Named Pipe analysis plugin for Volatility 3

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.

Motivation

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.

Installation

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.

Usage

The plugin ships three independent classes that operate on different data sources within the memory image.

PipeList — Pool-tag Scanner

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.PipeList

Example 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

How it works

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 CloseHandle on 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.


PipeScan — Process Handle-Table Walker

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 0xfa8003e8b080

Example 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

PipeFind — Kernel Buffer Reader

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 0xfa8003e8b080

Pipes 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

How it works

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.


Capabilities

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)

GrantedAccess Decoding

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.

Pipe Direction

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

C2 Suspicious-Name Detection

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)

Recommended Workflow

  1. Start with PipeScan for 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
  2. Follow up with PipeList to recover any pipe objects that were unlinked after the attacker cleaned up handles. The Suspicious column will flag matches automatically.

  3. Run PipeFind on pipes of interest (filter with --pid) to read the data currently buffered in kernel memory. Check the Strings column for plaintext C2 commands, SMB paths, credentials, or staging payloads — without capturing any network traffic.

  4. Cross-reference any Suspicious hits with other plugins (windows.pslist, windows.malfind, windows.dlllist) to build a fuller picture of the injected or malicious process.

Technical Notes

  • _FILE_OBJECT has no creation timestamp. The CreateTime column in PipeScan reflects 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_OBJECT found whose FileName is 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 in PipeScan and PipeFind.
  • The reverse handle-table lookup in PipeList._find_processes_with_pipe walks every process for every scanned object. On images with many processes and many pipe objects this can be slow; PipeScan is faster for targeted investigations.
  • PipeFind only 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.
  • PipeFind reads the CCB as an opaque byte blob. FsContext2 (falling back to FsContext) is resolved and up to --size bytes (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_OBJECT fields (FsContext2, FsContext), which are always available in the public PDB. The internal layout of the pointed-to structures is opaque.

References

About

A Volatility 3 plugin for enumerating and analysing Windows named pipes from memory images, including reading buffered pipe data directly from kernel memory.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages