面对IE保护模式的开发者生存之道Word文档下载推荐.docx
《面对IE保护模式的开发者生存之道Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《面对IE保护模式的开发者生存之道Word文档下载推荐.docx(18页珍藏版)》请在冰豆网上搜索。
被IE和WindowsMail使用来提供保护模式。
Windows中关于进程的信息包含它是使用哪一个完整性级别启动的。
一旦该进程启动后这个级别永远不会发生改变,它只能在进程被创建的时候被设定。
一个进程的完整性级别具有以下三个主要的作用:
(1)这个进程所创建的任何安全对象具有与它相同的完整性级别。
(2)这个进程不能够存取完整性级别比它高的资源。
(3)这个进程不能发送Windows消息到具有更高完整性级别的进程。
这不是一个完整的列表,但是以上所列的三个是对插件具有最大影响的。
前两条防止一个低完整性的进程篡改IPC资源,例如共享内存,包含敏感数据或一个应用程序正常运行所需要的数据。
最后一条被称为用户界面特权隔离(UIPI),被设计来防止类似粉碎窗口攻击(shatterattack)之类的攻击,在粉碎窗口攻击中,攻击者通过发送给进程一个它不希望收到的消息,从而导致它运行非信任的代码。
2.虚拟化
虚拟化(在某些微软文档中又被叫做重定向)功能可以防止一个进程修改注册表和文件系统中的保护区域,但是依然允许这个应用程序正常的运行。
对中等完整性级别的进程来说,这个保护区域是诸如HKLM、system32和ProgramFiles目录之类的系统关键区域。
一个低完整级别的进程受限制性更大——它只能对特定的低权限的注册表和文件系统区域进行写操作,任何对这些区域外部进行写操作的行为都将被阻止。
当一个进程试图对它没有权限的区域进行写操作的时候,虚拟化功能都会阻止它们并且把这个写操作重定向到当前用户配置文件下的目录(或者注册表键值),这个写操作实际上在这儿发生。
然后当这个应用程序试图读这个数据的时候,读操作同样被重定向,因此这个应用程序将看到它以前写的数据。
因为虚拟化功能不再能完成像替其他进程写注册表之类的功能,因此它影响IE扩展插件的编写。
另外,IE扩展插件写数据文件的区域也非常有限制——只有像收藏夹(Favorites)、Cookies等IE特定的目录可以写。
3.何时保护模式被打开?
在Vista的默认设置中,IE一直运行在保护模式。
如下图所示,从状态栏中可以看到保护模式被打开的提示:
你可以通过禁用用户帐号控制(UAC)来完全关闭保护模式,或者通过在IE选型对话框的安全标签中把启用保护模式前面的复选框去掉勾选。
你还可以通过运行一个新的提升权限的IE实例来临时的跳过保护模式,但是请记住这样做将使IE运行在高完整性等级,而不是像普通应用程序的中等完整性。
二示例应用程序和扩展插件
本文中的示例代码包含了两个项目。
第一个项目,IEEExtension,是停靠在IE窗口底部的一个条形窗口:
第二个项目,DemoApp,是一个与上面的应用通信的EXE程序。
DemoApp本身不完成太多任务,比较有趣的部分是与这个EXE程序通信的IEExtecntion。
这种通信会受到IE保护模式的很大影响,这两个项目都演示了它们如何在保护模式的限制下运行,而且依然可以实现正常的互相通信。
这个条形框中具有几个按钮来实现不同的IPC任务。
这些按钮成对显示:
一个不能工作在保护模式下的老技术(button1);
一个可以工作的新智能识别保护模式技术(button2)。
列表控件显示不同的状态信息,诸如从WindowsAPIs返回的值。
本文中的其余部分将关注这个扩展插件需要做什么才能在保护模式正常的运行。
我将介绍一些APIs,然后给出使用这些API的示例扩展插件的代码。
每一部分对应条形框(band)中的一个按钮(或一对按钮),因此你可以在阅读文章的时候查看相应的代码。
三在保护模式限制下运行
IE7有好几个新的API可以被扩展插件用来完成保护模式下限制的功能。
这些API都在ieframe.dll中。
你可以通过使用iepmapi.lib这个库来直接使用这些API,也可以在运行的时候使用LoadLibrary()/GetProcAddress()来得到这些函数的指针。
如果你想让你的插件加载到Vista以前的Windows版本上,第二种方法是你必须使用的选择。
执行提升权限操作的许多功能使用到了一个代理进程(brokerprocess),ieuser.exe。
由于IE进程运行在低完整性等级下,它不能靠自己来完成更高权限的任务;
ieuser.exe担任了这个角色。
你会经常在本文中和微软的文档中看到提到的这个代理进程。
1.运行时检测保护模式
为了判断我们的插件是否运行在一个保护模式IE进程中,我们使用IEISProtectedModeProcess():
HRESULTIEIsProtectedModeProcess(BOOL*pbResult);
如果返回值是一个成功的HRESULR和*pbResult是True,那么保护模式就被启用了。
根据在*pbResult中返回的值,你可以在你的代码中采取不同的操作:
HRESULThr;
BOOLbProtectedMode=FALSE;
hr=IEIsProtectedModeProcess(&
bProtectedMode);
if(SUCCEEDED(hr)&
&
bProtectedMode)
//IEisrunninginprotectedmode
else
//IEisn'
trunninginprotectedmode
示例band插件在一启动的时候调用了这个API,并显示了关于保护模式状态的一个消息。
2.写文件系统
当保护模式被启用的时候,扩展插件只能对用户配置下的几个目录进行写操作。
只有在Temp、Temporary、InternetFiles、Cookies和Favorites目录下的几个特定低完整性目录可以进行写操作。
IE还有某些兼容性补偿功能,它虚拟化其他常用目录。
对这些目录的写操作将被重定向到TemporaryInternetFiles的子目录中。
如果一个扩展插件试图对一个敏感进行写操作的话,例如对windows目录,这个操作将会失败。
当一个扩展插件希望写这个文件系统的时候,它应该使用IEGetWriteableFolderPath()这个API,而不是GetSpecialFolderPath()、GetFolderPath()或SHGetKnownFolderPath()。
IEGetWriteableFolderPath()可以发现保护模式,如果一个插件请求一个不允许写的目录,IEGetWriteableFolderPath()将返回E_ACCESSDENIED。
IEGetWriteableFolderPath()的原型如下:
HRESULTIEGetWriteableFolderPath(GUIDclsidFolderID,LPWSTR*lppwstrPath);
其中GUID是其中之一,在knownfolders.h:
FOLDERID_InternetCache,FOLDERID_Cookies,FOLDERID_History中定义。
对于Temp目录来说似乎没有一个GUID,因此当你需要写临时文件的时候,我推荐使用FOLDERID_InternetCache。
以下是在缓存中创建一个临时文件的代码片段:
LPWSTRpwszCacheDir=NULL;
TCHARszTempFile[MAX_PATH]={0};
hr=IEGetWriteableFolderPath(FOLDERID_InternetCache,&
pwszCacheDir);
if(SUCCEEDED(hr))
{
GetTempFileName(CW2CT(pwszCacheDir),_T("
bob"
),0,szTempFile);
CoTaskMemFree(pwszCacheDir);
//szTempFilenowhasthefullpathtothetempfile.
}
如果IEGetWriteableFolderPath()成功的话,它将分配一个缓冲器,并在pwszCacheDir中返回它的地址。
我们把那个目录传输给GetTempFileName(),然后使用CoTaskMemFree()释放这个缓冲器。
IEGetWriteableFolderPath()不仅仅可以用来写临时文件。
当一个插件使用保护模式版的文件保存对话框的时候,也会用到它,这一点我们将在下面的《提示用户保存文件》部分解释。
在这个演示项目中,当你点击了SaveLog按钮后,使用这个API。
3.写注册表
因为注册表是系统的关键部分,所以有一点很重要,运行在浏览器中的代码不允许来修改注册表的任何可能导致运行恶意软件的部分。
为了实现这个目的,只有一个键值是扩展插件可以写的。
对于文件系统来说,这个键值在当前用户的文件下的一个专门的低权限区域中。
要想得到这个键值的句柄,可以调用IEGetWriteableHKCU():
HRESULTIEGetWriteableHKCU(HKEY*phKey);
如果它成功的话,你可以在其他的注册表API中使用返回的HKEY来写任何需要的数据。
本演示项目没有使用这个注册表,但是这割API是非常简单的,在使用它的时候应该不会什么麻烦。
4.提示用户保存文件
当IE运行在保护模式的时候,还有一个方式让插件来间接的写低权限区域之外的文件系统。
插件可以通过调用IEShowSaveFileDialog()来显示一个常见的保存文件对话框。
如果用户输入一个文件名,这个插件然后能够通过调用IESaveFile()来让IE写这个文件。
注意这个操作总会让用户看到这个保存文件对话框;
这样可以确保用户知道一个文件将被写到计算机上。
保存一个文件的步骤如下:
(1)调用IEShowSaveFileDialog()来显示保存文件对话框。
(2)调用IEGetWriteableFolderPath()来得到IE缓存目录。
(3)写数据到缓冲目录中的一个临时文件中。
(4)调用IESaveFile()来拷贝数据到用户选择的文件名中。
(5)清空temp文件。
IEShowSaveFileDialog()是对普通的文件保存对话框的封装:
HRESULTIEShowSaveFileDialog(
HWNDhwnd,
LPCWSTRlpwstrInitialFileName,
LPCWSTRlpwstrInitialDir,
LPCWSTRlpwstrFilter,
LPCWSTRlpwstrDefExt,
DWORDdwFilterIndex,
DWORDdwFlags,
LPWSTR*lppwstrDestinationFilePath,
HANDLE*phState
);
hwnd是一个插件拥有的窗口,IE将使用它作为对话框的父窗口。
lppwstrDestinationFilePath是一个指向代表用户选择的文件路径的LPWSTR的指针。
它仅仅是一个数据信息,因为这个扩展插件不能直接写到这个路径。
如果用户选择了一个文件名,IEShowSaveFileDialog()返回S_OK,如果他取消了对话框则返回S_FALSE,或者如果这个API失败的话返回一个失败的HRESULT。
以下是在保存日志到文件的演示项目中的代码。
我们首先调用IEShowSaveFileDialog()来提示用户选择文件路径:
voidCBandDialog:
:
OnSaveLog(UINTuCode,intnID,HWNDhwndCtrl)
HANDLEhState;
LPWSTRpwszSelectedFilename=NULL;
constDWORDdwSaveFlags=
OFN_ENABLESIZING|OFN_HIDEREADONLY|OFN_PATHMUSTEXIST|
OFN_OVERWRITEPROMPT;
//Getafilenamefromtheuser.
hr=IEShowSaveFileDialog(
m_hWnd,L"
Savedlog.txt"
NULL,
L"
Textfiles|*.txt|Allfiles|*.*|"
txt"
1,dwSaveFlags,&
pwszSelectedFilename,
hState);
if(S_OK!
=hr)
return;
接下来,我们使用IEGetWriteableFolderPath()来获得缓冲区目录的位置。
//GetthepathtotheIEcachedir,whichisadirthatwe'
reallowed
//towritetoinprotectedmode.
hr=IEGetWriteableFolderPath(FOLDERID_InternetCache,&
pwszCacheDir);
//Getatempfilenameinthatdir.
GetTempFileName(CW2CT(pwszCacheDir),_T("
),0,szTempFile);
CoTaskMemFree(pwszCacheDir);
//Writeourdatatothattempfile.
hr=WriteLogFile(szTempFile);
如果一起都顺利的话,我们调用另一个保护模式API,IESaveFile()。
IESaveFile()获得IEShowSaveFileDialog()返回的状态句柄,以及我们的临时文件的路径。
注意这个HANDLE不是一个标准的句柄,不需要被关闭;
在IESaveFile()调用完后,这个HANDLE会被自动释放。
由于某些原因,我们没有结束调用IESaveFile(),举个例子来说,如果当写临时文件的时候出现一个错误,我们需要清除这个HANDLE和任何IEShowSaveFileDialog()分配的任何内部数据。
我们通过调用IECancelSaveFile()来实现:
//Ifwewrotethefilesuccessfully,haveIEsavethatdatato
//thepaththattheuserchose.
hr=IESaveFile(hState,T2CW(szTempFile));
//Cleanupourtempfile.
DeleteFile(szTempFile);
//Wecouldn'
tcompletethesaveoperation,socancelit.
IECancelSaveFile(hState);
}
四启用你的插件和其他应用程序之间的通信
看到这儿,我们已经可以使用运行在IE进程中的代码来处理所有文件系统和注册表了。
下面我们看一个更复杂的话题,它也需要一个更复杂的解决方案:
运行在一个更高完整性级别的另一个进程的IPC。
我们将介绍两种不同形式的IPC:
内核对象和windows消息。
1.创建一个IPC对象
当一个插件和一个单独的进程希望通信的时候,这种通信发生在两个代码之间,而不用经过IE封装。
NT安全API和强制性完整级别现在可以发挥它们的作用了,默认情况下从一个插件到一个单独的应用程序的通信是被阻挡的,因为这个应用程序运行在比IE更高的完整性级别。
如果这个单独的应用程序创建了一个插件需要使用的内核对象(例如,一个事件或互斥对象),在插件存取它们之前,这个应用程序必须降低这个对象的完整性级别。
这个应用程序可以使用安全API来修改对象的ACL来降低它的完整性级别。
下面的代码是从MSDN的文章“UnderstandingandWorkinginProtectedModeInternetExplorer”摘取的,将一个HANDLE赋给一个内核对象,并设置它的完整性级别为低。
//TheLABEL_SECURITY_INFORMATIONSDDLSACLtobesetforlowintegrity
LPCWSTRLOW_INTEGRITY_SDDL_SACL_W=L"
S:
(ML;
;
NW;
LW)"
boolSetObjectToLowIntegrity(
HANDLEhObject,SE_OBJECT_TYPEtype=SE_KERNEL_OBJECT)
boolbRet=false;
DWORDdwErr=ERROR_SUCCESS;
PSECURITY_DESCRIPTORpSD=NULL;
PACLpSacl=NULL;
BOOLfSaclPresent=FALSE;
BOOLfSaclDefaulted=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);
returnbRet;
这个示例代码使用了两个mutex,其目的是让这个插件告诉什么时候这个应用程序在运行。
当这个进程启动的时候DemoAPPEXE创建了它们,当你点击其中一个OpenMutex按钮的时候这个插件尝试去打开它们。
Mutex1具有默认的完整性级别,而mutex2则通过上面所说的SetObjectToLowIntgrity()函数设置到低完整性级别。
这意味着当保护模式启用的时候,这个插件将只能存取mutex2。
以下是当你点击两个OpenMutex按钮时所看到的结果:
保护模式的另一个影响是,一个插件不能让一个单独的应用程序继承一个内核对象的句柄。
举个例子来说,当保护模式被启用的时候,我们的插件不能创建一个文件映射对象,运行这个单独的应用程序(传递bInheritHandles的参数TRUE给CreateProcess()),并且让这个应用程序继承文件映射对象的句柄。
HANDLEhMapping;
SECURITY_ATTRIBUTESsa={sizeof(SECURITY_ATTRIBUTES)};
sa.bInheritHandle=TRUE;
hMapping=CreateFileMapping(INVALID_HANDLE_VALUE,&
sa,
PAGE_READWRITE,0,cbyData,NULL);
//Omitted:
Putdatainthesharedmemoryblock...
//RuntheEXEandpassitthesharedmemoryhandle.
CStringsCommandLine;
BOOLbSuccess;
STARTUPINFOsi={sizeof(STARTUPINFO)};
PROCESS_INFORMATIONpi={0};
sCommandLine.Format(_T("
\"
C:
\\path\\to\\DemoApp.exe\"
/h:
%p"
),
hMapping);
bSuccess=CreateProcess(
NULL,sCommandLine.GetBuffer(0),NULL,NULL,
TRUE,//TRUE=>
thene