65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (5投票s)

2000年4月28日

viewsIcon

54595

downloadIcon

758

允许低权限的 COM 客户端将调用委托给运行在较高权限 NT 用户帐户下的 COM 服务器。

  • 下载源代码 - 40 Kb
  • 在我的一项项目中,我需要 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 函数被通知到达的数据。函数 ServerGetSizeServerFillBuffer 在服务器侧的返回路径上被调用,最后,在调用返回到客户端侧时,通过 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 指出了一个优雅的解决方案来解决我的问题。

    © . All rights reserved.