Net only impersonation with advapi32.dll and AppV applications

0

I am trying to run an application on a machine with credentials from a different domain, and the below code works as expected for applications running with typical installation methods. That said my organization has a wide adoption of AppV, and I am running into an issue where the launched process hangs and locks up when run with the below method. I had assumed it was some sort of incompatibility between advapi32.dll and AppV, but if i use the runas /netonly to launch the same application in AppV everything seems to work as expected. Am I calling something differently than the RunAs executable that could be causing the difference in functionality?

Run as a user from a different domain.

Impersonator.ProcessInformation processInfo;
Impersonator.StartUpInfo startupInfo = new Impersonator.StartUpInfo();
startupInfo.cb = Marshal.SizeOf(startupInfo);
startupInfo.lpTitle = null;
startupInfo.dwFlags = (int)Impersonator.StartUpInfoFlags.UseCountChars;
startupInfo.dwYCountChars = 50;
Impersonator.StartProcessWithLogon(usrCred.UserName, usrCred.Domain, usrCred.Password, 
    Impersonator.LogonFlags.NetworkCredentialsOnly, progName, argstring, Impersonator.CreationFlags.NewConsole, 
    IntPtr.Zero, runDir, ref startupInfo, out processInfo);

The Impersonator class

public class Impersonator : IDisposable
{
    #region Public methods.
    // ------------------------------------------------------------------

    /// <summary>
    /// Constructor. Starts the impersonation with the given credentials.
    /// Please note that the account that instantiates the Impersonator class
    /// needs to have the 'Act as part of operating system' privilege set.
    /// </summary>
    /// <param name="userName">The name of the user to act as.</param>
    /// <param name="domainName">The domain name of the user to act as.</param>
    /// <param name="password">The password of the user to act as.</param>
    public Impersonator(
        string userName,
        string domainName,
        string password)
    {
        ImpersonateValidUser(userName, domainName, password);
    }

    // ------------------------------------------------------------------
    #endregion

    #region IDisposable member.
    // ------------------------------------------------------------------

    public void Dispose()
    {
        UndoImpersonation();
    }

    // ------------------------------------------------------------------
    #endregion

    #region P/Invoke.
    // ------------------------------------------------------------------

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool CreateProcessWithLogonW(
        string lpszUsername,
        string lpszDomain,
        string lpszPassword, 
        int dwLogonFlags, 
        string applicationName, 
        StringBuilder commandLine, 
        uint creationFlags, 
        IntPtr environment, 
        string currentDirectory, 
        ref StartUpInfo sui, 
        out ProcessInformation processInfo);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern int LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int DuplicateToken(
        IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool RevertToSelf();

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

    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
    private const int LOGON32_PROVIDER_DEFAULT = 0;

    [StructLayout(LayoutKind.Sequential)]
    public struct ProcessInformation
    {
        /// <summary>
        /// Handle to the newly created process. The handle is used to specify the process in all functions that perform operations on the process object.
        /// </summary>
        public IntPtr hProcess;
        /// <summary>
        /// Handle to the primary thread of the newly created process. The handle is used to specify the thread in all functions that perform operations on the thread object.
        /// </summary>
        public IntPtr hThread;
        /// <summary>
        /// Value that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.
        /// </summary>
        public int dwProcessId;
        /// <summary>
        /// Value that can be used to identify a thread. The value is valid from the time the thread is created until the time the thread is terminated.
        /// </summary>
        public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct StartUpInfo
    {
        /// <summary>
        /// Size of the structure, in bytes.
        /// </summary>
        public int cb;
        /// <summary>
        /// Reserved. Set this member to NULL before passing the structure to CreateProcess.
        /// </summary>
        [MarshalAs(UnmanagedType.LPTStr)]
        public string lpReserved;
        /// <summary>
        /// Pointer to a null-terminated string that specifies either the name of the desktop, or the name of both the desktop and window station for this process. A backslash in the string indicates that the string includes both the desktop and window station names.
        /// </summary>
        [MarshalAs(UnmanagedType.LPTStr)]
        public string lpDesktop;
        /// <summary>
        /// For console processes, this is the title displayed in the title bar if a new console window is created. If NULL, the name of the executable file is used as the window title instead. This parameter must be NULL for GUI or console processes that do not create a new console window.
        /// </summary>
        [MarshalAs(UnmanagedType.LPTStr)]
        public string lpTitle;
        /// <summary>
        /// The x offset of the upper left corner of a window if a new window is created, in pixels.
        /// </summary>
        public int dwX;
        /// <summary>
        /// The y offset of the upper left corner of a window if a new window is created, in pixels.
        /// </summary>
        public int dwY;
        /// <summary>
        /// The width of the window if a new window is created, in pixels.
        /// </summary>
        public int dwXSize;
        /// <summary>
        /// The height of the window if a new window is created, in pixels.
        /// </summary>
        public int dwYSize;
        /// <summary>
        /// If a new console window is created in a console process, this member specifies the screen buffer width, in character columns.
        /// </summary>
        public int dwXCountChars;
        /// <summary>
        /// If a new console window is created in a console process, this member specifies the screen buffer height, in character rows.
        /// </summary>
        public int dwYCountChars;
        /// <summary>
        /// The initial text and background colors if a new console window is created in a console application.
        /// </summary>
        public int dwFillAttribute;
        /// <summary>
        /// Bit field that determines whether certain StartUpInfo members are used when the process creates a window.
        /// </summary>
        public int dwFlags;
        /// <summary>
        /// This member can be any of the SW_ constants defined in Winuser.h.
        /// </summary>
        public short wShowWindow;
        /// <summary>
        /// Reserved for use by the C Runtime; must be zero.
        /// </summary>
        public short cbReserved2;
        /// <summary>
        /// Reserved for use by the C Runtime; must be null.
        /// </summary>
        public IntPtr lpReserved2;
        /// <summary>
        /// A handle to be used as the standard input handle for the process.
        /// </summary>
        public IntPtr hStdInput;
        /// <summary>
        /// A handle to be used as the standard output handle for the process.
        /// </summary>
        public IntPtr hStdOutput;
        /// <summary>
        /// A handle to be used as the standard error handle for the process.
        /// </summary>
        public IntPtr hStdError;
    }


    [FlagsAttribute]
    public enum LogonFlags
    {
        /// <summary>
        /// Log on, then load the user's profile in the HKEY_USERS registry key. The function returns after the profile has been loaded. Loading the profile can be time-consuming, so it is best to use this value only if you must access the information in the HKEY_CURRENT_USER registry key.
        /// </summary>
        WithProfile = 1,
        /// <summary>
        /// Log on, but use the specified credentials on the network only. The new process uses the same token as the caller, but the system creates a new logon session within LSA, and the process uses the specified credentials as the default credentials.
        /// </summary>
        NetworkCredentialsOnly = 2
    }


    [FlagsAttribute]
    public enum CreationFlags
    {
        /// <summary>
        /// The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.
        /// </summary>
        Suspended = 0x00000004,
        /// <summary>
        /// The new process has a new console, instead of inheriting the parent's console.
        /// </summary>
        NewConsole = 0x00000010,
        /// <summary>
        /// The new process is the root process of a new process group.
        /// </summary>
        NewProcessGroup = 0x00000200,
        /// <summary>
        /// This flag is only valid starting a 16-bit Windows-based application. If set, the new process runs in a private Virtual DOS Machine (VDM). By default, all 16-bit Windows-based applications run in a single, shared VDM. 
        /// </summary>
        SeperateWOWVDM = 0x00000800,
        /// <summary>
        /// Indicates the format of the lpEnvironment parameter. If this flag is set, the environment block pointed to by lpEnvironment uses Unicode characters.
        /// </summary>
        UnicodeEnvironment = 0x00000400,
        /// <summary>
        /// The new process does not inherit the error mode of the calling process.
        /// </summary>
        DefaultErrorMode = 0x04000000,
    }


    [FlagsAttribute]
    public enum StartUpInfoFlags : uint
    {
        /// <summary>
        /// If this value is not specified, the wShowWindow member is ignored.
        /// </summary>
        UseShowWindow = 0x0000001,
        /// <summary>
        /// If this value is not specified, the dwXSize and dwYSize members are ignored.
        /// </summary>
        UseSize = 0x00000002,
        /// <summary>
        /// If this value is not specified, the dwX and dwY members are ignored.
        /// </summary>
        UsePosition = 0x00000004,
        /// <summary>
        /// If this value is not specified, the dwXCountChars and dwYCountChars members are ignored.
        /// </summary>
        UseCountChars = 0x00000008,
        /// <summary>
        /// If this value is not specified, the dwFillAttribute member is ignored.
        /// </summary>
        UseFillAttribute = 0x00000010,
        /// <summary>
        /// Indicates that the process should be run in full-screen mode, rather than in windowed mode.
        /// </summary>
        RunFullScreen = 0x00000020,
        /// <summary>
        /// Indicates that the cursor is in feedback mode after CreateProcess is called. The system turns the feedback cursor off after the first call to GetMessage.
        /// </summary>
        ForceOnFeedback = 0x00000040,
        /// <summary>
        /// Indicates that the feedback cursor is forced off while the process is starting. The Normal Select cursor is displayed.
        /// </summary>
        ForceOffFeedback = 0x00000080,
        /// <summary>
        /// Sets the standard input, standard output, and standard error handles for the process to the handles specified in the hStdInput, hStdOutput, and hStdError members of the StartUpInfo structure. If this value is not specified, the hStdInput, hStdOutput, and hStdError members of the STARTUPINFO structure are ignored.
        /// </summary>
        UseStandardHandles = 0x00000100,
        /// <summary>
        /// When this flag is specified, the hStdInput member is to be used as the hotkey value instead of the standard-input pipe.
        /// </summary>
        UseHotKey = 0x00000200,
        /// <summary>
        /// When this flag is specified, the StartUpInfo's hStdOutput member is used to specify a handle to a monitor, on which to start the new process. This monitor handle can be obtained by any of the multiple-monitor display functions (i.e. EnumDisplayMonitors, MonitorFromPoint, MonitorFromWindow, etc...).
        /// </summary>
        UseMonitor = 0x00000400,
        /// <summary>
        /// Use the HICON specified in the hStdOutput member (incompatible with UseMonitor).
        /// </summary>
        UseIcon = 0x00000400,
        /// <summary>
        /// Program was started through a shortcut. The lpTitle contains the shortcut path.
        /// </summary>
        TitleShortcut = 0x00000800,
        /// <summary>
        /// The process starts with normal priority. After the first call to GetMessage, the priority is lowered to idle.
        /// </summary>
        Screensaver = 0x08000000
    }

    // ------------------------------------------------------------------
    #endregion

    #region Private member.
    // ------------------------------------------------------------------

    /// <summary>
    /// Does the actual impersonation.
    /// </summary>
    /// <param name="userName">The name of the user to act as.</param>
    /// <param name="domainName">The domain name of the user to act as.</param>
    /// <param name="password">The password of the user to act as.</param>
    private void ImpersonateValidUser(
        string userName,
        string domain,
        string password)
    {
        WindowsIdentity tempWindowsIdentity = null;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        try
        {
            if (RevertToSelf())
            {
                if (LogonUser(
                    userName,
                    domain,
                    password,
                    LOGON32_LOGON_NEW_CREDENTIALS,
                    LOGON32_PROVIDER_DEFAULT,
                    ref token) != 0)
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                    }
                    else
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
                else
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
            else
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        finally
        {
            if (token != IntPtr.Zero)
            {
                CloseHandle(token);
            }
            if (tokenDuplicate != IntPtr.Zero)
            {
                CloseHandle(tokenDuplicate);
            }
        }
    }


    public static System.Diagnostics.Process StartProcessWithLogon(string userName,
        string domain, string password, LogonFlags logonFlags, string applicationName,
        string commandLine, CreationFlags creationFlags, IntPtr environment,
        string currentDirectory, ref StartUpInfo startupInfo,
        out ProcessInformation processInfo)
    {
        StringBuilder cl = new StringBuilder(commandLine.Length);
        cl.Append(commandLine);
        bool retval = CreateProcessWithLogonW(userName, domain, password,
            (int)logonFlags, applicationName, cl, (uint)creationFlags, environment,
            currentDirectory, ref startupInfo, out processInfo);
        if (!retval)
        {
            throw new Win32Exception();
        }
        else
        {
            CloseHandle(processInfo.hProcess);
            CloseHandle(processInfo.hThread);
            return System.Diagnostics.Process.GetProcessById(processInfo.dwProcessId);
        }
    }

    public static System.Diagnostics.Process StartProcessWithLogon(string userName,
        string domain, string password, string applicationName, string commandLine,
        string currentDirectory)
    {
        ProcessInformation processInfo;
        StartUpInfo startupInfo = new StartUpInfo();
        startupInfo.cb = Marshal.SizeOf(startupInfo);
        startupInfo.lpTitle = null;
        startupInfo.dwFlags = (int)StartUpInfoFlags.UseCountChars;
        startupInfo.dwYCountChars = 50;

        return StartProcessWithLogon(userName, domain, password, LogonFlags.WithProfile,
            applicationName, commandLine, CreationFlags.NewConsole, IntPtr.Zero,
            currentDirectory, ref startupInfo, out processInfo);
    }

    /// <summary>
    /// Reverts the impersonation.
    /// </summary>
    private void UndoImpersonation()
    {
        if (impersonationContext != null)
        {
            impersonationContext.Undo();
        }
    }

    private WindowsImpersonationContext impersonationContext = null;

    // ------------------------------------------------------------------
    #endregion
}
c#
appv
asked on Stack Overflow Feb 12, 2018 by rtranchilla

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0