Using CreateRemoteThread from C#
If you want to create a new thread in your process in C#, you can use Thread.Start. But things are a little harder if you want to create a thread in another process, ala kernel32!CreateRemoteThread. Disclaimer: CreateRemoteThread is evil, could dead-lock, doesn't always work (particularly across-sessions), and should be avoided in favor or friendlier inter-process communication libraries unless you're a rocket scientist.
Dangers and evils aside, here's how you can use it from C#.
The challenge with CreateRemoteThread is to to pass a thread proc that's a valid function pointer in the target process. In C#, we use delegates instead of function-pointers, but delegates are only valid in the current process and don't serialize. The basic trick is to use Marshal.GetFunctionPointerForDelegate to get an IntPtr to the method that will serve as the thread proc. While delegates can't be serialized across a process boundary, IntPtrs can.
Here's a snippet that demos CreateRemoteThread in C#. When invoked with no command line args, it will:
- Create a delegate to the thread proc, MyThreadProc
- Get an IntPtr (fpProc) for that via Marshal.GetFunctionPointerForDelegate
- spin up a 2nd instance of its own process, and pass it the parent's process id and fpProc on the command line. (So we're using the command line + parent/child process relationships as our initial IPC mechanism)
- The child (which knows it's the child because it has command line args) then parses the command line to recover the parents's pid and fpProc. and then gets the handle for the parent process.
- The child then makes the call to CreateRemoteThread, and passes some arbitrary data (in this case, the number 6789) as the thread proc's data.
Here's the output. Both apps print to the same console so that their text is interweaved to display causality:
Pid 5944:Started Parent process Pid 5796:Started Child process Pid 5944: Inside my new thread!. Param=6789 Pid 5796: Thread exited with code: 1
Here's the interesting code snippet (built with VS2005):
// stdcall
static int MyThreadProc(IntPtr param)
{
int pid = Process.GetCurrentProcess().Id;
Console.WriteLine("Pid {0}: Inside my new thread!. Param={1}", pid, param.ToInt32());
return 1;
}
// Helper to wait for a thread to exit and print its exit code
static void WaitForThreadToExit(IntPtr hThread)
{
WaitForSingleObject(hThread, unchecked((uint)-1));
uint exitCode;
GetExitCodeThread(hThread, out exitCode);
int pid = Process.GetCurrentProcess().Id;
Console.WriteLine("Pid {0}: Thread exited with code: {1}", pid, exitCode);
}
// Main function
static void Main(string[] args)
{
int pid = Process.GetCurrentProcess().Id;
if (args.Length == 0)
{
Console.WriteLine("Pid {0}:Started Parent process", pid);
// Spawn the child
string fileName = Process.GetCurrentProcess().MainModule.FileName.Replace(".vshost", "");
// Get thread proc as an IntPtr, which we can then pass to the 2nd-process.
// We must keep the delegate alive so that fpProc remains valid
ThreadProc proc = new ThreadProc(MyThreadProc);
IntPtr fpProc = Marshal.GetFunctionPointerForDelegate(proc);
// Spin up the other process, and pass our pid and function pointer so that it can
// use that to call CreateRemoteThraed
string arg = String.Format("{0} {1}", pid, fpProc);
ProcessStartInfo info = new ProcessStartInfo(fileName, arg);
info.UseShellExecute = false; // share console, output is interlaces.
Process processChild = Process.Start(info);
processChild.WaitForExit();
GC.KeepAlive(proc); // keep the delegate from being collected
return;
}
else
{
Console.WriteLine("Pid {0}:Started Child process", pid);
uint pidParent = uint.Parse(args[0]);
IntPtr fpProc = new IntPtr(int.Parse(args[1]));
IntPtr hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pidParent);
uint dwThreadId;
// Create a thread in the first process.
IntPtr hThread = CreateRemoteThread(
hProcess,
IntPtr.Zero,
0,
fpProc, new IntPtr(6789),
0,
out dwThreadId);
WaitForThreadToExit(hThread);
return;
}
}
The full sample code is available at: https://blogs.msdn.com/jmstall/articles/sample_create_remote_thread.aspx.
More on the marshalling:
The simple way to do call CreateThread (the local version) from managed code would be something like this:
// Thread proc, to be used with Create*Thread
public delegate int ThreadProc(IntPtr param);
// Friendly version, marshals thread-proc as friendly delegate
[DllImport("kernel32")]
public static extern IntPtr CreateThread(
IntPtr lpThreadAttributes,
uint dwStackSize,
ThreadProc lpStartAddress, // ThreadProc as friendly delegate
IntPtr lpParameter,
uint dwCreationFlags,
out uint dwThreadId);
Notice that the CLR's marshaller is smart enough to marshal the friendly type-safe ThreadProc delegate as an evil unsafe function pointer. At this pointer, unless we needed some very fine control over thread-creation (eg specific flags), we're probably still better off using Thread.Start. The other IntPtrs (like lpThreadAttributes) could also be marshaled as type-safe structures instead of intptrs (see on https://pinvoke.net for more examples). We could have marshaled it like:
// Marshal with ThreadProc's function pointer as a raw IntPtr.
[DllImport("kernel32", EntryPoint="CreateThread")]
public static extern IntPtr CreateThreadRaw(
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress, // ThreadProc as raw IntPtr
IntPtr lpParameter,
uint dwCreationFlags,
out uint dwThreadId);
The raw function pointer doesn't really buy us much in the local case. But once we view it like that, the leap to CreateRemoteThread is smaller:
// CreateRemoteThread, since ThreadProc is in remote process, we must use a raw function-pointer.
[DllImport("kernel32")]
public static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress, // raw Pointer into remote process
IntPtr lpParameter,
uint dwCreationFlags,
out uint lpThreadId
);
Comments
Anonymous
September 29, 2006
Will this work on a 64 bit machine ? WaitForSingleObject(hThread, unchecked((uint)-1));Anonymous
September 30, 2006
Pour ceux qui auraient un jour besoin d'utiliser l'API CreateRemoteThread en C#, Mike Stall nous livre...Anonymous
September 30, 2006
Blandest - it should still work, though it's not the cleanest.
INFINITE is defined in the windows headers as:
#define INFINITE 0xFFFFFFFF // Infinite timeout
and 'uint' in C# is 32-bits on both 32 and 64 machines.
If you have a C# app print
(unchecked((uint)-1).ToString("x"))
it will print the same thing on both 32 and 64-bit.
It would have been cleaner if I defined a constant and used that, but this was just a quick demo of CreateRemoteThread.Anonymous
October 03, 2006
How would make sure that the delegate wasn't collected. Would you make it static?Anonymous
October 05, 2006
Dmitriy - Ah! You got me. I updated the sample with a GC.KeepAlive to address that.Anonymous
April 22, 2008
In the previous article I discussed a few of the benefits of stack allocation as well as a couple of