这篇文章假定你已经熟悉C++、GUI和COM编程。示例代码是使用Visual Studio 2005、WTL 7.5和Windows SDK建立的。如果你想更多的了解WTL,可以查看我的关于WTL的系列文章(http://www.codeproject.com/wtl/wtl4mfc1.asp)。WTL只用于图形化界面;本文介绍的概念适用于所有应用程序,没有依赖于任何类库。
一.介绍保护模式
IE浏览器的保护模式是Vista中的一个新功能,是类似用户帐号控制(UAC)的一种安全功能。在保护模式下,通过限制运行在IE进程下的代码不能修改系统部分文件,从而来保护计算机的安全。这样,即使一个恶意网页利用了IE或IE插件中的一个代码注入漏洞,注入的代码也不能够对系统造成破坏。
在我们深入了解保护模式对IE插件开发者意味着什么之前,我们需要先快速了解一下相关的安全功能。
1.完整性级别和UIPI
Vista引入了一种叫做强制完整性级别的安全对象新属性。分为四个级别:
·系统:被操作系统组件使用,不应被应用程序所使用;
·高:在提升的完全管理权限下运行的进程;
·中:正常方式启动的进程;
·低:被IE和Windows Mail使用来提供保护模式。
Windows中关于进程的信息包含它是使用哪一个完整性级别启动的。一旦该进程启动后这个级别永远不会发生改变,它只能在进程被创建的时候被设定。一个进程的完整性级别具有以下三个主要的作用:
(1)这个进程所创建的任何安全对象具有与它相同的完整性级别。
(2)这个进程不能够存取完整性级别比它高的资源。
(3)这个进程不能发送Windows消息到具有更高完整性级别的进程。
这不是一个完整的列表,但是以上所列的三个是对插件具有最大影响的。前两条防止一个低完整性的进程篡改IPC资源,例如共享内存,包含敏感数据或一个应用程序正常运行所需要的数据。最后一条被称为用户界面特权隔离(UIPI),被设计来防止类似粉碎窗口攻击(shatter attack)之类的攻击,在粉碎窗口攻击中,攻击者通过发送给进程一个它不希望收到的消息,从而导致它运行非信任的代码。
2.虚拟化
虚拟化(在某些微软文档中又被叫做重定向)功能可以防止一个进程修改注册表和文件系统中的保护区域,但是依然允许这个应用程序正常的运行。对中等完整性级别的进程来说,这个保护区域是诸如HKLM、system32和Program Files目录之类的系统关键区域。一个低完整级别的进程受限制性更大——它只能对特定的低权限的注册表和文件系统区域进行写操作,任何对这些区域外部进行写操作的行为都将被阻止。
当一个进程试图对它没有权限的区域进行写操作的时候,虚拟化功能都会阻止它们并且把这个写操作重定向到当前用户配置文件下的目录(或者注册表键值),这个写操作实际上在这儿发生。然后当这个应用程序试图读这个数据的时候,读操作同样被重定向,因此这个应用程序将看到它以前写的数据。
因为虚拟化功能不再能完成像替其他进程写注册表之类的功能,因此它影响IE扩展插件的编写。另外,IE扩展插件写数据文件的区域也非常有限制——只有像收藏夹(Favorites)、Cookies等IE特定的目录可以写。
3.何时保护模式被打开?
在Vista的默认设置中,IE一直运行在保护模式。如下图所示,从状态栏中可以看到保护模式被打开的提示:
你可以通过禁用用户帐号控制(UAC)来完全关闭保护模式,或者通过在IE选型对话框的安全标签中把启用保护模式前面的复选框去掉勾选。你还可以通过运行一个新的提升权限的IE实例来临时的跳过保护模式,但是请记住这样做将使IE运行在高完整性等级,而不是像普通应用程序的中等完整性。
二 示例应用程序和扩展插件
本文中的示例代码包含了两个项目。第一个项目,IEEExtension,是停靠在IE窗口底部的一个条形窗口:
第二个项目,DemoApp,是一个与上面的应用通信的EXE程序。DemoApp本身不完成太多任务,比较有趣的部分是与这个EXE程序通信的IEExtecntion。这种通信会受到IE保护模式的很大影响,这两个项目都演示了它们如何在保护模式的限制下运行,而且依然可以实现正常的互相通信。
这个条形框中具有几个按钮来实现不同的IPC任务。这些按钮成对显示:一个不能工作在保护模式下的老技术(button 1);一个可以工作的新智能识别保护模式技术(button 2)。列表控件显示不同的状态信息,诸如从Windows APIs返回的值。
本文中的其余部分将关注这个扩展插件需要做什么才能在保护模式正常的运行。我将介绍一些APIs,然后给出使用这些API的示例扩展插件的代码。每一部分对应条形框(band)中的一个按钮(或一对按钮),因此你可以在阅读文章的时候查看相应的代码。
三 在保护模式限制下运行
IE 7有好几个新的API可以被扩展插件用来完成保护模式下限制的功能。这些API都在ieframe.dll中。你可以通过使用iepmapi.lib这个库来直接使用这些API,也可以在运行的时候使用LoadLibrary()/GetProcAddress()来得到这些函数的指针。如果你想让你的插件加载到Vista以前的Windows版本上,第二种方法是你必须使用的选择。
执行提升权限操作的许多功能使用到了一个代理进程(broker process),ieuser.exe。由于IE进程运行在低完整性等级下,它不能靠自己来完成更高权限的任务;ieuser.exe担任了这个角色。你会经常在本文中和微软的文档中看到提到的这个代理进程。
1.运行时检测保护模式
为了判断我们的插件是否运行在一个保护模式IE进程中,我们使用IEISProtectedModeProcess():
HRESULT IEIsProtectedModeProcess(BOOL* pbResult);如果返回值是一个成功的HRESULR和*pbResult是True,那么保护模式就被启用了。根据在*pbResult中返回的值,你可以在你的代码中采取不同的操作:
HRESULT hr; BOOL bProtectedMode = FALSE; hr = IEIsProtectedModeProcess ( &bProtectedMode ); if ( SUCCEEDED(hr) && bProtectedMode ) // IE is running in protected mode else // IE isn't running in protected mode示例band插件在一启动的时候调用了这个API,并显示了关于保护模式状态的一个消息。
2.写文件系统
当保护模式被启用的时候,扩展插件只能对用户配置下的几个目录进行写操作。只有在Temp、Temporary、Internet Files、Cookies和Favorites目录下的几个特定低完整性目录可以进行写操作。IE还有某些兼容性补偿功能,它虚拟化其他常用目录。对这些目录的写操作将被重定向到Temporary Internet Files的子目录中。如果一个扩展插件试图对一个敏感进行写操作的话,例如对windows目录,这个操作将会失败。
当一个扩展插件希望写这个文件系统的时候,它应该使用IEGetWriteableFolderPath()这个API,而不是GetSpecialFolderPath()、GetFolderPath()或SHGetKnownFolderPath()。IEGetWriteableFolderPath()可以发现保护模式,如果一个插件请求一个不允许写的目录,IEGetWriteableFolderPath()将返回E_ACCESSDENIED。IEGetWriteableFolderPath()的原型如下:
HRESULT IEGetWriteableFolderPath(GUID clsidFolderID, LPWSTR* lppwstrPath);
其中GUID是其中之一,在knownfolders.h: FOLDERID_InternetCache, FOLDERID_Cookies, FOLDERID_History中定义。对于Temp目录来说似乎没有一个GUID,因此当你需要写临时文件的时候,我推荐使用FOLDERID_InternetCache。
以下是在缓存中创建一个临时文件的代码片段:
HRESULT hr; LPWSTR pwszCacheDir = NULL; TCHAR szTempFile[MAX_PATH] = {0}; hr = IEGetWriteableFolderPath(FOLDERID_InternetCache, &pwszCacheDir); if ( SUCCEEDED(hr) ) { GetTempFileName(CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile); CoTaskMemFree(pwszCacheDir); // szTempFile now has the full path to the temp file. }如果IEGetWriteableFolderPath() 成功的话,它将分配一个缓冲器,并在pwszCacheDir中返回它的地址。我们把那个目录传输给GetTempFileName(),然后使用CoTaskMemFree()释放这个缓冲器。
IEGetWriteableFolderPath() 不仅仅可以用来写临时文件。当一个插件使用保护模式版的文件保存对话框的时候,也会用到它,这一点我们将在下面的《提示用户保存文件》部分解释。在这个演示项目中,当你点击了Save Log按钮后,使用这个API。
3.写注册表
因为注册表是系统的关键部分,所以有一点很重要,运行在浏览器中的代码不允许来修改注册表的任何可能导致运行恶意软件的部分。为了实现这个目的,只有一个键值是扩展插件可以写的。对于文件系统来说,这个键值在当前用户的文件下的一个专门的低权限区域中。要想得到这个键值的句柄,可以调用IEGetWriteableHKCU():
HRESULT IEGetWriteableHKCU(HKEY* phKey);如果它成功的话,你可以在其他的注册表API中使用返回的HKEY来写任何需要的数据。本演示项目没有使用这个注册表,但是这割API是非常简单的,在使用它的时候应该不会什么麻烦。
4.提示用户保存文件
当IE运行在保护模式的时候,还有一个方式让插件来间接的写低权限区域之外的文件系统。插件可以通过调用IEShowSaveFileDialog()来显示一个常见的保存文件对话框。如果用户输入一个文件名,这个插件然后能够通过调用IESaveFile()来让IE写这个文件。注意这个操作总会让用户看到这个保存文件对话框;这样可以确保用户知道一个文件将被写到计算机上。
保存一个文件的步骤如下:
(1)调用IEShowSaveFileDialog() 来显示保存文件对话框。
(2)调用IEGetWriteableFolderPath() 来得到IE缓存目录。
(3)写数据到缓冲目录中的一个临时文件中。
(4)调用IESaveFile()来拷贝数据到用户选择的文件名中。
(5)清空temp文件。
IEShowSaveFileDialog()是对普通的文件保存对话框的封装:
HRESULT IEShowSaveFileDialog( HWND hwnd, LPCWSTR lpwstrInitialFileName, LPCWSTR lpwstrInitialDir, LPCWSTR lpwstrFilter, LPCWSTR lpwstrDefExt, DWORD dwFilterIndex, DWORD dwFlags, LPWSTR* lppwstrDestinationFilePath, HANDLE* phState );hwnd是一个插件拥有的窗口,IE将使用它作为对话框的父窗口。lppwstrDestinationFilePath是一个指向代表用户选择的文件路径的LPWSTR的指针。它仅仅是一个数据信息,因为这个扩展插件不能直接写到这个路径。
如果用户选择了一个文件名,IEShowSaveFileDialog()返回S_OK,如果他取消了对话框则返回S_FALSE,或者如果这个API失败的话返回一个失败的HRESULT。
以下是在保存日志到文件的演示项目中的代码。我们首先调用IEShowSaveFileDialog()来提示用户选择文件路径:
void CBandDialog::OnSaveLog(UINT uCode, int nID, HWND hwndCtrl) { HRESULT hr; HANDLE hState; LPWSTR pwszSelectedFilename = NULL; const DWORD dwSaveFlags = OFN_ENABLESIZING | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT; // Get a filename from the user. hr = IEShowSaveFileDialog ( m_hWnd, L"Saved log.txt", NULL, L"Text files|*.txt|All files|*.*|", L"txt", 1, dwSaveFlags, &pwszSelectedFilename, &hState ); if ( S_OK != hr ) return;接下来,我们使用IEGetWriteableFolderPath()来获得缓冲区目录的位置。
LPWSTR pwszCacheDir = NULL; TCHAR szTempFile[MAX_PATH] = {0}; // Get the path to the IE cache dir, which is a dir that we're allowed // to write to in protected mode. hr = IEGetWriteableFolderPath ( FOLDERID_InternetCache, &pwszCacheDir ); if ( SUCCEEDED(hr) ) { // Get a temp file name in that dir. GetTempFileName ( CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile ); CoTaskMemFree ( pwszCacheDir ); // Write our data to that temp file. hr = WriteLogFile ( szTempFile ); }如果一起都顺利的话,我们调用另一个保护模式API,IESaveFile()。IESaveFile()获得IEShowSaveFileDialog()返回的状态句柄,以及我们的临时文件的路径。注意这个HANDLE不是一个标准的句柄,不需要被关闭;在IESaveFile()调用完后,这个HANDLE会被自动释放。
由于某些原因,我们没有结束调用IESaveFile(),举个例子来说,如果当写临时文件的时候出现一个错误,我们需要清除这个HANDLE和任何IEShowSaveFileDialog()分配的任何内部数据。我们通过调用IECancelSaveFile()来实现:
if ( SUCCEEDED(hr) ) { // If we wrote the file successfully, have IE save that data to // the path that the user chose. hr = IESaveFile ( hState, T2CW(szTempFile) ); // Clean up our temp file. DeleteFile ( szTempFile ); } else { // We couldn't complete the save operation, so cancel it. IECancelSaveFile ( hState ); }
四 启用你的插件和其他应用程序之间的通信
看到这儿,我们已经可以使用运行在IE进程中的代码来处理所有文件系统和注册表了。下面我们看一个更复杂的话题,它也需要一个更复杂的解决方案:运行在一个更高完整性级别的另一个进程的IPC。我们将介绍两种不同形式的IPC:内核对象和windows消息。
1.创建一个IPC对象
当一个插件和一个单独的进程希望通信的时候,这种通信发生在两个代码之间,而不用经过IE封装。NT安全API和强制性完整级别现在可以发挥它们的作用了,默认情况下从一个插件到一个单独的应用程序的通信是被阻挡的,因为这个应用程序运行在比IE更高的完整性级别。
如果这个单独的应用程序创建了一个插件需要使用的内核对象(例如,一个事件或互斥对象),在插件存取它们之前,这个应用程序必须降低这个对象的完整性级别。这个应用程序可以使用安全API来修改对象的ACL来降低它的完整性级别。下面的代码是从MSDN的文章“Understanding and Working in Protected Mode Internet Explorer”摘取的,将一个HANDLE赋给一个内核对象,并设置它的完整性级别为低。
// The LABEL_SECURITY_INFORMATION SDDL SACL to be set for low integrity LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)"; bool SetObjectToLowIntegrity( HANDLE hObject, SE_OBJECT_TYPE type = SE_KERNEL_OBJECT) { bool bRet = false; DWORD dwErr = ERROR_SUCCESS; PSECURITY_DESCRIPTOR pSD = NULL; PACL pSacl = NULL; BOOL fSaclPresent = FALSE; BOOL fSaclDefaulted = FALSE; if ( ConvertStringSecurityDescriptorToSecurityDescriptorW ( LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL ) ) { if ( GetSecurityDescriptorSacl ( pSD, &fSaclPresent, &pSacl, &fSaclDefaulted ) ) { dwErr = SetSecurityInfo ( hObject, type, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, pSacl ); bRet = (ERROR_SUCCESS == dwErr); } LocalFree ( pSD ); } return bRet; }这个示例代码使用了两个mutex,其目的是让这个插件告诉什么时候这个应用程序在运行。当这个进程启动的时候DemoAPP EXE创建了它们,当你点击其中一个Open Mutex按钮的时候这个插件尝试去打开它们。Mutex 1具有默认的完整性级别,而mutex 2则通过上面所说的SetObjectToLowIntgrity()函数设置到低完整性级别。这意味着当保护模式启用的时候,这个插件将只能存取mutex 2。以下是当你点击两个Open Mutex按钮时所看到的结果:
保护模式的另一个影响是,一个插件不能让一个单独的应用程序继承一个内核对象的句柄。举个例子来说,当保护模式被启用的时候,我们的插件不能创建一个文件映射对象,运行这个单独的应用程序(传递bInheritHandles的参数TRUE给CreateProcess()),并且让这个应用程序继承文件映射对象的句柄。
HANDLE hMapping; SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) }; sa.bInheritHandle = TRUE; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, cbyData, NULL ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the shared memory handle. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /h:%p"), hMapping ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, TRUE, // TRUE => the new process should inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );DemoApp然后将从/h交换读取句柄的值,并使用这个值来调用MapViewOfFile()来读取数据。这是让新的进程自动接受一个内核对象句柄的标准技术,但是当保护模式被启用的时候,新的进程实际上是由代理进程发起的。因为IE进程没有直接启动这个新的进程,句柄继承不再起作用。
为了解决这个限制,插件可以为一个IPC对象使用预先定义好的名称,那么这个单独的应用程序将能偶访问这个对象,因为这个对象具有低完整性级别。如果你不想使用一个预先定义好的名称,你可以在运行的时候产生一个名称(例如使用一个GUID作为名字),并将这个名称传递给这个单独的应用程序:
// Get a GUID that we'll use as the name of the shared memory object. GUID guid = {0}; WCHAR wszGuid[64] = {0}; HRESULT hr; CoCreateGuid( &guid ); StringFromGUID2( guid, wszGuid, _countof(wszGuid) ); // Create the file mapping object, we don't need a SECURITY_ATTRIBUTES // struct since the handle won't be inherited. HANDLE hMapping; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the name of the shared memory object. // Note that the bInheritHandles param to CreateProcess() is FALSE. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /n:%ls"), wszGuid ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, FALSE, // FALSE => the new process does not inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );使用这个方法,这个EXE程序在命令行中接受到这个IPC对象的名称。它然后能调用OpenFileMapping()来访问这个对象。但是,这个方法有些复杂,因为你需要对于对象生命周期管理非常谨慎。当使用句柄继承的时候,事件的顺序是:
1、插件创建IPC对象,并将其引用计数设为1。
2、插件起动继承了句柄的进程。这增加对象的引用计数到2。
3、插件现在可以立即关闭它的句柄,因为它不再需要这个对象了。引用计数降到0。
4、这个新的进程完成任何它需要与这个IPC对象的东西。因为它依然有一个开放的句柄,这个对象在这个新的进程关闭它的句柄之前一直存在。
当我们只把对象名传递给EXE的时候如果我们使用以上步骤,我们将产生一个竞态条件,在这个EXE程序有机会打开其上的一个句柄前,这个插件可能会关闭它的句柄(因此会删除这个IPC对象),过程如下:
1、该插件创建IPC对象,将其引用计数设为1。
2、插件起动新进程,将IPC对象的名字传递给它。引用计数依然是1。
3、插件不能立即关闭它的句柄,它需要等到新进程打开一个到对象的句柄。要实现这个任务需要某些类别的同步。
4、新的进程打开一个到对象的句柄,然后读取数据。这个时候,它可以发信号给插件以唤醒插件线程。现在插件可以安全的关闭它的句柄了。
在示例项目中我选择的做法是,在DemoApp创建主对话框前,让它从共享内存中读数据。插件在CreateProcess()后调用WaitForInputIdle(),这将使该线程被阻挡,直到DemoApp的主对话框已经被创建和显示。一旦DemoApp的线程空闲后,它已经结束了对共享内存的使用,对插件来说,关闭它的句柄已经安全。
band演示了通过共享内存传递数据的两种方法。当你点击Run EXE 1按钮的时候,band写当前的日期和时间到共享内存中,然后传递一个句柄给DemoApp。当保护模式被启用的时候,这个方法将失败,DemoApp将报告非法的句柄错误。点击Run EXE 2按钮,传递文件映射对象的名称给DemoApp,它将显示它从共享内存中读取的数据:
2.接收Windows消息
UIPI阻止特定的windows消息被从一个低完整性级别进程发送到一个高完整性级别进程。如果你的应用程序需要接收来自你的插件的消息,你可以调用ChangeWindowMessageFilter()来允许一个特定的消息通过:
HANDLE hMapping; SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) }; sa.bInheritHandle = TRUE; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, cbyData, NULL ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the shared memory handle. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /h:%p"), hMapping ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, TRUE, // TRUE => the new process should inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );DemoApp然后将从/h交换读取句柄的值,并使用这个值来调用MapViewOfFile()来读取数据。这是让新的进程自动接受一个内核对象句柄的标准技术,但是当保护模式被启用的时候,新的进程实际上是由代理进程发起的。因为IE进程没有直接启动这个新的进程,句柄继承不再起作用。
为了解决这个限制,插件可以为一个IPC对象使用预先定义好的名称,那么这个单独的应用程序将能偶访问这个对象,因为这个对象具有低完整性级别。如果你不想使用一个预先定义好的名称,你可以在运行的时候产生一个名称(例如使用一个GUID作为名字),并将这个名称传递给这个单独的应用程序:
// Get a GUID that we'll use as the name of the shared memory object. GUID guid = {0}; WCHAR wszGuid[64] = {0}; HRESULT hr; CoCreateGuid( &guid ); StringFromGUID2( guid, wszGuid, _countof(wszGuid) ); // Create the file mapping object, we don't need a SECURITY_ATTRIBUTES // struct since the handle won't be inherited. HANDLE hMapping; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the name of the shared memory object. // Note that the bInheritHandles param to CreateProcess() is FALSE. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /n:%ls"), wszGuid ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, FALSE, // FALSE => the new process does not inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );使用这个方法,这个EXE程序在命令行中接受到这个IPC对象的名称。它然后能调用OpenFileMapping()来访问这个对象。但是,这个方法有些复杂,因为你需要对于对象生命周期管理非常谨慎。当使用句柄继承的时候,事件的顺序是:
1、插件创建IPC对象,并将其引用计数设为1。
2、插件起动继承了句柄的进程。这增加对象的引用计数到2。
3、插件现在可以立即关闭它的句柄,因为它不再需要这个对象了。引用计数降到0。
4、这个新的进程完成任何它需要与这个IPC对象的东西。因为它依然有一个开放的句柄,这个对象在这个新的进程关闭它的句柄之前一直存在。
当我们只把对象名传递给EXE的时候如果我们使用以上步骤,我们将产生一个竞态条件,在这个EXE程序有机会打开其上的一个句柄前,这个插件可能会关闭它的句柄(因此会删除这个IPC对象),过程如下:
1、该插件创建IPC对象,将其引用计数设为1。
2、插件起动新进程,将IPC对象的名字传递给它。引用计数依然是1。
3、插件不能立即关闭它的句柄,它需要等到新进程打开一个到对象的句柄。要实现这个任务需要某些类别的同步。
4、新的进程打开一个到对象的句柄,然后读取数据。这个时候,它可以发信号给插件以唤醒插件线程。现在插件可以安全的关闭它的句柄了。
在示例项目中我选择的做法是,在DemoApp创建主对话框前,让它从共享内存中读数据。插件在CreateProcess()后调用WaitForInputIdle(),这将使该线程被阻挡,直到DemoApp的主对话框已经被创建和显示。一旦DemoApp的线程空闲后,它已经结束了对共享内存的使用,对插件来说,关闭它的句柄已经安全。
band演示了通过共享内存传递数据的两种方法。当你点击Run EXE 1按钮的时候,band写当前的日期和时间到共享内存中,然后传递一个句柄给DemoApp。当保护模式被启用的时候,这个方法将失败,DemoApp将报告非法的句柄错误。点击Run EXE 2按钮,传递文件映射对象的名称给DemoApp,它将显示它从共享内存中读取的数据:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171118.gif)
2.接收Windows消息
UIPI阻止特定的windows消息被从一个低完整性级别进程发送到一个高完整性级别进程。如果你的应用程序需要接收来自你的插件的消息,你可以调用ChangeWindowMessageFilter()来允许一个特定的消息通过:
BOOL ChangeWindowMessageFilter(UINT message, DWORD dwFlag);参数message是消息的值,而dwFlag则指明这个消息是被允许还是阻挡。传递MSGFLT_ADD则是允许消息传递,而MSGFLT_REMOVE则是阻挡它。当处理来自其他进程的windows消息时一定要非常小心——如果通过消息来接收数据,你必须将它看作非信任的,你应该在根据它行动前对它进行验证确认。(Remember that inter-process messages can be sent from anywhere, and such messages can be used for attacks, as mentioned earlier.)
本文中的演示项目显示了如何通过registered windows消息进行通信。对于mutex示例,有两个消息。DemoApp通过OnInitDialog()中的代码允许第二个消息通过过滤器:
m_uRegisteredMsg1 = RegisterWindowMessage( REGISTERED_MSG1_NAME ); m_uRegisteredMsg2 = RegisterWindowMessage( REGISTERED_MSG2_NAME ); ChangeWindowMessageFilter( m_uRegisteredMsg2, MSGFLT_ADD );当你点击band中的两个Send Message按钮后,你将看到如下结果:
第一个消息没有被允许通过过滤器,SendMessage()返回了0。
HANDLE hMapping; SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) }; sa.bInheritHandle = TRUE; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, cbyData, NULL ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the shared memory handle. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /h:%p"), hMapping ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, TRUE, // TRUE => the new process should inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );DemoApp然后将从/h交换读取句柄的值,并使用这个值来调用MapViewOfFile()来读取数据。这是让新的进程自动接受一个内核对象句柄的标准技术,但是当保护模式被启用的时候,新的进程实际上是由代理进程发起的。因为IE进程没有直接启动这个新的进程,句柄继承不再起作用。
为了解决这个限制,插件可以为一个IPC对象使用预先定义好的名称,那么这个单独的应用程序将能偶访问这个对象,因为这个对象具有低完整性级别。如果你不想使用一个预先定义好的名称,你可以在运行的时候产生一个名称(例如使用一个GUID作为名字),并将这个名称传递给这个单独的应用程序:
// Get a GUID that we'll use as the name of the shared memory object. GUID guid = {0}; WCHAR wszGuid[64] = {0}; HRESULT hr; CoCreateGuid( &guid ); StringFromGUID2( guid, wszGuid, _countof(wszGuid) ); // Create the file mapping object, we don't need a SECURITY_ATTRIBUTES // struct since the handle won't be inherited. HANDLE hMapping; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the name of the shared memory object. // Note that the bInheritHandles param to CreateProcess() is FALSE. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /n:%ls"), wszGuid ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, FALSE, // FALSE => the new process does not inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );使用这个方法,这个EXE程序在命令行中接受到这个IPC对象的名称。它然后能调用OpenFileMapping()来访问这个对象。但是,这个方法有些复杂,因为你需要对于对象生命周期管理非常谨慎。当使用句柄继承的时候,事件的顺序是:
1、插件创建IPC对象,并将其引用计数设为1。
2、插件起动继承了句柄的进程。这增加对象的引用计数到2。
3、插件现在可以立即关闭它的句柄,因为它不再需要这个对象了。引用计数降到0。
4、这个新的进程完成任何它需要与这个IPC对象的东西。因为它依然有一个开放的句柄,这个对象在这个新的进程关闭它的句柄之前一直存在。
当我们只把对象名传递给EXE的时候如果我们使用以上步骤,我们将产生一个竞态条件,在这个EXE程序有机会打开其上的一个句柄前,这个插件可能会关闭它的句柄(因此会删除这个IPC对象),过程如下:
1、该插件创建IPC对象,将其引用计数设为1。
2、插件起动新进程,将IPC对象的名字传递给它。引用计数依然是1。
3、插件不能立即关闭它的句柄,它需要等到新进程打开一个到对象的句柄。要实现这个任务需要某些类别的同步。
4、新的进程打开一个到对象的句柄,然后读取数据。这个时候,它可以发信号给插件以唤醒插件线程。现在插件可以安全的关闭它的句柄了。
在示例项目中我选择的做法是,在DemoApp创建主对话框前,让它从共享内存中读数据。插件在CreateProcess()后调用WaitForInputIdle(),这将使该线程被阻挡,直到DemoApp的主对话框已经被创建和显示。一旦DemoApp的线程空闲后,它已经结束了对共享内存的使用,对插件来说,关闭它的句柄已经安全。
band演示了通过共享内存传递数据的两种方法。当你点击Run EXE 1按钮的时候,band写当前的日期和时间到共享内存中,然后传递一个句柄给DemoApp。当保护模式被启用的时候,这个方法将失败,DemoApp将报告非法的句柄错误。点击Run EXE 2按钮,传递文件映射对象的名称给DemoApp,它将显示它从共享内存中读取的数据:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171118.gif)
2.接收Windows消息
UIPI阻止特定的windows消息被从一个低完整性级别进程发送到一个高完整性级别进程。如果你的应用程序需要接收来自你的插件的消息,你可以调用ChangeWindowMessageFilter()来允许一个特定的消息通过:
BOOL ChangeWindowMessageFilter(UINT message, DWORD dwFlag);参数message是消息的值,而dwFlag则指明这个消息是被允许还是阻挡。传递MSGFLT_ADD则是允许消息传递,而MSGFLT_REMOVE则是阻挡它。当处理来自其他进程的windows消息时一定要非常小心——如果通过消息来接收数据,你必须将它看作非信任的,你应该在根据它行动前对它进行验证确认。(Remember that inter-process messages can be sent from anywhere, and such messages can be used for attacks, as mentioned earlier.)
本文中的演示项目显示了如何通过registered windows消息进行通信。对于mutex示例,有两个消息。DemoApp通过OnInitDialog()中的代码允许第二个消息通过过滤器:
m_uRegisteredMsg1 = RegisterWindowMessage( REGISTERED_MSG1_NAME ); m_uRegisteredMsg2 = RegisterWindowMessage( REGISTERED_MSG2_NAME ); ChangeWindowMessageFilter( m_uRegisteredMsg2, MSGFLT_ADD );当你点击band中的两个Send Message按钮后,你将看到如下结果:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171239.gif)
第一个消息没有被允许通过过滤器,SendMessage()返回了0。
五 保护模式中的其他限制
1.运行其他应用程序
IE具有另一个防止恶意代码启动或与其他进程通信的机制。如果一个插件试图启动另一个进程,IE将在启动这个进程前要求该用户同意。举个例子来说,使用查看源代码命令会引起如下提示:
如果你的插件需要运行一个单独的EXE,你可以增加一个注册表键值来告诉IE你的EXE可以被信任并能够无须提示就被运行。这个控制这种行为的注册表键值是HKLM\Software\Microsoft\Internet Explorer\Low Rights\ElevationPolicy。创建一个新的GUID,然后在名称为那个GUID的ElevationPolicy下增加一个键值。在这个新的键值中,创建三个值:
·AppName:可执行文件的文件名,例如“DempApp.exe”。
· AppPath:EXE所在的目录。
·Policy:设置成3的DWORD值
如果你的安装没有创建一个类似的键的话,当你选择对话框中的“Do not show me the warning for this program again”,IE自己将创建一个。
HANDLE hMapping; SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) }; sa.bInheritHandle = TRUE; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, cbyData, NULL ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the shared memory handle. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /h:%p"), hMapping ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, TRUE, // TRUE => the new process should inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );DemoApp然后将从/h交换读取句柄的值,并使用这个值来调用MapViewOfFile()来读取数据。这是让新的进程自动接受一个内核对象句柄的标准技术,但是当保护模式被启用的时候,新的进程实际上是由代理进程发起的。因为IE进程没有直接启动这个新的进程,句柄继承不再起作用。
为了解决这个限制,插件可以为一个IPC对象使用预先定义好的名称,那么这个单独的应用程序将能偶访问这个对象,因为这个对象具有低完整性级别。如果你不想使用一个预先定义好的名称,你可以在运行的时候产生一个名称(例如使用一个GUID作为名字),并将这个名称传递给这个单独的应用程序:
// Get a GUID that we'll use as the name of the shared memory object. GUID guid = {0}; WCHAR wszGuid[64] = {0}; HRESULT hr; CoCreateGuid( &guid ); StringFromGUID2( guid, wszGuid, _countof(wszGuid) ); // Create the file mapping object, we don't need a SECURITY_ATTRIBUTES // struct since the handle won't be inherited. HANDLE hMapping; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the name of the shared memory object. // Note that the bInheritHandles param to CreateProcess() is FALSE. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /n:%ls"), wszGuid ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, FALSE, // FALSE => the new process does not inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );使用这个方法,这个EXE程序在命令行中接受到这个IPC对象的名称。它然后能调用OpenFileMapping()来访问这个对象。但是,这个方法有些复杂,因为你需要对于对象生命周期管理非常谨慎。当使用句柄继承的时候,事件的顺序是:
1、插件创建IPC对象,并将其引用计数设为1。
2、插件起动继承了句柄的进程。这增加对象的引用计数到2。
3、插件现在可以立即关闭它的句柄,因为它不再需要这个对象了。引用计数降到0。
4、这个新的进程完成任何它需要与这个IPC对象的东西。因为它依然有一个开放的句柄,这个对象在这个新的进程关闭它的句柄之前一直存在。
当我们只把对象名传递给EXE的时候如果我们使用以上步骤,我们将产生一个竞态条件,在这个EXE程序有机会打开其上的一个句柄前,这个插件可能会关闭它的句柄(因此会删除这个IPC对象),过程如下:
1、该插件创建IPC对象,将其引用计数设为1。
2、插件起动新进程,将IPC对象的名字传递给它。引用计数依然是1。
3、插件不能立即关闭它的句柄,它需要等到新进程打开一个到对象的句柄。要实现这个任务需要某些类别的同步。
4、新的进程打开一个到对象的句柄,然后读取数据。这个时候,它可以发信号给插件以唤醒插件线程。现在插件可以安全的关闭它的句柄了。
在示例项目中我选择的做法是,在DemoApp创建主对话框前,让它从共享内存中读数据。插件在CreateProcess()后调用WaitForInputIdle(),这将使该线程被阻挡,直到DemoApp的主对话框已经被创建和显示。一旦DemoApp的线程空闲后,它已经结束了对共享内存的使用,对插件来说,关闭它的句柄已经安全。
band演示了通过共享内存传递数据的两种方法。当你点击Run EXE 1按钮的时候,band写当前的日期和时间到共享内存中,然后传递一个句柄给DemoApp。当保护模式被启用的时候,这个方法将失败,DemoApp将报告非法的句柄错误。点击Run EXE 2按钮,传递文件映射对象的名称给DemoApp,它将显示它从共享内存中读取的数据:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171118.gif)
2.接收Windows消息
UIPI阻止特定的windows消息被从一个低完整性级别进程发送到一个高完整性级别进程。如果你的应用程序需要接收来自你的插件的消息,你可以调用ChangeWindowMessageFilter()来允许一个特定的消息通过:
BOOL ChangeWindowMessageFilter(UINT message, DWORD dwFlag);参数message是消息的值,而dwFlag则指明这个消息是被允许还是阻挡。传递MSGFLT_ADD则是允许消息传递,而MSGFLT_REMOVE则是阻挡它。当处理来自其他进程的windows消息时一定要非常小心——如果通过消息来接收数据,你必须将它看作非信任的,你应该在根据它行动前对它进行验证确认。(Remember that inter-process messages can be sent from anywhere, and such messages can be used for attacks, as mentioned earlier.)
本文中的演示项目显示了如何通过registered windows消息进行通信。对于mutex示例,有两个消息。DemoApp通过OnInitDialog()中的代码允许第二个消息通过过滤器:
m_uRegisteredMsg1 = RegisterWindowMessage( REGISTERED_MSG1_NAME ); m_uRegisteredMsg2 = RegisterWindowMessage( REGISTERED_MSG2_NAME ); ChangeWindowMessageFilter( m_uRegisteredMsg2, MSGFLT_ADD );当你点击band中的两个Send Message按钮后,你将看到如下结果:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171239.gif)
第一个消息没有被允许通过过滤器,SendMessage()返回了0。
HANDLE hMapping; SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) }; sa.bInheritHandle = TRUE; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, cbyData, NULL ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the shared memory handle. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /h:%p"), hMapping ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, TRUE, // TRUE => the new process should inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );DemoApp然后将从/h交换读取句柄的值,并使用这个值来调用MapViewOfFile()来读取数据。这是让新的进程自动接受一个内核对象句柄的标准技术,但是当保护模式被启用的时候,新的进程实际上是由代理进程发起的。因为IE进程没有直接启动这个新的进程,句柄继承不再起作用。
为了解决这个限制,插件可以为一个IPC对象使用预先定义好的名称,那么这个单独的应用程序将能偶访问这个对象,因为这个对象具有低完整性级别。如果你不想使用一个预先定义好的名称,你可以在运行的时候产生一个名称(例如使用一个GUID作为名字),并将这个名称传递给这个单独的应用程序:
// Get a GUID that we'll use as the name of the shared memory object. GUID guid = {0}; WCHAR wszGuid[64] = {0}; HRESULT hr; CoCreateGuid( &guid ); StringFromGUID2( guid, wszGuid, _countof(wszGuid) ); // Create the file mapping object, we don't need a SECURITY_ATTRIBUTES // struct since the handle won't be inherited. HANDLE hMapping; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the name of the shared memory object. // Note that the bInheritHandles param to CreateProcess() is FALSE. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /n:%ls"), wszGuid ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, FALSE, // FALSE => the new process does not inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );使用这个方法,这个EXE程序在命令行中接受到这个IPC对象的名称。它然后能调用OpenFileMapping()来访问这个对象。但是,这个方法有些复杂,因为你需要对于对象生命周期管理非常谨慎。当使用句柄继承的时候,事件的顺序是:
1、插件创建IPC对象,并将其引用计数设为1。
2、插件起动继承了句柄的进程。这增加对象的引用计数到2。
3、插件现在可以立即关闭它的句柄,因为它不再需要这个对象了。引用计数降到0。
4、这个新的进程完成任何它需要与这个IPC对象的东西。因为它依然有一个开放的句柄,这个对象在这个新的进程关闭它的句柄之前一直存在。
当我们只把对象名传递给EXE的时候如果我们使用以上步骤,我们将产生一个竞态条件,在这个EXE程序有机会打开其上的一个句柄前,这个插件可能会关闭它的句柄(因此会删除这个IPC对象),过程如下:
1、该插件创建IPC对象,将其引用计数设为1。
2、插件起动新进程,将IPC对象的名字传递给它。引用计数依然是1。
3、插件不能立即关闭它的句柄,它需要等到新进程打开一个到对象的句柄。要实现这个任务需要某些类别的同步。
4、新的进程打开一个到对象的句柄,然后读取数据。这个时候,它可以发信号给插件以唤醒插件线程。现在插件可以安全的关闭它的句柄了。
在示例项目中我选择的做法是,在DemoApp创建主对话框前,让它从共享内存中读数据。插件在CreateProcess()后调用WaitForInputIdle(),这将使该线程被阻挡,直到DemoApp的主对话框已经被创建和显示。一旦DemoApp的线程空闲后,它已经结束了对共享内存的使用,对插件来说,关闭它的句柄已经安全。
band演示了通过共享内存传递数据的两种方法。当你点击Run EXE 1按钮的时候,band写当前的日期和时间到共享内存中,然后传递一个句柄给DemoApp。当保护模式被启用的时候,这个方法将失败,DemoApp将报告非法的句柄错误。点击Run EXE 2按钮,传递文件映射对象的名称给DemoApp,它将显示它从共享内存中读取的数据:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171118.gif)
2.接收Windows消息
UIPI阻止特定的windows消息被从一个低完整性级别进程发送到一个高完整性级别进程。如果你的应用程序需要接收来自你的插件的消息,你可以调用ChangeWindowMessageFilter()来允许一个特定的消息通过:
BOOL ChangeWindowMessageFilter(UINT message, DWORD dwFlag);参数message是消息的值,而dwFlag则指明这个消息是被允许还是阻挡。传递MSGFLT_ADD则是允许消息传递,而MSGFLT_REMOVE则是阻挡它。当处理来自其他进程的windows消息时一定要非常小心——如果通过消息来接收数据,你必须将它看作非信任的,你应该在根据它行动前对它进行验证确认。(Remember that inter-process messages can be sent from anywhere, and such messages can be used for attacks, as mentioned earlier.)
本文中的演示项目显示了如何通过registered windows消息进行通信。对于mutex示例,有两个消息。DemoApp通过OnInitDialog()中的代码允许第二个消息通过过滤器:
m_uRegisteredMsg1 = RegisterWindowMessage( REGISTERED_MSG1_NAME ); m_uRegisteredMsg2 = RegisterWindowMessage( REGISTERED_MSG2_NAME ); ChangeWindowMessageFilter( m_uRegisteredMsg2, MSGFLT_ADD );当你点击band中的两个Send Message按钮后,你将看到如下结果:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171239.gif)
第一个消息没有被允许通过过滤器,SendMessage()返回了0。
五 保护模式中的其他限制
1.运行其他应用程序
IE具有另一个防止恶意代码启动或与其他进程通信的机制。如果一个插件试图启动另一个进程,IE将在启动这个进程前要求该用户同意。举个例子来说,使用查看源代码命令会引起如下提示:
如果你的插件需要运行一个单独的EXE,你可以增加一个注册表键值来告诉IE你的EXE可以被信任并能够无须提示就被运行。这个控制这种行为的注册表键值是HKLM\Software\Microsoft\Internet Explorer\Low Rights\ElevationPolicy。创建一个新的GUID,然后在名称为那个GUID的ElevationPolicy下增加一个键值。在这个新的键值中,创建三个值:
·AppName:可执行文件的文件名,例如“DempApp.exe”。
· AppPath:EXE所在的目录。
·Policy:设置成3的DWORD值
如果你的安装没有创建一个类似的键的话,当你选择对话框中的“Do not show me the warning for this program again”,IE自己将创建一个。
2、拖拽到其他应用程序
如果你试图从一个网页的内容拖拽到另一个应用程序中,会出现一个类似的提示窗口:
这个提示窗口也可通过一个注册表键来禁止。格式如上面所述,但是你的应用程序的键应该放在DragDrop下,而不是前面的ElevationPolicy。
DemoApp作为一个拖拽的对象来演示,如果你在IE中选择了一些文本内容,并把它拖拽到DemoApp对话框中,它将显示一个消息来表示它已经收到这个拖拽动作:
HANDLE hMapping; SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) }; sa.bInheritHandle = TRUE; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, cbyData, NULL ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the shared memory handle. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /h:%p"), hMapping ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, TRUE, // TRUE => the new process should inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );DemoApp然后将从/h交换读取句柄的值,并使用这个值来调用MapViewOfFile()来读取数据。这是让新的进程自动接受一个内核对象句柄的标准技术,但是当保护模式被启用的时候,新的进程实际上是由代理进程发起的。因为IE进程没有直接启动这个新的进程,句柄继承不再起作用。
为了解决这个限制,插件可以为一个IPC对象使用预先定义好的名称,那么这个单独的应用程序将能偶访问这个对象,因为这个对象具有低完整性级别。如果你不想使用一个预先定义好的名称,你可以在运行的时候产生一个名称(例如使用一个GUID作为名字),并将这个名称传递给这个单独的应用程序:
// Get a GUID that we'll use as the name of the shared memory object. GUID guid = {0}; WCHAR wszGuid[64] = {0}; HRESULT hr; CoCreateGuid( &guid ); StringFromGUID2( guid, wszGuid, _countof(wszGuid) ); // Create the file mapping object, we don't need a SECURITY_ATTRIBUTES // struct since the handle won't be inherited. HANDLE hMapping; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the name of the shared memory object. // Note that the bInheritHandles param to CreateProcess() is FALSE. CString sCommandLine; BOOL bSuccess; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = {0}; sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /n:%ls"), wszGuid ); bSuccess = CreateProcess( NULL, sCommandLine.GetBuffer(0), NULL, NULL, FALSE, // FALSE => the new process does not inherit handles NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );使用这个方法,这个EXE程序在命令行中接受到这个IPC对象的名称。它然后能调用OpenFileMapping()来访问这个对象。但是,这个方法有些复杂,因为你需要对于对象生命周期管理非常谨慎。当使用句柄继承的时候,事件的顺序是:
1、插件创建IPC对象,并将其引用计数设为1。
2、插件起动继承了句柄的进程。这增加对象的引用计数到2。
3、插件现在可以立即关闭它的句柄,因为它不再需要这个对象了。引用计数降到0。
4、这个新的进程完成任何它需要与这个IPC对象的东西。因为它依然有一个开放的句柄,这个对象在这个新的进程关闭它的句柄之前一直存在。
当我们只把对象名传递给EXE的时候如果我们使用以上步骤,我们将产生一个竞态条件,在这个EXE程序有机会打开其上的一个句柄前,这个插件可能会关闭它的句柄(因此会删除这个IPC对象),过程如下:
1、该插件创建IPC对象,将其引用计数设为1。
2、插件起动新进程,将IPC对象的名字传递给它。引用计数依然是1。
3、插件不能立即关闭它的句柄,它需要等到新进程打开一个到对象的句柄。要实现这个任务需要某些类别的同步。
4、新的进程打开一个到对象的句柄,然后读取数据。这个时候,它可以发信号给插件以唤醒插件线程。现在插件可以安全的关闭它的句柄了。
在示例项目中我选择的做法是,在DemoApp创建主对话框前,让它从共享内存中读数据。插件在CreateProcess()后调用WaitForInputIdle(),这将使该线程被阻挡,直到DemoApp的主对话框已经被创建和显示。一旦DemoApp的线程空闲后,它已经结束了对共享内存的使用,对插件来说,关闭它的句柄已经安全。
band演示了通过共享内存传递数据的两种方法。当你点击Run EXE 1按钮的时候,band写当前的日期和时间到共享内存中,然后传递一个句柄给DemoApp。当保护模式被启用的时候,这个方法将失败,DemoApp将报告非法的句柄错误。点击Run EXE 2按钮,传递文件映射对象的名称给DemoApp,它将显示它从共享内存中读取的数据:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171118.gif)
2.接收Windows消息
UIPI阻止特定的windows消息被从一个低完整性级别进程发送到一个高完整性级别进程。如果你的应用程序需要接收来自你的插件的消息,你可以调用ChangeWindowMessageFilter()来允许一个特定的消息通过:
BOOL ChangeWindowMessageFilter(UINT message, DWORD dwFlag);参数message是消息的值,而dwFlag则指明这个消息是被允许还是阻挡。传递MSGFLT_ADD则是允许消息传递,而MSGFLT_REMOVE则是阻挡它。当处理来自其他进程的windows消息时一定要非常小心——如果通过消息来接收数据,你必须将它看作非信任的,你应该在根据它行动前对它进行验证确认。(Remember that inter-process messages can be sent from anywhere, and such messages can be used for attacks, as mentioned earlier.)
本文中的演示项目显示了如何通过registered windows消息进行通信。对于mutex示例,有两个消息。DemoApp通过OnInitDialog()中的代码允许第二个消息通过过滤器:
m_uRegisteredMsg1 = RegisterWindowMessage( REGISTERED_MSG1_NAME ); m_uRegisteredMsg2 = RegisterWindowMessage( REGISTERED_MSG2_NAME ); ChangeWindowMessageFilter( m_uRegisteredMsg2, MSGFLT_ADD );当你点击band中的两个Send Message按钮后,你将看到如下结果:
![](http://tech.it168.com/sourcefiles/pic/2007-12-4/Image/2007124171239.gif)
第一个消息没有被允许通过过滤器,SendMessage()返回了0。