inject: Add initial DLL injector tool

This commit is contained in:
Decaf Code
2018-11-10 11:24:57 -05:00
parent ceea0677a4
commit 71bbe49615
7 changed files with 665 additions and 0 deletions
+181
View File
@@ -0,0 +1,181 @@
#include <windows.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static HRESULT debug_wstr(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi);
static bool debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi);
HRESULT debug_main(HANDLE process, uint32_t pid)
{
DEBUG_EVENT ev;
DWORD status;
HRESULT hr;
BOOL ok;
for (;;) {
ok = WaitForDebugEvent(&ev, INFINITE);
if (!ok) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "WaitForDebugEvent failed: %x\n", (int) hr);
return hr;
}
switch (ev.dwDebugEventCode) {
case CREATE_PROCESS_DEBUG_EVENT:
CloseHandle(ev.u.CreateProcessInfo.hFile);
break;
case EXIT_PROCESS_DEBUG_EVENT:
if (ev.dwProcessId == pid) {
return S_OK;
}
break;
case LOAD_DLL_DEBUG_EVENT:
CloseHandle(ev.u.LoadDll.hFile);
break;
case OUTPUT_DEBUG_STRING_EVENT:
if (ev.dwProcessId == pid) {
if (ev.u.DebugString.fUnicode) {
hr = debug_wstr(process, &ev.u.DebugString);
} else {
hr = debug_str(process, &ev.u.DebugString);
}
if (FAILED(hr)) {
return hr;
}
}
break;
}
if (ev.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) {
status = DBG_CONTINUE;
} else {
status = DBG_EXCEPTION_NOT_HANDLED;
}
ok = ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, status);
if (!ok) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "ContinueDebugEvent failed: %x\n", (int) hr);
return hr;
}
}
}
static HRESULT debug_wstr(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
{
char *str;
wchar_t *wstr;
int nbytes_w;
int nbytes_a;
int result;
HRESULT hr;
BOOL ok;
str = NULL;
nbytes_w = odsi->nDebugStringLength * sizeof(wchar_t);
wstr = malloc(nbytes_w);
if (wstr == NULL) {
hr = E_OUTOFMEMORY;
goto end;
}
ok = ReadProcessMemory(
process,
odsi->lpDebugStringData,
wstr,
nbytes_w,
NULL);
if (!ok) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr,
"%s: ReadProcessMemory failed: %x\n",
__func__,
(int) hr);
goto end;
}
nbytes_a = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
str = malloc(nbytes_a);
if (str == NULL) {
hr = E_OUTOFMEMORY;
goto end;
}
result = WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, nbytes_a, NULL,NULL);
if (result == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "WideCharToMultiByte failed: %x\n", (int) hr);
goto end;
}
fputs(str, stdout);
hr = S_OK;
end:
free(str);
free(wstr);
return hr;
}
static bool debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
{
char *str;
HRESULT hr;
BOOL ok;
str = malloc(odsi->nDebugStringLength);
if (str == NULL) {
hr = E_OUTOFMEMORY;
goto end;
}
ok = ReadProcessMemory(
process,
odsi->lpDebugStringData,
str,
odsi->nDebugStringLength,
NULL);
if (!ok) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "%s: ReadProcessMemory failed: %x", __func__, (int) hr);
goto end;
}
fputs(str, stdout);
hr = S_OK;
end:
free(str);
return hr;
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include <stdint.h>
HRESULT debug_main(HANDLE process, uint32_t pid);
+250
View File
@@ -0,0 +1,250 @@
#include <windows.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "inject/debug.h"
#include "inject/options.h"
static HRESULT inject_dll(HANDLE process, const char *dll_name);
static HRESULT inject_pause(HANDLE process);
static HRESULT inject_resume(HANDLE thread);
int main(int argc, char **argv)
{
struct options opt;
char *cmdline;
const char *hook_dll;
PROCESS_INFORMATION pi;
STARTUPINFO si;
HRESULT hr;
BOOL ok;
hr = options_init(&opt, argc, argv);
if (FAILED(hr) || opt.help) {
options_help(stderr);
return EXIT_FAILURE;
}
cmdline = NULL;
hr = options_target_cmdline(&opt, &cmdline);
if (FAILED(hr)) {
goto end;
}
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
ok = CreateProcessA(
NULL,
cmdline,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi);
if (!ok) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "Failed to launch executable: %x\n", (int) hr);
goto end;
}
while (options_next_dll(&opt, &hook_dll) == S_OK) {
hr = inject_dll(pi.hProcess, hook_dll);
if (FAILED(hr)) {
goto end;
}
}
if (opt.debug_pause) {
hr = inject_pause(pi.hProcess);
if (FAILED(hr)) {
goto end;
}
}
if (opt.debug) {
ok = DebugActiveProcess(pi.dwProcessId);
if (!ok) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "DebugActiveProcess failed: %x\n", (int) hr);
goto end;
}
}
hr = inject_resume(pi.hThread);
if (FAILED(hr)) {
goto end;
}
if (opt.debug) {
hr = debug_main(pi.hProcess, pi.dwProcessId);
}
if (opt.wait) {
WaitForSingleObject(pi.hProcess, INFINITE);
}
end:
if (pi.hProcess != NULL) {
if (FAILED(hr)) {
TerminateProcess(pi.hProcess, EXIT_FAILURE);
}
CloseHandle(pi.hProcess);
}
if (pi.hThread != NULL) {
CloseHandle(pi.hThread);
}
free(cmdline);
return FAILED(hr) ? EXIT_FAILURE : EXIT_SUCCESS;
}
static HRESULT inject_dll(HANDLE process, const char *dll_name)
{
size_t nchars;
void *remote_addr;
HANDLE remote_thread;
DWORD found;
HRESULT hr;
BOOL ok;
remote_addr = NULL;
remote_thread = NULL;
found = SearchPathA(NULL, dll_name, NULL, 0, NULL, NULL);
if (found == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "%s: Hook DLL not found: %x\n", dll_name, (int) hr);
goto end;
}
nchars = strlen(dll_name);
remote_addr = VirtualAllocEx(
process,
NULL,
nchars + 1,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE);
if (remote_addr == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "VirtualAllocEx failed: %x\n", (int) hr);
goto end;
}
ok = WriteProcessMemory(
process,
remote_addr,
dll_name,
nchars + 1,
NULL);
if (!ok) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "WriteProcessMemory failed: %x\n", (int) hr);
goto end;
}
remote_thread = CreateRemoteThread(
process,
NULL,
0,
(LPTHREAD_START_ROUTINE) LoadLibraryA,
remote_addr,
0,
NULL);
if (remote_thread == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "CreateRemoteThread failed: %x\n", (int) hr);
goto end;
}
hr = S_OK;
end:
if (remote_thread != NULL) {
WaitForSingleObject(remote_thread, INFINITE);
CloseHandle(remote_thread);
}
if (remote_addr != NULL) {
ok = VirtualFreeEx(process, remote_addr, 0, MEM_RELEASE);
if (!ok) {
fprintf(stderr, "VirtualFreeEx failed\n");
}
}
return hr;
}
static HRESULT inject_pause(HANDLE process)
{
HRESULT hr;
BOOL present;
BOOL ok;
printf("Waiting for debugger to attach.\n");
do {
Sleep(1000);
ok = CheckRemoteDebuggerPresent(process, &present);
if (!ok) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "CheckRemoteDebuggerPresent failed: %x\n", (int)hr);
return hr;
}
} while (!present);
printf("Debugger attached, resuming\n");
return S_OK;
}
static HRESULT inject_resume(HANDLE thread)
{
DWORD result;
HRESULT hr;
result = ResumeThread(thread);
if (result == -1) {
hr = HRESULT_FROM_WIN32(GetLastError());
fprintf(stderr, "Failed to resume target thread: %x\n", (int) hr);
return hr;
}
return S_OK;
}
+12
View File
@@ -0,0 +1,12 @@
executable(
'inject',
include_directories : inc,
c_pch : '../precompiled.h',
sources : [
'debug.c',
'debug.h',
'main.c',
'options.c',
'options.h',
],
)
+193
View File
@@ -0,0 +1,193 @@
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "inject/options.h"
void options_help(FILE *f)
{
assert(f != NULL);
fputs( "Usage: inject [options] program args...\n"
"All options must precede the program name.\n"
"\n"
"The following options are understood:\n"
"\n"
"-h\tPrint this message.\n"
"\n"
"-d\tAttach to target as a debugger and print debug messages.\n"
"\n"
"-p\tPause the target until a debugger attaches to it.\n"
" \tCannot be used with -d.\n"
"\n"
"-w\tWait for target to terminate.\n"
" \tCannot be used with -d.\n"
"\n"
"-k dll\tInject the named DLL into the target process.\n"
" \tCan be specified more than once.\n"
"\n",
f);
}
HRESULT options_init(struct options *opt, int argc, char **argv)
{
int nconsumed;
const char *arg;
int i;
assert(opt != NULL);
memset(opt, 0, sizeof(*opt));
nconsumed = 1;
for (i = 1 ; i < argc && argv[i][0] == '-' ; i++) {
arg = argv[i];
nconsumed++;
switch (arg[1]) {
case 'h':
opt->help = true;
break;
case 'd':
if (opt->debug_pause || opt->wait) {
return E_FAIL;
}
opt->debug = true;
break;
case 'p':
if (opt->debug) {
return E_FAIL;
}
opt->debug_pause = true;
break;
case 'w':
if (opt->debug) {
return E_FAIL;
}
opt->wait = true;
break;
case 'k':
if (i + 1 >= argc) {
return E_FAIL;
}
/* These get pulled by options_next_dll. Consume its argument as
well though. */
nconsumed++;
i++;
break;
default:
return E_FAIL;
}
}
if (nconsumed == argc) {
return E_FAIL;
}
opt->orig_argc = argc;
opt->orig_argv = argv;
opt->target_argc = argc - nconsumed;
opt->target_argv = argv + nconsumed;
opt->dll_pos = 1;
return S_OK;
}
HRESULT options_target_cmdline(const struct options *opt, char **out)
{
char *str;
char *pos;
size_t nchars;
size_t len;
size_t i;
assert(opt != NULL);
assert(out != NULL);
*out = NULL;
/* Measure string. Each element requires an opening quote, a closing quote
and either a trailing space or a trailing NUL. */
nchars = 3 * opt->target_argc;
for (i = 0 ; i < opt->target_argc ; i++) {
nchars += strlen(opt->target_argv[i]);
}
str = malloc(nchars);
if (str == NULL) {
return E_OUTOFMEMORY;
}
/* Construct string. This doesn't escape quotes within individual args yet
but ugh I'll fix that later if it really becomes necessary, it's a pain
to deal with. */
for (i = 0, pos = str ; i < opt->target_argc ; i++) {
len = strlen(opt->target_argv[i]);
*pos++ = '"';
memcpy(pos, opt->target_argv[i], len);
pos += len;
*pos++ = '"';
if (i + 1 < opt->target_argc) {
*pos++ = ' ';
} else {
*pos++ = '\0';
}
}
*out = str;
return S_OK;
}
HRESULT options_next_dll(struct options *opt, const char **out)
{
const char *arg;
assert(opt != NULL);
assert(opt->orig_argv != NULL);
assert(out != NULL);
*out = NULL;
while (opt->dll_pos < opt->orig_argc) {
arg = opt->orig_argv[opt->dll_pos];
if (arg[0] != '-') {
break;
}
opt->dll_pos++;
if (arg[1] == 'k' && opt->dll_pos < opt->orig_argc) {
*out = opt->orig_argv[opt->dll_pos++];
return S_OK;
}
}
return S_FALSE;
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <stdbool.h>
#include <stdio.h>
struct options {
bool help;
bool wait;
bool debug;
bool debug_pause;
int dll_pos;
int orig_argc;
char **orig_argv;
int target_argc;
char **target_argv;
};
void options_help(FILE *f);
HRESULT options_init(struct options *opt, int argc, char **argv);
HRESULT options_target_cmdline(const struct options *opt, char **str);
HRESULT options_next_dll(struct options *opt, const char **dll);
+1
View File
@@ -17,3 +17,4 @@ add_project_link_arguments(
inc = include_directories('.')
subdir('hook')
subdir('inject')