使用 COM 通道挂钩机制进行 COM 委托






4.88/5 (5投票s)
2000年4月28日

54595

758
允许低权限的 COM 客户端将调用委托给运行在较高权限 NT 用户帐户下的 COM 服务器。
在我的一项项目中,我需要 COM 的委托功能。服务器以高权限帐户运行,并且需要代表低权限客户端向另一个 COM 服务器发出 COM 调用。在 MSDN 中搜索后,我发现目前的情况非常简单:“COM 不支持委托”。尽管 Windows NT 环境中的命名管道支持单机操作的委托——但 COM 不支持。为了克服这种情况,我基于 COM 通道挂钩接口的使用创建了自己的用户模拟机制。
COM 通道挂钩机制背后的思想在 1998 年 1 月的 MSJ 期刊中有非常精彩和全面的描述。简而言之,应用程序可以注册一个 COM 回调接口,当 COM 引擎发送或接收调用时,该接口会被调用。因此,应用程序可以以对用户透明的方式添加任何它需要的信息。
回调接口 IChannelHook
由以下函数组成
void ClientGetSize(REFGUID uExtent, REFIID riid, ULONG *pDataSize); void ClientFillBuffer(REFGUID uExtent, REFIID riid, ULONG *pDataSize, void *pDataBuffer); void ClientNotify(REFGUID uExtent, REFIID riid, ULONG cbDataSize, void *pDataBuffer, DWORD lDataRep, HRESULT hrFault); void ServerNotify(REFGUID uExtent, REFIID riid, ULONG cbDataSize, void *pDataBuffer, DWORD lDataRep); void ServerGetSize(REFGUID uExtent, REFIID riid, HRESULT hrFault, ULONG *pDataSize); void ServerFillBuffer(REFGUID uExtent, REFIID riid, ULONG *pDataSize, void *pDataBuffer, HRESULT hrFault);
当 COM 客户端向服务器发出调用时,首先在客户端侧调用 ClientGetSize
来确定附加数据的大小,然后调用 ClientFillBuffer
来填充实际数据的缓冲区。当 COM 调用到达服务器时——服务器端通过 ServerNotify
函数被通知到达的数据。函数 ServerGetSize
和 ServerFillBuffer
在服务器侧的返回路径上被调用,最后,在调用返回到客户端侧时,通过 ClientNotify
进行通知。
为了实现委托,我将以下信息附加到 COM 挂钩缓冲区:
- 发出调用的进程的进程 ID
- 发出调用的计算机的网络名称
- 发出调用的线程的用户令牌(如果存在)或进程范围的用户令牌(如果不存在)
当数据到达服务器端时,代码首先检查客户端和服务器是否位于同一台机器上,使用传输的机器名称。这是因为我们传输的用户令牌仅在同一台机器上才有效。如果机器是同一台,服务器会尝试打开发出调用的进程并为其复制令牌,使其对服务器进程有效。然后,它使用复制的令牌调用 ImpersonateLoggedOnUser
函数来执行实际的用户模拟。
所有关于传入调用的信息都保存在线程本地存储 (TLS) 中,按线程进行。为了避免在递归 COM 调用时丢失信息,关于传入调用的信息被保存为一个堆栈,每个新调用都会将新的上下文推送到堆栈上,并在返回服务器的途中将其移除。
只要 DLL 被加载到进程内存中,COM 通道挂钩就会自动安装。这可以通过显式调用 LoadLibrary
函数,或者通过创建 IClientInfo
接口的 CoCreateInstance
调用来隐式完成。
为了使讨论的用户模拟方法正常工作,必须在客户端和服务器进程中都安装通道挂钩。在我的项目中,我通过初始化来自 MSVC Wizard 自动生成的 Proxy-Stub DLL 的 IClientInfo
接口,实现了对客户端和服务器的自动安装。要做到这一点,您需要创建一个新的“c”文件,如下所示:
//CODE----------------------- (file rcserverps_dllmain.c)-------------------------- #include <windows.h> #include "..\COMchannelinfo\dcom_channel_info.h" #include "..\dcomchannelinfo\dcom_channel_info_i.c" //previous DLLMain function BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); BOOL WINAPI newDllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { HRESULT hr; static IClientInfo* client_info_iface; switch (fdwReason) { case DLL_PROCESS_ATTACH: client_info_iface = NULL; hr = CoCreateInstance(&CLSID_ClientInfo, NULL, CLSCTX_INPROC_SERVER, &IID_IClientInfo, (void**)&client_info_iface); if (FAILED(hr)) { return FALSE; } break; case DLL_PROCESS_DETACH: __try { if (client_info_iface != NULL) { client_info_iface->lpVtbl->Release(client_info_iface); } } __except (1) { //we do not care if something has happened on shutdown }; break; } return DllMain(hinstDLL, fdwReason, lpvReserved); } //CODE-----------------------------------------------------------------------------
然后,修改您的代理-存根 DLL 的生成makefile(通常以项目名称加上“ps.mk”结尾),并进行以下更改:
//CODE----------------------------------------------------------------------------- rcserverps.dll: dlldata.obj rcserver_p.obj rcserver_i.obj rcserverps_dllmain.obj (this is a new object file) link /dll /out:rcserverps.dll /def:rcserverps.def /entry:newDllMain \ (this is new DLL entry point) dlldata.obj rcserver_p.obj rcserver_i.obj rcserverps_dllmain.obj \(this is a new object file) kernel32.lib rpcndr.lib rpcns4.lib rpcrt4.lib oleaut32.lib uuid.lib ole32.lib (additional standard library) //CODE-----------------------------------------------------------------------------
此操作的效果如下:一旦您对 COM 接口进行第一次调用,COM 引擎就会自动加载代理-存根 DLL。在其 DLLMain
中,它会加载并安装我们的 COM 通道挂钩,该挂钩会自动传输有关被调用用户的所需信息。您将免费获得一个高级模拟例程!:)
在应用程序中使用该接口。
您需要使用 regsvr32.exe dcom_channel_info.dll.
命令注册 DLL。
源代码、演示、更新和法律事务。
最新的源代码和应用程序更新可以在 这里 找到。
该接口本身用于远程控制应用程序(在其服务器部分),可用作此模拟方法的用法示例。也可以在 这里 找到。
您可以通过 这里 发送电子邮件给作者。
非常感谢 Don Box 指出了一个优雅的解决方案来解决我的问题。