Search code examples
c#windows-servicesimpersonationadvapi32

Get an application to run as a different user from a windows service


I have done a lot of research and not sure why this is not working, probably missing something simple. The code does start the program but it starts it as a local system user instead of as the intended user.

Part of service that executes code:

APIProcess.PROCESS_INFORMATION PI = new APIProcess.PROCESS_INFORMATION();
if (!APIProcess.Launch(@"C:\Windows\System32\notepad.exe", ".",
                "admin", "test", out string MSG, out PI))
            {
                logger.Debug(MSG);
            }
            else
            {
                logger.Debug(MSG);
                logger.Debug(PI.dwProcessID);
            }

Part of code that has the behind function:

public static bool Launch(string appCmdLine, string Domain, string Username, string Password,out string MSG, out PROCESS_INFORMATION pi)
    {
        MSG = "";
        pi = new PROCESS_INFORMATION();
        bool ret = false;

        IntPtr Token = IntPtr.Zero;

        if (LogonUserA(Username,Domain,Password,LogonType.LOGON_NEW_CREDENTIALS,LogonProvider.PROVIDER_WINNT50,ref Token))
        {
            if (Token != IntPtr.Zero)
            {
                IntPtr envBlock = GetEnvironmentBlock(Token);
                ret = LaunchProcessAsUser(appCmdLine, Token, envBlock, out MSG, out pi);
                MSG = string.Join(", ",Token.ToString(),envBlock.ToString());
                if (envBlock != IntPtr.Zero)
                    DestroyEnvironmentBlock(envBlock);


                CloseHandle(Token);
            }

        }
        else
        {
            MSG = "Failed To Logon User";
        }
        return ret;
    }

[DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LogonUserA(
        string Username,
        string Domain,
        string Password,
        LogonType LogonType,
        LogonProvider LogonProvider,
        ref IntPtr Token);

private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock, out string Error, out PROCESS_INFORMATION pi)
    {
        bool result = false;
        Error = "";

        pi = new PROCESS_INFORMATION();
        SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
        SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
        saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
        saThread.nLength = (uint)Marshal.SizeOf(saThread);

        STARTUPINFO si = new STARTUPINFO();
        si.cb = (uint)Marshal.SizeOf(si);


        si.lpDesktop = @"WinSta0\Default"; //Modify as needed 
        si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
        si.wShowWindow = SW_SHOW;


        result = CreateProcessAsUser(
            token,
            null,
            cmdLine,
            ref saProcess,
            ref saThread,
            false,
            CREATE_UNICODE_ENVIRONMENT,
            envBlock,
            null,
            ref si,
            out pi);


        if (result == false)
        {
            int error = Marshal.GetLastWin32Error();
            string message = String.Format("CreateProcessAsUser Error: {0}", error);
            Error = message;
            //Debug.WriteLine(message);

        }

        return result;
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool CreateProcessAsUser(
       IntPtr hToken,
       string lpApplicationName,
       string lpCommandLine,
       ref SECURITY_ATTRIBUTES lpProcessAttributes,
       ref SECURITY_ATTRIBUTES lpThreadAttributes,
       bool bInheritHandles,
       uint dwCreationFlags,
       IntPtr lpEnvironment,
       string lpCurrentDirectory,
       ref STARTUPINFO lpStartupInfo,
       out PROCESS_INFORMATION lpProcessInformation);

I have some logging put in and it looks like the user token and environment token are created just fine. It also does launch the intended application, but it launches as the system user instead of the intended impersonation user. I can make it work if there is already a program running under that user. For the example I changed the desired program to launch and the domain/username/password as well.


Solution

  • The problem is with the use of the LOGON_NEW_CREDENTIALS type in the call to LogonUser. From the documentation:

    This logon type allows the caller to clone its current token and specify new credentials for outbound connections. The new logon session has the same local identifier but uses different credentials for other network connections.

    Try using LOGON32_LOGON_BATCH instead.