Win 7 下跨越Session Id 0的Windows Service 并与活动Session UI进行交互

来源:互联网 发布:知乎 音乐播放器 编辑:程序博客网 时间:2024/05/20 10:53

常规的WinForm程式截图比较简单,只需利用Graphics的CopyFromScreen函数即可截取当前屏幕图像,如下4行代码即可完成截图并保存文件的功能:

Bitmap snapShot= new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);Graphics g = Graphics.FromImage(g as Image);g.CopyFromScreen(0, 0, 0, 0, printscreen.Size);snapShot.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);

但是在编写Windows Service程式时,将上面代码写入到Service的loop内,想在指定时间截图当前屏幕图像并保存到本地硬盘内,却发现产生的jpg图像文件都是黑色画面,没有成功截取到当前屏幕图像,即使启动VS2010,附加到Windows Service进程进行Debug也有发现确实有执行到截图代码部分。

虽然没有必要在Windows Service里面来进行截屏,但本着问题本身,还是经过一番搜索。

后来Google了下,发现其他人也有类似的问题。在XP下运行正常,但是在Win Vista以及之后的Win7都会出现截取图像为黑色画面的情况。再后来经查证是Session 0隔离的问题。

简单来说就是在Windows XP, Windows Server 2003或者更早期的Windows操作系统中,所有的服务和应用程序都是运行在与第一个登录到控制台的用户得Session中。这个Session叫做Session 0。在Session 0 中一起运行服务和用户应用程序,由于服务是以高权限运行的,所以会造成一些安全风险。这些因素使得一些恶意代理利用这点,来寻找提升他们自身权限的结构。

而在Windows Vista中,服务在一个叫做Session 0 的特殊Session中承载。由于应用程序运行在用户登录到系统后所创建的Session 0 之后的Session中,所以应用程序和服务也就隔离开来:第一个登录的用户在Session 1中,第二个在Session 2中,以此类推。事实上运行在不同的Session中,如果没有特别将其放入全局命名空间(并且设置了相应的访问控制配置),是不能互相传递窗体消息,共享UI元素或者共享kernel对象。

参考:http://msdn.microsoft.com/zh-cn/library/ee663077.aspx

MSDN上介绍了通过使用WTSSendMessage 来向当前活动Session Desktop UI发送跨Session的MessageBox消息(从Session 0 发送到Session 1 等等)

[DllImport("wtsapi32.dll", SetLastError = true)]        public static extern bool WTSSendMessage(            IntPtr hServer,            int SessionId,            String pTitle,            int TitleLength,            String pMessage,            int MessageLength,            int Style,            int Timeout,            out int pResponse,            bool bWait);
WTSSendMessage第二个参数即是要传送的Session Id,可通过WTSGetActiveConsoleSessionId()来获取当前活动的Console Session Id。这只是方法一,其实还有其他两种方式获取,第二种方式是通过WTSEnumerateSessions来枚举列出当前所有Session的WTS_SESSION_INFO,并通过其state字段是否为WTSActive来获得活动的Console Session Id。第三种方式是,因为在Service里面也可以枚举出当前Active Session的各种进程Process信息,从其中的explorer.exe进程信息里面可以得到Active Session Id,如下图:


由此可见,由于Window Service本身运行在Session 0 里面,只有通过发送Message的方式与活动UI进行交互。可在Active Session Id里创建进程的方式让新产生的进程能够与活动UI进行交互(如在Service里面创建之前讲到的截屏程式的进程),需要使用到CreateProcessAsUser函数,原型如下:

[DllImport("advapi32.dll", SetLastError = true)]        public static extern bool CreateProcessAsUser(            IntPtr hToken,             string lpApplicationName,             string lpCommandLine,            ref SECURITY_ATTRIBUTES lpProcessAttributes,            ref SECURITY_ATTRIBUTES lpThreadAttributes,            bool bInheritHandle,             Int32 dwCreationFlags,            IntPtr lpEnvrionment,            string lpCurrentDirectory,            ref STARTUPINFO lpStartupInfo,            ref PROCESS_INFORMATION lpProcessInformation);

简单来说,可通过得到的Active Session Id来创建相应的hToken参数,以此启动的新进程会运行在Active Session Id内,而不会运行在Session 0隔离区内。

当然核心是CreateProcessAsUser函数,其它还会相应用到WTSQueryUserToken,DuplicateTokenEx,CreateEnvironmentBlock,DestroyEnvironmentBlock等函数。基于此,可写出一个供C#直接调用的class。 

{_ProcessUtil.cs} 代码如下

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;using System.Diagnostics;using System.Security.Principal;using System.ComponentModel;using System.IO;namespace dllCoast{    public class _ProcessUtil    {        #region ~declare struct,enum        [StructLayout(LayoutKind.Sequential)]        public struct STARTUPINFO        {            public Int32 cb;            public string lpReserved;            public string lpDesktop;            public string lpTitle;            public Int32 dwX;            public Int32 dwY;            public Int32 dwXSize;            public Int32 dwXCountChars;            public Int32 dwYCountChars;            public Int32 dwFillAttribute;            public Int32 dwFlags;            public Int16 wShowWindow;            public Int16 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 Int32 dwProcessID;            public Int32 dwThreadID;        }        [StructLayout(LayoutKind.Sequential)]        public struct SECURITY_ATTRIBUTES        {            public Int32 Length;            public IntPtr lpSecurityDescriptor;            public bool bInheritHandle;        }        [StructLayout(LayoutKind.Sequential)]        public struct WTS_SESSION_INFO        {            public int SessionId;            public IntPtr pWinStationName;            public WTS_CONNECTSTATE_CLASS State;        }        public enum SECURITY_IMPERSONATION_LEVEL        {            SecurityAnonymous,            SecurityIdentification,            SecurityImpersonation,            SecurityDelegation        }        public enum TOKEN_TYPE        {            TokenPrimary = 1,            TokenImpersonation        }        public enum WTS_CONNECTSTATE_CLASS {             WTSActive,            WTSConnected,            WTSConnectQuery,            WTSShadow,            WTSDisconnected,            WTSIdle,            WTSListen,            WTSReset,            WTSDown,            WTSInit         }        public enum _SESSION_TYPE        {            SessionFromActiveConsoleSessionId=0,            SessionFromEnumerateSessions,            SessionFromProcessExplorerSession        }        #endregion        #region ~delacre const value        public const int GENERIC_ALL_ACCESS = 0x10000000;        public const int CREATE_NO_WINDOW = 0x08000000;        public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;        public const Int32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;        public const Int32 STANDARD_RIGHTS_READ = 0x00020000;        public const Int32 TOKEN_ASSIGN_PRIMARY = 0x0001;        public const Int32 TOKEN_DUPLICATE = 0x0002;        public const Int32 TOKEN_IMPERSONATE = 0x0004;        public const Int32 TOKEN_QUERY = 0x0008;        public const Int32 TOKEN_QUERY_SOURCE = 0x0010;        public const Int32 TOKEN_ADJUST_PRIVILEGES = 0x0020;        public const Int32 TOKEN_ADJUST_GROUPS = 0x0040;        public const Int32 TOKEN_ADJUST_DEFAULT = 0x0080;        public const Int32 TOKEN_ADJUST_SESSIONID = 0x0100;        public const Int32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);        public const Int32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |            TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |            TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |            TOKEN_ADJUST_SESSIONID);        public const int MB_ABORTRETRYIGNORE = 0x0002;        public const int MB_CANCELTRYCONTINUE =0x0006;        public const int MB_HELP =0x4000;         public const int MB_OK =0x0000;        public const int MB_OKCANCEL= 0x0001;        public const int MB_RETRYCANCEL =0x0005;         public const int MB_YESNO= 0x0004;         public const int MB_YESNOCANCEL =0x0003;         #endregion        #region ~import functions with Win32 API from win32 system dll        [DllImport("wtsapi32.dll", SetLastError = true)]        public static extern bool WTSSendMessage(            IntPtr hServer,            int SessionId,            String pTitle,            int TitleLength,            String pMessage,            int MessageLength,            int Style,            int Timeout,            out int pResponse,            bool bWait);        [DllImport("kernel32.dll", SetLastError = true)]        public static extern int WTSGetActiveConsoleSessionId();        [DllImport("wtsapi32.dll", SetLastError = true)]        public static extern bool WTSQueryUserToken(Int32 sessionId, out IntPtr Token);        [DllImport("userenv.dll", SetLastError = true)]        static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);        [DllImport("userenv.dll", SetLastError = true)]        static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);                 [DllImport("kernel32.dll", SetLastError = true)]        public static extern bool CloseHandle(IntPtr handle);        [DllImport("advapi32.dll", SetLastError = true)]        public static extern bool CreateProcessAsUser(            IntPtr hToken,             string lpApplicationName,             string lpCommandLine,            ref SECURITY_ATTRIBUTES lpProcessAttributes,            ref SECURITY_ATTRIBUTES lpThreadAttributes,            bool bInheritHandle,             Int32 dwCreationFlags,            IntPtr lpEnvrionment,            string lpCurrentDirectory,            ref STARTUPINFO lpStartupInfo,            ref PROCESS_INFORMATION lpProcessInformation);        [DllImport("advapi32.dll", SetLastError = true)]        public static extern bool DuplicateTokenEx(            IntPtr hExistingToken,             Int32 dwDesiredAccess,            ref SECURITY_ATTRIBUTES lpThreadAttributes,            Int32 ImpersonationLevel,             Int32 dwTokenType,            ref IntPtr phNewToken);        [DllImport("wtsapi32.dll", SetLastError = true)]        public static extern void WTSFreeMemory(IntPtr pMemory);        [DllImport("wtsapi32.dll", SetLastError = true)]        public static extern bool WTSEnumerateSessions(            IntPtr hServer,            int Reserved,            int Version,            ref  IntPtr ppSessionInfo,//WTS_SESSION_INFO PWTS_SESSION_INFO *ppSessionInfo,            ref  int pCount            );        #endregion        /// <summary>        /// Show a MessageBox on the active UI desktop        /// </summary>        /// <param name="title">title of the MessageBox</param>        /// <param name="message">message to show to the user</param>        /// <param name="bWait">indicates if wait for some time by parameter timeout  </param>        /// <returns>success to return true</returns>        public static bool SendMessageBoxToRemoteDesktop(string title, string message, bool bWait)        {            int pResponse = 0;            IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;            return WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, GetSessionIdFromActiveConsoleSessionId(), title, title.Length, message, message.Length, MB_OK, 0, out pResponse, bWait);        }        /// <summary>        /// Show a MessageBox on the active UI desktop        /// </summary>        /// <param name="title">title of the MessageBox</param>        /// <param name="message">message to show to the user</param>        /// <param name="button_style">can be a combination of MessageBoxButtons and MessageBoxIcon, need to convert it to int</param>        /// <param name="timeout">timeout to determine when to return this function call, 0 means wait until the user response the MessageBox</param>        /// <param name="bWait">indicates if wait for some time by parameter timeout  </param>        /// <returns>success to return true</returns>        public static bool SendMessageBoxToRemoteDesktop(string title, string message, int button_style, int timeout, bool bWait)        {            int pResponse = 0;            IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;            return WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, GetSessionIdFromActiveConsoleSessionId(), title, title.Length, message, message.Length, button_style, timeout, out pResponse, bWait);        }        /// <summary>        /// Show a MessageBox on the active UI desktop        /// </summary>        /// <param name="title">title of the MessageBox</param>        /// <param name="message">message to show to the user</param>        /// <param name="button_style">can be a combination of MessageBoxButtons and MessageBoxIcon, need to convert it to int</param>        /// <param name="timeout">timeout to determine when to return this function call, 0 means wait until the user response the MessageBox</param>        /// <param name="pResponse">pointer to receive the button result which clicked by user</param>        /// <param name="bWait">indicates if wait for some time by parameter timeout  </param>        /// <returns>success to return true</returns>        public static bool SendMessageBoxToRemoteDesktop(string title, string message, int button_style, int timeout, out int pResponse, bool bWait)        {            IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;            return WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, GetSessionIdFromActiveConsoleSessionId(), title, title.Length, message, message.Length, button_style, timeout, out pResponse, bWait);        }        public static int GetSessionIdFromActiveConsoleSessionId()        {            int dwSessionID = WTSGetActiveConsoleSessionId();            return dwSessionID;        }        public static int GetSessionIdFromEnumerateSessions()        {            IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;            int dwSessionId = 0;            IntPtr pSessionInfo = IntPtr.Zero;            int dwCount = 0;            WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1,                                 ref pSessionInfo, ref dwCount);            Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));            Int32 current = (int)pSessionInfo;            for (int i = 0; i < dwCount; i++)            {                WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure(                    (System.IntPtr)current, typeof(WTS_SESSION_INFO));                if (WTS_CONNECTSTATE_CLASS.WTSActive == si.State)                {                    dwSessionId = si.SessionId;                    break;                }                current += dataSize;            }            WTSFreeMemory(pSessionInfo);            return dwSessionId;        }        public static int GetSessionIdFromExplorerSessionId()        {            int dwSessionId = 0;            Process[] process_array = Process.GetProcessesByName("explorer");            if (process_array.Length>0)            {                dwSessionId = process_array[0].SessionId;            }            return dwSessionId;             }        public static Process CreateProcessAsUser(string filename, string args, _SESSION_TYPE session_method)        {            IntPtr hToken = IntPtr.Zero;//WindowsIdentity.GetCurrent().Token;            int dwSessionId = 0;            IntPtr hDupedToken = IntPtr.Zero;            Int32 dwCreationFlags = 0;            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();            SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();            sa.Length = Marshal.SizeOf(sa);            STARTUPINFO si = new STARTUPINFO();            si.cb = Marshal.SizeOf(si);            si.lpDesktop = "";            IntPtr lpEnvironment = IntPtr.Zero;            string full_filepath = Path.GetFullPath(filename);            string working_Dir = Path.GetDirectoryName(full_filepath);            try            {                #region ~ get sessionid                switch (session_method)                {                    case _SESSION_TYPE.SessionFromActiveConsoleSessionId:                        dwSessionId = GetSessionIdFromActiveConsoleSessionId();                        break;                    case _SESSION_TYPE.SessionFromEnumerateSessions:                        dwSessionId = GetSessionIdFromEnumerateSessions();                        break;                    case _SESSION_TYPE.SessionFromProcessExplorerSession:                        dwSessionId = GetSessionIdFromExplorerSessionId();                        break;                    default:                        dwSessionId = GetSessionIdFromActiveConsoleSessionId();                        break;                }                #endregion                #region ~ retrieve Token from a specified SessionId                bool bResult = WTSQueryUserToken(dwSessionId, out hToken);                if (!bResult)                {                    throw new Win32Exception(Marshal.GetLastWin32Error());                }                #endregion                       #region ~ Duplicate from the specified Token                if (!DuplicateTokenEx(                        hToken,                        GENERIC_ALL_ACCESS,                        ref sa,                        (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,                        (int)TOKEN_TYPE.TokenPrimary,                        ref hDupedToken                    ))                    throw new Win32Exception(Marshal.GetLastWin32Error());                #endregion                #region ~ Create a Environment Block from specifid Token                bool result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false);                if (!result)                {                    lpEnvironment = IntPtr.Zero;    //if fail, reset it to Zero                }                else                {                    dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;   //if success, set the CreationsFlags to CREATE_UNICODE_ENVIRONMENT, then pass it into CreateProcessAsUser                }                #endregion                #region ~ Create a Process with Specified Token                if (!CreateProcessAsUser(                    hDupedToken,                    full_filepath,                    string.Format("\"{0}\" {1}", filename.Replace("\"", ""), args),                    ref sa,                     ref sa,                    false,                     dwCreationFlags,                    lpEnvironment,                    working_Dir,                     ref si,                     ref pi                    ))                    throw new Win32Exception(Marshal.GetLastWin32Error());                #endregion                #region ~ Destroy the Environment Block which is Created by CreateEnvironment                DestroyEnvironmentBlock(lpEnvironment);                #endregion                return Process.GetProcessById(pi.dwProcessID);            }            catch(Win32Exception e)            {                #region ~handle win32 exception                int pResponse = 0;                string errMsg =                    "NativeErrorCode:\t" + e.NativeErrorCode                    + "\n\nSource:  " + e.Source                    + "\n\nMessage:  " + e.Message                    + "\n\nStackTrace:  " + e.StackTrace;                SendMessageBoxToRemoteDesktop("Win32 Exception!!", errMsg, MB_OK, 0, out pResponse, false); //send the error message to the remote desktop                return null;                #endregion            }            finally            {                #region ~ release hanlde                if (pi.hProcess != IntPtr.Zero)                    CloseHandle(pi.hProcess);                if (pi.hThread != IntPtr.Zero)                    CloseHandle(pi.hThread);                if (hDupedToken != IntPtr.Zero)                    CloseHandle(hDupedToken);                #endregion            }        }    }}


0 0
原创粉丝点击