建立使用者桌面程序(突破Session 0隔離)
阿新 • • 發佈:2019-01-07
這裡就引入了Windows Vista之後的Session 0隔離機制。
有些服務可能需要在使用者介面上顯示對話方塊,或需要與使用者的應用程式通訊,這種型別的功能“通常”屬於Windows XP服務,因為在Windows XP中,這樣做很容易。如果服務恰好需要顯示某些使用者介面物件,例如對話方塊,或者需要與應用程式通訊,則在Windows 7下執行可能會遇到問題。
如果在Windows 7上執行需要顯示對話方塊的服務,此時並不能看到所需的對話方塊,在工作列會上看到一個閃爍的圖示。而且,如果單擊這個閃爍圖示,還會看到一個對話方塊。更具體來說,在Windows 7中執行時,可能會遇到下列一個或多個現象,這個服務可能會:
- 正常執行,但無法完成既定工作,同時耗費大量CPU時鐘和記憶體。
- 正常執行,但其他程序無法與該服務通訊,該服務業無法與使用者或其他應用程式/服務通訊。
- 嘗試通過Windows訊息機制與使用者應用程式通訊,但Windows訊息機制無法實現目標。
- 在工作列上顯示一個閃爍的圖示,代表該服務希望與桌面互動。
如圖所示:
將服務和使用者應用程式一起在Session 0中執行會導致安全風險,因為服務會使用提升後的許可權執行,而使用者應用程式使用使用者特權(大部分都是非管理員使用者)執行,這會使得惡意軟體以某個服務為攻擊目標,通過“劫持”該服務,達到提升自己許可權級別的目的。
從Windows Vista開始,只有服務可以託管到Session 0中,使用者應用程式和服務之間會被隔離,並需要執行在使用者登入到系統時建立的後續會話中。例如第一個登入的使用者建立 Session 1,第二個登入的使用者建立Session 2,以此類推,如下圖所示。
使用不同會話執行的實體(應用程式或服務)如果不將自己明確標註為全域性名稱空間,並提供相應的訪問控制設定,將無法
上一篇中,因為我們使用全域性的名稱空間,因此實現的共享核心物件。 關於Session 0,就簡單介紹這些,具體的詳細內容,請查閱微軟的官方文件。我們今天的重點並不在此。 回到正題。雖然Windows 7的Session 0給服務層和應用層的通訊造成了很大的難度,但並不代表沒有辦法實現服務層與應用層的通訊與互動。 微軟提供了一系列WTS( Windows Terminal Service Windows終端服務)開頭的函式,來完成服務層與應用層的互動。 如圖所示:
實現程式碼如下: [cpp] view plaincopyprint?
- DWORD _stdcall CATLDemoServiceModule::LaunchWin7SessionProcess( LPTSTR lpCommand )
- {
- DWORD dwRet = 0;
- PROCESS_INFORMATION pi;
- STARTUPINFO si;
- DWORD dwSessionId;//當前會話的ID
- HANDLE hUserToken = NULL;//當前登入使用者的令牌
- HANDLE hUserTokenDup = NULL;//複製的使用者令牌
- HANDLE hPToken = NULL;//程序令牌
- DWORD dwCreationFlags;
- //得到當前活動的會話ID,即登入使用者的會話ID
- dwSessionId = WTSGetActiveConsoleSessionId();
- do
- {
- WTSQueryUserToken(dwSessionId,&hUserToken);//讀取當前登入使用者的令牌資訊
- dwCreationFlags = NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE;//建立引數
- ZeroMemory(&si,sizeof(STARTUPINFO));
- ZeroMemory(&pi,sizeof(pi));
- si.cb = sizeof(STARTUPINFO);
- si.lpDesktop = L"winsta0\\default";//指定建立程序的視窗站,Windows下唯一可互動的視窗站就是WinSta0\Default
- TOKEN_PRIVILEGES tp;
- LUID luid;
- //開啟程序令牌
- if (!OpenProcessToken(GetCurrentProcess(),
- TOKEN_ADJUST_PRIVILEGES|
- TOKEN_QUERY|
- TOKEN_DUPLICATE|
- TOKEN_ASSIGN_PRIMARY|
- TOKEN_ADJUST_SESSIONID|
- TOKEN_READ|
- TOKEN_WRITE,&hPToken))
- {
- dwRet = GetLastError();
- break;
- }
- //查詢DEBUG許可權的UID
- if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))
- {
- dwRet = GetLastError();
- break;
- }
- //設定令牌資訊
- tp.PrivilegeCount = 1;
- tp.Privileges[0].Luid = luid;
- tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
- //複製當前使用者的令牌
- if (!DuplicateTokenEx(hPToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,
- TokenPrimary,&hUserTokenDup))
- {
- dwRet = GetLastError();
- break;
- }
- //設定當前程序的令牌資訊
- if (!SetTokenInformation(hUserTokenDup,TokenSessionId,(void*)&dwSessionId,sizeof(DWORD)))
- {
- dwRet = GetLastError();
- break;
- }
- //應用令牌許可權
- if (!AdjustTokenPrivileges(hUserTokenDup,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),
- (PTOKEN_PRIVILEGES)NULL,NULL))
- {
- dwRet = GetLastError();
- break;
- }
- //建立程序環境塊,保證環境塊是在使用者桌面的環境下
- LPVOID pEnv = NULL;
- if (CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))
- {
- dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
- }
- else
- {
- pEnv = NULL;
- }
- //建立使用者程序
- if (!CreateProcessAsUser(hUserTokenDup,NULL,lpCommand,NULL,NULL,FALSE,
- dwCreationFlags,pEnv,NULL,&si,&pi))
- {
- dwRet = GetLastError();
- break;
- }
- } while (0);
- //關閉控制代碼
- if (NULL != hUserToken)
- {
- CloseHandle(hUserToken);
- }
- if (NULL != hUserTokenDup)
- {
- CloseHandle(hUserTokenDup);
- }
- if (NULL != hPToken)
- {
- CloseHandle(hPToken);
- }
- return dwRet;
- }
呼叫方式: [cpp] view plaincopyprint?
- LaunchWin7SessionProcess("C:\\Windows\\notepad.exe");
這樣,服務程式就可以在使用者的桌面上建立一個應用程式了,而且具有System許可權。 總結一下服務程式與使用者桌面程式通訊的解決辦法:
- 如果服務需要通過傳送訊息的方式與使用者互動,請使用WTSSendMessage函式。在功能上,該函式幾乎與MessageBox相同,對於不需要過於完整的UI的服務,這是一種簡單易行的方法,而且足夠安全,因為所顯示的資訊框無法用於奪取對底層服務的控制。
- 如果您的服務需要更完整的UI,請使用CreateProcessAsUser函式在發起請求的使用者的桌面上建立程序。但是這裡需要注意,您依然需要在新建立的程序和原始服務之間進行通訊,因此這裡就需要用到下一點內容。
- 如果需要雙向互動,請使用Windows Communication Foundation(WCF),這是一種用於.NET Remoting命名管線或任何其他程序間通訊(IPC)的機制(Windows訊息除外),可用於實現跨會話的通訊。如果需要提升,WCF和Remoting是一種比直接提升使用者(假設UAC沒有過關閉)更安全的做法。
- 請確保需要跨會話共享的核心物件使用了帶有Global\字串的名稱字首,該字串意味著這個物件屬於會話全域性(Session-Global)名稱空間。