On Windows, named pipes are a form of interprocess communication (IPC) that allows processes to communicate with one another, both locally and across the network. Named pipes serve as a mechanism to transfer data between Windows components as well as third-party applications and services. Both locally as well as on a domain. From an offensive perspective, named pipes may leak some information that could be useful for reconnaissance purposes. Since named pipes can also be used (depending on configuration) to access services remotely – they could allow remote exploits (MS08-067).
In this post we will explore how named pipes can be listed remotely in offensive operations, for example via an implant running on a compromised Windows system.
Several tools already exist to list named pipes.
- To display locally bound named pipes you could use SysInternals’ PipeList.
- Bobby Cooke (@boku7) made the xPipe BOF to list local pipes and their DACLs.
- To list named pipes on a remote system you could use
smbclient.pyin impacket ornmap.

That’s a lot of information – in the usual configuration we can typically list these remote named pipes (e.g. with smbclient.py) using regular domain credentials against domain-joined systems.

Part of the reason is explained on the PipeList web page:
Did you know that the device driver that implements named pipes is actually a file system driver? In fact, the driver’s name is NPFS.SYS, for “Named Pipe File System”. What you might also find surprising is that its possible to obtain a directory listing of the named pipes defined on a system. This fact is not documented, nor is it possible to do this using the Win32 API. Directly using NtQueryDirectoryFile, the native function that the Win32 FindFile APIs rely on, makes it possible to list the pipes. The directory listing NPFS returns also indicates the maximum number of pipe instances set for each pipe and the number of active instances.


While we can locally use the Win32 APIs to list locally available named pipes, we cannot interact directly with the NtQueryDirectoryFile API remotely. Tools like smbclient.py still allow us to get a remote named pipe listing because they implement the entire SMB stack themselves and can manually call SMB functions like SMB2_FIND_FULL_DIRECTORY_INFO – regardless of the Tree Connect Response. Apparently, this SMB function calls the same NtQueryDirectoryFile API.


Unfortunately, while it seems to be not possible to list remote named pipes due to this from a Beacon Object File (BOF) in an implant that calls Win32 functions, we could still reimplement the SMB stack in BOF. And while that sounds like a lot of work, there are open-source implementations already available. We therefore built a small POC on top of SMBLibrary, an SMB library written in C#.
RemotePipeList
We’ve added a small tool to our C2 Tool Collection that uses SMBLibrary to list remotely available named pipes. Through inline assembly execution in Cobalt Strike / Stage1, we can then easily use this tool via an implant from the operator’s C2 framework.

We have added an aggressor script for Cobalt Strike and a new task for the Stage1 C2 server. Both can be invoked via the remotepipelist command.
View the RemotePipeList tool on GitHub in the C2 Tool Collection.
Use PowerShell to get the PIDs associated with Named Pipes
| { | |
| ( | |
| [()] | |
| [] | |
| ) | |
| TypeDefinition | |
| public static class Kernel32 | |
| public static extern IntPtr CreateFile( | |
| public static extern bool GetNamedPipeServerProcessId(IntPtr hPipe, out int ClientProcessId); | |
| public static extern bool CloseHandle(IntPtr hObject); | |
| Path \\.\pipe\ select ExpandProperty FullName | |
| () | |
| ( ) | |
| { | |
| Get handle to pipe | |
| []::CreateFile( []::Read []::None []::Zero []::Open []::[]::Zero) | |
| Get the owner of the pipe | |
| []::GetNamedPipeServerProcessId([] []) | |
| () | |
| { | |
| Get process name | |
| Query SELECT Caption FROM Win32_Process WHERE ProcessID = select ExpandProperty Caption | |
| Add to the output results | |
| PSObject Property { | |
| } | |
| } | |
| close the handle | |
| []::CloseHandle() | |
| () | |
| { | |
| } | |
| } | |
| () | |
| { | |
| NoTypeInformation | |
| } | |
| { | |
| } | |
| } |




