Introduction
There are already plenty of guides on doing remote code or DLL injection out there. The remote code injections usually present strict limitations on the code injected, and the DLL injection naturally requires a DLL.
Here, I will present a method which you could say is functionally equivalent to DLL injection, except that it does not require a DLL. In a nutshell, it takes an image of the current process, allocates memory for it in a remote process, performs relocations, copies the image over, and uses the well-known CreateRemoteThread
to transfer control to it. It's sort of like running two processes inside one.
This is mostly a proof-of-concept, since I have not seen this elsewhere. As such, I will not guarantee it will work in all scenarios, but in my limited testing it has worked flawlessly. In the sample, I use CreateRemoteThread
to transfer control to the entry point of the injected image, since it is the easiest way (and hey, you get a free thread as well). There are other methods, but that's beyond the scope of this article.
Using the Code
The code assumes that the process is a PE executable, and that it can find the headers at the module base (which it finds with GetModuleHandle(0)
). It also assumes that the memory from the module base to the end of the last section of that module is contiguous. This should be safe to assume, unless you are doing something really whacky.
The sample only works with x86 32-bit executables, and I've only tested using Visual C++ 9.0 (it depends on _mainCRTStartup
being available).
Entry Point & the Image
The code takes an image of the current process (that is, the main module). This image goes from the base address of the module to the end of the last section of it. Although the image can theoretically be taken at any time before the injection, it is best to do it before *anything* else, mostly because any pointers to allocated memory stored in the image will be invalid in the injected process. Therefore, if we wish to use the CRT (C Run-Time Libraries) in the injected process (and we certainly do), we must take the image before even the CRT is initialized, and that's before main()
is called.
So we need to make our own entry point and specify it to the linker. The entry point must then take the image before calling the entry point for the CRT, which will eventually call main()
. The entry point for the CRT in VC++9.0 is called _mainCRTStartup
, so the entry point for the sample looks like this:
extern "C" void mainCRTStartup();
void start() {
hmodule = GetModuleHandle(0);
take_image();
mainCRTStartup();
}
Since the image is taken so early on, there are two simple functions, image_set
and image_copy
which are used later to change variables in the image.
The Injection
The inject()
routine injects the image into a process. It does this by first allocating memory in the target process, relocating the image to that address, copying it over and transferring control to it using CreateRemoteThread
.
The most important step is the relocating. This is necessary because the image contains a lot of references to memory addresses (in both instructions and data). To do this, the code uses the relocation section, which Windows uses in order to relocate modules when loading them (unless they are loaded at their desired base address). Some linkers strip out the relocation section from executables either because it figures it will always be loaded first (hence will always get its desired address), or because it was specified that it should not support relocating (Fixed Base Address in VC++). VC++9.0 does not strip out the relocation section by default.
If the sample module does not have a relocation section, it will just crash.
There is actually one more thing to be concerned about once the injection is done. The IAT (Import Address Table), which contains pointers to all imported functions, is in the image, but there is no telling of the validity of the pointers after injection. Therefore, if the executable imports any functions from external modules, then those pointers will probably be invalid in the injected process. The main exception is imports from kernel32.dll. kernel32 is guaranteed to be loaded in every process, and at the same address space to boot, so any addresses to kernel32 in the IAT will still be valid and usable. user32.dll is also loaded at the same address in every process, but it is not guaranteed to actually be loaded in every process, so it's only safe to use functions from it if a LoadLibrary("user32.dll")
is done before calling any functions from it.
One solution to this problem is to go through the IAT and load every module and import after injection, but this is risky: the modules might not be in the current search path, or might not be available at all at the point of injection.
Nevertheless, I have included in my sample a method which attempts to load the IAT after injection. It silently ignores errors, so it might very well crash if it fails to load something. It is only included for completeness.
It is best to only use functions from kernel32, and then load any additional imports manually using LoadLibrary
/GetProcAddress
. Alternatively, it is also possible to use delay-loaded DLLs if your linker supports it.
Oh, and it seems best to always use the "Use MFC in a Static Library" option under VC++; it always crashes otherwise. :)
Conclusion
While relocating an entire executable into another process space may seem complicated at first glance, it is actually rather simple and does not require a whole lot of code. Avoiding the need for a separate DLL while keeping the advantages of injecting an entire module instead of a small piece of a code is, to me, very convenient.
History
- 18th September, 2009: Initial post