Why does regedit.exe (or any other Microsoft program) crash when I try to create a remote thread into it using the CreateRemoteThread API?

Question:

On Windows XP, I am starting regedit.exe in suspended mode and forcing it to call the LoadLibrary() API to load an external DLL by using the CreateRemoteThread API. After loading the DLL, when I try to resume the suspended thread, the application terminates. Is this a known issue?

Answer:

Creating a remote thread into another process is a poor way to build extensibility into an application. Extendable applications should have some form of an API, a scripting language, plug-in DLL support, etc.

Regedit.exe was not meant to be extended by any means—it has no API and no plug-in interface and therefore does not expect to run any code or threads which are not built into it. Therefore, it also is not designed to run properly when another process calls the CreateRemoteThread to start a new thread within it. It may run or it may not. If it runs without a problem, it is only by coincidence.

In fact, Microsoft strongly discourages using CreateRemoteThread/CreateRemoteThreadEx to start a thread in any process. Its original purpose is to write debuggers and profilers. Even with these tools, creating a thread in another process has a risk that the target process can fail. Although the APIs themselves are documented and they will create a thread in another process (as documented), the effects the new thread has on the target process completely depend on what the target process and the new thread do – how they interact. It’s the latter that causes the all the problems.

When you create a new thread in a process that doesn’t expect it, there are many interactions which can occur. Here are some things that can happen:

a. The new thread requires a stack in the application’s address space. The application may want that memory for something else. Perhaps the application will fail to make an allocation and then crash because it can’t recover from something that should “never” happen.

b. It causes all the DllMain() functions of all the DLLs to get called for DLL_THREAD_ATTACH. One of these could call an API which shouldn’t be called from DllMain(), causing a deadlock.

c. It causes an extra DLL to get loaded somewhere in the process’s address space. It could be that the DLL is loaded into a place where the app wants to allocate memory. The DLL may load at its preferred address space or it may not—it depends on what is already in the process’s memory.

d. The application may use the single-threaded CRT (C runtime library) which now could have multiple threads calling it. Since the single-threaded CRT doesn’t set up the necessary data structures and locks to handle multiple threads, the CRT can crash.

e. The injected code could simply have a bug and cause the application to crash.

f. The thread could cause a deadlock if it uses any synchronization objects that other parts of the application use.

g. Anti-malware and anti-virus programs may get triggered and kill the target process, especially if it is a known administrative tool that should be prevented from being hacked.

Microsoft’s efforts to make the platform more secure means we invest in technologies like DEP (data execution prevention) which happen to do more than simply prevent someone from executing data. DEP also prevent exception handlers from executing in dynamically allocated code (because malware uses this technique), they may mark portions of the address space as no-access so that a DLL can’t be injected, address space layout randomization, required executable signing for kernel-mode modules, etc. Microsoft will continue to improve the security of its products by similar technical means, so there is a greater chance in the future these techniques such as creating remote threads will fail more frequently. (At the same time, a provision is made to allow debuggers to do what they need to do to be useful.)