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.
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.
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?).
This code can be used without any warranties on your own risk. Please, send bug reports and suggestions to Vitaly Meytin.