1

Alright, I'm injecting some code into another process using the CreateRemoteThread/LoadLibrary "trick".

I end up with a thread id, and a process with a DLL of my choice spinning up. At least in theory, the DLL does nothing at the moment so verifying this is a little tricky. For the time being I'm willing to accept it on faith alone. Besides, this question needs to be answered before I push to hard in this direction.

Basically, you can't block in DllMain. However, all I've got to communicate with the remote thread is its id. This practically begs for PostThreadMessage/GetMessage shenanigans which block. I could spin up another thread in DllMain, but I have no way of communicating its id back to the creating thread and no way of passing the another thread's id to the remote one.

In a nutshell, if I'm creating a remote thread in a process how should I be communicating with the original process?

Kevin Montrose
  • 22,191
  • 9
  • 88
  • 137

2 Answers2

7

Step zero; the injected DLL should have an entry point, lets call it Init() that takes a LPCWSTR as its single parameter and returns an int; i.e. the same signature as LoadLibrary() and therefore equally valid as a thread start function address...

Step one; inject using load library and a remote thread. Do nothing clever in the injected DLLs DLLMain(). Store the HMODULE that is returned as the exit code of the injecting thread, this is the HMODULE of the injected DLL and the return value of LoadLibrary().

Note that this is no longer a reliable approach on x64 if /DYNAMICBASE and ASLR (Address space layout randomisation) is enabled as the HMODULE on x64 is larger than the DWORD value returned from GetThreadExitCode() and the address space changes mean that it's no longer as likely that the HMODULE's value is small enough to fit into the DWORD. See the comments below and the linked question (here) for a work around using shared memory to communicate the HMODULE

Step two; load the injected DLL using LoadLibrary into the process that is doing the injecting. Then find the offset of your Init() entrypoint in your address space and subtract from it the HMODULE of your injected DLL in your address space. You now have the relative offset of the Init() function. Take the HMODULE of the injected DLL in the target process (i.e. the value you saved in step one) and add the relative address of Init() to it. You now have the address of Init() in your target process.

Step three; call Init() in the target process using the same 'remote thread' approach that you used to call LoadLibrary(). You can pass a string to the Init() call, this can be anything you fancy.

What I tend to do is pass a unique string key that I use as part of a named pipe name. The Injected DLL and the injecting process now both know the name of a named pipe and you can communicate between them. The Init() function isn't DLLMain() and doesn't suffer from the restrictions that affect DLLMain() (as it's not called from within LoadLibrary, etc) and so you can do normal stuff in it. Once the injected DLL and the injecting process are connected via a named pipe you can pass commands and data results back and forth as you like. Since you pass the Init() function a string you can make sure that the named pipe is unique for this particular instance of your injecting process and this particular injected DLL which means you can run multiple instances of the injecting process at the same time and each process can inject into multiple target processes and all of these communication channels are unique and controllable.

Len Holgate
  • 21,282
  • 4
  • 45
  • 92
  • I don't see how passing a string to CreateRemoteThread(...,dllExportAddrInRemoteProcess,stringBuffer,...) could work (Without WriteProcessMemory). But, both processes know the process id and thread id of the injected thread so you could use those as part of a name used with IPC – Anders Jul 22 '09 at 11:55
  • Yes, you use WriteProcessMemory() in exactly the same way that you did to call LoadLibrary(). I agree you could fudge up a unique id by other means but you need to call something other than DLLMain() to allow you to do initialisation that you can't do in DLLMain() due to the restrictions. – Len Holgate Jul 22 '09 at 13:09
  • 1
    The type of exit code is DWORD - a 32bit unsigned int value, how to get HMODULE if the process is 64bit ? – amanjiang Dec 06 '14 at 04:42
  • Good question. In my code I just cast it... It just works as dll's tend to be loaded into the area of the address space that fits into a 32bit value and so the truncation doesn't matter. I wonder if you can break this by setting the dll's preferred base address to something that doesn't fit... I've been looking for some definitive answer regarding dll load addresses and a process' virtual address space but can't find anything... – Len Holgate Dec 06 '14 at 15:08
  • @amanjiang, see here: http://stackoverflow.com/a/27348017/7925 for some suggestions. – Len Holgate Dec 07 '14 at 21:26
1

You don't have the thread id of a thread in the remote process, because the one you used to load the dll exited when your module was successfully loaded into the address space of your process.

You can easily use the normal interprocess communication methods like named sections/pipes/creating a named window/etc. to communicate with your 'injecting' process.

Christopher
  • 8,912
  • 3
  • 33
  • 38