If youve ever had to test a product that customers install, you probably wanted to know exactly what registry entries any COM servers generated when they self-registered. Its a given that a COM server will install registry entries somewhere in HKEY_CLASSES_ROOT\CLSID, but unless youre the developer, you might find it hard to actually discover what they all are. The added complication is that actually doing the self registration will alter your system and may have a downstream effect on later testing.
This article shows how you can write code that redirects the registry I/O of a given COM server during self registration. Ill even show how to make this work for out-of-process COM servers, by injecting code into the target process before it begins executing.
Microsoft recognizes that installation folks worry about installation registry modifications and has provided a new Windows 2000 function that helps. The prototype for this new function is:
LONG RegOverridePredefKey (HKEY hKey, HKEY hNewHKey);
The idea is that you create your own registry key (hNewHKey) and then call this function to tell Windows 2000 to make your key start receiving the registry I/O destined for one of the predefined registry keys (HKEY_CLASSES_ROOT, HKEY_LOCAL_MACHINE, etc.). You can restore registry I/O to its normal behavior by passing NULL in the second parameter when you call this function.
For example, if you use RegCreateKeyEx() to create your own key, you can then say something like:
RegOverridePredefKey (HKEY_CLASSES_ROOT, hNewHKey);
This is a process-wide effect, so any time the process refers to HKEY_CLASSES_ROOT from that time on, it will get redirected to the substitute key. This provides the means to build a program to capture COM registration entries and collect them in a single location instead of being scattered all over HKEY_CLASSES_ROOT.
Consider the case where the COM server is a DLL. COM in- process servers export a DllRegisterServer() function that performs the registration, so you could load the DLL, use RegOverridePreDefKey() to redirect registry I/O to one or more private keys, and then invoke the DLLs DllRegisterServer(). Thats exactly what the code in Figure 1 does. This is a fragment of the Visual C++ RegSpy project in this months code archive.
The code in Figure 1 creates two new registry keys (keycr and keylm) and uses them to override two registry keys: HKEY_CLASSES_ROOT and HKEY_LOCAL_MACHINE. Most COM registration code updates only HKEY_CLASSES_ROOT, but every now and then, youll find updates to HKEY_LOCAL_MACHINE. For example, if youve ever used the Visual C++ ATL wizards to create a snap-in for MMC (Microsoft Management Console), you may have noticed that registry entries get created underneath HKLM\Software\Microsoft\MMC, and I wanted to capture these, too. You may also find applications that will create event log access entries in:
HKEY_LOCAL_MACHINE \SYSTEM \CurrentControlSet \Services \Eventlog \Application
RegSpy (of which Figure 1 is a snippet) takes a command-line argument, and if the argument is a path to a DLL, it redirects the DLLs self-installation registry entries to a substitute registry location. Its useful to put the name of the server in the substitute registry entry to make it easy to identify, so I called the basic key
HKEY_CURRENT_USER \SOFTWARE \Substitute \Registry \<server name>
and created sub-keys called literally HKCR and HKLM, to which I redirect HKEY_CLASSES_ROOT and HKEY_LOCAL_MACHINE access. This technique works fine for in-process COM servers (DLLs); however, theres more to do yet.
COM servers can be out-of-process (.exes instead of DLLs), and capturing their registry entries is more difficult because you have to arrange for RegOverridePredefKey() to run in the context of a separate process: the out-of-process server. If youre the developer writing the out-of-process server, you could add a command-line option that does the same as the -Regserver command, except that it uses RegOverridePredefKey() before calling the registration code. This would let the developer and the install team check what registry entries are being created without actually creating them in HKEY_CLASSES_ROOT and HKEY_LOCAL_MACHINE. However, I needed to find a way to get these entries without requiring the developer to change the code.
It is possible to execute some of your own code in the context of another process, a technique generally referred to as code injection. First, I use CreateProcess() to create the out-of-process server with the -Regserver command line. I will create it in suspended mode until Im ready to let it perform its registration. Next, Ill use CreateRemoteThread() to inject into the process my code that calls RegOverridePredefKey(). Finally, my injected code will use ResumeThread() to resume the servers main thread so it will perform its registration, using my substitute registry keys. However, the devil, as they say, is in the details.
Basic Code Injection
So, how do you inject your code into a remote process? CreateRemoteThread() requires the address of a function in the remote process, so you have to somehow copy into the remote process address space the function that you want to inject. You can solve this problem in two steps.
The first step is to allocate memory in the remote process to contain the function that youre going to copy over. VirtualAllocEx() lets you allocate memory in an arbitrary process, and CreateProcess() (used to start the remote process in the suspended state) provides the process handle needed to give to VirtualAllocEx(). How much memory should you allocate? You can calculate the size of a single function from a symbolic link map, by figuring the difference between the address of that function and the address of the next symbol in the map.
The second step is to use WriteProcessMemory() to copy your code into the remote process. Among other things, this function requires a process handle (again, the handle you got back from CreateProcess()), the destination address provided by VirtualAllocEx(), the source address of your function, and the size of this code that you computed for the VirtualAllocEx() call.
The Reference Problem
The code you inject into the remote process must be written carefully to avoid any absolute references. For example, if you inject a function called Tinker() into the remote process, and Tinker() makes a call to another local function called Helper(), then the call to Helper() will fail when you run Tinker() in the context of the remote process. Even if you also copied Tinker() into the memory space of the remote process, this would still fail, since the actual CALL instruction will still contain an address for Helper() that was only correct in the original process. Another problem arises with static data. For example, if Tinker() refers to a string constant, that will almost always produce an assembly language instruction that refers to an absolute address that is only valid in the calling process.
Clearly, its not impossible to locate all of these problematic references, copy the additional code or data into the remote process, and then patch all the absolute addresses so they refer to addresses that are correct in the remote process. However, thats a great deal of effort, and its usually simpler just to make the injected function avoid absolute references, mainly by avoiding static data and only calling DLL functions whose correct addresses can be obtained dynamically.
RegSpy needs to make calls to RegOverridePredefKey() and other registry functions that reside in advapi32.dll, so the function it injects will need to use LoadLibrary() and GetProcAddress() to obtain valid addresses for these functions. That introduces a Catch-22 situation, however: the injected function can only call functions whose address it obtained from LoadLibrary() and GetProcAddress(), but how can it then obtain the addresses of LoadLibrary() and GetProcAddress()?
The solution is that LoadLibrary() and GetProcAddress() both reside in kernel32.dll, which should be loaded at the same address in every process. RegSpy can therefore obtain the addresses of LoadLibrary() and GetProcAddress() and pass them in a structure to the injected function, which can then use them to obtain the addresses of other functions. Because kernel32.dll is loaded at the same address in all processes, those function addresses will still be correct in the remote process.
You cant just pass a structure to the remote thread by passing the address of the structure, since the thread will be executing in a separate address space. I write the structure of function pointers (and other information) to the address space of the external server process in much the same way that I copy the function code to the process using VirtualAllocEx() and WriteProcessMemory(). The other data I put in this structure is the thread handle that was returned by CreateProcess() because the injected code will resume this thread to cause the process to self-register. I also put the names of the substitute registry keys that RegOverridePredefKey() will use into this structure. I dont want to go into too much detail about injecting code and data into other processes because this article is primarily about capturing registry activity. This months code archive contains the complete source for RegSpy, and there are pointers at the end of this article to other places you can learn about code injection.
The Injected Function
Figure 2 shows the function that RegSpy injects into out-of-process COM servers. The code is less readable because of all the function calls that must be made through pointers obtained dynamically, rather than directly.
RegSpy creates the COM server process in a suspended state (by passing the CREATE_SUSPENDED flag to CreateProcess()) and injects the code in Figure 2 into the process while it is still suspended. The injected function, ThreadProc(), gets passed the address of the data structure that I injected into the server process with VirtualAllocEx() and WriteProcessMemory(). ThreadProc() loads advapi32.dll, gets the address of GetProcAddress(), and uses it to set up calls to RegOverridePredefKey() and RegCreateKeyEx(). After creating the substitute key, ThreadProc() calls RegOverridePredefKey() and then calls ResumeThread() to resume the main thread of the server process. This causes it to continue and run through its registration code. ThreadProc() waits for this thread to finish and then returns.
If our injected code faults, the server process will fault, not RegSpy. Heres a debugging tip: if you print out the address of the injected version of ThreadProc() (as returned by VirtualAllocEx()), you can subtract that from a fault address to determine the relative offset within ThreadProc() where the fault occurred. You can load RegSpy in your debugger, view the disassembled version of ThreadProc(), and add the previously obtained relative offset to see exactly which instruction failed.
Having done all this and seeing it run without failing, I found that the registry entries did not show up in my substitute key as I expected. To figure out why, I copied the RegOverridePredefKey() code into a COM server and stepped through it in debug mode to see why it was failing. There turned out to be a CoCreateInstance() call in the registration process, and it was failing (this was an ATL server) and not visibly reporting an error. The reason it failed was that RegOverridePredefKey() was causing searches through HKEY_CLASSES_ROOT to be redirected to my substitute key. This was somewhat of a surprise because, for some reason, I expected RegOverridePredefKey() to apply only to write operations to the registry. In order for the registration to work, it meant I had to find the server for the missing object (which turned out to be atl.dll) and write the corresponding HKEY_CLASSES_ROOT\CLSID entries to my substitute key so that the CoCreateInstance() would find it there. Having done that, it worked.
The fact that RegOverridePredefKey() affects both reads and writes to the registry makes it difficult to use in some circumstances. As Ive mentioned, if your registration code calls COM components, you may need to provide their registry entries in your substitute key. It also means that you cant really use this function to monitor the creation of Perfmon registry entries these require reading existing registry entries in
HKEY_LOCAL_MACHINE \Software \Microsoft \Windows NT \CurrentVersion \Perflib
and updating them. (See Paula Tomlinsons Understanding NT article in the September 2000 issue.) In any situation where your registration code requires access to existing registry entries, you may find RegOverridePredefKey() difficult to use. If Microsoft ever builds an Ex version, an option that substitutes only writes to the registry would be a useful addition.
ATL COM servers can also be NT services, and these have code to install with a -service command-line argument. When I ran the server with this command line and looked at the captured registry entries there were indeed some extra ones as I expected. In particular, the HKEY_CLASSES_ROOT\AppID entry contained an AppID GUID and a LocalService entry. Perhaps more interestingly, the NT service was successfully created, and the related registry entries for the service were not substituted. It seems that RegOverridePredefKey() is ignored for code running in the kernel. Another perhaps surprising entry captured was the creation of a
HKEY_LOCAL_MACHINE \Software \Microsoft \Cryptography \RNG
entry. This is probably related to the CoInitializeSecurity() call in the COM server and random number generators used for security.
RegOverridePredefKey() is a useful addition to the Windows API, but it has to be used carefully and with some understanding of its limitations. It can be a useful tool to see what applications are doing to the registry without necessarily letting them actually make the changes.
Jeffrey Richters Programming Applications for Microsoft Windows 4th edition (Microsoft Press, 1999) has a chapter on injecting code into other processes. Youll also find CreateRemoteThread() examples and explanations on the Web at www.mvps.org and www.codepile.com.
Phil Wilson is a developer at Unisys Corporation, building COM objects and services. Since February 2000, Phil has been architect for migrating installations to use Windows Installer technology. He has a BSc in Chemistry from the University of Aston in the UK. You can reach him at email@example.com.