首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

Windows 7 開發實作:Session 0 隔離與服務 UI 互動解決方案

     編輯:關於.NET

概覽

Windows 服務在系統啟動時即於背景執行,無需使用者登入。部分服務嘗試顯示 UI 對話框或與使用者應用程式通訊,在 Windows 7 (含 Vista) 的 Session 0 隔離 架構下將面臨相容性問題:服務運行於隔離的 Session 0,無法直接存取使用者互動桌面 (Session 1+)。若不處理安全性與通訊機制,服務將無法正常運作。

學習目標

  • 重新設計並修復嘗試直接顯示 UI 的服務
  • 為服務與應用程式共享的 Kernel 物件設定正確的安全性與存取層級

系統需求

  • Microsoft Visual Studio 2008
  • Windows 7
  • Windows Sysinternals Process Explorer

練習 #1:服務直接顯示 UI 的問題與解決

任務 1:安裝並執行服務 (觸發互動式服務偵測)

  1. VS 開啟 Session0_Starter 方案,建置 (注意 Debug/Release、x86/x64)。
  2. 管理員命令提示字元:
    sc create TimeService binPath= C:\Session0_Starter\Debug\TimeService.exe (路徑依實際輸出調整,binPath= 後須有空格)。
  3. Win+R 輸入 services.msc 開啟服務管理單元,啟動 TimeService。
  4. 彈出互動式服務偵測對話框 (Windows 內建機制),提示服務嘗試顯示 UI。
    服務管理單元 互動式服務偵測對話框
  5. 點選「稍後提醒」或「顯示訊息」切換至 Session 0 桌面查看訊息方塊。
  6. 停止服務。

任務 2:使用 WTSSendMessage 快速替代 (訊息方塊)

若服務僅需簡單通知使用者,可用 WTSSendMessage 直接在使用者桌面顯示訊息方塊,避免互動式服務偵測彈窗。

  1. 編輯 UI\Native\TimeService.cpp,找到第一個 //TODO,替換 MessageBox:
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 /* 無限等待 */, &dwResponse, TRUE);
  1. 重建、啟動服務 —— 直接在主桌面顯示訊息方塊,無互動式服務偵測對話框。
    WTSSendMessage 訊息方塊
  2. 點選「否」關閉,停止服務。

任務 3:在使用者 Session 啟動獨立互動 UI 進程 (完整 UI)

若服務需豐富 UI (非簡單訊息方塊),應在使用者 Session 啟動獨立進程 (TimeServiceClient.exe) 顯示 UI,再透過 IPC 與服務通訊。

  1. 編輯 TimeService.cpp 第二個 //TODO 區塊,完整步驟:
// 1. 取得作用中主控台 Session ID 與使用者 Token
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (!WTSQueryUserToken(dwSessionID, &hToken)) goto Cleanup;

// 2. 複製 Token 以建立進程
HANDLE hDuplicatedToken = NULL;
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken)) goto Cleanup;

// 3. 建立環境區塊
LPVOID lpEnvironment = NULL;
if (!CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE)) goto Cleanup;

// 4. 取得用戶端執行檔完整路徑 (同目錄下 TimeServiceClient.exe)
WCHAR lpszClientPath[MAX_PATH];
GetModuleFileName(NULL, lpszClientPath, MAX_PATH);
PathRemoveFileSpec(lpszClientPath);
wcscat_s(lpszClientPath, L"\\TimeServiceClient.exe");

// 5. 以使用者身分建立進程
STARTUPINFO si = {0}; si.cb = sizeof(si);
PROCESS_INFORMATION pi = {0};
if (!CreateProcessAsUser(hDuplicatedToken, lpszClientPath, NULL, NULL, NULL, FALSE,
    NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
    lpEnvironment, NULL, &si, &pi)) 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) CloseHandle(hToken);
if (hDuplicatedToken) CloseHandle(hDuplicatedToken);
if (lpEnvironment) DestroyEnvironmentBlock(lpEnvironment);
if (g_hAlertEvent) CloseHandle(g_hAlertEvent);
  1. 重建、啟動服務 —— 主桌面直接顯示用戶端 UI (無偵測對話框),點選「是」啟動完整客戶端應用程式。
  2. 關閉客戶端,停止服務。

安全警示: 範例為教學簡化,未遵循所有安全程式碼標準。實務中跨 Session 建立進程、共享 Kernel 物件、IPC 通訊,必須進行威脅建模與安全性審查,設定最嚴格的 DACL/SACL,避免提權攻擊面。

練習 #2:共享 Kernel 物件的安全性設定 (事件同步)

任務 1:安裝服務並觀察存取失敗

  1. 啟用 UAC (滑桿非「從不通知」)。
    UAC 啟用
  2. 建置方案,管理員命令列安裝 AlertService:
    sc create AlertService binPath= C:\Session0_Starter\Debug\AlertService.exe
  3. 服務管理單元啟動 AlertService。
  4. 開啟標準命令提示字元 (非管理員),執行 AlertServiceClient —— 錯誤 2 (找不到檔案/事件),因服務建立事件於 Session 0 命名空間,客戶端於 Session 1 無法看見。
  5. 停止服務。

任務 2:修改服務在 Global 命名空間建立事件

  1. 編輯 Security\Native\AlertService.cpp //TODO STEP 1:
    g_hAlertEvent = CreateEvent(NULL, FALSE, FALSE, L"Global\\AlertServiceEvent");
  2. 編輯 AlertServiceClient.cpp //TODO STEP 1:
    HANDLE hEvent = OpenEvent(SYNCHRONIZE, FALSE, L"Global\\AlertServiceEvent");
  3. 重建、啟動服務、執行客戶端 —— 仍失敗 (錯誤 5 拒絕存取),因預設安全描述僅允許 SYSTEM/Administrators 存取。

任務 3:為事件設定自訂安全描述子 (DACL/SACL) 允許當前使用者存取

  1. 編輯 AlertService.cpp //TODO STEP 2 完整流程:
// 1. 取得當前使用者 Token 與 SID
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (!WTSQueryUserToken(dwSessionID, &hToken)) goto Cleanup;

DWORD dwLength;
TOKEN_USER* account = NULL;
if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto Cleanup;
account = (TOKEN_USER*)new BYTE[dwLength];
if (!GetTokenInformation(hToken, TokenUser, account, dwLength, &dwLength)) goto Cleanup;

// 2. SID 轉字串並建構 SDDL
LPWSTR lpszSid = NULL;
if (!ConvertSidToStringSid(account->User.Sid, &lpszSid)) goto Cleanup;

WCHAR sddl[1000];
wsprintf(sddl, L"O:SYG:BAD:(A;;GA;;;SY)(A;;GA;;;%s)S:(ML;;NW;;;ME)", lpszSid);
// O:SY (擁有者 SYSTEM) G:BAD (主要群組 Built-in Administrators)
// DACL: (A;;GA;;;SY) 允許 SYSTEM 完整存取, (A;;GA;;;%s) 允許當前使用者完整存取
// SACL: (ML;;NW;;;ME) 強制完整性 Medium (允許 Medium 完整性以上進程寫入)

// 3. SDDL 轉安全描述子並建立事件
PSECURITY_DESCRIPTOR sd = NULL;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL)) goto Cleanup;

SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = sd;
sa.bInheritHandle = FALSE;

g_hAlertEvent = CreateEvent(&sa, FALSE, FALSE, L"Global\\AlertServiceEvent");
if (!g_hAlertEvent) goto Cleanup;

Cleanup:
if (hToken) CloseHandle(hToken);
if (account) delete[] account;
if (lpszSid) LocalFree(lpszSid);
if (sd) LocalFree(sd);
if (!g_hAlertEvent) CloseHandle(g_hAlertEvent);
  1. 重建、啟動服務、執行客戶端 —— 成功開啟事件並接收訊息。

驗證安全描述子 (Process Explorer)

  1. 執行 Process Explorer (需「顯示所有使用者進程」)。
  2. 選取 AlertService,Ctrl+H 開啟控制代碼視圖。
  3. 找 \BaseNamedObjects\AlertServiceEvent,右鍵 Properties > Security —— 可見允許存取的群組包含 SYSTEM、Administrators、當前使用者。
    Process Explorer 控制代碼 事件安全性分頁

注意: 範例 SDDL 非最佳實務。實務中必須依最小權限原則設定 DACL/SACL,並完成威脅建模,避免建立安全漏洞。

練習 #3:檔案物件完整性層級設定

任務 1:安裝服務並觀察檔案刪除失敗

  1. 管理員命令列安裝 managed 服務:installutil LogService.exe。
  2. 服務管理單元啟動 LoggingService,隨後停止。
  3. 總管開啟 C:\LogService.txt,嘗試刪除 —— 拒絕存取,因檔案由 SYSTEM 建立,預設完整性層級為 System,標準使用者 (Medium) 無法寫入/刪除。

任務 2:降低檔案完整性層級至 Medium

  1. 編輯 Security\Managed\LoggingService.cs //TODO:
IntegrityLevelHelper.SetFileIntegrityLevel(@"C:\LogService.txt", IntegrityLevel.Medium);
  1. 重建、啟動服務、停止服務 —— 使用者現可刪除檔案。

總結

本實驗完整展示 Session 0 隔離導致的三大問題及解決方案:

  1. 服務直接顯示 UI → WTSSendMessage (簡易通知) 或 使用者 Session 啟動獨立 UI 進程 (豐富介面)。
  2. 共享 Kernel 物件 (事件) 跨 Session 不可見/不可存取 → Global 命名空間 + 自訂 DACL/SACL (SDDL) 授權當前使用者。
  3. 服務建立檔案完整性層級過高 → 降低檔案完整性層級至 Medium 允許標準使用者存取。

核心原則:服務運行於 Session 0,不直接觸碰使用者桌面;跨 Session 共享資源必須顯式設定命名空間與安全描述子;遵循最小權限原則,完成威脅建模。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved