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
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
Before we make the actual changes we have to
store the original code (five bytes that will be erased by the
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
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.