Windows 服務的Session 0 隔離機制
服務是整合在Microsoft Windows操作系統中 的結構。服務與用戶的應用程序不同,因為你可以對他們進行配置,不需要一個激活的用戶 登錄,就可以使這些服務在系統啟動的時候運行,直到系統關閉。Windows中的服務,負責所 有種類的後台活動,但不包括從遠程過程調用(RPC)服務到網絡位置服務的用戶。
一些服務可能會試圖顯示一些用戶界面對話框,或者與用戶的應用程序進行通信。那麼這 些服務將會遇到與Windows 7 的兼容性問題。可能你有一個服務,試圖顯示一個對話框,但 是,可能僅僅是在任務欄中出現一個閃爍的圖標。無獨有偶,你的服務可能會多多少少遇到 下面的一些現象。這些服務:
• 正在運行,但是沒有做他們本希望去做的事情
• 正在運行,但是其 他的一些進程卻不能和這個服務進行通信
• 試圖通過窗體消息,與用戶的應用 程序進行通信,但是窗體消息並沒有被目標所接收到
• 在任務欄中,顯示一個 閃爍的圖標,說明此服務希望與桌面信息進行交互
這些是Windows 7的服務的Session 0隔離機制的一些症狀。它們可以大致分為兩大類:
• 服務顯示用戶界面失敗, 或者僅僅顯示了一個簡化的用戶界面
當一個服務試圖去展示任何有關用戶界面的元素 (即使這個服務被允許與桌面信息進行交互),一個叫做Interactive services dialog detection的簡單對話框也會彈出,用於提示用戶。用戶可以進入Session 0的安全桌面中查 看服務的用戶界面,但是,工作流中的干擾,使得這個變成了一個嚴重的應用程序兼容性問 題。
• 很難實現服務和應用程序共享對象,並且這個對象將不可見
當一個對象由服務創建出 來,並且允許標准應用程序訪問(以標准用戶權限運行),那麼這個對象將不能在全局命名 空間中找到(它將被Session 0 中私有)。此外,安全變更,也將保證即使對象是可見的, 但它也是不能被訪問的。這些將有可能影響其他的與你的服務進行交互的進程(比如普通用 戶的應用程序)。
真正的問題是Windows 7 服務的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對象。下面的圖例中,將進行圖解 :
了解更多 關於Session 0 隔離機制,閱讀Windows Vista 中的驅動和服務的Session 0 隔離機制的影 響:http://www.microsoft.com/whdc/system/vista/services.mspx
解決方案
• 如果一個服務,需要向用戶傳遞一條消息,那麼可以使用WTSSendMessage方 法。這個方法和MessageBox基本相同。它將可以給不需要復雜的UI界面的服務,一個簡單而 有效的解決方案,同時,由於所顯示的消息對話框不能用於控制底層的服務,所以,這個解 決方案也是安全的。
• 如果需要使用一個復雜的UI界面,那麼可以使用 CreateProcessAsUser方法,在提出請求的用戶桌面中創建一個進程。
• 如果這 兩種交互方式都需要,那麼可以使用諸如Windows Communication Foundation (WCF),.NET 遠程處理,命名管道或者其他的進程間通信(IPC)結構(除窗體消息之外)來跨Session通 信。
• 安全通信和其他共享對象(比如,命名管道,文件地圖),可以使用任 意訪問控制列表(DACL)來嚴格控制用戶的權限設置。使用系統訪問控制列表(SACL),來 確保中低權限的進程依然可以訪問由系統或高權限服務所創建的結構。
• 確保 跨Session訪問的kernel對象是以Global\字符串為首字母進行命名,這意味著他們是屬於全 局Session命名空間中的。
解決方案步驟
目前,我們已經遇到了所有的 Windows服務的Session 0 隔離機制的情況,解釋了什麼是服務隔離,它是如何影響你的服務 和應用程序的,並且提出了一些解決方案。下面的一些測試和一些操作,可以幫助你找到問 題,並且解決它。
試驗 #1
1.打開進程浏覽器。
a.下載或了解更多關於進程浏覽器,請在Microsoft TechNet網站參看Process Explorer Web site 。
2.確保進程浏覽器顯示了所有的進程:
a.點擊 File.
b.選擇 Show processes from all users.
3.定位到可疑的服務,並且查看它的屬性:
a.右鍵點擊進程。
b.選擇 Properties 。
c.導航到 Security 標簽。
d.請注意服務運行在哪個Session中(通常在Session 0),並且它的全部級別。
下面是兩個進程的例子-其中一個以中級權限運行(在Session 1),另外一個則以系統權 限運行(在Session 1):
如果你的服務是以高權限運行在Session 0中,那麼它將不能直接顯示UI。不過即使這樣 ,當你與服務進行共享kernel對象或者文件的時候,你可能還會遇到一些問題。
試驗 #2
1.打開進程浏覽器。
2.確保進程浏覽器顯示了所有的進程:
a.點擊 File.
b.選擇 Show processes from all users.
3.定位到可疑的服務。
4.如果服務包含了你已知 的與用戶應用程序共享的對象,請在下面的Handles窗口中檢查他們的句柄(使用CTRL+H查看 ,或者從View菜單中進入)。
a.右鍵點擊每一個可以的句柄,選擇Properties。
b.切換到Security標簽頁,查看當前用戶和組是否允許使用當前句柄來訪問對象。
下面的圖中,展示了一個不管是否是在Session 0 中運行的系統服務中,所有人都可 以訪問的共享對象(“同步”權限):
下面的 圖中,展示了一個只允許管理員和系統組才能訪問的共享對象:
5.如果服務需要創建一個用戶應用程序可以訪問的文件,請依照以下步驟:
a.打開命令行窗口:
i.點擊 Start
ii.指向 Programs
iii.點擊 Accessories
iv.點擊 Command Prompt
b.運行icacls工具查看文件或者目錄的集合權限和DACL信息,並且修改它們。
( 請在Microsoft TechNet網站查看關於icacls的文檔,以獲取更多信息。)
c.運行 icacls MyFile來顯示叫MyFile的文件的訪問控制列表。
d.運行icacls MyFile /setintegritylevel Medium來將MyFile的集合權限級別修改為中級(這將使它可以訪問大部 分用戶應用程序。)
工具
• 進程浏覽器 –Windows進程的監視工 具,可以顯示進程的集合級別和對象的安全信息。
o 更多 信息: http://technet.microsoft.com/en-us/sysinternals/bb896645.aspx
o 下載: http://download.sysinternals.com/Files/ProcessExplorer.zip
• icacls –Windows實用工具中的一個,用於更改文件系統對象的ACL和整合級別。
o 更多信息: http://technet.microsoft.com/en- us/library/cc753525.aspx
代碼示例
下面包含了一些示例代碼,用來演示如 何:
• 使用WTSSendMessage從服務端向當前活動用戶的桌面發送消息
• 當需要顯示一個相對復雜的UI界面時,如何在當前活動用戶的桌面創建一個 進程
• 創建一個屬於全局命名空間的事件,並且包含安全配置,以便於它可以 被當前活動用戶所訪問。
使用WTSSendMessage 從服務端向當前活動用戶的桌面發送 消息:
void ShowMessage(LPWSTR lpszMessage, LPWSTR lpszTitle)
{
DWORD dwSession = WTSGetActiveConsoleSessionId();
DWORD dwResponse = 0;
WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, dwSession,
lpszTitle,
static_cast<DWORD>((wcslen(lpszTitle) + 1) * sizeof(wchar_t)),
lpszMessage,
static_cast<DWORD>((wcslen(lpszMessage) + 1) * sizeof(wchar_t)),
0, 0, &dwResponse, FALSE);
}
當需要顯示一個相對復雜的UI 界面時,如何在當前活動用戶的桌面創建一個進程:
BOOL bSuccess = FALSE;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
HANDLE hDuplicatedToken = NULL;
if (DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken) == FALSE)
{
goto Cleanup;
}
LPVOID lpEnvironment = NULL;
if (CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE) == FALSE)
{
goto Cleanup;
}
WCHAR lpszClientPath[MAX_PATH];
if (GetModuleFileName(NULL, lpszClientPath, MAX_PATH) == 0)
{
goto Cleanup;
}
PathRemoveFileSpec(lpszClientPath);
wcscat_s(lpszClientPath, sizeof(lpszClientPath)/sizeof(WCHAR), L"\\TimeServiceClient.exe");
if (CreateProcessAsUser(hDuplicatedToken, lpszClientPath, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment, NULL, &si, &pi) == FALSE)
{
goto Cleanup;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
bSuccess = TRUE;
Cleanup:
if (!bSuccess)
{
ShowMessage(L"An error occurred while creating fancy client UI", L"Error");
}
if (hToken != NULL)
CloseHandle(hToken);
if (hDuplicatedToken != NULL)
CloseHandle(hDuplicatedToken);
if (lpEnvironment != NULL)
DestroyEnvironmentBlock (lpEnvironment);
創建一個屬於全局命名空間的事件,並且包含安全配置,以便於它可以被當前活動用戶所 訪問(從DACL 和SACL ):
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
DWORD dwLength;
TOKEN_USER* account = NULL;
if (GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength) == FALSE &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
goto Cleanup;
}
account = (TOKEN_USER*)new BYTE[dwLength];
if (GetTokenInformation(hToken, TokenUser, (LPVOID)account, dwLength, &dwLength) == FALSE)
{
goto Cleanup;
}
LPWSTR lpszSid = NULL;
if (ConvertSidToStringSid(account->User.Sid, &lpszSid) == FALSE)
{
goto Cleanup;
}
WCHAR sddl[1000];
wsprintf(sddl, L"O:SYG:BAD:(A;;GA;;;SY)(A;;GA;;;%s)S:(ML;;NW;;;ME)", lpszSid);
PSECURITY_DESCRIPTOR sd = NULL;
if (ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL) == FALSE)
{
goto Cleanup;
}
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = sd;
sa.nLength = sizeof(sa);
g_hAlertEvent = CreateEvent(&sa, FALSE, FALSE, L"Global\\AlertServiceEvent");
if (g_hAlertEvent == NULL)
{
goto Cleanup;
}
while (!g_Stop)
{
Sleep(5000);
SetEvent(g_hAlertEvent);
}
Cleanup:
if (hToken != NULL)
CloseHandle(hToken);
if (account != NULL)
delete[] account;
if (lpszSid != NULL)
LocalFree(lpszSid);
if (sd != NULL)
LocalFree(sd);
if (g_hAlertEvent == NULL)
CloseHandle(g_hAlertEvent);