打通IE保护模式(五)启用你的插件和其他应用程序之间的通信

看到这儿,我们已经可以使用运行在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()来允许一个特定的消息通过:

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。

 

为了兼容xp, 应该动态加载user32.dll

  

 

BOOL CancleMessageFilter()
{
 BOOL bRet = FALSE;
 HMODULE hUserMod = LoadLibrary(TEXT("user32.dll"));
 if( NULL == hUserMod )
 {
  return FALSE;
 }
 _ChangeWindowMessageFilter pChangeWindowMessageFilter =
  (_ChangeWindowMessageFilter)GetProcAddress( hUserMod, "ChangeWindowMessageFilter" );
 if( NULL == pChangeWindowMessageFilter )
 {
  return FALSE;
 }
 
 //MSGFLT_ADD: 1, MSGFLT_REMOVE: 2


 bRet = pChangeWindowMessageFilter(XOLI_BEGIN, MSGFLT_ADD);
 bRet &= pChangeWindowMessageFilter(XOLI_EXIT_PROCESS, MSGFLT_ADD);
 bRet &= pChangeWindowMessageFilter(XOLI_RUN_AS_ADMIN, MSGFLT_ADD);
 if( NULL != hUserMod )
 {
  FreeLibrary( hUserMod );
 }
 return bRet;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值