概覽
服務是整合在Microsoft Windows操作系統中的結構。服務與用戶的應用程序 不同,因為你可以對他們進行配置,不需要一個激活的用戶登錄,就可以使這些服務在系統 啟動的時候運行,直到系統關閉。Windows中的服務,負責所有種類的後台活動,但不包括從 遠程過程調用(RPC)服務到網絡位置服務的用戶。
一些服務可能會試圖顯示一些用戶界面對話框,或者與用戶的應用程序進行通信。這些服 務都將面臨與Windows 7 的兼容性問題。如果不去討論與你的應用程序進行通信的必要的安 全准備,那麼,你的服務將不能在Windows 7上工作。
目標
在本實驗中,你將學會如何:
• 重新設計和修復一個試圖顯示 UI界面的服務
• 對服務和應用程序間共享的kernel對象,設置適當的安全和訪 問級別
系統需求
完成本實驗,你必須包含以下組件:
• Microsoft Visual Studio 2008
• Windows 7
• Windows Sysinternals進程浏覽器
練習 #1: 簡潔的服務UI 界面
在這個 練習中,你將安裝並且運行一個直接向用戶展示UI用戶界面的服務。你將看到對用戶體驗十 分有效的Windows內置的自動簡潔窗口(交互的服務偵測對話框),還有可以修改服務,使其 不會直接的展示UI用戶界面。
你還可以修改服務,使其在當前的活動用戶下,在一個獨立的進程中,使用簡潔的UI用戶 界面。
任務 1 –安裝和運行服務
作為這個任務的一部分,你需要通過 使用sc命令行安裝服務,並且首先運行它。這個服務將試圖展示一個會觸發簡潔UI用戶界面 服務的用戶對話框。
1.使用Visual Studio,打開Session0_Starter解決方案。
2.生成當前解決方案(請注意你使用的生成配置- Debug/Release,x86/x64)
3.打開管理員命令窗口:
4.點擊 Start.
5.指向所有程序。
6. 指向 Accessories.
7.右擊 Command Prompt.
8.點擊以管理員身份運行。
9.使用cd命令,導向包含應用程序的輸出目錄。例如,如果輸出目錄是 C:\Session0_Starter\Debug,那麼就是用下面的目錄,導向到相應目錄:
CMD
C:
cd C:\Session0_Starter\Debug
10.使用下面的命令, 創建TimeService服務
CMD
sc create TimeService binPath= C:\Session0_Starter\Debug\TimeService.exe
幫助
確認你已經將Step 9中的 目錄替換,並且確認在“binPath=”後,復制了空格。
11.使用組合鍵 +R,並且在Run對話框中輸入services.msc來打開MMC服務管理單元。
12.定 位到TimeService服務,右鍵點擊,並且點擊Start。
13.隨後,你將看到一個類似下圖的對話框。
14.這個就是交互服務偵測對話框,它將檢測試圖展示UI用戶界面的服務,並且顯示這個 簡潔的對話框。
15.點擊Remind me in a few minutes解除這個消息,或者點擊Show me the message切換 到安全Session 0桌面去查看服務UI用戶界面(一個消息框)。
16.返回MMC服務管理單元,定位到TimeService服務,右鍵點擊,然後點擊Stop來停止服 務。
使用WTSSendMessage (快速定位)來修改服務
作為此任務的一部分,你將使用WTSSendMessage方法向用戶展示一個消息對話框。它將給 用戶提供一個交互服務偵測對話框快速定位和替換。
1.如果你還沒有做這項操作,請根據任務1中的Step 1-5安裝TimeService服務。
2.如果你完成了任務1後還沒有做這個,請確認你已經停止了TimeService服務(參考任務 1中的Step 10)
3.使用Visual Studio,打開Session0_Starter解決方案。
4.在UI\Native解決方案文件夾中找到TimeService項目,然後打開TimeService.cpp文件 。
5.在文件中,找到第一個//TODO注釋。找到MessageBox方法調用,並使用下面的代碼進行 替換:
C++
LPWSTR lpszTitle = L"Time Change";
LPWSTR lpszText = L"Notification: 5 seconds have elapsed.\r\nWould you like to see more details?";
DWORD dwSession = WTSGetActiveConsoleSessionId();
WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, dwSession, lpszTitle,
static_cast<DWORD>((wcslen(lpszTitle) + 1) * sizeof(wchar_t)),
lpszText, static_cast<DWORD>((wcslen (lpszText) + 1) * sizeof(wchar_t)),
MB_YESNO|MB_ICONINFORMATION, 0 /*wait indefinitely*/, &dwResponse, TRUE);
6.生成該解決方案。
7.重復任務1中的Step 6-7 。你應該能看到在你的主桌面,會出現一個對話框詢問你,而 不是交互服務偵測對話框。
8.點擊No解除消息。
9.停止服務(參看任務1中Step 10)。
任務 3 –使用不同的用戶憑證浏覽UI 用戶界面
作為該任務的一部分,你需要修改服務,以便於讓其在當前的活動用戶下運行一個新的交 互UI用戶界面進程,來顯示代表服務的用戶交互界面。
1.重復任務2中的Step 1-4操作。
2.在TimeService.cpp文件中找到第二個//TODO注釋。
3.以檢索到的活動的Session ID和與其相關的用戶權證(參看 http://en.wikipedia.org/wiki/Token_(Windows_NT_architecture) 用戶權證背景)來使用 WTSGetActiveConsoleSessionId 和 WTSQueryUserToken方法。這個是用來創建交互UI用戶界 面進程的權證。插入下面的代碼:
C++
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;
}
4.使用DuplicateTokenEx方法來復制權證,以便於它能夠創建進程。插入下面的代碼:
C++
HANDLE hDuplicatedToken = NULL;
if (DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken) == FALSE)
{
goto Cleanup;
}
5.使用CreateEnvironmentBlock方法為交互進程創建一個環境塊。插入下面的代碼:
C++
LPVOID lpEnvironment = NULL;
if (CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE) == FALSE)
{
goto Cleanup;
}
6.通過檢索服務執行的完整路徑,來獲取客戶端應用程序的完整路徑(使用 GetModuleFileName),除去文件名稱(使用PathRemoveFileSpec),然後連結客戶端應用程 序名稱。插入下面的代碼:
C++
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");
7.使用CreateProcessAsUser方法,在目標用戶中創建一個進程:
C++
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;
8.請確認在這項工作期間,你有代碼去分配空余資源。插入下面的代碼:
C++
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);
9.生成解決方案。
10.重復任務1中的Step 6-7操作。除了交互服務偵測對話框,你應該看到在你的主桌面將 出現一個對話框詢問你問題。點擊Yes,客戶端應用程序將啟動,並且及時展現給你。
11.關閉客戶端應用程序並且停止進程(參考任務1中的Step 10)。
注意
為了達到這個練習的目的,我們簡化了示例代碼,並且在我們設計和完成這個項目的時候 ,沒有遵照所有的安全代碼標准。請在另外用戶下創建進程並且使用該進程與後台服務通信 之前,仔細考慮可能引起的安全問題。
練習 #2: 對共享對象進行安全驗證
在這個練習中,你需要安裝和運行一個服務來創建一個標准應用程序的共享kernel對象( 事件)。你將會看到,標准應用程序是不能訪問這個事件的,因為它沒有駐存在同一個 Session的命名空間中,並且沒有設置相應的訪問控制權限。
任務 1 –安裝和運行服務
作為該任務的一部分,你將使用sc命令行安裝一個服務,然後首先運行它。你將看到,當 它去試圖使用服務所創建的事件時,服務的客戶端將收到“Access Denied”的錯誤信息。
1.激活用戶賬戶控制(UAC)。在開始菜單,點擊查詢,然後輸入“User Account Control”。並在查詢結果中選擇“Change User Account Control settings”。然後確認滑塊不是設置在Never notify上的。
2.使用Visual Studio,打開Session0_Starter解決方案。
3.生成當前的解 決方案(確認你使用的生成配置- Debug/Release, x86/x64)。
4.打開一個管理員命 令行窗口,點擊Start,指向 All Programs,然後指向 Accessories,再右鍵點擊 Command Prompt。點擊 Run as administrator。
5.使用cd命令,定位到包含應用程序的輸出 目錄。例如,如果輸出目錄是C:\Session0_Starter\Debug,那麼就是用下面的命令指向相應 的目錄:
CMD
C:
cd C:\Session0_Starter\Debug
6.使用下面的 命令,創建AlertService服務注意:確認你已經使用Step 4 中的路徑,替換了服務的路徑, 並且請確認在“binPath=”後,你復制了空格。
CMD
sc create AlertService binPath= C:\Session0_Starter\Debug\AlertService.exe
7.使用快捷 鍵+R,並且在運行對話框中輸入services.msc進入MMC服務管理單元。
8.定 位到AlertService服務,右鍵點擊,並且點擊Start。
9.打開一個標准的命令行窗口。從開始菜單中,指向All Programs,點擊Accessories, 然後點擊Command Prompt(注意:不要使用管理員方式運行命令行窗口)。
10.在標准命令行窗口中重復Step 5 。
11.使用下面的命令運行AlertService客戶端應用程序,這個服務將嘗試去打開服務創建 的事件,並且用它同步(WaitForSingleObject)。
CMD
AlertServiceClient
12.注意到客戶端在打開事件時失敗,錯誤為2,此錯誤表示沒有找到相應的事件。
13.返回到MMC服務管理單元,定位到AlertService服務,右鍵點擊,然後點擊Stop來停止 該服務。
任務 2 –修改服務,以便能夠在全局命名空間中創建對象
作為該任務的一部分,你需要將對象的名稱進行更改,使其包含全局命名空間的前綴。
1.如果你還不能做這項操作,請根據任務1中的Step 1-5,安裝AlertService服務。
2.如果你在完成了任務1的前提下,還不能做這項操作,請確認AlertService服務已經停 止(查看任務1中的Step 10)。
3.使用 Visual Studio, 打開 Session0_Starter 解決方案。
4.在Security\Native解決方案的文件夾中,定位到AlertService項目,然後打開 AlertService.cpp文件。
5.在該文件中,找到標記為“STEP 1”的//TODO注釋,然後使用下面的代碼將 CreateEvent的調用替換:
C++
g_hAlertEvent = CreateEvent(NULL, FALSE, FALSE, L"Global\\AlertServiceEvent");
6.在Security\Native解決方案的文件夾中,定位到AlertServiceClient項目,然後打開 AlertServiceClient.cpp文件。
7.在該文件中,找到標記為“STEP 1”的//TODO注釋,然後使用下面的代碼將OpenEvent 的調用替換:
C++
HANDLE hEvent = OpenEvent(SYNCHRONIZE, FALSE, L"Global\\AlertServiceEvent");
8.生成該解決方案。
9.重復任務1中的Step 7-13 。注意現在客戶端還是不能訪問事件(這次是因為安全設置 ,而不是因為命名空間的問題)。
10.從Windows Sysinternals中運行進程浏覽器(如果你沒有,請從 http://technet.microsoft.com/en-us/sysinternals/0e18b180-9b7a-4c49-8120- c47c5a693683.aspx 下載工具)。
11.在進程列表中選擇AlertService服務(如果這個服務沒有出現在出現列表中,從File 菜單中,點擊Show processes from all users使用管理員權限重啟進程浏覽器)。
12.確認對話框下方的窗口是可見的,並且能夠顯示進程句柄(使用快捷鍵CTRL+H,或者 從View菜單中打開該窗口)。
13.在句柄列表中,找到\BaseNamedObjects\AlertServiceEvent事件,右擊該事件,並且 點擊Properties 。
14.在Properties對話框中,選擇Security標簽。請注意能夠訪問該事件的安全的組,只 有SYSTEM 和Administrators組。
任務 3 –修改服務,以便於為對象提供安全屬性(DACL 和SACL )
作為該任務的其中 一部分,你將修改服務,以便於能夠為事件對象設置適當的訪問控制權限(DACL和SACL), 使其客戶端能對其訪問。
1.重復任務2中的Step 1-4操作。
2.在文件中,找到 標記為“STEP 2”的//TODO注釋。
3.使用WTSGetActiveConsoleSessionId 和WTSQueryUserToken方法,找到當前活動的Session ID和與其相關聯的用戶權證。插入下面 的代碼:
C++
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
4.使用 GetTokenInformation方法檢索用戶賬號的SID(安全標識)。請注意,此時需要兩個通行證 –一個用於判斷TOKEN_USER結構,另外一個則實際的填充它。插入下面的代碼:
C++
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;
}
5.使用ConvertSidToStringSid方法,將用戶賬號的SID轉換成字符串形式,然後根據它再 去構建一個SDDL字符串,用來表示服務以後創建的事件的安全說明。插入下面的代碼:
C++
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);
6.使用ConvertStringSecurityDescriptorToSecurityDescriptor方法,將SDDL安全說明 字符串,轉換成安全說明形式。插入下面的代碼:
C++
PSECURITY_DESCRIPTOR sd = NULL;
if (ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL) == FALSE)
{
goto Cleanup;
}
7.將step 6 中創建的安全說明,序列化為一個SECURITY_ATTRIBUTES結構,並且使用下面 的代碼創建事件:
C++
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;
}
8.定位到標記為“STEP 3”的//TODO注釋,並且插入下面的代碼,來釋放序列化事件所需 要的資源:
C++
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);
9.生成該解決方案。
10.重復任務1中的Step 7-13 。請注意目前客戶端已經可以成功打開事件,並且可以接收 到從AlertService服務發出的消息了。
11.重復任務2中的Step 9-13 。請注意,現在事件是允許當前的活動用戶進行訪問的,這 也就是為什麼AlertService客戶端即使沒有當前系統的管理員權限,也是能夠打開事件的原 因所在。
當心
示例中所使用的安全說明字符串(SDDL),並不是最好的表現形式。在你的應用程序中, 確認你已經為服務和應用程序共享的資源設置了最高的安全級別,並且進行了威脅分析和模 型來確保你不是創建了一個安全漏洞。
練習 #3: 為文件對象進行加密
在這個練習中,你需要安裝和運行一個服務來創建一個用戶可以訪問的日志文件。但是, 用戶是不能對這個文件進行編寫或者刪除的,除非服務對其進行了安全屬性的設置,包括允 許用戶訪問文件的整合級別。
任務 1 – 安裝和運行服務
作為當前任務的其中一部分,你需要使用installutil命令安裝一個服務,然後首先運行 這個服務。當你試圖去刪除服務所創建的文件時,將看到用戶會收到一個“Access Denied” 的錯誤。
1.使用 Visual Studio,打開 Session0_Starter 解決方案。
2.生成當前的解決方案(請注意你所使用的生成配置 - Debug/Release, x86/x64)。
3.要打開管理員命令窗口,點擊Start,指向All Programs,找到Accessories,然後右鍵 點擊Command Prompt,選中Run as administrator。
4.使用cd命令導航到部署應用程序的輸出目錄。例如,如果輸出目錄是 C:\Session0_Starter\Debug,那麼就是用下面的命令,導航到指定的目錄中:
CMD
C:
cd C:\Session0_Starter\Debug
5.使用下面的命令創建一個LoggingService服務(確認你已經使用step 4 中的路徑,替 換了服務的路徑,並且在“binPath=”之後,復制了空格)。
CMD
installutil LogService.exe
6.使用快捷鍵+R,在運行對話框中輸入services.msc打開MMC服務管理單元。
7.定位到LoggingService服務,右鍵點擊,然後單擊Start。
8.如果打開一個標准命令行窗口,點擊Start,指向All Programs,選擇Accessories,點 擊Command Prompt(注意:不要使用管理員權限運行命令行窗口)。
9.返回MMC服務管理單元,定位到LoggingService服務,右鍵點擊,然後選擇Stop來停止 服務。
10.打開一個Windows Explorer窗口(從我的電腦進入,或者直接打開),然後指向到C:\ root目錄。找到LogService.txt文件。
11.嘗試使用Windows Explorer去刪除文件(右鍵點擊文件,然後選擇Delete,或者點擊 Del鍵)。那麼這個嘗試將由於拒絕訪問錯誤而失敗,因為我們沒有授權用戶去寫或者刪除該 文件。
任務 2 –修改日志文件的整合級別
作為該任務的其中一部分,你需要修改服務所創建的日志文件的整合級別。這樣,用戶就 能夠對日志文件進行寫操作,甚至可以刪除它。
1.如果你還是不能做這項操作,那麼請參照任務1中的step 1-5安裝LoggingService服務 。
2.如果你已經完成了任務1還是不能做這項操作,請確認LoggingService服務已經停止( 參看任務1中的step 10)。
3.使用 Visual Studio,打開 Session0_Starter 解決方案。
4.在Security\Managed解決方案文件夾中,找到LogService項目,然後打開 LoggingService.cs文件。
5.在文件中,找到標記為?的//TODO注釋,然後添加下面的代碼:
C++
IntegrityLevelHelper.SetFileIntegrityLevel(@"C:\LogService.txt", IntegrityLevel.Medium);
6.生成解決方案。
7.這次,由於日志文件已經不再受系統整合級別的保護,所以用戶可以刪除該日志文件了 。
摘要
在這個實驗中,你已經診斷了由Session 0隔離機制所造成的兩個問題,為這些問題設計 了一些修復應用程序,並且實現了這些修復。你也已經使用了快速修復策略,比如使用 WTSSendMessage方法在服務中向交互用戶發送消息,與具有良好設計的解決方案一樣,比如 為共享的kernel對象或者文件配置訪問控制,並且在當前的活動用戶下,在服務中執行進程 的UI用戶界面。