vista和win7在windows服務(wù)中交互桌面權(quán)限問(wèn)題解決方法:穿透Session 0 隔離
Windows 服務(wù)在后臺(tái)執(zhí)行著各種各樣任務(wù),支持著我們?nèi)粘5淖烂娌僮?。有時(shí)候可能需要服務(wù)與用戶進(jìn)行信息或界面交互操作,這種方式在XP 時(shí)代是沒(méi)有問(wèn)題的,但自從Vista 開始你會(huì)發(fā)現(xiàn)這種方式似乎已不起作用。
Session 0 隔離實(shí)驗(yàn)
下面來(lái)做一個(gè)名叫AlertService 的服務(wù),它的作用就是向用戶發(fā)出一個(gè)提示對(duì)話框,我們看看這個(gè)服務(wù)在Windows 7 中會(huì)發(fā)生什么情況。
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() { } } }
程序編譯后通過(guò)Installutil 將其加載到系統(tǒng)服務(wù)中:
在服務(wù)屬性中勾選“Allow service to interact with desktop” ,這樣可以使AlertService 與桌面用戶進(jìn)行交互。
在服務(wù)管理器中將AlertService 服務(wù)“啟動(dòng)”,這時(shí)任務(wù)欄中會(huì)閃動(dòng)一個(gè)圖標(biāo):
點(diǎn)擊該圖標(biāo)會(huì)顯示下面窗口,提示有個(gè)程序(AlertService)正在試圖顯示信息,是否需要瀏覽該信息:
嘗試點(diǎn)擊“View the message”,便會(huì)顯示下圖界面(其實(shí)這個(gè)界面我已經(jīng)不能從當(dāng)前桌面操作截圖了,是通過(guò)Virtual PC 截屏的,其原因請(qǐng)繼續(xù)閱讀)。注意觀察可以發(fā)現(xiàn)下圖的桌面背景已經(jīng)不是Windows 7 默認(rèn)的桌面背景了,說(shuō)明AlertService 與桌面系統(tǒng)的Session 并不相同,這就是Session 0 隔離作用的結(jié)果。
在Windows XP、Windows Server 2003 或早期Windows 系統(tǒng)時(shí)代,當(dāng)?shù)谝粋€(gè)用戶登錄系統(tǒng)后服務(wù)和應(yīng)用程序是在同一個(gè)Session 中運(yùn)行的。這就是Session 0 如下圖所示:
但是這種運(yùn)行方式提高了系統(tǒng)安全風(fēng)險(xiǎn),因?yàn)榉?wù)是通過(guò)提升了用戶權(quán)限運(yùn)行的,而應(yīng)用程序往往是那些不具備管理員身份的普通用戶運(yùn)行的,其中的危險(xiǎn)顯而易見。
從Vista 開始Session 0 中只包含系統(tǒng)服務(wù),其他應(yīng)用程序則通過(guò)分離的Session 運(yùn)行,將服務(wù)與應(yīng)用程序隔離提高系統(tǒng)的安全性。如下圖所示:
這樣使得Session 0 與其他Session 之間無(wú)法進(jìn)行交互,不能通過(guò)服務(wù)向桌面用戶彈出信息窗口、UI 窗口等信息。這也就是為什么剛才我說(shuō)那個(gè)圖已經(jīng)不能通過(guò)當(dāng)前桌面進(jìn)行截圖了。
在實(shí)際開發(fā)過(guò)程中,可以通過(guò)Process Explorer 檢查服務(wù)或程序處于哪個(gè)Session,會(huì)不會(huì)遇到Session 0 隔離問(wèn)題。我們?cè)赟ervices 中找到之前加載的AlertService 服務(wù),右鍵屬性查看其Session 狀態(tài)。
可看到AlertService 處于Session 0 中:
再來(lái)看看Outlook 應(yīng)用程序:
很明顯在Windows 7 中服務(wù)和應(yīng)用程序是處于不同的Session,它們之間加隔了一個(gè)保護(hù)墻,在下篇文章中將介紹如何穿過(guò)這堵保護(hù)墻使服務(wù)與桌面用戶進(jìn)行交互操作。
如果在開發(fā)過(guò)程中確實(shí)需要服務(wù)與桌面用戶進(jìn)行交互,可以通過(guò)遠(yuǎn)程桌面服務(wù)的API 繞過(guò)Session 0 的隔離完成交互操作。
對(duì)于簡(jiǎn)單的交互,服務(wù)可以通過(guò)WTSSendMessage 函數(shù),在用戶Session 上顯示消息窗口。對(duì)于一些復(fù)雜的UI 交互,必須調(diào)用CreateProcessAsUser或其他方法(WCF、.NET遠(yuǎn)程處理等)進(jìn)行跨Session 通信,在桌面用戶上創(chuàng)建一個(gè)應(yīng)用程序界面。
如果服務(wù)只是簡(jiǎn)單的向桌面用戶Session 發(fā)送消息窗口,則可以使用WTSSendMessage 函數(shù)實(shí)現(xiàn)。首先,在上一篇下載的代碼中加入一個(gè)Interop.cs 類,并在類中加入如下代碼:
public static void ShowMessageBox(string message, string title) { int resp = 0; WTSSendMessage( WTS_CURRENT_SERVER_HANDLE, WTSGetActiveConsoleSessionId(), title, title.Length, message, message.Length, 0, 0, out resp, false); } [DllImport("kernel32.dll", SetLastError = true)] public static extern int WTSGetActiveConsoleSessionId(); [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 函數(shù)中調(diào)用了WTSSendMessage 來(lái)發(fā)送信息窗口,這樣我們就可以在Service 的OnStart 函數(shù)中使用,打開Service1.cs 加入下面代碼:
protected override void OnStart(string[] args) { Interop.ShowMessageBox("This a message from AlertService.", "AlertService Message"); }
編譯程序后在服務(wù)管理器中重新啟動(dòng)AlertService 服務(wù),從下圖中可以看到消息窗口是在當(dāng)前用戶桌面顯示的,而不是Session 0 中。
如果想通過(guò)服務(wù)向桌面用戶Session 創(chuàng)建一個(gè)復(fù)雜UI 程序界面,則需要使用CreateProcessAsUser 函數(shù)為用戶創(chuàng)建一個(gè)新進(jìn)程用來(lái)運(yùn)行相應(yīng)的程序。打開Interop 類繼續(xù)添加下面代碼:
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(); result = WTSQueryUserToken(dwSessionID, out hToken); 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); if (!result) { ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message"); } 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 函數(shù)中同時(shí)也涉及到DuplicateTokenEx、WTSQueryUserToken、CreateEnvironmentBlock 函數(shù)的使用,有興趣的朋友可通過(guò)MSDN 進(jìn)行學(xué)習(xí)。完成CreateProcess 函數(shù)創(chuàng)建后,就可以真正的通過(guò)它來(lái)調(diào)用應(yīng)用程序了,回到Service1.cs 修改一下OnStart 我們來(lái)打開一個(gè)CMD 窗口。如下代碼:
protected override void OnStart(string[] args)
{
Interop.CreateProcess("cmd.exe",@"C:\Windows\System32\");
}
重新編譯程序,啟動(dòng)AlertService 服務(wù)便可看到下圖界面。至此,我們已經(jīng)可以通過(guò)一些簡(jiǎn)單的方法對(duì)Session 0 隔離問(wèn)題進(jìn)行解決。大家也可以通過(guò)WCF 等技術(shù)完成一些更復(fù)雜的跨Session 通信方式,實(shí)現(xiàn)在Windows 7 及Vista 系統(tǒng)中服務(wù)與桌面用戶的交互操作。
相關(guān)文章
c# Winform 程序自動(dòng)更新實(shí)現(xiàn)方法
Winform程序自動(dòng)更新我也是第一次做,網(wǎng)上找了自動(dòng)更新的源碼,后來(lái)又根據(jù)在網(wǎng)上看到的一些方法,自己試了很久,最終還是有寫錯(cuò)誤,所以花了錢讓別人幫忙調(diào)試成功的,下面是我自己搗騰出來(lái)的,方便大家借鑒,如果有什么錯(cuò)誤的地方歡迎指正2017-02-02C#簡(jiǎn)單的通用基礎(chǔ)字典實(shí)現(xiàn)方法
這篇文章主要介紹了C#簡(jiǎn)單的通用基礎(chǔ)字典實(shí)現(xiàn)方法,包含了字典的索引、記錄、回調(diào)與查詢等技巧,需要的朋友可以參考下2014-12-12C#開發(fā)微信門戶及應(yīng)用(5) 用戶分組信息管理
這篇文章主要為大家詳細(xì)介紹了C#開發(fā)微信門戶及應(yīng)用第五篇,用戶分組信息管理,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06C# 啟用事務(wù)提交多條帶參數(shù)的SQL語(yǔ)句實(shí)例代碼
這篇文章主要介紹了C# 啟用事務(wù)提交多條帶參數(shù)的SQL語(yǔ)句實(shí)例代碼,需要的朋友可以參考下2018-02-02C# 微信支付 wx.chooseWXPay 簽名錯(cuò)誤的解決方法
本篇文章主要介紹了C# 微信支付 wx.chooseWXPay 簽名錯(cuò)誤的解決方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12