COM, OLE and ActiveX Hooking | |||||||||
- General information - Auto Monitoring - Auto Monitoring COM Objects Creation File Syntax - Auto Monitoring Files Syntax - Static Monitoring - Static Monitoring Files Syntax - Static Monitoring Files: How to generate ? - Interface Methods Creating Objects (since 5.1 version only) - Overriding, or Adding Pre/Post call hooks - Hooking Objects Creation And Destruction - COM Options - COM Tools - Advanced information |
|||||||||
General information | |||||||||
COM (and so OLE and ActiveX) hooking is an extension to API hooking. That means all processes, modules, monitoring and overriding filters work as the classical API way. Monitoring files too support the same functions and parameter flags.
The first thing you should decide is if you want to use automatic COM hooking or static COM hooking. - Automatic hooks (recommanded) spy all objects created with a set of functions (and matching some specified classes according to CLSID filters). They allow a lazy way for classes supporting IDispatch Interface (you don't need to write a monitoring file). They allow to use the COM Interaction Tool, which allow you to do call methods of hooked interfaces (as the Remote Call dialog for API). It spyes object life cycle too. - Use static hooks only if you exactly know which components you want to hooks, and if you don't want to use the COM Interaction Tool. In conclusion, auto monitoring gives you more control on COM objects.
|
|||||||||
Auto Monitoring | |||||||||
COM auto hooking works spying a set of API. So the first thing to do is to specify which API creating COM objects you want to hook. The configuration file (COM options / "File defining hooked function for COM objects creation") comes with some well-known API. So you just need to adjust it according to your needs. To avoid to auto hook all created COM objects with specified API, you can use an exclusion or inclusion CLSID filter file (COM options / "Use CLSID filter") COM options allow you to ask for various reports too : reporting created COM objects, reporting Interfaces with no monitoring files associated... Monitoring files for COM auto hooking are located in "monitoring files\COM\" directory. (syntax is described here) If no monitoring file is available for a specified Interface, and Interface support the IDispatch Interface (and according to COM options), IDispatch interface will be parsed automatically. That means even you haven't created a monitoring file for such interface, methods exposed throw IDispatch will be hooked. Notice : Methods exposed throw IDispatch are methods you can view with an Ole/ActiveX methods browser (also known as tlb browser). For Interfaces not supporting IDispatch, you sadly still have to create monitoring files (see COM Auto Monitoring files syntax). When you disable COM Auto Monitoring while hooking is started, you will be asked if you want to unhook already hooked objects. If you answer "Yes", you won't see COM logs anymore : every non static hooks to COM methods will be destroyed. By ansewering "No", you will continue to see logs generated by already hooked COM methods, but no new method will be hooked. |
|||||||||
Auto Monitoring: COM Objects Creation File Syntax | |||||||||
3 things are necessary to hook a created COM object : - the class ID (CLSID) used for creation - the interface ID (IID) used for creation - the pointer to created object returned by the function So API definitions must specify where to find these information. The syntax is the following: |
|||||||||
DllName|FunctionName|Option1|Option2...|OptionN or WinApiOverrideDllNameLike|FunctionName|Option1|Option2...|OptionN |
|||||||||
where options are 1) Stack size (the stack size needed for all parameters) StackSize= / x64StackSize= for all definitions, specify the stack size in bytes of all parameters. 2) CLSID (for all definitions provide information on CLSID) CLSIDStackIndex= / x64CLSIDStackIndex= bytes count between stack position (ESP/RSP) and CLSID parameter address REFCLSIDStackIndex= / x64REFCLSIDStackIndex= bytes count between stack position (ESP/RSP) and CLSID* parameter address CLSIDValue= CLSID string value (used for function not requiring a CLSID parameter) 3) IID (not needed if function has a MULTI_QI parameter) IIDStackIndex= / x64IIDStackIndex= bytes count between stack position (ESP/RSP) and IID parameter address REFIIDStackIndex= / x64REFIIDStackIndex= bytes count between stack position (ESP/RSP) and IID* parameter address IIDValue= IID string value (used for function not requiring an IID parameter like DirectDrawCreate) 4) Object(s) pointer(s) a) For single object creation (like CoCreateInstance) ObjectStackIndex= / x64ObjectStackIndex= bytes count between stack position (ESP/RSP) and object pointer parameter address b) For multiple objects creation (like CoCreateInstanceEx) ObjectArrayStackIndex= / x64ObjectArrayStackIndex= bytes count between stack position (ESP/RSP) and MULTI_QI array parameter address ObjectArraySizeStackIndex= / x64ObjectArraySizeStackIndex= bytes count between stack position (ESP/RSP) and MULTI_QI array size parameter address c) If object pointer is the returned value (like Direct3DCreate8) since 5.1 version only ObjectIsReturnedValue
1) For CoCreateInstance(REFCLSID rclsid,LPUNKNOWN pUnkOuter,DWORD dwClsContext,REFIID riid,LPVOID * ppv); ole32.dll|CoCreateInstance|StackSize=20|REFCLSIDStackIndex=0|REFIIDStackIndex=12|ObjectStackIndex=16|x64StackSize=8|x64REFCLSIDStackIndex=RCX|x64REFIIDStackIndex=R9|x64ObjectStackIndex=0 2) For CoCreateInstanceEx(REFCLSID rclsid,IUnknown* punkOuter,DWORD dwClsCtx,COSERVERINFO* pServerInfo, ULONG cmq, MULTI_QI* pResults); ole32.dll|CoCreateInstanceEx|StackSize=24|REFCLSIDStackIndex=0|ObjectArraySizeStackIndex=16|ObjectArrayStackIndex=20|x64StackSize=16|x64REFCLSIDStackIndex=RCX|x64ObjectArraySizeStackIndex=0|x64ObjectArrayStackIndex=8 2) IDirect3D8* Direct3DCreate8(UINT SDKVersion); D3d8.dll|Direct3DCreate8|StackSize=4|CLSIDValue={1DD9E8DA-1C77-4d40-B0CF-98FEFDFF9512}|IIDValue={1DD9E8DA-1C77-4d40-B0CF-98FEFDFF9512}|ObjectIsReturnedValue|x64StackSize=0 Notice : When you found no information on CLSID, do not hesitate to put object IID as CLSID, because internally in WinApiOverride, CLSID are only used as a unique identifier to identify objects of the same type (that's we do in the example of Direct3DCreate8) Notice: A particular treatment is done for CoGetClassObject to hook CreateInstance method of IID_IClassFactory (this function definition is provided in the default COM Object Creation Hooked Functions file that comes with WinAPIOverride binaries) As methods of interface can create objects too, you may want to hook these created objects. See Interface Methods Creating Objects in this case |
|||||||||
Auto Monitoring Files Syntax | |||||||||
1) Monitoring files for COM auto hooking must be located in "monitoring files\COM\" directory. 2) Monitoring file name must be "Interface_ID.txt" where Interface_ID is the IID of the interface |
|||||||||
By the way for IDispatch auto monitoring file name is "{00020400-0000-0000-C000-000000000046}.txt" | |||||||||
Definitions are API like. The only difference is that you have to specify vtbl index instead of dll name. |
|||||||||
VTBLIndex=Index| [ReturnType] FuncName( ParamType [paramName] ) [;] where Index is 0 based index |
|||||||||
Examples VTBLIndex=0|IUnknown::QueryInterface(IUnknown* pObject,IID* pIid,PVOID* ppInterface)|Out|FailureIfNegativeRet VTBLIndex=1|IUnknown::AddRef(IUnknown* pObject); VTBLIndex=2|IUnknown::Release(IUnknown* pObject); VTBLIndex=3|HRESULT IDispatch::GetTypeInfoCount(IUnknown* pObject,UINT* pctinfo);|Out|FailureIfNegativeRet Notice: the interface name "IUnknown::" or "IDispatch::" is optionnal. I find it better because it's avoid confusion with API having the same name; and it gives information on interfaces in logs.
As COM use a lot of derived interfaces, a keyword has been introduced to avoid to copy and past base interfaces definitions. If your interface derived from another one, you can use |
|||||||||
BaseIID=IID where IID is like {00000000-0000-0000-C000-000000000046} |
|||||||||
Examples By the way if you want to include IUnknown ;include IUnknown BaseIID={00000000-0000-0000-C000-000000000046} Using this notation, the IDispatch monitoring file can be like following ;include IUnknown BaseIID={00000000-0000-0000-C000-000000000046} VTBLIndex=3|HRESULT IDispatch::GetTypeInfoCount(IUnknown* pObject,UINT* pctinfo);|Out|FailureIfNegativeRet VTBLIndex=4|HRESULT IDispatch::GetTypeInfo(IUnknown* pObject, UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo);|Out|FailureIfNegativeRet VTBLIndex=5|HRESULT IDispatch::GetIDsOfNames(IUnknown* pObject,REFIID riid,OLECHAR** rgszNames,UINT cNames, LCID lcid, DISPID* rgDispId);|Out|FailureIfNegativeRet VTBLIndex=6|HRESULT IDispatch::Invoke(IUnknown* pObject,DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult,EXCEPINFO* pExcepInfo, UINT* puArgErr);|Out|FailureIfNegativeRet These includes are transmitted through monitoring files interfaces definitions. That means, if your interface is derived from IDispatch (which is derived from IUnknown), as IDispatch monitoring file contains the definition "BaseIID={00000000-0000-0000-C000-000000000046}" for IUnknown, you only have to add IDispatch monitoring file not the IUnknown one. That means you only have to write "BaseIID={00020400-0000-0000-C000-000000000046}" to include both IDispatch and IUnknown definitions.
Example : create a COM monitoring file from Interface definition |
|||||||||
![]() |
|||||||||
Static Monitoring | |||||||||
Static monitoring files can be loaded and unloaded using the same way as API monitoring files. Static monitoring is usefull if object creation is not catched by COM auto hooking; it can occur for object created with none standard methods like CoCreateInstance. In fact if mehtod used to create COM object is not referenced in the file "COM_ObjectCreationHookedFunctions.txt", the object and it's child objects methods won't be hooked. It can be used if you want to target a only specific set of COM method. The synatx is the following |
|||||||||
Static Monitoring Files Syntax | |||||||||
COM@CLSID@IIDVTBLIndex={IID}:Index| [ReturnType] FuncName( ParamType [paramName] ) [;] where CLSID is the class ID of the object, IID is the interface ID, and Index the 0 based VTBL index; or COM@CLSID@BaseIID={IID} where CLSID is the class ID of the object and IID is the interface ID. In this second case, static hook will hook all functions defined inside the interface monitoring file used for COM auto hooking. |
|||||||||
Examples For single function definition COM@{C74190B6-8589-11D1-B16A-00C0F0283628}@IIDVTBLIndex={00020400-0000-0000-C000-000000000046}:56|HRESULT AboutBox(IDispatch* pIDispatch);|FailureIfNegativeRet |BreakBeforeAndAfterCall For hooking a full interface already defined in a COM auto monitoring file COM@{d45fd2fc-5c6e-11d1-9ec1-00c04fd7081f}@BaseIID={A7B93C91-7B81-11D0-AC5F-00C04FD97575} |
|||||||||
Static Monitoring Files: How to generate ? | |||||||||
There are 2 cases depending if COM object can be created by CoCreateInstance Case 1: The object can be created by CoCreateInstance Imagine you like to hook IMsRdpClient2 of the Microsoft activeX Microsoft RDP Client Control (mstscax.dll) First find the CLSID. From the "COM Interaction and Tools" menu, use the "Show Computer ActiveX / Known CLSID" |
|||||||||
![]() |
|||||||||
Next enter "mstscax" in the search field | |||||||||
![]() |
|||||||||
Copy the CLSID or Prog Id of the found item; and then use the "Show Methods Addresses" menu | |||||||||
![]() |
|||||||||
Inside the "Methods Address" dialog, enter the found Prog Id and the
interface Id of Interface name (Interface ID aka IID can be found from the "IID <-> Interface Name Menu") Once done, click the "Show" button. You will get all the methods address with there VTBL indexes and address, and methods address. |
|||||||||
![]() |
|||||||||
With this you will get method address inside the dll So you have to create a monitoring file (use the on created for interface {E7E17DC4-3B71-4BA7-A8E6-281FFADCA28F} aka {E7E17DC4-3B71-4BA7-A8E6-281FFADCA28F}.txt) and replace vtblindex by Dll_Internal@ syntax aka DLL_INTERNAL@0xHexAddress@DllName| [ReturnType] FuncName(ParamType [paramName]) [;] (see monitoringfiles:basic) //VTBLIndex=7|VOID* __stdcall IMsRdpClient2::Server(IUnknown* pObject, BSTR) DLL_INTERNAL@0x2ec760@mstscax.dll|VOID* __stdcall IMsRdpClient2::Server(IUnknown* pObject, BSTR) //VTBLIndex=8|BSTR __stdcall IMsRdpClient2::Server(IUnknown* pObject, PVOID* RetValue)|Out DLL_INTERNAL@0x2ecb10@mstscax.dll|BSTR __stdcall IMsRdpClient2::Server(IUnknown* pObject, PVOID* RetValue)|Out //VTBLIndex=9|VOID* __stdcall IMsRdpClient2::Domain(IUnknown* pObject, BSTR) //VTBLIndex=10|BSTR __stdcall IMsRdpClient2::Domain(IUnknown* pObject, PVOID* RetValue)|Out //VTBLIndex=11|VOID* __stdcall IMsRdpClient2::UserName(IUnknown* pObject, BSTR) DLL_INTERNAL@0x2ecb80@mstscax.dll|VOID* __stdcall IMsRdpClient2::UserName(IUnknown* pObject, BSTR) //VTBLIndex=12|BSTR __stdcall IMsRdpClient2::UserName(IUnknown* pObject, PVOID* RetValue)|Out DLL_INTERNAL@0x2ecbb0@mstscax.dll|BSTR __stdcall IMsRdpClient2::UserName(IUnknown* pObject, PVOID* RetValue)|Out Notices : 1) use the "Relative Virtual Address" value (the Raw address can be used for disassembly if you want to see COM function asm code) 2) Depending on the control version you may won't get same RVA !!! Once done save the file under mstscax_static_hook.txt inside the "monitoring files" folder (not the COM sub folder) Disable COM Auto hooking (you don't need it) Load the mstscax_static_hook.txt monitoring file at startup This is dirty raw method but you will be sure you won't miss a function call even if you miss the COM object creation Case 2: The object can't be created by CoCreateInstance In case object creation can't be done with CoCreatInstance, the "Methods Address" dialog of case 1 will display no information at all on your COM object and methods (you get an "Error creating object" error). In that case you have to find method address manually. To do so, you need to create a small test program calling the method for which you need information. Build it and put a breakpoint before the function call. When breakpoint is reached, go to the disasm window; hit step into and bypass the first jump which is vtbl indirection by hitting step into once more. At this step you should be at function startup; but warning this is the virtual address and dll can be loaded elsewhere in other process, so you need to compute relative address. To get the RVA, you have to remove module start address from the virtual address you just found. RVA=VA-dll_base_address Dll start address should be shown inside the modules view of your debugger. If your debugger doesn't display modules start addresses, use Dumper provided with WinApiOverride, and click the process of your test program. You will get all modules start address. Once done, write the monitoring file manually like described in case 1 (don't forget to add the first argument "IUnknown* pObject") |
|||||||||
Interface Methods creating objects | |||||||||
Not only API can create COM objects. Some methods of interface can do it too like IDirect3D8::CreateDevice. So you have to have a way to hook these created objects. The solution is to use a syntax similar to COM Object Creation File Syntax options added after the Auto Monitoring File Syntax Syntax |
|||||||||
VTBLIndex=Index| [ReturnType] FuncName( ParamType [paramName] ) [;] |ObjectCreation(Option1|Option2...|OptionN) | |||||||||
It can be resumed by | |||||||||
AutoMonitoringFileSyntax | ObjectCreation( ComObjectCreationFileSyntax_Options ) | |||||||||
You have one more key word than ComObjectCreationFileSyntax which is CurrentObjectCLSID and is used like |CLSIDValue=CurrentObjectCLSID This allow to add interface to the current CLSID used (can be usefull in some case like for IDispatch::GetTypeInfo) Examples Notice : definition must be on a single line, they are on multiple lines here only for presentation a) For IDirect3D8::CreateDevice, as we don't know CLSID of IDirect3DDevice8, we use IID_IDirect3DDevice8 as CLSID (see COM object creation file syntax notice), and IID_IDirect3DDevice8 value is {7385E5DF-8FE8-41D5-86B6-D7B48547B6CF} so we have VTBLIndex=15|HRESULT IDirect3D8::CreateDevice(IUnknown* pObject, UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice8** ppReturnedDeviceInterface);|FailureIfNegativeRet |ObjectCreation(CLSIDValue={7385E5DF-8FE8-41D5-86B6-D7B48547B6CF}|IIDValue={7385E5DF-8FE8-41D5-86B6-D7B48547B6CF}|ObjectStackIndex=24|x64ObjectStackIndex=16) b) For IDispatch::GetTypeInfo, we have to use same CLSID as the currently used, and the IID_ITypeInfo value is {00020401-0000-0000-C000-000000000046} so we have VTBLIndex=4|HRESULT IDispatch::GetTypeInfo(IUnknown* pObject,UINT iTInfo,LCID lcid,ITypeInfo** ppTInfo);|Out|FailureIfNegativeRet |ObjectCreation(CLSIDValue=CurrentObjectCLSID|IIDValue={00020401-0000-0000-C000-000000000046}|ObjectStackIndex=12|x64ObjectStackIndex=R9) |
|||||||||
Overriding, or Adding Pre/Post call hooks | |||||||||
Notice: COM overriding doesn't need COM auto hooking to be enabled. Example can be found in directory "Overriding Dll SDK\COM\QueryInterfaceRestriction" Overriding a COM method can be done using COM@CLSID@IIDVTBLIndex={IID}:Index as dll name, in an overriding, pre or post dll array (see overriding dll) where CLSID is the class ID of the object, IID is the interface ID, and Index the 0 based VTBL index By the way to override the AboutBox method of Microsoft TreeView Control 6 SP4 we can do the following replacing method with the following stupid function
A full COM overriding dll source code is available in "Overriding Dll SDK\COM\QueryInterfaceRestriction\" folder |
|||||||||
Hooking Objects Creation And Destruction | |||||||||
Notice: COM objects creation hooking doesn't need COM auto hooking to be enabled. Example can be found in directory "Overriding Dll SDK\COM\COMObjectCreationSpy" To hook COM object creation, you only have to create an overriding dll exporting the following function
The parameter 4, PRE_POST_API_CALL_HOOK_INFOS* pHookInfos, gives you some information like: - the return address of API having created object, - the handle of module having done call - if current call match current overriding modules filters To hook COM object creation, you only have to create an overriding dll exporting the following function
|