Impersonation with C#

By | October 15, 2010

.Net offers multiple ways to manage impersonation and its level. The important point to understand is what is being impersonated: the thread or the process also is the impersonation happening on the process or is it happening only on the network. Below classes will show you how to impersonate in all this cases.

First class: ImpersonateManager – allows starting impersonation and will apply to the thread scope. You will need to allow unsafe code in your project build properties.  Below program is an example of using the ImpersonateManager.

class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);

            try
            {
                ImpersonateManager.ImpersonateUser("domainName", "userName", "password");
                Console.WriteLine("Impersonated User: " + WindowsIdentity.GetCurrent().Name);
            }
            catch (System.ComponentModel.Win32Exception e)
            {
                Console.WriteLine("Exception while trying to impersonate: " + e);
            }

            ImpersonateManager.StopImpersonation();
            Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);

            Console.ReadKey();
        }
    }

The ImpersonateManager.cs is like this:

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

[assembly: SecurityPermissionAttribute(SecurityAction.RequestMinimum, UnmanagedCode = true)]
[assembly: PermissionSetAttribute(SecurityAction.RequestMinimum, Name = "FullTrust")]
namespace ImpersonateThread
{
    public class ImpersonateManager
    {
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
            int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        private unsafe static extern int FormatMessage(int dwFlags, ref IntPtr lpSource,
            int dwMessageId, int dwLanguageId, ref String lpBuffer, int nSize, IntPtr* Arguments);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public extern static bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
            int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);

        // OurIdentity
        private static WindowsImpersonationContext _impersonatedUser;

        // Tokens
        private static IntPtr tokenHandle = new IntPtr(0);
        private static IntPtr dupeTokenHandle = new IntPtr(0);

        // If you incorporate this code into a DLL, be sure to demand FullTrust.
        [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
        public static void ImpersonateUser(string domainName, string userName, string password)
        {
            const int LOGON32_PROVIDER_DEFAULT = 0;
            const int LOGON32_LOGON_INTERACTIVE = 2;

            tokenHandle = IntPtr.Zero;

            // Call LogonUser to obtain a handle to an access token.
            bool returnValue = LogonUser(userName, domainName, password,
                LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                ref tokenHandle);

            if (false == returnValue)
            {
                // Logon failure
                int ret = Marshal.GetLastWin32Error();
                throw new System.ComponentModel.Win32Exception(ret);
            }

            // Use the token handle returned by LogonUser.
            WindowsIdentity newId = new WindowsIdentity(tokenHandle);

            // Now the thread is impersonated.
            _impersonatedUser = newId.Impersonate();
        }

        public static void StopImpersonation()
        {
            // Stop impersonating the thread.
            _impersonatedUser.Undo();

            // Free the tokens.
            if (tokenHandle != IntPtr.Zero)
            {
                CloseHandle(tokenHandle);
            }
        }
    }
}

 

Now this might not be enough for your need, you might need more than thread impersonation.

There are basically two main logon scenarios in this case:

1)      The user you want to impersonate is on the same domain as the current process

  • Load the profile in the registry (like runas /profile)
  • Sample with: ProcessImpersonator.ImpersonateProcess_WithProfile()

2)      The user you want to impersonate is on a domain without trust relationship

  • Use the specified credentials on the network only (like runas /netuse)
  • Sample with : ProcessImpersonator.ImpersonateProcess_NetCredentials()

Below program do exactly this, it will start another executable (located in the same folder and having a name of test.exe).

class Program
    {
        static void Main(string[] args)
        {
            // Will impersonate the process based on a user existing on the same domain
            ProcessImpersonator.ImpersonateProcess_WithProfile(@"C:test.exe",
                "domain", "user", "password");

            // Will impersonate the call from the process based on a user on a domain
            // with no trust relationship.
            ProcessImpersonator.ImpersonateProcess_NetCredentials(@"C:test.exe",
                "Otherdomain", "user", "password");
            Console.ReadKey();
        }
    }

ProcessImpersonator.cs looks like this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Permissions;
using System.Text;

namespace ImpersonateThread
{
    public class ProcessImpersonator
    {
        [Flags]
        enum LogonFlags
        {
            LOGON_WITH_PROFILE = 0x00000001,
            LOGON_NETCREDENTIALS_ONLY = 0x00000002
        }

        [Flags]
        enum CreationFlags
        {
            CREATE_SUSPENDED = 0x00000004,
            CREATE_NEW_CONSOLE = 0x00000010,
            CREATE_NEW_PROCESS_GROUP = 0x00000200,
            CREATE_UNICODE_ENVIRONMENT = 0x00000400,
            CREATE_SEPARATE_WOW_VDM = 0x00000800,
            CREATE_DEFAULT_ERROR_MODE = 0x04000000,
        }

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

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        struct StartupInfo
        {
            public int cb;
            public string reserved1;
            public string desktop;
            public string title;
            public uint dwX;
            public uint dwY;
            public uint dwXSize;
            public uint dwYSize;
            public uint dwXCountChars;
            public uint dwYCountChars;
            public uint dwFillAttribute;
            public uint dwFlags;
            public ushort wShowWindow;
            public short reserved2;
            public int reserved3;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true,
         SetLastError = true)]
        static extern bool CreateProcessWithLogonW(
            string principal,
            string authority,
            string password,
            LogonFlags logonFlags,
            string appName,
            string cmdLine,
            CreationFlags creationFlags,
            IntPtr environmentBlock,
            string currentDirectory,
            ref StartupInfo startupInfo,
            out ProcessInfo processInfo);

        [DllImport("kernel32.dll")]
        static extern bool CloseHandle(IntPtr h);

        ///
        /// This will use the Logon_NetCredentials_only value.
        /// Usefull for inter-domain scenario without trust relationship
        /// but the system does not validate the credentials.
        ///
        public static void ImpersonateProcess_NetCredentials(string appPath, string domain,
            string user, string password)
        {
            ImpersonateProcess(appPath, domain, user, password,
             LogonFlags.LOGON_NETCREDENTIALS_ONLY);
        }

        ///
        /// This will use the Logon_With_Profile value.
        /// Useful to get the identity of an user in the same domain.
        ///
        public static void ImpersonateProcess_WithProfile(string appPath, string domain,
            string user, string password)
        {
            ImpersonateProcess(appPath, domain, user, password, LogonFlags.LOGON_WITH_PROFILE);
        }

        ///
        /// Call CreateProcessWithLogonW
        ///
        private static void ImpersonateProcess(string appPath, string domain, string user,
            string password, LogonFlags lf)
        {
            StartupInfo si = new StartupInfo();
            si.cb = Marshal.SizeOf(typeof(StartupInfo));
            ProcessInfo pi = new ProcessInfo();

            //
            if (CreateProcessWithLogonW(user, domain, password,
            lf,
            appPath, null,
            0, IntPtr.Zero, null,
            ref si, out pi))
            {
                CloseHandle(pi.hProcess);
                CloseHandle(pi.hThread);
            }
            else
            {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }
        }
    }
}

For more information on the topic:

MSDN – CreateProcessWithLogonW.
MSDN – WindowsIdentity.
Geeks with blogs – Managed CreateProcessWithLogonW.

 
1 Kudos
Don't
move!

8 thoughts on “Impersonation with C#

  1. mihai

    Hello,
    The use of CreateProcessWithLogonW is only working in case the parrent process is started on a user that has access tot the desktop.

    If you try to change the user from System to Administrator with that function you’ll not be able to show any window on the screen.
    CreateProcessAsUser should be used to solve this issue.

    Reply
  2. dan

    Thanks, your code seems to work great so far even with dot net 2.x

    Do you have code that works at the thread level to logon on to a different domain? Your example only works for me if on the same domain.

    Reply
  3. Ahmet Post author

    Hi Dan, sorry for late answer…

    Above sample should work I think, as long as there is a trust relationship between both domain. Can you give it a try?

    Reply
  4. Vish

    Hi

    Thanks for sharing this piece of code. It worked like a charm. I had a requirement to launch a msi as a different user from the service and this code helped me a lot.

    thanks Again

    Vish

    Reply
  5. Tulasi Kumar G M

    Hi I have used this ProcessImpersonator.cs looks like this: here
    and i modified a bit Just i have added commandLine and returning process ID, i will use this command line another console app.exe to connect remote machine but it is not connecting(I have pass all valid arguement. ) please help me out this

    Reply

Thoughts?