如何查找连接到 Internet Explorer 的 COM 对象






4.90/5 (13投票s)
一篇关于如何从 COM 对象检索信息以确定 COM DLL 文件名的文章。

引言
从事 Internet Explorer 开发的程序员必须注意其他扩展 DLL 的位置。这是因为这些其他扩展 DLL 与 Internet Explorer 交互,并产生新的问题和功能。程序员必须处理这种情况。然而,关于这一点没有任何文档。在本文中,我将详细解释解决方案。
COM 组件
COM 不是一个简单的 Win32 DLL。COM 是通过接口创建的,要调用其函数,需要使用 Invoke
函数。出于这些原因,COM DLL 的函数不在 EXPORT 表中,无法通过 GetPRocAddress
检索其入口点。这就是我们无法访问其真实代码也无法知道其基础 DLL 的原因。
我们可以使用 ToolHelp
API 来了解 DLL 的所有基地址和大小。但是,对于 COM,DLL 的实例不在 DLL 地址区域中。因此,我们无法比较 ToolHelp
API 的结果。例如,如果我们使用函数 ABCD
,对于一个简单的 DLL,我们会像 ABCD();
这样使用它,并且可以通过 ABCD;
获取函数的地址。但是,对于 COM,我们必须通过 Invoke
函数使用它。所以,ABCD
函数(方法)没有原始访问点。
Internet Explorer 和连接点
为了接收来自 Internet Explorer 的事件,请使用连接点接口连接 COM 对象。就像 C++ 中的虚函数一样,已连接的 COM 对象函数将被 Internet Explorer 调用。如果您想了解更多细节,请参考有关 COM 的书籍。连接对象有一个连接点;此接口提供了一个枚举连接点的方法。要使用此方法,我们可以获取 IUnknown
接口指针和 cookie 值。cookie 值用于连接到 Internet Explorer。
要连接到 Internet Explorer,对象会使用 Advise
和 Unadvise
函数。这些函数的返回值是一个 cookie。它是一个连接标识符。有趣的一点是,即使 cookie 值不是您的,Unadvise
函数也会断开对象与 Internet Explorer 的连接。通过此方法获得的 IUnknown
对象必须通过 Release
函数释放。这是因为在 Enumeration
(在 Enumeration
函数上)过程中,会调用 AddRef
,它会增加引用计数。
深入了解 COM
让我们深入了解 COM 组件。要了解 COM,我们从 COM 组件的实现开始。打开 Unknwn.h 文件,我们可以看到以下代码
extern "C++"
{
MIDL_INTERFACE("00000000-0000-0000-C000-000000000046")
IUnknown
{
public:
BEGIN_INTERFACE
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
template<class>
HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
{
return QueryInterface(__uuidof(Q), (void **)pp);
}
END_INTERFACE
};
} // extern C++
代码表明 IUnknown
接口由三个 Virtual
函数组成。Virtual
函数保存实现函数的地址。要理解这段代码,您必须了解 Virtual
函数的实现。让我们看下面的例子来学习 Virtual
函数
class CVirtualTest {
public:
virtual void VFunc1()
{
}
virtual void VFunc2()
{
}
};
class CVirtualTest2 : public CVirtualTest{
public:
void VFunc1()
{
DWORD dwEIP = 0;
_asm {
call GETEIP3
GETEIP3:
pop eax
mov dword ptr[dwEIP], eax
}
TRACE("CVirtualTest2::VFunc1 = EIP : %X\n", dwEIP);
}
void VFunc2()
{
DWORD dwEIP = 0;
_asm {
call GETEIP4
GETEIP4:
pop eax
mov dword ptr[dwEIP], eax
}
TRACE("CVirtualTest2::VFunc2 = EIP : %X\n", dwEIP);
}
};
void main()
{
CVirtualTest2 v;
TRACE("v is %X\n", &v);
v.VFunc1();
v.VFunc2();
}
让我们尝试调试。首先,在 main
函数处设置断点并启动应用程序。这是创建 v
后的 TRACE
和调试器结果。

我们可以看到地址。v
的地址是 0x0012F5F8
,__vfptr
的地址是 0x004154d8
。Virtual
函数的地址是 0x00401014
和 0x0040100a
。如您所见,Virtual
函数表(Virtual Function Table)的实际地址是实际代码的地址,如果您知道这个地址,我们就能知道代码在哪里。根据 IUnknown
的定义,IUnknown
是一个 struct
(class
),它有一个 Virtual
函数。所以,如果我们确切地了解 Virtual
函数表,我们就可以说“我们了解 IUnknown
。” 首先,让我们看看 CVirtualTest'
的地址(0x12F5F8
)。

我们在 0x0012F5F8
中看到 0x004154D8
。这个看起来很熟悉吧?没错!它与调试器窗口中的 __vfptr
相同。换句话说,当 class
只有 Virtual
函数时,
__vfptr
是 struct
的第一个成员。让我们转到 __vfptr
的地址。

我们在 0x004154D8
处看到 0x00401014
、0x0040
。这些是 100A
VFunc1
和 VFunc2
的地址。最后,我们获得了 Virtual
函数的地址。收集这些地址,转到类对象地址,我们可以获得 Virtual
函数表的地址。转到 Virtual
函数表的地址,我们可以获得每个函数的地址。
整合代码
回到主题:查找 Internet Explorer 已连接的连接点。这项工作可以通过 IConnectionPoint::EnumConnections
完成。我们可以从 EnumConnections
方法获取 IEnumConnections
对象,并通过 IEnumConnections::Next
方法获取每个连接信息。这将返回 CONNECTDATA
。CONNECTDATA
包含 IUnknown
接口,我们可以从中获取 IDispatch
。IDispatch
接口的格式与 IUnknown
相似,并且继承自 IUnknown
。
IEnumConnections* pConn = NULL;
hr = pConnectionPoint->EnumConnections(&pConn);
if(!SUCCEEDED(hr)) {
return ;
}
CONNECTDATA sConnData;
ULONG uRet = 0;
while(true) {
uRet = 0;
memset(&sConnData, 0, sizeof(CONNECTDATA));
hr = pConn->Next(1, &sConnData, &uRet);
if(hr != S_OK || uRet != 1)
break;
LPVOID* lpVFT = (LPVOID*)(sConnData.pUnk);
if(IsBadReadPtr(lpVFT, sizeof(LPVOID)) == FALSE) {
CString szOutput;
szOutput.Format("Filename : %s, 0x%x",
GetProcessFileName( (DWORD)(*lpVFT) ), *lpVFT );
OutputDebugString(szOutput):
}
sConnData.pUnk->Release();
}
在这段代码中,我们将 IUnknown
指针强制转换为 LPVOID*
,并使用 IsBadReadPtr
确认地址。lpVFT
的内容是 Virtual
函数表的地址。因此,*lpVFT
是 lpVFT[0]
。总之,它是 Virtual
函数表的第一项,即第一个函数的地址。将此地址与 ToolHelp
API 的结果进行比较,我们可以看到哪个 DLL 拥有该函数。GetProcessFileName
函数在最近使用的代码中执行此操作。
CString GetProcessFileName(DWORD dwAddress)
{
// reload....
BOOL bRet = FALSE;
BOOL bFound = FALSE;
HANDLE hModuleSnap = NULL;
MODULEENTRY32 me32 = {0};
DWORD dwBase, dwSize;
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
if (hModuleSnap == INVALID_HANDLE_VALUE)
return "";
me32.dwSize = sizeof(MODULEENTRY32);
if (Module32First(hModuleSnap, &me32)) {
do {
dwBase = (DWORD)me32.modBaseAddr;
dwSize = (DWORD)me32.modBaseSize;
if((dwBase < dwAddress) && ((dwBase + dwSize) > dwAddress)) {
CloseHandle(hModuleSnap);
return me32.szExePath;
}
} while (Module32Next(hModuleSnap, &me32));
}
CloseHandle (hModuleSnap);
return "";
}
关注点
COM 组件的实现很有趣。如今,Windows 几乎被 COM 组件所封装,没有 COM 就无法进行编程。因此,理解 COM 组件的实现非常重要。
历史
- 2008年1月20日:首次发布