lundi 11 mai 2015

C# admin service: log user into desktop, spawn a process that can interact with the desktop

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

http://ift.tt/1QBszNw

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
}

Aucun commentaire:

Enregistrer un commentaire