淺析Visual Studio在Win7下用戶界面特權(quán)隔離
Windows 7即將隆重發(fā)布,但是很多程序員已經(jīng)通過RTM等版本嘗到了Windows 7的甜處。那么在Windows 7下用戶界面特權(quán)隔離,將是本文我們介紹的重點(diǎn)。51CTO編輯推薦《Visual Studio 2010應(yīng)用與開發(fā)詳解》
我們介紹了操作系統(tǒng)服務(wù)的Session 0隔離,通過Session 0隔離,Windows 7實(shí)現(xiàn)了各個(gè)Session之間的獨(dú)立和更加安全的互訪,使得操作系統(tǒng)的安全性有了較大的提高。從操作系統(tǒng)服務(wù)的Session 0隔離嘗到了甜頭后,雷德蒙的程序員們仿佛愛上了隔離這一招式?,F(xiàn)在他們又將隔離引入了同一個(gè)Session之中的各個(gè)進(jìn)程之間,帶來全新的用戶界面特權(quán)隔離。
用戶界面特權(quán)隔離
在早期的Windows操作系統(tǒng)中,在同一用戶下運(yùn)行的所有進(jìn)程有著相同的安全等級(jí),擁有相同的權(quán)限。例如,一個(gè)進(jìn)程可以自由地發(fā)送一個(gè)Windows消息到另外一個(gè)進(jìn)程的窗口。從Windows Vista開始,當(dāng)然也包括Windows 7,對(duì)于某些Windows消息,這一方式再也行不通了。進(jìn)程(或者其他的對(duì)象)開始擁有一個(gè)新的屬性——特權(quán)等級(jí)(Privilege Level)。一個(gè)特權(quán)等級(jí)較低的進(jìn)程不再可以向一個(gè)特權(quán)等級(jí)較高的進(jìn)程發(fā)送消息,雖然他們?cè)谙嗤挠脩魴?quán)限下運(yùn)行。這就是所謂的用戶界面特權(quán)隔離(User Interface Privilege Isolation ,UIPI)。
UIPI的引入,最大的目的是防止惡意代碼發(fā)送消息給那些擁有較高權(quán)限的窗口以對(duì)其進(jìn)行攻擊,從而獲取較高的權(quán)限等等。這就像一個(gè)國(guó)家,原本人人平等,大家之間可以互相交流問候,但是后來壞人多了,為了防止壞人以下犯上,獲得不該有的權(quán)利,就人為地給每個(gè)人劃分等級(jí),等級(jí)低的不可以跟等級(jí)高的說話交流。在人類社會(huì),這是一種令人討厭的等級(jí)制度,但是在計(jì)算機(jī)系統(tǒng)中,這卻是一種維護(hù)系統(tǒng)安全的合適方式。
UIPI的運(yùn)行機(jī)制
在Windows 7中,當(dāng)UAC(User Account Control)啟用的時(shí)候,UIPI的運(yùn)行可以得到最明顯的體現(xiàn)。在UAC中,當(dāng)一個(gè)管理員用戶登錄系統(tǒng)后,操作系統(tǒng)會(huì)創(chuàng)建兩個(gè)令牌對(duì)象(Token Object):第一個(gè)是管理員令牌,擁有大多數(shù)特權(quán)(類似于Windows Vista之前的System中的用戶),而第二個(gè)是一個(gè)經(jīng)過過濾后的簡(jiǎn)化版本,只擁有普通用戶的權(quán)限。
默認(rèn)情況下,以普通用戶權(quán)限啟動(dòng)的進(jìn)程擁有普通特權(quán)等級(jí)(UIPI的等級(jí)劃分為低等級(jí)(low),普通(normal),高等級(jí)(high),系統(tǒng)(system))。相同的,以管理員權(quán)限運(yùn)行的進(jìn)程,例如,用戶右鍵單擊選擇“以管理員身份運(yùn)行”或者是通過添加“runas”參數(shù)調(diào)用ShellExecute運(yùn)行的進(jìn)程,這樣的進(jìn)程就相應(yīng)地?fù)碛幸粋€(gè)較高(high)的特權(quán)等級(jí)。
這將導(dǎo)致系統(tǒng)會(huì)運(yùn)行兩種不同類型,不同特權(quán)等級(jí)的進(jìn)程(當(dāng)然,從技術(shù)上講這兩個(gè)進(jìn)程都是在同一用戶下)。我們可以使用Windows Sysinternals工具集中的進(jìn)程瀏覽器(Process Explorer)查看各個(gè)進(jìn)程的特權(quán)等級(jí)。 (http://www.microsoft.com/technet/sysinternals)
圖1 進(jìn)程瀏覽器
下圖展示了以不同特權(quán)等級(jí)運(yùn)行的同一個(gè)應(yīng)用程序,進(jìn)程瀏覽器顯示了它們擁有不同的特權(quán)等級(jí):

圖2 不同特權(quán)等級(jí)的同一應(yīng)用程序
所以,當(dāng)你發(fā)現(xiàn)你的進(jìn)程之間Windows消息通信發(fā)生問題時(shí),不妨使用進(jìn)程瀏覽器查看一下兩個(gè)進(jìn)程之間是否有合適的特權(quán)等級(jí)。
UIPI所帶來的限制
正如我們前文所說,等級(jí)的劃分,是為了防止以下犯上。所以,有了用戶界面特權(quán)隔離,一個(gè)運(yùn)行在較低特權(quán)等級(jí)的應(yīng)用程序的行為就受到了諸多限制,它不可以:
驗(yàn)證由較高特權(quán)等級(jí)進(jìn)程創(chuàng)建的窗口句柄
通過調(diào)用SendMessage和PostMessage向由較高特權(quán)等級(jí)進(jìn)程創(chuàng)建的窗口發(fā)送Windows消息
使用線程鉤子處理較高特權(quán)等級(jí)進(jìn)程
使用普通鉤子(SetWindowsHookEx)監(jiān)視較高特權(quán)等級(jí)進(jìn)程
向一個(gè)較高特權(quán)等級(jí)進(jìn)程執(zhí)行DLL注入
但是,一些特殊Windows消息是容許的。因?yàn)檫@些消息對(duì)進(jìn)程的安全性沒有太大影響。這些Windows消息包括:
0x000 - WM_NULL
0x003 - WM_MOVE
0x005 - WM_SIZE
0x00D - WM_GETTEXT
0x00E - WM_GETTEXTLENGTH
0x033 - WM_GETHOTKEY
0x07F - WM_GETICON
0x305 - WM_RENDERFORMAT
0x308 - WM_DRAWCLIPBOARD
0x30D - WM_CHANGECBCHAIN
0x31A - WM_THEMECHANGED
0x313, 0x31B (WM_???)
修復(fù)UIPI問題
基于Windows Vista之前的操作系統(tǒng)行為所設(shè)計(jì)的應(yīng)用程序,可能希望Windows消息能夠在進(jìn)程之間自由的傳遞,以完成一些特殊的工作。當(dāng)這些應(yīng)用程序在Windows 7上運(yùn)行時(shí),因?yàn)閁IPI機(jī)制,這種消息傳遞被阻斷了,應(yīng)用程序就會(huì)遇到兼容性問題。為了解決這個(gè)問題,Windows Vista引入了一個(gè)新的API函數(shù)ChangeWindowMessageFilter。利用這個(gè)函數(shù),我們可以添加或者刪除能夠通過特權(quán)等級(jí)隔離的Windows消息。這就像擁有較高特權(quán)等級(jí)的進(jìn)程,設(shè)置了一個(gè)過濾器,允許通過的Windows消息都被添加到這個(gè)過濾器的白名單,只有在這個(gè)白名單上的消息才允許傳遞進(jìn)來。
如果我們想容許一個(gè)消息可以發(fā)送給較高特權(quán)等級(jí)的進(jìn)程,我們可以在較高特權(quán)等級(jí)的進(jìn)程中調(diào)用ChangeWindowMessageFilter函數(shù),以MSGFLT_ADD作為參數(shù)將消息添加進(jìn)消息過濾器的白名單。同樣的,我們也可以以MSGFLT_REMOVE作為參數(shù)將這個(gè)消息從白名單中刪除。例如:
- // 需要的頭文件
- #include
- #include
- #include "resource.h"
- // 全局對(duì)象
- HINSTANCE g_hInstance;
- HWND g_hFirstWnd;
- // 消息處理函數(shù)
- INT_PTR CALLBACK PingPongDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
- // 主函數(shù)
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int nCmdShow) {
- g_hInstance = hInstance;
- // 獲得窗口的句柄
- g_hFirstWnd = FindWindow(NULL, _T("用戶界面特權(quán)等級(jí)隔離"));
- TCHAR text[256];
- LoadString(g_hInstance, g_hFirstWnd ? IDS_PING : IDS_PONG, text, 256);
- return DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_PINGPONG), NULL, PingPongDlgProc, (LPARAM)text);
- }
- // 處理對(duì)話框消息
- INT_PTR CALLBACK PingPongDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) {
- static LPCTSTR pszPingPong;
- static HWND hWndSend;
- static UINT uMsgBall;
- TCHAR sMessageBall[256];
- switch (message) {
- case WM_INITDIALOG:
- pszPingPong = (LPCTSTR)lParam;
- if(!LoadString(g_hInstance, IDS_MESSAGE_BALL, sMessageBall, 256))
- return FALSE;
- // 注冊(cè)新的全局自定義消息,這里的全局,針對(duì)此進(jìn)程而言
- uMsgBall = RegisterWindowMessage(sMessageBall);
- if(!uMsgBall) return FALSE;
- // 調(diào)用ChangeWindowMessageFilter函數(shù),允許此消息可以傳遞進(jìn)來
- ChangeWindowMessageFilter(uMsgBall, MSGFLT_ADD);
- // 開始發(fā)送消息給其他進(jìn)程,也就是此應(yīng)用程序的另一個(gè)實(shí)例
- if (g_hFirstWnd)
- PostMessage(g_hFirstWnd, uMsgBall, (WPARAM)hwndDlg, 0);
- return TRUE;
- case WM_TIMER:
- KillTimer(hwndDlg, 1);
- SetDlgItemText(hwndDlg, IDC_TEXT, _T(""));
- PostMessage(hWndSend, uMsgBall, (WPARAM)hwndDlg, 0); //Send the ball.
- return TRUE;
- default:
- if(message == uMsgBall) { // 如果是我們自定義的消息
- SetDlgItemText(hwndDlg, IDC_TEXT, pszPingPong);
- hWndSend = (HWND)wParam;
- SetTimer(hwndDlg, 1, 500, 0);
- return TRUE;
- }
- }
- return FALSE;
- }
現(xiàn)在,我們可以調(diào)用ShellExecute,以不同的特權(quán)等級(jí)運(yùn)行這個(gè)應(yīng)用程序,他們雖然處于不同的特權(quán)等級(jí),但是由于我們使用ChangeWindowMessageFilter將自定義的消息添加進(jìn)了白名單,他們都可以處理這個(gè)自定義的消息了。
- // 應(yīng)用程序名稱
- LPCTSTR exeName = _T("UIPIDemo.exe");
- // 以不同的特權(quán)等級(jí)運(yùn)行同一個(gè)應(yīng)用程序
- // 更高權(quán)限
- HINSTANCE h1 = ShellExecute(NULL, _T("runas"), exeName, NULL, NULL, SW_SHOWDEFAULT);
- // 中等權(quán)限
- HINSTANCE h2 = ShellExecute(NULL, _T("open"), exeName, NULL, NULL, SW_SHOWDEFAULT);
- 在Windows 7中,為了更加靈活地控制消息的傳入,它引入了一個(gè)新的函數(shù)ChangeWindowMessageFilterEx,這個(gè)新的擴(kuò)展函數(shù)可以為某個(gè)特定的窗口制定消息白名單,而不是像ChangeWindowMessageFilter一樣為整個(gè)進(jìn)程制定白名單。
- // Windows 7新引入的函數(shù)
- BOOL ChangeWindowMessageFilterEx(
- HWND hWnd, UINT message, DWORD action,
- PCHANGEFILTERSTRUCT pChangeFilterStruct
- );
在這個(gè)函數(shù)中,參數(shù)action表示這個(gè)函數(shù)的動(dòng)作,它可以是MSGFLT_ALLOW (類似于 MSGFLT_ADD),MSGFLT_DISALLOW (類似于 MSGFLT_REMOVE), 和 MSGFLT_RESET,表示將窗口設(shè)置為它的默認(rèn)過濾器。
托管代碼中繞過UIPI
以上的例子演示了在非托管代碼中調(diào)用ChangeWindowMessageFilter實(shí)現(xiàn)消息過濾白名單,允許消息通過用戶界面特權(quán)隔離的過程。在托管代碼中,我們還是使用這個(gè)API函數(shù)。為了便于使用,我們對(duì)這個(gè)API函數(shù)做一些包裝。在托管代碼中,我們用一個(gè)類來封裝所有我們所需要的API函數(shù):
- // 用類封裝對(duì)API函數(shù)的調(diào)用
- internal static class NativeWrappers {
- [DllImport("user32")]
- public static extern uint RegisterWindowMessage(string msg);
- [DllImport("user32")]
- public static extern bool PostMessage(IntPtr hWnd,
- uint msg, IntPtr wParam, IntPtr lParam);
- public enum ChangeWindowMessageFilterFlags : uint {
- Add = 1, Remove = 2
- };
- [DllImport("user32")]
- public static extern bool ChangeWindowMessageFilter(uint msg,
- ChangeWindowMessageFilterFlags flags);
- }
完成API的封裝后,我們就可以在主程序中直接使用這個(gè)類,完成進(jìn)程消息過濾器白名單的設(shè)置。
- public PingPongForm() {
- InitializeComponent();
- // 注冊(cè)消息
- _message = NativeWrappers.RegisterWindowMessage("BALL");
- if(_message == 0)
- Close();
- else {
- // 添加可以通過的消息
- NativeWrappers.ChangeWindowMessageFilter(_message,
- NativeWrappers.ChangeWindowMessageFilterFlags.Add);
- // 發(fā)送消息
- NativeWrappers.PostMessage(Program.hOtherForm,
- _message, Handle, IntPtr.Zero);
- }
- }
用戶特權(quán)等級(jí)隔離,就像進(jìn)程窗口的門神,把不受歡迎的Windows消息隔離在外,把列在客人名單上的Windows消息請(qǐng)進(jìn)來。門神守候,家宅無憂!
【編輯推薦】