One way to hook a system function and how to improve performance hooking up CLSIDFromProgID procedure.

Environment: VC6 SP3, NT4 SP5

We're working on very large software project using MTS. We have about 300 components written in C++ and Visual Basic registered in MTS package. During profiling session I noticed that for some scenarios about 10-15% were spent in CLSIDFromProgID function (ole32.dll). The solution is simple - we need to cache ProgIDs in memory instead of retrieving them from the registry every time somebody wants to instantinate an object. To achieve this goal without major changes in the project (and still having a third-party code accessing the registry that slows down the system as well) I wrote a simple DLL that implements CLSIDFromProgIDProc in "my way" - first time ProgID is taken from the registry and stored in CMap, after that it is looked up in the map that's 30 times more faster than call to the "regular" procedure.

To redirect all callers to my function I had to use WriteProcessMemory function to modify code in OLE32.DLL at run- time. Take a look at disassembler (addresses from NT SP5):

CLSIDFromProgID
77B518E1 6A 00          push 0
77B518E3 FF 74 24 0C    push dword ptr [esp+0Ch]
77B518E7 FF 74 24 0C    push dword ptr [esp+0Ch]
77B518EB E8 A3 FF FF FF call CoImpersonateClient+0BB3h (77b51893)
77B518F0 C2 08 00       ret  8

It has more than enough room to insert five bytes of jmp instruction (one byte for jmp itself = 0xE9, four bytes for offset to our hook procedure). It's very important to make sure the code being injected will not overwrite some neighborhood function (and therefore instantly crash the process).

The offset may be calculated as following:

HMODULE hModOLE32 = GetModuleHandle ("ole32.dll");
PVOID pfnCLSIDFromProgIDOrg = (PVOID)GetProcAddress (hModOle32, "CLSIDFromProgID");
PVOID pfnHookProc = (PVOID) CLSIDFromProgIDProc; //our hook procedure
DWORD dwOffset = (DWORD) pfnHookProc - (DWORD) pfnCLSIDFromProgIDOrg + dwJmpInstructionLen;

dwJmpInstructionLen is 5 (bytes) and should be taken in account because address of next instruction is calculated as EIP after jmp execution + offset (IP - instruction pointer).

Before we make the actual changes we have to store the original code (five bytes that will be erased by the jmp indstruction):

    ReadProcessMemory  (hProcess, //obtained from GetCurrentProcess ()
                        pfnCLSIDFromProgIDOrg, 
                        pbCodeBuffer, // some buffer at least dwJmpInstructionLen bytes length
                        dwJmpInstructionLen, // nuber bytes to read
                        &dwRead); // how many bytes have been returned in pbCodeBuffer

Now we're ready to build up in memory the jmp instruction:


	BYTE byteJMP = 0xE9;

// write the one byte instruction first
   WriteProcessMemory (hProcess,
                        pfnCLSIDFromProgIDOrg,
                        &byteJMP,
                        sizeof(BYTE),
                        &dwWritten);

// write the instruction argument - offset (DWORD)
    WriteProcessMemory (hProcess,
                        (LPVOID) ((DWORD)pfnCLSIDFromProgIDOrg + 1), 
                        &dwOffset,
                        sizeof(DWORD),
                        &dwWritten);


At this point every call will be redirected to our CLSIDFromProgIDProc. Notice, that it must be defined as __stdcall because the callee must pop its own arguments from the stack.

Since Windows NT has copy-on-write protection the current process gets its own private copy of ole32.dll and the other processes will work as usual.

In the attached example the hook is established in the DLL's InitInstance and is removed in ExitInstance. Therefore the only thing a client should do is call to LoadLibrary ("CLSIDHookDLL.dll") to take advantage of CLSID cache and FreeLibrary when it finished.

Of course, the CLSIDHookDLL.dll should reside in the current directory or in the path. If running under MTS, the DLL should be found in the path if LoadLibrary is called from a component's InitInstance (during package startup the working directory is usually set to %SystemRoot%).

This example may be relevant only for projects where performance is very important and you see in profiler that CLSIDFromProgID gets a high score, but the explained technique may be employed for different goals (especially if you want for some reason to write your own version of API procedure).

There is another way to hook an imorted function by "finding the import section of the executable, looking for the particular function you would like to hook, and then writing the hook function address into its place" (John Robbins, MSJ, 2/98). You can search MSDN for "HookImportedFunctionsByName" word and get "Bugslayer" column by John Robbins describing this technique.

Demo project

was built using VC6 SP3. It demonstrate the difference between unhooked and hooked versions of CLSIDFromProgID function calling it several times and representing the result in CPU clocks.

Limitations:

1. It was tested only under Windows NT (SP3 - SP5) for Intel CPU.

2. If the cached CLSID has been changed in the registry, or ProgID has been removed from the registry, it still will be returned from the cache (but who will do that when an application is running?).

Download demo project - [12 KB] Kb

Download source - [24 KB] Kb

This code can be used without any warranties on your own risk. Please, send bug reports and suggestions to Vitaly Meytin.

History



Comments

  • .Net version ?

    Posted by vitoto on 01/02/2006 11:37pm

    Hi you have some new version for .Net ?

    Reply
  • Not sure

    Posted by Legacy on 09/12/2003 12:00am

    Originally posted by: mbol

    Not sure it's thread safe. A better idea might be to go to all the relocation thunks and update your code from the stubs.

    Reply
  • How to catch a Win32 function?

    Posted by Legacy on 08/11/2003 12:00am

    Originally posted by: ihoapm

    Good. Would you like to tell me how to catch an arbitrary Win32 function (for example, I want to catch function "TextOut(...)")?

    Reply
  • How about portability??

    Posted by Legacy on 10/18/1999 12:00am

    Originally posted by: Alex Zatvornitskiy

    How to be sure this code will not crash system with different SP (large project - lot's of workstations with different software installed)?
    At last, how about Win'95, '98, ... Win'2000?

    Reply
  • CLSIDfromProgID

    Posted by Legacy on 10/01/1999 12:00am

    Originally posted by: Alaeddin Mohammed

    Greate.

    Reply
  • Fantastic

    Posted by Legacy on 09/12/1999 12:00am

    Originally posted by: Christiaan

    Once you tried "regmon" from Mark Russinovich, one wants to start hooking the whole registry ....

    Reply
  • Good

    Posted by Legacy on 07/02/1999 12:00am

    Originally posted by: lurui

    A simple but very great idea!

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • Best-in-Class organizations execute on a strategy that supports the multi-channel nature of customer requests. These leading organizations do not just open up their service infrastructures to accommodate new channels, but also empower their teams to deliver an effective and consistent experience regardless of the channel selected by the customer. This document will highlight the key business capabilities that support a Best-in-Class customer engagement strategy.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds