본문으로 바로가기

C# 윈도우 서비스로 UI 프로그램 실행하는 방법

category 프로그래밍/.NET C# 2018. 8. 17. 12:05

 Windows Service 프로그램은 일반 콘솔이나 UI 프로그램과 달리, Session 0라고 불리우는 백그운드에서 실행되어 사용자가 UI 출력을 볼 수 없다. 

이유는 Session 0은 윈도우 핸들이 생기지 않기 때문에 UI가 없어서이다. 0번 이 아닌 다른 Session ID로 실행 시켜야 한다.


원리는 간단하다.

1. 윈도우 API를 써서 CONSOLE SESSION 을 얻어옴(대게 1임) 

2. 윈도우에 로그온 된 상태인지 확인한다.(Winlogon.exe가 콘솔 SESSION ID로 실행 중인지 윈도우 API로 확인)

    

3. winlogon.exe의 PID를 가져옴 위 그림에서는 848임. 

4. OpenProcess 윈도우 API를 이용하여 winlogon의 프로세스 핸들을 열어준다.

4. 프로세스 권한을 복제한 후 실행시킬 프로그램을 USER가 실행시키는 것처럼 가져온 SESSION ID로  실행시켜준다.


참조 원문 : http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-and-bit-Archite


<소스 코드>


///서비스 메인 시작 메서드

protected override void OnStart(string[] args)

{

      string applicationName = //실행 파일 전체 경로

      ApplicationLoader.PROCESS_INFORMATION procInfo;

      ApplicationLoader.StartProcessAndBypassUAC(applicationName, out procInfo); 

 }



C# 코드 


ApplicationLoader 클래스

ApplicationLoader.cs



using System;

using System.Security;

using System.Diagnostics;

using System.Runtime.InteropServices;

using System.IO;


namespace WindowsService1

{

    /// <summary>

    /// Class that allows running applications with full admin rights. In

    /// addition the application launched will bypass the Vista UAC prompt.

    /// </summary>

    public class ApplicationLoader

    {

        #region Structures


        [StructLayout(LayoutKind.Sequential)]

        public struct SECURITY_ATTRIBUTES

        {

            public int Length;

            public IntPtr lpSecurityDescriptor;

            public bool bInheritHandle;

        }


        [StructLayout(LayoutKind.Sequential)]

        public struct STARTUPINFO

        {

            public int cb;

            public String lpReserved;

            public String lpDesktop;

            public String lpTitle;

            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 short wShowWindow;

            public short cbReserved2;

            public IntPtr lpReserved2;

            public IntPtr hStdInput;

            public IntPtr hStdOutput;

            public IntPtr hStdError;

        }


        [StructLayout(LayoutKind.Sequential)]

        public struct PROCESS_INFORMATION

        {

            public IntPtr hProcess;

            public IntPtr hThread;

            public uint dwProcessId;

            public uint dwThreadId;

        }


        #endregion


        #region Enumerations


        enum TOKEN_TYPE : int

        {

            TokenPrimary = 1,

            TokenImpersonation = 2

        }


        enum SECURITY_IMPERSONATION_LEVEL : int

        {

            SecurityAnonymous = 0,

            SecurityIdentification = 1,

            SecurityImpersonation = 2,

            SecurityDelegation = 3,

        }


        #endregion


        #region Constants


        public const int TOKEN_DUPLICATE = 0x0002;

        public const uint MAXIMUM_ALLOWED = 0x2000000;

        public const int CREATE_NEW_CONSOLE = 0x00000010;


        public const int IDLE_PRIORITY_CLASS = 0x40;

        public const int NORMAL_PRIORITY_CLASS = 0x20;

        public const int HIGH_PRIORITY_CLASS = 0x80;

        public const int REALTIME_PRIORITY_CLASS = 0x100;


        #endregion


        #region Win32 API Imports


        [DllImport("kernel32.dll", SetLastError = true)]

        private static extern bool CloseHandle(IntPtr hSnapshot);


        [DllImport("kernel32.dll")]

        static extern uint WTSGetActiveConsoleSessionId();


        [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]

        public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,

            ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,

            String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);


        [DllImport("kernel32.dll")]

        static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);


        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]

        public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,

            ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,

            int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);


        [DllImport("kernel32.dll")]

        static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);


        [DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]

        static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);


        #endregion


        /// <summary>

        /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt

        /// </summary>

        /// <param name="applicationName">The name of the application to launch</param>

        /// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>

        /// <returns></returns>

        public static bool StartProcessAndBypassUAC(String applicationName, out PROCESS_INFORMATION procInfo)

        {

            uint winlogonPid = 0;

            IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;

            procInfo = new PROCESS_INFORMATION();


            // obtain the currently active session id; every logged on user in the system has a unique session id

            uint dwSessionId = WTSGetActiveConsoleSessionId();


            // obtain the process id of the winlogon process that is running within the currently active session

            Process[] processes = Process.GetProcessesByName("winlogon");

            foreach (Process p in processes)

            {

                if ((uint)p.SessionId == dwSessionId)

                {

                    winlogonPid = (uint)p.Id;

                }

            }


            // obtain a handle to the winlogon process

            hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);


            // obtain a handle to the access token of the winlogon process

            if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))

            {

                CloseHandle(hProcess);

                return false;

            }



            // Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser

            // I would prefer to not have to use a security attribute variable and to just 

            // simply pass null and inherit (by default) the security attributes

            // of the existing token. However, in C# structures are value types and therefore

            // cannot be assigned the null value.

            SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();

            sa.Length = Marshal.SizeOf(sa);



            // copy the access token of the winlogon process; the newly created token will be a primary token

            if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))

            {

                CloseHandle(hProcess);

                CloseHandle(hPToken);

                return false;

            }


            // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning

            // the window station has a desktop that is invisible and the process is incapable of receiving

            // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 

            // interaction with the new process.

            STARTUPINFO si = new STARTUPINFO();

            si.cb = (int)Marshal.SizeOf(si);

            si.lpDesktop = @"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop


            // flags that specify the priority and creation method of the process

            int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;


            // create a new process in the current user's logon session

            bool result = CreateProcessAsUser(hUserTokenDup,        // client's access token

                                            null,                   // file to execute

                                            applicationName,        // command line

                                            ref sa,                 // pointer to process SECURITY_ATTRIBUTES

                                            ref sa,                 // pointer to thread SECURITY_ATTRIBUTES

                                            false,                  // handles are not inheritable

                                            dwCreationFlags,        // creation flags

                                            IntPtr.Zero,            // pointer to new environment block 

                                            null,                   // name of current directory 

                                            ref si,                 // pointer to STARTUPINFO structure

                                            out procInfo            // receives information about new process

                                            );


            // invalidate the handles

            CloseHandle(hProcess);

            CloseHandle(hPToken);

            CloseHandle(hUserTokenDup);


            return result; // return the result

        }


    }

}