Passing multiple parameters using CreateRemoteThread in C#

5.1k views Asked by At

My goal is to call a function in a remote process using P/Invoke in C# (CreateRemoteThread). The problem is that the function takes more than one parameter. Is there a way to pass multiple parameters to the function?

2

There are 2 answers

1
Colin Smith On BEST ANSWER
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);

[Flags]
public enum AllocationType
{
    Commit = 0x1000,
    Reserve = 0x2000,
    Decommit = 0x4000,
    Release = 0x8000,
    Reset = 0x80000,
    Physical = 0x400000,
    TopDown = 0x100000,
    WriteWatch = 0x200000,
    LargePages = 0x20000000
}

[Flags]
public enum MemoryProtection
{
    Execute = 0x10,
    ExecuteRead = 0x20,
    ExecuteReadWrite = 0x40,
    ExecuteWriteCopy = 0x80,
    NoAccess = 0x01,
    ReadOnly = 0x02,
    ReadWrite = 0x04,
    WriteCopy = 0x08,
    GuardModifierflag = 0x100,
    NoCacheModifierflag = 0x200,
    WriteCombineModifierflag = 0x400
}

[StructLayout(LayoutKind.Sequential, Pack=1]
public struct RemoteThreadParams
{
    [MarshalAs(UnmanagedType.U1)]
    public byte Param1;

    [MarshalAs(UnmanagedType.I4)]
    public int Param2;

    ...
}

[DllImport("kernel32")]
public static extern IntPtr CreateRemoteThread(        
  IntPtr hProcess,
  IntPtr lpThreadAttributes,
  uint dwStackSize,
  IntPtr lpStartAddress,
  IntPtr lpParameter,
  uint dwCreationFlags,
  out uint lpThreadId
);

RemoteThreadParams params = new RemoteThreadParams();
parms.Param1 = 10;
parms.Param2 = 200;

// Allocate some native heap memory in your process big enough to store the
// parameter data
IntPtr iptrtoparams = Marshal.AllocHGlobal(Marshal.SizeOf(RemoteThreadParams));

// Copies the data in your structure into the native heap memory just allocated
Marshal.StructureToPtr(params, iptrtoparams, false);

// Use to get a handle to the process you intend to create a thread in.
OpenProcess(...,...,...);

// Use to alloc "committed" memory that is addressable by other process
IntPtr iptrremoteallocatedmemory = VirtualAllocEx()...

// Copy from your process memory to the memory the remoteprocess will be accessing
WriteProcessMemory(...,iptrremoteallocatedmemory,iptrtoparams,...,...);

Marshal.FreeHGlobal(iptrtoparams); // safe to free, as you have done the copy

CreateRemoteThread(...,...,...,...,iptrremoteallocatedmemory,...,...);

// Free the memory that was allocated for the other process...but be
// careful of its lifetime.
//
// Only free when the thread will no longer be accessing the allocated native
// memory i.e. when it's finished.

VirtualFreeEx(...,...,...,...);

In your C/C++ code have:

#pragma pack(push,1)
struct tagRemoteThreadParams
{
    BYTE Param1;
    int Param2;
} RemoteThreadParams, *PRemoteThreadParams;
#pragma pack(pop)

Cast the LPVOID received by the thread function to PRemoteThreadParams (i.e. *RemoteThreadParams).

If you have some "strings" that you want as one of your parameters, then you would have to do some more work to marshal them across. For more help see:

Some other references:

1
GuidedHacking On

If the function has more than one parameter it is not possible to pass them to the function being called by CreateRemoteThread() without using shellcode.

Passing a pointer to a structure or an array of arguments will not work.

The first argument will get passed correctly, the other arguments will exist in the memory you wrote them too, but they will not be placed into registers or the stack where they are required for the calling convention to properly access them.

If the function takes 2 arguments and you pass a pointer to a structure (as I have seen mentioned in other answers) the first argument will be placed on the stack or in a register correctly, but when the function tries to access the subsequent arguments, it will just pull whatever data was on the stack or inside the registers prior to that.

Essentially it will consider this junk data to be the arguments.

The only way to properly get the arguments where they need to be is to write shellcode to the process which loads the arguments into the proper registers and stack before performing a call or jmp to the target function.

You can easily test this by trying to perform either of these with CreateRemoteThread:

MessageBoxA(0,0,0,0);
Beep(500, 500);

You can follow the assembly yourself and easily see the problem, at no point does the assembly ever attempt to touch the addresses following the first argument. Instead it simply touches the data in the positions where the arguments are supposed to be (on the stack and in the registers, not in your structure you wrote to memory).

If your function takes a pointer to a structure, then the methods provided in the other answers will work.