Window stations 和桌面可能是Windows NT服務中最與眾不同的了
絕大多數編程者都不會直接接觸這兩種對象
盡管用戶每時每刻都要碰到它們
window station 和桌面對象就象其它Windows NT對象
如事件
互斥量和信號量一樣
是安全(securable)的
一個window station 對象包括一個剪貼板
一個全局原子集和更多的桌面對象
一個window station或者是可見的或者是不可見的
一個可見的window station接收用戶來自於鼠標或鍵盤的輸入
一個顯示設備也與之相連這樣信息可以顯示給交互式用戶
在 Windows NT
中
只有一個window station 能被看得到
就是WinSta
可見的window station也被定義成交互式的
一個不可見的window station是不可交互的
而且也不能接收任何用戶的輸入也沒有顯示設備與之相連
如前所述
桌面包含在window station對象中
一個桌面對象包含一個邏輯的顯示表面
和窗口
菜單等
只有屬於可見window station的桌面才能被看見並接收用戶的輸入
這個桌面叫做活動桌面
作為交互式用戶你在不同的時候碰到三種不同的桌面
缺省(Default)
Winlogon和Screen
saver
Winlogon 桌面是當你按下Ctrl+Alt+Delete組合鍵時顯示在你面前的對話框
缺省(Default)桌面是浏覽器(Explorer)或者是由交互式用戶啟動的所有進程
它更應當被理解成交互式的應用程序桌面
最終的桌面是Screen
saver
它顯示你的屏幕saver
你可能已經注意到可以在不同的桌面之間切換
當一個用戶按下Ctrl+Alt+Delete組合鍵時
操作系統可以從缺省狀態切換到Winlogon桌面
當你在登錄對話框中選擇取消
系統將再切換回缺省桌面
有人問我當切換進行的時候
是否其它桌面上的東西都被破壞掉了
答案是
不
雖然你看不到其它桌面
但它們仍然在那裡
系統中所有的進程都與window station 和桌面相聯系
當一個用戶第一次登錄時
交互式window station
WinSta
和缺省桌面都與這個用戶的Shell進程相關聯
這樣用戶就能看到shell了
如果不是這樣
用戶是什麼也看不到的
而且在這之後
由shell啟動的所有進程也會和WinSta
及缺省桌面相關聯
你還可以通過STARTUPINFO 數據結構的lpDesktop 成員指定你的進程同哪個window station 和桌面相關聯
這個數據結構傳遞給CreateProcess 和 CreateProcessAsUser兩個函數
你可以將lpDesktop 初始化為NULL
意思是讓CreateProcess函數使用和調用進程相同的window station 和桌面
你可以將你自己的window station 和桌面組合定義成
WinSta
\Default
或者就定義成空字符串
這個參數會讓操作系統為啟動進程創建一個新的不可見的window station 及桌面
與這兩個新對象關聯的安全性授予每個組對它們的完全訪問權限
typedef struct _STARTUPINFO { // si
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved
;
LPBYTE lpReserved
;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO
*LPSTARTUPINFO;
Window stations和桌面是具有安全性的對象
與window station 和桌面將關聯的進程必須由對這些對象的合適的訪問權限
如果進程沒有訪問權
你會看到這兩個消息之一
User
dll initialization failure(User
dll初始化失敗)
或
Kernel
dll initialization failure
(Kernel
dll初始化失敗)
由進程返回的退出碼為
或 ERROR_NO_WAIT_CHILDREN
那麼我所指的合適的訪問權是什麼意思呢?假如你有一個文件這樣的對象
你可以為這個文件創建一個DACL以使用戶具有對這個文件的讀訪問權
Window station 和 桌面是以相同的方式工作的
對於一個桌面對象的一個訪問權限叫做DESKTOP_CREATEWINDOW
如果用戶沒有被授予這個訪問權限
任何由這個用戶啟動的進程都不能創建窗口
不幸的是
象CreateWindow 這樣的USER APIs 在發生同CreateFile 或 CreateMutex API類似的安全問題時
不會返回
Access Denied(訪問被拒絕)
消息
User
dll 中的Windows 應用程序將會被終止
導致DLL初始化錯誤的消息
Kernel
dll 初始化過程是在創建一個控制板窗口時發生的
一個例程在沒有對window station和桌面的合適的訪問權限的時候啟動cmd
exe
然而不幸的是
CreateProcess 沒有任何機制來檢查這個錯誤
當用戶不具有對window station和桌面的合適訪問權限時
它並不返回一個錯誤信息
CreateProcess將會啟動這個應用程序
然後這個應用程序本身在DLL失敗後終止
編程人員還可以有一種方法越過
User
dll initialization failure(User
dll初始化失敗
消息
系統有一個堆用來為window station分配內存
內存是有限的
缺省設置允許創建七個或八個window station對象
如果你用光了所有的內存
你就會看到這個消息
不過
值得慶幸的是
有一個注冊表關鍵字可以用來增加這個設置
(參見Knowledge Base article Q
)
如果你沒在開發服務而只是普通的應用程序
Window stations 和桌面就不是真正的問題
你的應用程序只同交互式桌面WinSta
\Default 相關聯
如果你是在開發一個服務
那麼它可能就會同下面的window station 及桌面組合關聯
WinSta
\Default
Service
x
e
$\Default
Service Account
s Logon SID\Default
WinSta
\Default 同運行在LocalSystem帳戶的
並且與桌面交互的服務關聯
(在ServiceType必須指明SERVICE_INTERACTIVE_PROCESS標志)
如果服務不同桌面交互
那麼它是與Service
x
e
$\Default相關聯的
這是個不可見的window station
你一定很疑惑這亂七八糟的
x
e
$ 是什麼
它是服務的登錄SID
登錄SID是獨一無二的
它指的是用戶所屬的組
系統中的所有用戶都會有一個登錄SID
有一個有趣的現象是盡管你為同一個服務帳戶配置了兩個服務
但由於它們的登錄SID是不同的
所以每個都將有一個獨一無二的window station 及桌面
用戶的SID是相同的但是由於它們運行在不同的服務帳戶登錄段中所以它們每一個都有獨一無二的登錄段
對於交互式服務和非交互式的LocalSystem 服務
情況就不同了
因為它們既同WinSta
關聯又同Service
x
e
$關聯
為什麼知道哪個window station和桌面與服務相關聯是如此的重要呢?第一
交互性
如果你的服務沒有和WinSta
\Default相關聯
它就不能以缺省狀態把任何USER對象展示給用戶
這意味著你不能顯示一個窗口或者是獲得用戶的輸入
你可以為一個非交互式的服務顯示一個MessageBox
關鍵字標志的類型是MB_SERVICE_NOTIFICATION 和 MB_DEFAULT_DESKTOP
你不能用它來發送消息
這個動作與你的進程相關聯的桌面有關系
如果你不為進程重新分配正確的window station 及桌面
你就始終不能做這件事
另外兩種基於服務帳號的登錄SID的window station 和桌面組合是不可見的
正如我剛剛提到的
與一個不可見window station相關聯的桌面永遠不能顯示或接收來自交互式用戶的輸入信息(除非MessageBox使用兩個特殊的標志)
你仍然可以創建USER對象
但是用戶永遠不會看到它們
很多服務的開發者們經常要犯的一個錯誤是顯示一個對話框提示用戶輸入信息
當服務被測試時
開發者會注意到服務被掛起了
這就是因為服務同一個不可見的window station相關聯
操作系統是成功地創建了對話框
問題是用戶看不到它
所以你應當怎樣才能使你的服務能顯示並獲得從用戶那來的信息呢?首先為用戶寫一個啟動的客戶應用程序
客戶應用程序將實現顯示及從用戶處獲得信息的功能
然後使用進程間通信機制把信息發回給服務
這樣做的最大好處是你不必再為window stations及桌面操心
一個缺點就是需要用戶做一些事情來啟動你的客戶應用程序
另外一個方法要求你為LocalSystem帳戶配置你的服務
要將服務類型說明為SERVICE_INTERACTIVE_PROCESS
這將把你的服務與正確的window station及桌面相關聯
唯一的問題就是服務將不能通過NTLM進行任何的網絡訪問
有兩種辦法可以解決
第一種辦法是
NULL 段訪問可以在服務器端通過注冊表調整
如果服務開發者有對服務器的合適的安全訪問權限
注冊表的關鍵字將會改變
允許對LocalSystem 進程的訪問
另一個解決的辦法是扮演一個已經訪問過NTLM 安全網絡資源的用戶
一個問題是服務必須知道用戶的口令以通過LogonUser API生成用戶的標志
為什麼用戶不能為服務帳戶配置一個服務
那樣就只須重新為服務分配交互式的window station 及桌面就可以了?答案只有一個
就是安全
對交互式window station 及桌面具有完全訪問權限的唯一用戶是LocalSystem帳戶和交互式用戶(如果有的話)
我特別指出完全訪問權限的原因是屬於本地管理員組的用戶對交互式window station 及桌面擁有部分訪問權限
有一部分USER API 調用可能會由於安全的原因無法運行
最好的賭注是冒點風險使用本地管理員帳戶
交互式用戶的訪問權限是基於組的登錄SID 而不是單個用戶的SID
這意味著你可能有一個與交互式用戶相同帳戶的服務但是組的登錄SID是不同的
允許一個為服務帳戶配置的服務具有對交互式window station及桌面訪問的唯一條件是為交互式用戶更改安全性以允許服務訪問
現在你有了一個為交互式window station 及桌面配置的服務
你還會碰上什麼樣的問題呢?第一個要考慮的問題就是當用戶退出時將會發生什麼
如果服務是交互式的
那麼是否缺省桌面被破壞了呢?不
缺省桌面仍然存在
唯一的區別是Winlogon 桌面現在是活動桌面
當第二個用戶登錄系統時
系統將會切換回缺省桌面
用戶顯示的任何事物都可以被交互式用戶看到
交互式服務和由交互式用戶啟動的進程之間的區別是
當沒有用戶登錄時
服務仍然可以運行
這就導致了一些有趣的問題
例如
基於安全的原因
當交互式用戶退出系統後
操作系統竟全局原子表清為
如果一個交互式服務依賴於存儲在全局原子表中的信息
那麼當這個交互式用戶退出後
信息全部消失
另一個例子是自啟動交互式服務
第一個用戶登錄之前缺省桌面還沒有創建
這意味著在用戶登錄之前試圖顯示信息的交互式服務一定會出問題
我想討論的最後一件事是交互式服務是暴露給交互式用戶的
這個交互式用戶可以通過任務管理器殺死服務
如果你有一個運行在LocalSystem 帳戶上的服務
則交互式用戶將不具備必須的安全性來殺死你的進程
假設你進入任務管理器列出所有的進程
如果你點擊了End Process(終止進程)按紐來終止運行在LocalSystem帳戶上的進程時
你將會得到意料之中的
Access is denied(訪問被拒絕)
消息框
但是如果這個服務有一個顯示在外層的窗口
你可以在任務管理器中列出所有應用程序
當你點擊End Task(終止任務)按紐
你就可以通過這個暴露的窗口殺死這個服務
避免這種情況發生的一種方法就是讓你的窗口不要顯示在應用程序標簽中
創建一個隱藏的窗口
然後將可見的窗口作為這個隱藏窗口的兒子窗口
記住
最後的手段才是將你的服務交互化
最好的選擇是創建一個交互式的客戶應用程序
注冊表
蜂箱
蜂箱
是操作系統用來存儲用戶注冊信息的
在注冊表中存儲的注冊信息包括桌面
應用程序
打印和網絡設置
系統將用戶的注冊信息備份在一個象蜂箱一樣的文件中
Windows NT的每個用戶都分配一個蜂箱
這個蜂箱能放在本地的或遠程的服務器上
當一個用戶登錄到Windows NT機器上時
系統把用戶的蜂箱裝載到注冊表中
在HKEY_USERS 關鍵字下
注冊表關鍵字名稱代表蜂箱時基於用戶的SID的
如果你通過regedt
exe 或regedit
exe檢測到HKEY_USERS下的注冊表關鍵字
你將看到至少兩個子關鍵字
DEFAULT 和以S
開頭的長字符串
這是用戶的SID
如果你還不清楚HKEY_CURRENT_USER代表的是什麼意思
那麼我告訴你
基本上說
它是一個HKEY_USERS\user
s SID的映射
通過regedt
exe 或regedit
exe可以得到驗證
HKEY_USER\user
s SID和HKEY_CURRENT_USER的子關鍵字實際上是一模一樣的
當一個應用程序是關於HKEY_CURRENT_USER的
系統將會基於用戶的安全上下文把用戶映射到合適的蜂箱中
包括用戶的SID
它用來在HKEY_USERS下查找正確的蜂箱
如果由於某種原因用戶的蜂箱沒有被裝載
系統將會把用戶的蜂箱映射到
DEFAULT
現在
看起來一切都好了
但是還有很多問題
首先
服務要依賴用戶的注冊信息
絕大多數應用程序在HKEY_CURRENT_USER中存儲用戶的注冊信息
例如
用戶的打印信息存儲在用戶的蜂箱中
如果服務配置配置給不同的帳戶
那這個服務將沒有任何打印信息
不是所有的服務都使用
DEFAULT 蜂箱
如果一個服務配置給一個具有蜂箱的服務帳戶
那麼 Service Control Manager將會裝載喲功能戶的蜂箱
Windows NT
中已經增添了這一功能
Service Control Manager不能做的一件事是為服務正確地准備用戶的環境
環境變量存儲早用戶的蜂箱中
服務繼承的唯一一個環境變量來自於Service Control Manager
它的環境基於系統環境變量
如果你改變系統環境變量
那麼只有在重新啟動機器後
所做的改變才能反映到服務中
當系統環境變量改變時
Service Control Manager不象其它系統進程一樣處理WM_SETTINGCHANGE 消息
讓我們返回到LocalSystem 帳戶的情況中
怎樣得到一個已合理配置的
蜂箱
呢?有這樣幾種選擇
如果你有創建注冊關鍵字的應用程序的控制權
就可以創建兩次關鍵字
一次是為HKEY_CURRENT_USER
另一次是為HKEY_USERS\Default
另一種方法是判斷應用程序創建了那些關鍵字
進行復制
這種方法可以通過手工或編程實現
如果你不想和注冊表攪在一起
你可以用編程的方式裝載你自己的
蜂箱
如果你知道一個已經正確初始化的
蜂箱
的用戶名
服務將基於用戶名決定蜂箱的位置
通過RegLoadKey API裝載
蜂箱
然後扮演這個用戶
問題是你需要一個進程標志來扮演這個用戶
一個進程標志可以通過LogonUser API調用生成
當然
你需要一個用戶口令
如果由於某種原因你所感興趣的用戶有一些進程運行在系統中
服務將會列舉這些進程知道運行在目標用戶的安全上下文中的進程
一旦一個進程被定位
這個服務將通過調用OpenProcessToken獲得用戶標志
在這時
服務將用獲得標志扮演這個用戶
結尾
討論了在Windows NT服務和由交互式用戶啟動的應用程序之間的區別
也討論了服務開發人員最關心的三個領域
Windows NT 安全
window stations 和桌面以及注冊表
蜂箱
這些信息將使Windows NT服務的開發變得容易一些並能應用於服務中使用的其它技術中
From:http://tw.wingwit.com/Article/os/xtgl/201311/9056.html