注:这套程序在这里只是作为了解,博主本人并未测试,所有测试均来自文章最底层链接的原文章。
在实际开发过程中,可以通过Process Explorer 检查服务或程序处于哪个Session,会不会遇到Session 0 隔离问题。可以看到svchost.exe处于SESSION 0。QQ作为用户进程运行在SESSION 1。
下面来做一个名叫AlertService 的服务,它的作用就是向用户发出一个提示对话框,我们看看这个服务在Windows 7 中会发生什么情况。
using System.ServiceProcess; using System.Windows.Forms; namespace AlertService { public partial class Service1 : ServiceBase { public Service1() { InitializeComponent(); } protected override void OnStart(string[] args) { MessageBox.Show("A message from AlertService."); } protected override void OnStop() { } } }
对于简单的交互,服务可以通过 WTSSendMessage 函数,在用户 Session 上显示消息窗口。
对于一些复杂的UI 交互,必须调用CreateProcessAsUser 或其他方法(WCF、.NET远程处理等)进行跨 Session 通信,在桌面用户上创建一个应用程序界面。
如果服务只是简单的向桌面用户 Session 发送消息窗口,则可以使用WTSSendMessage 函数实现。首先,加入如下代码:
public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; public static void ShowMessageBox(string message, string title) { int resp = 0; // 调用WTSSendMessage函数 WTSSendMessage( WTS_CURRENT_SERVER_HANDLE, WTSGetActiveConsoleSessionId(), title, title.Length, message, message.Length, 0, 0, out resp, false); } // 获取kernel32.dll的导出函数WTSGetActiveConsoleSessionId [DllImport("kernel32.dll", SetLastError = true)] public static extern int WTSGetActiveConsoleSessionId(); // 获取wtsapi32.dll的导出函数WTSSendMessage [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);
在ShowMessageBox 函数中调用了WTSSendMessage 来发送信息窗口,这样我们就可以在Service 的OnStart 函数中使用,加入下面代码:
protected override void OnStart(string[] args) { ShowMessageBox("This a message from AlertService.","AlertService Message"); }
编译程序后在服务管理器中重新启动AlertService 服务,从下图中可以看到消息窗口是在当前用户桌面显示的,而不是Session 0 中。
如果想通过服务向桌面用户 Session 创建一个复杂 UI 程序界面,则需要使用 CreateProcessAsUser 函数为用户创建一个新进程用来运行相应的程序。需要注意这个CreateProcessAsUser需要system权限,仅admin是不行的,虽然网上有例子说可以手动提升admin权限,但我在本地安全策略里添加了相关权限依旧不行。
CreateProcessAsUser,打开程序之前需要一个令牌降权,有两种方法,一个是获取用户的令牌
dwSessionID = ::WTSGetActiveConsoleSessionId();//获取用户id if (FALSE == ::WTSQueryUserToken(dwSessionID, &hToken)//hToken获取到令牌 { int i = GetLastError();//获取错误编码 }
另一种方法是通过获取其他程序的令牌,当然这种很有可能出现意外
LPWSTR lpa = ConvertCharToLPWSTR(_T("EXPLORER.EXE")) GetTokenByName(hToken, lpa) BOOL GetTokenByName(HANDLE &hToken, LPWSTR lpName) { if (!lpName) { return FALSE; } HANDLE hProcessSnap = NULL; BOOL bRet = FALSE; PROCESSENTRY32 pe32 = { 0 }; hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) return (FALSE); pe32.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hProcessSnap, &pe32)) { do { CString exefile = pe32.szExeFile; CString paraname = lpName; LPCSTR cname = wtoc(lpName).c_str; if (!exefile.CompareNoCase(cname)) { HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID); bRet = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken); CloseHandle(hProcessSnap); return (bRet); } } while (Process32Next(hProcessSnap, &pe32)); bRet = TRUE; } else bRet = FALSE; CloseHandle(hProcessSnap); return (bRet); }
这里使用的是第一种方案。
public static void CreateProcess(string app, string path) { bool result; IntPtr hToken = WindowsIdentity.GetCurrent().Token; IntPtr hDupedToken = IntPtr.Zero; 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); int dwSessionID = WTSGetActiveConsoleSessionId(); // 获得当前Session ID result = WTSQueryUserToken(dwSessionID, out hToken); // 获得当前Session的用户令牌 if (!result) { ShowMessageBox("WTSQueryUserToken failed", "AlertService Message"); } // 复制令牌 result = DuplicateTokenEx( hToken, GENERIC_ALL_ACCESS, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hDupedToken ); if (!result) { ShowMessageBox("DuplicateTokenEx failed", "AlertService Message"); } IntPtr lpEnvironment = IntPtr.Zero; result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false); // 创建用户Session环境 if (!result) { ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message"); } // 在复制的用户Session下执行应用程序,创建进程 result = CreateProcessAsUser( hDupedToken, app, String.Empty, ref sa, ref sa, false, 0, IntPtr.Zero, path, ref si, ref pi); if (!result) { int error = Marshal.GetLastWin32Error(); string message = String.Format("CreateProcessAsUser Error: {0}", error); ShowMessageBox(message, "AlertService Message"); } if (pi.hProcess != IntPtr.Zero) CloseHandle(pi.hProcess); if (pi.hThread != IntPtr.Zero) CloseHandle(pi.hThread); if (hDupedToken != IntPtr.Zero) CloseHandle(hDupedToken); } [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; } public enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous, SecurityIdentification, SecurityImpersonation, SecurityDelegation } public enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation } public const int GENERIC_ALL_ACCESS = 0x10000000; [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern bool CloseHandle(IntPtr handle); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] 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 bool WTSQueryUserToken( Int32 sessionId, out IntPtr Token); [DllImport("userenv.dll", SetLastError = true)] static extern bool CreateEnvironmentBlock( out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
在CreateProcess 函数中同时也涉及到DuplicateTokenEx、WTSQueryUserToken、CreateEnvironmentBlock 函数的使用,有兴趣的朋友可通过MSDN 进行学习。完成CreateProcess 函数创建后,就可以真正的通过它来调用应用程序了,回到Service1.cs 修改一下OnStart 我们来打开一个CMD 窗口。如下代码:
protected override void OnStart(string[] args) { Interop.CreateProcess("cmd.exe", @"C:\Windows\System32\"); }
重新编译程序,启动AlertService 服务便可看到下图界面。至此,我们已经可以通过一些简单的方法对Session 0 隔离问题进行解决。大家也可以通过WCF 等技术完成一些更复杂的跨Session 通信方式,实现在Windows 7 及Vista 系统中服务与桌面用户的交互操作。
参考文章链接:
https://www.cnblogs.com/gnielee/archive/2010/04/07/session0-isolation-part1.html/
https://www.cnblogs.com/moshuixiong/p/12169281.html/