Admin service: log user into desktop, spawn a process that can interact with the desktop
Asked Answered
M

3

7

I'm having a difficult time getting a very specific use case to work. The application in question has two components: a Windows service, which needs to run in a privileged context outside of the desktop (i.e. to accept connections while a user is logged in or not) and a client Winforms app. The service accepts websocket connections, and should the connection request succeed, it is supposed to log the user in interactively (into the desktop) and spawn a process as that user with desktop access. I've used the following links, and while they are able to impersonate a user, they don't actually log the user into the desktop, i.e. if I watch the system using VNC, or if I test it on my local system, the user doesn't get logged in. The process does, however, get spawned as the user, but obviously not with desktop access.

Does anyone have a code sample that will log a user into the desktop?

The links and code I've tried:

Using Process.Start() to start a process as a different user from within a Windows Service

How to use LogonUser properly to impersonate domain user from workgroup client public

Launch a process under another user's credentials

https://social.msdn.microsoft.com/Forums/vstudio/en-US/9fb068b0-507b-4b6b-879a-b0fbe492ba92/c-start-program-with-different-user-credentials

The current code is:

using Cassia;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.DirectoryServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
using System.ServiceProcess;

namespace program
{
    public partial class service
    {
        #region Interop

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID
        {
            public UInt32 LowPart;
            public Int32 HighPart;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID_AND_ATTRIBUTES
        {
            public LUID Luid;
            public UInt32 Attributes;
        }

        public struct TOKEN_PRIVILEGES
        {
            public UInt32 PrivilegeCount;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
            public LUID_AND_ATTRIBUTES[] Privileges;
        }

        enum TOKEN_INFORMATION_CLASS
        {
            TokenUser = 1,
            TokenGroups,
            TokenPrivileges,
            TokenOwner,
            TokenPrimaryGroup,
            TokenDefaultDacl,
            TokenSource,
            TokenType,
            TokenImpersonationLevel,
            TokenStatistics,
            TokenRestrictedSids,
            TokenSessionId,
            TokenGroupsAndPrivileges,
            TokenSessionReference,
            TokenSandBoxInert,
            TokenAuditPolicy,
            TokenOrigin,
            TokenElevationType,
            TokenLinkedToken,
            TokenElevation,
            TokenHasRestrictions,
            TokenAccessInformation,
            TokenVirtualizationAllowed,
            TokenVirtualizationEnabled,
            TokenIntegrityLevel,
            TokenUIAccess,
            TokenMandatoryPolicy,
            TokenLogonSid,
            MaxTokenInfoClass
        }

        [Flags]
        enum CreationFlags : uint
        {
            CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
            CREATE_DEFAULT_ERROR_MODE = 0x04000000,
            CREATE_NEW_CONSOLE = 0x00000010,
            CREATE_NEW_PROCESS_GROUP = 0x00000200,
            CREATE_NO_WINDOW = 0x08000000,
            CREATE_PROTECTED_PROCESS = 0x00040000,
            CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
            CREATE_SEPARATE_WOW_VDM = 0x00001000,
            CREATE_SUSPENDED = 0x00000004,
            CREATE_UNICODE_ENVIRONMENT = 0x00000400,
            DEBUG_ONLY_THIS_PROCESS = 0x00000002,
            DEBUG_PROCESS = 0x00000001,
            DETACHED_PROCESS = 0x00000008,
            EXTENDED_STARTUPINFO_PRESENT = 0x00080000
        }

        public enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation
        }

        public enum SECURITY_IMPERSONATION_LEVEL
        {
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        }

        [Flags]
        enum LogonFlags
        {
            LOGON_NETCREDENTIALS_ONLY = 2,
            LOGON_WITH_PROFILE = 1
        }

        enum LOGON_TYPE
        {
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK,
            LOGON32_LOGON_BATCH,
            LOGON32_LOGON_SERVICE,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT,
            LOGON32_LOGON_NEW_CREDENTIALS
        }

        enum LOGON_PROVIDER
        {
            LOGON32_PROVIDER_DEFAULT,
            LOGON32_PROVIDER_WINNT35,
            LOGON32_PROVIDER_WINNT40,
            LOGON32_PROVIDER_WINNT50
        }

        struct SECURITY_ATTRIBUTES
        {
            public uint Length;
            public IntPtr SecurityDescriptor;
            public bool InheritHandle;
        }

        [Flags]
        enum SECURITY_INFORMATION : uint
        {
            OWNER_SECURITY_INFORMATION = 0x00000001,
            GROUP_SECURITY_INFORMATION = 0x00000002,
            DACL_SECURITY_INFORMATION = 0x00000004,
            SACL_SECURITY_INFORMATION = 0x00000008,
            UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
            UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
            PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000,
            PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000
        }

        [StructLayoutAttribute(LayoutKind.Sequential)]
        struct SECURITY_DESCRIPTOR
        {
            public byte revision;
            public byte size;
            public short control; // public SECURITY_DESCRIPTOR_CONTROL control;
            public IntPtr owner;
            public IntPtr group;
            public IntPtr sacl;
            public IntPtr dacl;
        }

        struct STARTUPINFO
        {
            public uint cb;
            [MarshalAs(UnmanagedType.LPTStr)]
            public string Reserved;
            [MarshalAs(UnmanagedType.LPTStr)]
            public string Desktop;
            [MarshalAs(UnmanagedType.LPTStr)]
            public string Title;
            public uint X;
            public uint Y;
            public uint XSize;
            public uint YSize;
            public uint XCountChars;
            public uint YCountChars;
            public uint FillAttribute;
            public uint Flags;
            public ushort ShowWindow;
            public ushort Reserverd2;
            public byte bReserverd2;
            public IntPtr StdInput;
            public IntPtr StdOutput;
            public IntPtr StdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct PROCESS_INFORMATION
        {
            public IntPtr Process;
            public IntPtr Thread;
            public uint ProcessId;
            public uint ThreadId;
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool InitializeSecurityDescriptor(IntPtr pSecurityDescriptor, uint dwRevision);
        const uint SECURITY_DESCRIPTOR_REVISION = 1;

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool SetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR sd, bool daclPresent, IntPtr dacl, bool daclDefaulted);

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

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool LogonUser(
            string lpszUsername,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            out IntPtr phToken
            );

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

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        static extern bool CreateProcessAsUser(
            IntPtr Token,
            [MarshalAs(UnmanagedType.LPTStr)] string ApplicationName,
            [MarshalAs(UnmanagedType.LPTStr)] string CommandLine,
            ref SECURITY_ATTRIBUTES ProcessAttributes,
            ref SECURITY_ATTRIBUTES ThreadAttributes,
            bool InheritHandles,
            uint CreationFlags,
            IntPtr Environment,
            [MarshalAs(UnmanagedType.LPTStr)] string CurrentDirectory,
            ref STARTUPINFO StartupInfo,
            out PROCESS_INFORMATION ProcessInformation);

        [DllImport("Kernel32.dll")]
        extern static int CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

        [DllImport("advapi32.dll", SetLastError = true)]
        internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        internal struct TokPriv1Luid
        {
            public int Count;
            public long Luid;
            public int Attr;
        }

        internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
        internal const int TOKEN_QUERY = 0x00000008;
        internal const int TOKEN_DUPLICATE = 0x0002;
        internal const int TOKEN_ASSIGN_PRIMARY = 0x0001;

        #endregion

        public static bool LoginUser(string domain, string username, string password, string program, string workingDir)
        {
            IntPtr token = IntPtr.Zero;
            IntPtr primaryToken = IntPtr.Zero;

            try
            {
                bool result = LogonUser(username, domain, password, (int)LOGON_TYPE.LOGON32_LOGON_NETWORK, (int)LOGON_PROVIDER.LOGON32_PROVIDER_DEFAULT, out token);
                if (!result)
                {
                    int winError = Marshal.GetLastWin32Error();
                    Console.WriteLine("LoginUser unable to login user " + username + ", error: " + winError);
                    return false;
                }

                SECURITY_ATTRIBUTES processAttributes = new SECURITY_ATTRIBUTES();
                SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR();
                IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sd));
                Marshal.StructureToPtr(sd, ptr, false);
                InitializeSecurityDescriptor(ptr, SECURITY_DESCRIPTOR_REVISION);
                sd = (SECURITY_DESCRIPTOR)Marshal.PtrToStructure(ptr, typeof(SECURITY_DESCRIPTOR));

                result = SetSecurityDescriptorDacl(ref sd, true, IntPtr.Zero, false);
                if (!result)
                {
                    int winError = Marshal.GetLastWin32Error();
                }

                primaryToken = new IntPtr();
                result = DuplicateTokenEx(token, 0, ref processAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
                if (!result)
                {
                    int winError = Marshal.GetLastWin32Error();
                }

                processAttributes.SecurityDescriptor = ptr;
                processAttributes.Length = (uint)Marshal.SizeOf(sd);
                processAttributes.InheritHandle = true;

                SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
                threadAttributes.SecurityDescriptor = IntPtr.Zero;
                threadAttributes.Length = 0;
                threadAttributes.InheritHandle = false;

                bool inheritHandles = true;
                IntPtr environment = IntPtr.Zero;

                STARTUPINFO startupInfo = new STARTUPINFO();
                startupInfo.Desktop = "";

                PROCESS_INFORMATION processInformation;

                result = CreateProcessAsUser(
                    primaryToken,
                    program,
                    program, 
                    ref processAttributes, 
                    ref threadAttributes, 
                    inheritHandles, 
                    16, 
                    environment, 
                    workingDir,
                    ref startupInfo, 
                    out processInformation);

                if (!result)
                {
                    int winError = Marshal.GetLastWin32Error();
                    Console.WriteLine("LoginUser unable to create process as user " + username + ", error: " + winError);
                    return false;
                }

                return true;
            } 
            catch (Exception e) 
            {
                Console.WriteLine("LoginUser exception encountered: " + e.Message());
                return false;
            } 
            finally
            {
                if (token != IntPtr.Zero)
                {
                    int x = CloseHandle(token);
                    if (x == 0)
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    x = CloseHandle(primaryToken);
                    if (x == 0)
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
        }

        public static SecureString securePassword(string password)
        {
            if (string_null(password)) return null;
            SecureString secure = new SecureString();
            foreach (char c in password)
            {
                secure.AppendChar(c);
            }
            return secure;
        }
    }
}

And my goal is to be able to call it simply as:

if (!LoginUser("machinename", "username", "password", "c:\\path\\to\\program.exe", "c:\\path\\to"))
{
  // error
}
else
{
  // success, user is logged into desktop and app is launch
  // as user with desktop access
}
Middlemost answered 8/5, 2015 at 20:29 Comment(0)
A
3

I had the same scenario before and things get complicated so I just used PSEXEC in combination with Process.Start

Just using PSEXEC like whats indicated on their sample all you have to do is something like this

Copy or install your executable via code or manually (i.e. program.exe) to the remote system and execute it interactively, running under the account DannyGlover:

psexec \\workstation64 -c program.exe -u YourUser -p YourPa55w0rd

Now you know your parameters you can then use Process.Start to run it. So it will look something like this

using System.Diagnostics;
...
Process process = new Process();
process.StartInfo.FileName = "program.exe";
process.StartInfo.Arguments = "\\workstation64 -c program.exe -u YourUser -p YourPa55w0rd";
process.Start();
process.WaitForExit();

By the way you can learn more and download PSEXEC here http://ss64.com/nt/psexec.html

Assemblyman answered 11/5, 2015 at 23:58 Comment(5)
Hi Raymund, will this actually log the user YourUser into the desktop and launch the app?Middlemost
It wont physically but it will run the application on the desktop/workstation you are referring as that user you indicated. I used this a lot specially on our scenario where we had servers on 200+ shops where we need to distribute an executable and run it as a certain userAssemblyman
Thanks, the issue though is that the application I need to run on behalf of the user is using services that require that the account have desktop access, so this won't work.Middlemost
This will run on behalf and will have desktop access. Have you tried it? you just need those 5 lines and the namespace compared to writing a big chunk of code. Used this method eversince as we have an old application that does not have API where we need to run the process as the user.Assemblyman
Hi Raymund, yes, I did try this (had to change process.StartInfo.Filename to 'psexec.exe', \\workstation64 to \\<machinename>, and program.exe accordingly). It did not spawn the application on the user's desktop. It spawned in the background.Middlemost
A
0

I found your problem on Elance.com and found this link.

I had same kind of issue with one of my project. That required the windows service to launch a POS Terminal communicator exe and that should be with UI with administrator access. I tried same impersonation of winlogon.exe to avoid UAC but that did not help.

For my problem that i used Scheduled Tasks and created my task using c#. You can find a very good library here http://taskscheduler.codeplex.com. Now on a required call you can run the task dynamically and you have full control over that.

You can create your own task/edit/delete dynamically and can run your apps by not avoiding UAC.

Aftmost answered 15/5, 2015 at 15:47 Comment(0)
P
0

You're seeking to create a new interactive session. Unfortunately, this is not possible. As far as I understand it, you want to launch a process and then control the machine remotely with VNC. Maybe you just need to use Remote Desktop?

Parma answered 15/5, 2015 at 21:27 Comment(1)
Hi Codeguard, it's technically possible as many kiosks have built their own authentication providers to do exactly this and I'm looking to do it within my own app. For reasons I can't go into here, it has to be done programmatically without allowing the user to see or interact with the login screen directly.Middlemost

© 2022 - 2024 — McMap. All rights reserved.