Skip to content

Commit

Permalink
Get return values from remote calls.
Browse files Browse the repository at this point in the history
  • Loading branch information
mridgers committed Nov 29, 2017
1 parent fddf92e commit 424df27
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 41 deletions.
6 changes: 1 addition & 5 deletions clink/app/src/loader/inject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,7 @@ int inject(int argc, char** argv)

// On Windows a DLL will have the same address in every process' address
// space, hence we're able to use 'initialise_clink' directly here.
vm target_vm(target_pid);
vm::region remote_app_desc = target_vm.alloc(1);
target_vm.write(remote_app_desc.base, &app_desc, sizeof(app_desc));
ret |= process(target_pid).remote_call(initialise_clink, remote_app_desc.base);
target_vm.free(remote_app_desc);
ret |= (process(target_pid).remote_call(initialise_clink, app_desc) != nullptr);

return ret;
}
11 changes: 5 additions & 6 deletions clink/process/include/process/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ class process
bool get_file_name(str_base& out) const;
arch get_arch() const;
int get_parent_pid() const;
bool inject_module(const char* dll);
template <typename T> int remote_call(T* function, void* param);
void* inject_module(const char* dll);
template <typename T> void* remote_call(void* function, T const& param);
void pause();
void unpause();

private:
typedef void (__stdcall *funcptr_t)();
int remote_call_impl(funcptr_t function, void* param);
void* remote_call(void* function, const void* param, int param_size);
void pause_impl(bool suspend);
int m_pid;

Expand All @@ -47,9 +46,9 @@ inline int process::get_pid() const

//------------------------------------------------------------------------------
template <typename T>
int process::remote_call(T* function, void* param)
void* process::remote_call(void* function, T const& param)
{
return remote_call_impl(funcptr_t(function), param);
return remote_call(function, &param, sizeof(param));
}

//------------------------------------------------------------------------------
Expand Down
95 changes: 65 additions & 30 deletions clink/process/src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

#include <core/path.h>
#include <core/str.h>

#include <PsApi.h>
#include <TlHelp32.h>
#include <stddef.h>

//------------------------------------------------------------------------------
process::process(int pid)
Expand Down Expand Up @@ -107,63 +109,96 @@ void process::pause_impl(bool suspend)
}

//------------------------------------------------------------------------------
bool process::inject_module(const char* dll_path)
void* process::inject_module(const char* dll_path)
{
// Check we can inject into the target.
if (process().get_arch() < get_arch())
return false;

// Create a buffer in the process to write data to.
vm target_vm(m_pid);
vm::region region = target_vm.alloc(1);
if (region.base == nullptr)
return false;

target_vm.write(region.base, dll_path, strlen(dll_path) + 1);
if (process().get_arch() != get_arch())
return nullptr;

int thread_ret = 0;

// Get the address to LoadLibrary. Note that we do with without using any
// Windows API calls in case someone's hook LoadLibrary. We'd get the wrong
// address. Address are the same across processes.
// Get the address to LoadLibrary. Note that we get LoadLibrary address
// directly from kernel32.dll's export table. If our import table has had
// LoadLibraryW hooked then we'd get a potentially invalid address if we
// were to just use &LoadLibraryW.
pe_info kernel32(LoadLibrary("kernel32.dll"));
auto* thread_proc = kernel32.get_export("LoadLibraryA");
if (thread_proc != nullptr)
thread_ret = remote_call_impl(thread_proc, region.base);
void* thread_proc = kernel32.get_export("LoadLibraryW");

// Clean up and quit
target_vm.free(region);
return (thread_ret != 0);
wstr<280> wpath(dll_path);
return remote_call(thread_proc, wpath.data(), wpath.length() * sizeof(wchar_t));
}

//------------------------------------------------------------------------------
int process::remote_call_impl(funcptr_t function, void* param)
void* process::remote_call(void* function, const void* param, int param_size)
{
// Open the process so we can operate on it.
handle process_handle = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_CREATE_THREAD,
FALSE, m_pid);
if (!process_handle)
return false;

#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4200)
#endif
struct thunk_data
{
void* (*func)(void*);
void* out;
char in[];
};
#if defined(_MSC_VER)
# pragma warning(pop)
#endif

const auto& thunk = [] (thunk_data& data) {
data.out = data.func(data.in);
};

const auto* stdcall_thunk = static_cast<void (__stdcall*)(thunk_data&)>(thunk);
static int thunk_size;
if (!thunk_size)
for (const auto* c = (unsigned char*)stdcall_thunk; ++thunk_size, *c++ != 0xc3;);

vm vm(m_pid);
vm::region region = vm.alloc(1, vm::access_write);
if (region.base == nullptr)
return nullptr;

int write_offset = 0;
const auto& vm_write = [&] (const void* data, int size) {
void* addr = (char*)region.base + write_offset;
vm.write(addr, data, size);
write_offset = (write_offset + size + 7) & ~7;
return addr;
};

vm_write(stdcall_thunk, thunk_size);
void* thunk_ptrs[2] = { decltype(thunk_data::func)(function) };
char* remote_thunk_data = (char*)vm_write(thunk_ptrs, sizeof(thunk_ptrs));
vm_write(param, param_size);
vm.set_access(region, vm::access_rwx); // writeable so thunk() can write output.

static_assert(sizeof(thunk_ptrs) == sizeof(thunk_data), "");
static_assert((offsetof(thunk_data, in) & 7) == 0, "");

pause();

// The 'remote call' is actually a thread that's created in the process and
// and then waited on for completion.
// then waited on for completion.
DWORD thread_id;
handle remote_thread = CreateRemoteThread(process_handle, nullptr, 0,
(LPTHREAD_START_ROUTINE)function, param, 0, &thread_id);
(LPTHREAD_START_ROUTINE)region.base, remote_thunk_data, 0, &thread_id);
if (!remote_thread)
{
unpause();
return 0;
}

// Wait for injection to complete.
DWORD thread_ret;
WaitForSingleObject(remote_thread, INFINITE);
GetExitCodeThread(remote_thread, &thread_ret);

unpause();

return thread_ret;
void* call_ret = nullptr;
vm.read(&call_ret, remote_thunk_data + offsetof(thunk_data, out), sizeof(call_ret));
vm.free(region);

return call_ret;
}

0 comments on commit 424df27

Please sign in to comment.