Environment: Windows NT, COM
In one of my projects I needed a delegation feature for COM mechanism. The server, running under high-privileged account needed to issue COM calls on behalf of low-privileged clients to another COM server. After searching in MSDN I have found that the current situation is pretty simple: “COM does not support delegation”. Although named pipes in WindowsNT environment support it for a single machine operations – COM does not. To overcome this situation I created my own user impersonation mechanism based on the usage of COM channel hook interface.
The idea behind the usage of COM channel hook mechanism is described in the very good and comprehensive way in January’98 issue of MSJ. In short, application could register COM callback interface that will be called by COM engine when it sends or receives a call. So application can add a whatever information it needs in the transparent for the interface user way. The callback interface IChannelHook consists of the following functions:
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);
When a COM client issues call to server, first ClientGetSize is called on the client’s side to determine size of additional data and then ClientFillBuffer to fill buffer with actual data. When COM call arrives to the server – server side gets notified about arrived data through ServerNotify function. Functions ServerGetSize and ServerFillBuffer are called on the way back on the server’s side and, finally, ClientNotify when the call returns on the client’s side.
To make delegation possible I attach the following information to the COM hook buffer:
- process ID of the process that issued a call
- network name of the computer that issued a call
- user token of the thread that issued a call (if it exists) or process-wide user token (if it does not)
When the data arrives on the server’s side the code first checks whether the client and server reside on the same machine, using transferred machine name, because user token that we have transferred as well makes sense only on the same machine. If machine is the same, server tries to open a process that issued a call and duplicate token, for it to be valid for the server’s process. And then it uses the duplicated token to call ImpersonateLoggedOnUser function to do actual user impersonation.
All information about incoming calls is kept in the thread local storage (TLS) on per-thread basis. To avoid losing information in case of recursive COM calls, the information about incoming calls is kept as a stack and every new call pushes its new context on the stack and removes it on the way back to server.
The COM channel hook will be installed automatically as long as DLL is loaded into memory of the process. This could be done either by explicit call to LoadLibrary function or implicitly by CoCreateInstance call, creating IClientInfo interface.
For the proper functionality of the discussed user impersonation method the channel hook must be installed both in the client and server processes. For my project I managed to do it automatically for client and for server by initializing interface to the IClientInfo from the Proxy-Stub DLL that is automatically generated by MSVC Wizard. To do so you need to create a new “c” file that looks like this :
#include#include "..COMchannelinfodcom_channel_info.h" #include "..dcomchannelinfodcom_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); }
And then patch a generated make-file for your proxy-stub DLL (usually it has name of the project + “ps.mk” at the end) and make the following changes to it:
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)
The effect of this operation is the following: as soon as you make a first call to your COM interface – proxy-stub DLL will be loaded automatically by COM engine and in its DLLMain it loads and installs our COM channel hook that transfers required information about called user automatically! You have an advanced impersonation routine for free! 🙂
Interface Usage
You need to register the DLL with the call to regsvr32.exe dcom_channel_info.dll.
Sources, Demos, Updates and Legal Stuff
- Latest sources and updates for the application could be found
here. - Interface itself is used in the Remote Control application (in its server part) which could be used as an example on usage of this impersonation method. It also could be found
here. - You can drop an e-mail to author
here.
Acknowledgements
Big thanks to
Don Box
for pointing an elegant solution to my problem.