1. 程式人生 > >建立使用者桌面程序(突破Session 0隔離)

建立使用者桌面程序(突破Session 0隔離)

這裡就引入了Windows Vista之後的Session 0隔離機制。

有些服務可能需要在使用者介面上顯示對話方塊,或需要與使用者的應用程式通訊,這種型別的功能“通常”屬於Windows XP服務,因為在Windows XP中,這樣做很容易。如果服務恰好需要顯示某些使用者介面物件,例如對話方塊,或者需要與應用程式通訊,則在Windows 7下執行可能會遇到問題。

如果在Windows 7上執行需要顯示對話方塊的服務,此時並不能看到所需的對話方塊,在工作列會上看到一個閃爍的圖示。而且,如果單擊這個閃爍圖示,還會看到一個對話方塊。更具體來說,在Windows 7中執行時,可能會遇到下列一個或多個現象,這個服務可能會:

  • 正常執行,但無法完成既定工作,同時耗費大量CPU時鐘和記憶體。
  • 正常執行,但其他程序無法與該服務通訊,該服務業無法與使用者或其他應用程式/服務通訊。
  • 嘗試通過Windows訊息機制與使用者應用程式通訊,但Windows訊息機制無法實現目標。
  • 在工作列上顯示一個閃爍的圖示,代表該服務希望與桌面互動。
在Windows XP、Windows Server 2003,以及更老版本的Windows作業系統中,服務和應用程式使用相同的會話(Session)執行,而這個會話是由第一個登入到控制檯的使用者啟動的。該會話就叫做Session 0,如下圖所示,在Windows Vista之前,Session 0不僅包含服務,也包含標準使用者應用程式。

如圖所示:

將服務和使用者應用程式一起在Session 0中執行會導致安全風險,因為服務會使用提升後的許可權執行,而使用者應用程式使用使用者特權(大部分都是非管理員使用者)執行,這會使得惡意軟體以某個服務為攻擊目標,通過“劫持”該服務,達到提升自己許可權級別的目的。

從Windows Vista開始,只有服務可以託管到Session 0中,使用者應用程式和服務之間會被隔離,並需要執行在使用者登入到系統時建立的後續會話中。例如第一個登入的使用者建立 Session 1,第二個登入的使用者建立Session 2,以此類推,如下圖所示。


使用不同會話執行的實體(應用程式或服務)如果不將自己明確標註為全域性名稱空間,並提供相應的訪問控制設定,將無法
互相傳送訊息,共享UI元素,或共享核心物件。這一過程如下圖所示:


上一篇中,因為我們使用全域性的名稱空間,因此實現的共享核心物件。 關於Session 0,就簡單介紹這些,具體的詳細內容,請查閱微軟的官方文件。我們今天的重點並不在此。 回到正題。雖然Windows 7的Session 0給服務層和應用層的通訊造成了很大的難度,但並不代表沒有辦法實現服務層與應用層的通訊與互動。 微軟提供了一系列WTS( Windows Terminal Service Windows終端服務)開頭的函式,來完成服務層與應用層的互動。 如圖所示:
實現程式碼如下: [cpp] view plaincopyprint?
  1. DWORD _stdcall CATLDemoServiceModule::LaunchWin7SessionProcess( LPTSTR lpCommand )  
  2. {  
  3.     DWORD dwRet = 0;  
  4.     PROCESS_INFORMATION pi;  
  5.     STARTUPINFO si;  
  6.     DWORD dwSessionId;//當前會話的ID
  7.     HANDLE hUserToken = NULL;//當前登入使用者的令牌
  8.     HANDLE hUserTokenDup = NULL;//複製的使用者令牌
  9.     HANDLE hPToken = NULL;//程序令牌
  10.     DWORD dwCreationFlags;  
  11.     //得到當前活動的會話ID,即登入使用者的會話ID
  12.     dwSessionId = WTSGetActiveConsoleSessionId();  
  13.     do
  14.     {  
  15.         WTSQueryUserToken(dwSessionId,&hUserToken);//讀取當前登入使用者的令牌資訊
  16.         dwCreationFlags = NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE;//建立引數
  17.         ZeroMemory(&si,sizeof(STARTUPINFO));  
  18.         ZeroMemory(&pi,sizeof(pi));  
  19.         si.cb = sizeof(STARTUPINFO);  
  20.         si.lpDesktop = L"winsta0\\default";//指定建立程序的視窗站,Windows下唯一可互動的視窗站就是WinSta0\Default
  21.         TOKEN_PRIVILEGES tp;  
  22.         LUID luid;  
  23.         //開啟程序令牌
  24.         if (!OpenProcessToken(GetCurrentProcess(),  
  25.             TOKEN_ADJUST_PRIVILEGES|  
  26.             TOKEN_QUERY|  
  27.             TOKEN_DUPLICATE|  
  28.             TOKEN_ASSIGN_PRIMARY|  
  29.             TOKEN_ADJUST_SESSIONID|  
  30.             TOKEN_READ|  
  31.             TOKEN_WRITE,&hPToken))  
  32.         {  
  33.             dwRet = GetLastError();  
  34.             break;  
  35.         }  
  36.         //查詢DEBUG許可權的UID
  37.         if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))  
  38.         {  
  39.             dwRet = GetLastError();  
  40.             break;  
  41.         }  
  42.         //設定令牌資訊
  43.         tp.PrivilegeCount = 1;  
  44.         tp.Privileges[0].Luid = luid;  
  45.         tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;  
  46.         //複製當前使用者的令牌
  47.         if (!DuplicateTokenEx(hPToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,  
  48.             TokenPrimary,&hUserTokenDup))  
  49.         {  
  50.             dwRet  = GetLastError();  
  51.             break;  
  52.         }  
  53.         //設定當前程序的令牌資訊
  54.         if (!SetTokenInformation(hUserTokenDup,TokenSessionId,(void*)&dwSessionId,sizeof(DWORD)))  
  55.         {  
  56.             dwRet = GetLastError();  
  57.             break;  
  58.         }  
  59.         //應用令牌許可權
  60.         if (!AdjustTokenPrivileges(hUserTokenDup,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),  
  61.             (PTOKEN_PRIVILEGES)NULL,NULL))  
  62.         {  
  63.             dwRet = GetLastError();  
  64.             break;  
  65.         }  
  66.         //建立程序環境塊,保證環境塊是在使用者桌面的環境下
  67.         LPVOID pEnv = NULL;  
  68.         if (CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))  
  69.         {  
  70.             dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;  
  71.         }  
  72.         else
  73.         {  
  74.             pEnv = NULL;  
  75.         }  
  76.         //建立使用者程序
  77.         if (!CreateProcessAsUser(hUserTokenDup,NULL,lpCommand,NULL,NULL,FALSE,  
  78.             dwCreationFlags,pEnv,NULL,&si,&pi))  
  79.         {  
  80.             dwRet = GetLastError();  
  81.             break;  
  82.         }  
  83.     } while (0);  
  84.     //關閉控制代碼
  85.     if (NULL != hUserToken)  
  86.     {  
  87.         CloseHandle(hUserToken);  
  88.     }  
  89.     if (NULL != hUserTokenDup)  
  90.     {  
  91.         CloseHandle(hUserTokenDup);  
  92.     }  
  93.     if (NULL != hPToken)  
  94.     {  
  95.         CloseHandle(hPToken);  
  96.     }  
  97.     return dwRet;  
  98. }  

呼叫方式: [cpp] view plaincopyprint?
  1. 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)名稱空間。
至些,關於服務程式與使用者桌面程式的相關技術點,總結完畢,歡迎大家補充和提出改進意見。