inject: Major refactoring
This addresses one major issue and multiple minor ones: Major: Inject's debugger is not attached to the process before injecting DLL files. This misses out on OutputDebugString calls by anything logging in the DllMain functions of the hook dlls. Minor: - Fix coloring of log entries - Add ASCII header to easily determine start - Fix file logging, log _everything_ to a single log file - Enhance inject's debugger: log further debug events to incrase visibility on issues, proper exception handling for inject - Re-iterated code structure of inject
This commit is contained in:
@@ -2,10 +2,14 @@ exes += inject
|
|||||||
|
|
||||||
ldflags_inject := \
|
ldflags_inject := \
|
||||||
-mconsole \
|
-mconsole \
|
||||||
|
-lpsapi \
|
||||||
|
|
||||||
libs_inject := \
|
libs_inject := \
|
||||||
util \
|
util \
|
||||||
|
|
||||||
src_inject := \
|
src_inject := \
|
||||||
|
debugger.c \
|
||||||
|
logger.c \
|
||||||
main.c \
|
main.c \
|
||||||
options.c \
|
options.c \
|
||||||
|
version.c \
|
||||||
|
|||||||
@@ -0,0 +1,642 @@
|
|||||||
|
#define LOG_MODULE "inject-debugger"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <psapi.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "inject/debugger.h"
|
||||||
|
#include "inject/logger.h"
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/mem.h"
|
||||||
|
#include "util/str.h"
|
||||||
|
|
||||||
|
struct debugger_thread_params {
|
||||||
|
const char *app_name;
|
||||||
|
char *cmd_line;
|
||||||
|
bool local_debugger;
|
||||||
|
};
|
||||||
|
|
||||||
|
static HANDLE debugger_thread_handle;
|
||||||
|
static HANDLE debugger_ready_event;
|
||||||
|
|
||||||
|
static PROCESS_INFORMATION pi;
|
||||||
|
|
||||||
|
// Source:
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/memory/obtaining-a-file-name-from-a-file-handle
|
||||||
|
static bool
|
||||||
|
debugger_get_file_name_from_handle(HANDLE hFile, char *filename, size_t bufsize)
|
||||||
|
{
|
||||||
|
HANDLE file_map;
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
success = false;
|
||||||
|
|
||||||
|
// Create a file mapping object.
|
||||||
|
file_map = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 1, NULL);
|
||||||
|
|
||||||
|
if (file_map) {
|
||||||
|
// Create a file mapping to get the file name.
|
||||||
|
void *mem = MapViewOfFile(file_map, FILE_MAP_READ, 0, 0, 1);
|
||||||
|
|
||||||
|
if (mem) {
|
||||||
|
if (GetMappedFileNameA(
|
||||||
|
GetCurrentProcess(), mem, filename, MAX_PATH)) {
|
||||||
|
|
||||||
|
// Translate path with device name to drive letters.
|
||||||
|
char tmp[bufsize];
|
||||||
|
tmp[0] = '\0';
|
||||||
|
|
||||||
|
if (GetLogicalDriveStringsA(bufsize - 1, tmp)) {
|
||||||
|
char name[MAX_PATH];
|
||||||
|
char drive[3] = " :";
|
||||||
|
BOOL found = FALSE;
|
||||||
|
char *p = tmp;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Copy the drive letter to the template string
|
||||||
|
*drive = *p;
|
||||||
|
|
||||||
|
// Look up each device name
|
||||||
|
if (QueryDosDevice(drive, name, MAX_PATH)) {
|
||||||
|
size_t name_len = strlen(name);
|
||||||
|
|
||||||
|
if (name_len < MAX_PATH) {
|
||||||
|
found =
|
||||||
|
strncmp(filename, name, name_len) == 0 &&
|
||||||
|
*(filename + name_len) == '\\';
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
// Reconstruct filename using tmp_file
|
||||||
|
// Replace device path with DOS path
|
||||||
|
char tmp_file[MAX_PATH];
|
||||||
|
sprintf(
|
||||||
|
tmp_file,
|
||||||
|
"%s%s",
|
||||||
|
drive,
|
||||||
|
filename + name_len);
|
||||||
|
|
||||||
|
strcpy(filename, tmp_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to the next NULL character.
|
||||||
|
while (*p++)
|
||||||
|
;
|
||||||
|
} while (!found && *p); // end of string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
success = true;
|
||||||
|
UnmapViewOfFile(mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(file_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
read_debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
|
||||||
|
{
|
||||||
|
log_assert(process);
|
||||||
|
log_assert(odsi);
|
||||||
|
|
||||||
|
char *str;
|
||||||
|
|
||||||
|
str = xmalloc(odsi->nDebugStringLength);
|
||||||
|
|
||||||
|
if (ReadProcessMemory(
|
||||||
|
process,
|
||||||
|
odsi->lpDebugStringData,
|
||||||
|
str,
|
||||||
|
odsi->nDebugStringLength,
|
||||||
|
NULL)) {
|
||||||
|
str[odsi->nDebugStringLength - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
free(str);
|
||||||
|
|
||||||
|
log_error(
|
||||||
|
"ReadProcessMemory for debug string failed: %08x",
|
||||||
|
(unsigned int) GetLastError());
|
||||||
|
str = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
read_debug_wstr(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
|
||||||
|
{
|
||||||
|
log_assert(process);
|
||||||
|
log_assert(odsi);
|
||||||
|
|
||||||
|
char *str;
|
||||||
|
wchar_t *wstr;
|
||||||
|
uint32_t nbytes;
|
||||||
|
|
||||||
|
nbytes = odsi->nDebugStringLength * sizeof(wchar_t);
|
||||||
|
wstr = xmalloc(nbytes);
|
||||||
|
|
||||||
|
if (ReadProcessMemory(
|
||||||
|
process, odsi->lpDebugStringData, wstr, nbytes, NULL)) {
|
||||||
|
if (wstr_narrow(wstr, &str)) {
|
||||||
|
str[odsi->nDebugStringLength - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
log_error("OutputDebugStringW: UTF-16 conversion failed");
|
||||||
|
str = NULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log_error(
|
||||||
|
"ReadProcessMemory for debug string failed: %08x",
|
||||||
|
(unsigned int) GetLastError());
|
||||||
|
str = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(wstr);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool log_debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
|
||||||
|
{
|
||||||
|
log_assert(odsi);
|
||||||
|
|
||||||
|
char *debug_str;
|
||||||
|
|
||||||
|
if (odsi->fUnicode) {
|
||||||
|
debug_str = read_debug_wstr(process, odsi);
|
||||||
|
} else {
|
||||||
|
debug_str = read_debug_str(process, odsi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug_str) {
|
||||||
|
logger_log(debug_str);
|
||||||
|
|
||||||
|
free(debug_str);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *exception_code_to_str(DWORD code)
|
||||||
|
{
|
||||||
|
switch (code) {
|
||||||
|
case EXCEPTION_ACCESS_VIOLATION:
|
||||||
|
return "EXCEPTION_ACCESS_VIOLATION";
|
||||||
|
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||||
|
return "EXCEPTION_DATATYPE_MISALIGNMENT";
|
||||||
|
case EXCEPTION_BREAKPOINT:
|
||||||
|
return "EXCEPTION_BREAKPOINT";
|
||||||
|
case EXCEPTION_SINGLE_STEP:
|
||||||
|
return "EXCEPTION_SINGLE_STEP";
|
||||||
|
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||||
|
return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
|
||||||
|
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||||
|
return "EXCEPTION_FLT_DENORMAL_OPERAND";
|
||||||
|
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||||
|
return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
|
||||||
|
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||||
|
return "EXCEPTION_FLT_INEXACT_RESULT";
|
||||||
|
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||||
|
return "EXCEPTION_FLT_INVALID_OPERATION";
|
||||||
|
case EXCEPTION_FLT_OVERFLOW:
|
||||||
|
return "EXCEPTION_FLT_OVERFLOW";
|
||||||
|
case EXCEPTION_FLT_STACK_CHECK:
|
||||||
|
return "EXCEPTION_FLT_STACK_CHECK";
|
||||||
|
case EXCEPTION_FLT_UNDERFLOW:
|
||||||
|
return "EXCEPTION_FLT_UNDERFLOW";
|
||||||
|
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||||
|
return "EXCEPTION_INT_DIVIDE_BY_ZERO";
|
||||||
|
case EXCEPTION_INT_OVERFLOW:
|
||||||
|
return "EXCEPTION_INT_OVERFLOW";
|
||||||
|
case EXCEPTION_PRIV_INSTRUCTION:
|
||||||
|
return "EXCEPTION_PRIV_INSTRUCTION";
|
||||||
|
case EXCEPTION_IN_PAGE_ERROR:
|
||||||
|
return "EXCEPTION_IN_PAGE_ERROR";
|
||||||
|
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||||
|
return "EXCEPTION_ILLEGAL_INSTRUCTION";
|
||||||
|
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||||
|
return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
|
||||||
|
case EXCEPTION_STACK_OVERFLOW:
|
||||||
|
return "EXCEPTION_STACK_OVERFLOW";
|
||||||
|
default:
|
||||||
|
log_warning("Unknown exception code: %ld", code);
|
||||||
|
return "EXCEPTION_UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool debugger_create_process(
|
||||||
|
bool local_debugger, const char *app_name, char *cmd_line)
|
||||||
|
{
|
||||||
|
log_assert(app_name);
|
||||||
|
log_assert(cmd_line);
|
||||||
|
|
||||||
|
STARTUPINFO si;
|
||||||
|
BOOL ok;
|
||||||
|
DWORD flags;
|
||||||
|
|
||||||
|
memset(&si, 0, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
|
||||||
|
flags = 0;
|
||||||
|
|
||||||
|
// CREATE_SUSPENDED that we have plenty of time to set up the debugger and
|
||||||
|
// theemote process environment with hook dlls.
|
||||||
|
flags |= CREATE_SUSPENDED;
|
||||||
|
|
||||||
|
if (local_debugger) {
|
||||||
|
// DEBUG_PROCESS is required to make this work properly. Otherwise,
|
||||||
|
// weird things like random remote process crashing are happening. Also,
|
||||||
|
// DEBUG_ONLY_THIS_PROCESS is NOT sufficient/ correct here. Maybe I
|
||||||
|
// didn't understand the documentation properly or it and various blog
|
||||||
|
// posts I read are not explaining things well enough or are even wrong.
|
||||||
|
flags |= DEBUG_PROCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc("Creating remote process %s...", app_name);
|
||||||
|
log_misc("Remote process cmd_line: %s", cmd_line);
|
||||||
|
|
||||||
|
ok = CreateProcess(
|
||||||
|
app_name, cmd_line, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
log_error(
|
||||||
|
"Failed to launch hooked EXE: %08x", (unsigned int) GetLastError());
|
||||||
|
|
||||||
|
free(cmd_line);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(cmd_line);
|
||||||
|
|
||||||
|
log_info("Remote process created, pid: %ld", pi.dwProcessId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool debugger_loop()
|
||||||
|
{
|
||||||
|
DEBUG_EVENT de;
|
||||||
|
DWORD continue_status;
|
||||||
|
char str_buffer[MAX_PATH + 1];
|
||||||
|
|
||||||
|
memset(str_buffer, 0, sizeof(str_buffer));
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (!WaitForDebugEvent(&de, INFINITE)) {
|
||||||
|
log_error(
|
||||||
|
"WaitForDebugEvent failed: %08x",
|
||||||
|
(unsigned int) GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changed if applicable, e.g. on exceptions
|
||||||
|
continue_status = DBG_CONTINUE;
|
||||||
|
|
||||||
|
switch (de.dwDebugEventCode) {
|
||||||
|
case EXCEPTION_DEBUG_EVENT:
|
||||||
|
log_misc(
|
||||||
|
"EXCEPTION_DEBUG_EVENT(pid %ld, tid %ld): x%s 0x%p",
|
||||||
|
de.dwProcessId,
|
||||||
|
de.dwThreadId,
|
||||||
|
exception_code_to_str(
|
||||||
|
de.u.Exception.ExceptionRecord.ExceptionCode),
|
||||||
|
de.u.Exception.ExceptionRecord.ExceptionAddress);
|
||||||
|
|
||||||
|
if (de.u.Exception.ExceptionRecord.ExceptionCode ==
|
||||||
|
EXCEPTION_BREAKPOINT) {
|
||||||
|
// Handle breakpoints by ignoring them. Some modules of some
|
||||||
|
// games set breakpoints when they detect a debugger likely
|
||||||
|
// used for actual development/debugging tasks
|
||||||
|
continue_status = DBG_EXCEPTION_HANDLED;
|
||||||
|
} else {
|
||||||
|
continue_status = DBG_EXCEPTION_NOT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CREATE_THREAD_DEBUG_EVENT:
|
||||||
|
log_misc(
|
||||||
|
"CREATE_THREAD_DEBUG_EVENT(pid %ld, tid %ld): hnd 0x%p, "
|
||||||
|
"addr 0x%p",
|
||||||
|
de.dwProcessId,
|
||||||
|
de.dwThreadId,
|
||||||
|
de.u.CreateThread.hThread,
|
||||||
|
de.u.CreateThread.lpStartAddress);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CREATE_PROCESS_DEBUG_EVENT:
|
||||||
|
GetProcessImageFileNameA(
|
||||||
|
de.u.CreateProcessInfo.hProcess,
|
||||||
|
str_buffer,
|
||||||
|
sizeof(str_buffer));
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"CREATE_PROCESS_DEBUG_EVENT(pid %ld, tid %ld): name %s, "
|
||||||
|
"pid %ld",
|
||||||
|
de.dwProcessId,
|
||||||
|
de.dwThreadId,
|
||||||
|
str_buffer,
|
||||||
|
GetProcessId(de.u.CreateProcessInfo.hProcess));
|
||||||
|
|
||||||
|
CloseHandle(de.u.CreateProcessInfo.hFile);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXIT_THREAD_DEBUG_EVENT:
|
||||||
|
log_misc(
|
||||||
|
"EXIT_THREAD_DEBUG_EVENT(pid %ld, tid %ld)",
|
||||||
|
de.dwProcessId,
|
||||||
|
de.dwThreadId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXIT_PROCESS_DEBUG_EVENT:
|
||||||
|
log_misc(
|
||||||
|
"EXIT_PROCESS_DEBUG_EVENT(pid %ld, tid %ld)",
|
||||||
|
de.dwProcessId,
|
||||||
|
de.dwThreadId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case LOAD_DLL_DEBUG_EVENT:
|
||||||
|
if (!debugger_get_file_name_from_handle(
|
||||||
|
de.u.LoadDll.hFile, str_buffer, sizeof(str_buffer))) {
|
||||||
|
strcpy(str_buffer, "--- Unknown ---");
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"LOAD_DLL_DEBUG_EVENT(pid %ld, tid %ld): name %s, base "
|
||||||
|
"0x%p",
|
||||||
|
de.dwProcessId,
|
||||||
|
de.dwThreadId,
|
||||||
|
str_buffer,
|
||||||
|
de.u.LoadDll.lpBaseOfDll);
|
||||||
|
|
||||||
|
CloseHandle(de.u.LoadDll.hFile);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UNLOAD_DLL_DEBUG_EVENT:
|
||||||
|
log_misc(
|
||||||
|
"UNLOAD_DLL_DEBUG_EVENT(pid %ld, tid %ld): base 0x%p",
|
||||||
|
de.dwProcessId,
|
||||||
|
de.dwThreadId,
|
||||||
|
de.u.UnloadDll.lpBaseOfDll);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OUTPUT_DEBUG_STRING_EVENT:
|
||||||
|
log_debug_str(pi.hProcess, &de.u.DebugString);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RIP_EVENT:
|
||||||
|
log_misc(
|
||||||
|
"RIP_EVENT(pid %ld, tid %ld)",
|
||||||
|
de.dwProcessId,
|
||||||
|
de.dwThreadId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Ignore other events
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ContinueDebugEvent(
|
||||||
|
de.dwProcessId, de.dwThreadId, continue_status)) {
|
||||||
|
log_error(
|
||||||
|
"ContinueDebugEvent failed: %08x",
|
||||||
|
(unsigned int) GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD WINAPI debugger_proc(LPVOID param)
|
||||||
|
{
|
||||||
|
struct debugger_thread_params *params;
|
||||||
|
|
||||||
|
params = (struct debugger_thread_params *) param;
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"Debugger thread start (local debugger: %d)", params->local_debugger);
|
||||||
|
|
||||||
|
if (!debugger_create_process(
|
||||||
|
params->local_debugger, params->app_name, params->cmd_line)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetEvent(debugger_ready_event);
|
||||||
|
|
||||||
|
// Don't run our local debugger loop if the user wants to attach a remote
|
||||||
|
// debugger or debugger is disabled
|
||||||
|
if (params->local_debugger) {
|
||||||
|
debugger_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
free(params);
|
||||||
|
|
||||||
|
log_misc("Debugger thread end");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debugger_init(bool local_debugger, const char *app_name, char *cmd_line)
|
||||||
|
{
|
||||||
|
struct debugger_thread_params *thread_params;
|
||||||
|
|
||||||
|
debugger_ready_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||||
|
|
||||||
|
if (!debugger_ready_event) {
|
||||||
|
free(cmd_line);
|
||||||
|
|
||||||
|
log_error(
|
||||||
|
"Creating event object failed: %08x",
|
||||||
|
(unsigned int) GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// free'd by thread if created successfully
|
||||||
|
thread_params = xmalloc(sizeof(struct debugger_thread_params));
|
||||||
|
|
||||||
|
thread_params->app_name = app_name;
|
||||||
|
thread_params->cmd_line = cmd_line;
|
||||||
|
thread_params->local_debugger = local_debugger;
|
||||||
|
|
||||||
|
debugger_thread_handle =
|
||||||
|
CreateThread(NULL, 0, debugger_proc, thread_params, 0, 0);
|
||||||
|
|
||||||
|
if (!debugger_thread_handle) {
|
||||||
|
free(cmd_line);
|
||||||
|
free(thread_params);
|
||||||
|
|
||||||
|
log_error(
|
||||||
|
"Creating debugger thread failed: %08x",
|
||||||
|
(unsigned int) GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForSingleObject(debugger_ready_event, INFINITE);
|
||||||
|
|
||||||
|
log_misc("Debugger initialized");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debugger_wait_for_remote_debugger()
|
||||||
|
{
|
||||||
|
BOOL res;
|
||||||
|
|
||||||
|
log_warning("Waiting until remote debugger attaches to remote process...");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
res = FALSE;
|
||||||
|
|
||||||
|
if (!CheckRemoteDebuggerPresent(pi.hProcess, &res)) {
|
||||||
|
log_error(
|
||||||
|
"CheckRemoteDebuggerPresent failed: %08x",
|
||||||
|
(unsigned int) GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
log_info("Remote debugger attached, resuming");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debugger_inject_dll(const char *path_dll)
|
||||||
|
{
|
||||||
|
log_assert(path_dll);
|
||||||
|
|
||||||
|
char dll_path[MAX_PATH];
|
||||||
|
DWORD dll_path_length;
|
||||||
|
void *remote_addr;
|
||||||
|
BOOL ok;
|
||||||
|
HANDLE remote_thread;
|
||||||
|
|
||||||
|
log_misc("Injecting: %s", path_dll);
|
||||||
|
|
||||||
|
dll_path_length =
|
||||||
|
SearchPath(NULL, path_dll, NULL, MAX_PATH, dll_path, NULL);
|
||||||
|
|
||||||
|
dll_path_length++;
|
||||||
|
|
||||||
|
remote_addr = VirtualAllocEx(
|
||||||
|
pi.hProcess,
|
||||||
|
NULL,
|
||||||
|
dll_path_length,
|
||||||
|
MEM_RESERVE | MEM_COMMIT,
|
||||||
|
PAGE_READWRITE);
|
||||||
|
|
||||||
|
if (!remote_addr) {
|
||||||
|
log_error("VirtualAllocEx failed: %08x", (unsigned int) GetLastError());
|
||||||
|
|
||||||
|
goto alloc_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = WriteProcessMemory(
|
||||||
|
pi.hProcess, remote_addr, dll_path, dll_path_length, NULL);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
log_error(
|
||||||
|
"WriteProcessMemory failed: %08x", (unsigned int) GetLastError());
|
||||||
|
|
||||||
|
goto write_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_thread = CreateRemoteThread(
|
||||||
|
pi.hProcess,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
(LPTHREAD_START_ROUTINE) LoadLibrary,
|
||||||
|
remote_addr,
|
||||||
|
0,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
if (remote_thread == NULL) {
|
||||||
|
log_error(
|
||||||
|
"CreateRemoteThread failed: %08x", (unsigned int) GetLastError());
|
||||||
|
|
||||||
|
goto inject_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForSingleObject(remote_thread, INFINITE);
|
||||||
|
CloseHandle(remote_thread);
|
||||||
|
|
||||||
|
ok = VirtualFreeEx(pi.hProcess, remote_addr, 0, MEM_RELEASE);
|
||||||
|
remote_addr = NULL;
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
log_error("VirtualFreeEx failed: %08x", (unsigned int) GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
inject_fail:
|
||||||
|
write_fail:
|
||||||
|
if (remote_addr != NULL) {
|
||||||
|
VirtualFreeEx(pi.hProcess, remote_addr, 0, MEM_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc_fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debugger_resume_process()
|
||||||
|
{
|
||||||
|
log_info("Resuming remote process...");
|
||||||
|
|
||||||
|
if (ResumeThread(pi.hThread) == -1) {
|
||||||
|
log_error(
|
||||||
|
"Error resuming remote process: %08x",
|
||||||
|
(unsigned int) GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugger_wait_process_exit()
|
||||||
|
{
|
||||||
|
log_misc("Waiting for remote process to exit...");
|
||||||
|
|
||||||
|
// Wait for the process as we might have a remote debugger attached, so our
|
||||||
|
// debugger thread exits after creating the process
|
||||||
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
|
|
||||||
|
// When the process exits, the debugger gets notified and the thread ends
|
||||||
|
WaitForSingleObject(debugger_thread_handle, INFINITE);
|
||||||
|
|
||||||
|
log_misc("Remote process exit'd");
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugger_finit(bool failure)
|
||||||
|
{
|
||||||
|
log_misc("Debugger finit");
|
||||||
|
|
||||||
|
if (failure) {
|
||||||
|
TerminateProcess(pi.hProcess, EXIT_FAILURE);
|
||||||
|
WaitForSingleObject(debugger_thread_handle, INFINITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(debugger_thread_handle);
|
||||||
|
CloseHandle(debugger_ready_event);
|
||||||
|
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize inject's logger backend.
|
||||||
|
*
|
||||||
|
* This takes care of hooking and merging the different log
|
||||||
|
* streams, e.g. inject's local logging and inject's debugger
|
||||||
|
* receiving remote logging events.
|
||||||
|
*
|
||||||
|
* @param log_file_path Path to the file to log to or NULL to
|
||||||
|
* disable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the debugger.
|
||||||
|
*
|
||||||
|
* This creates the remote process of the provided application.
|
||||||
|
*
|
||||||
|
* The remote process is created suspended. This allows you to
|
||||||
|
* to some more process setup tasks like injecting hook DLLs
|
||||||
|
* before you call debugger_resume_process to actually start
|
||||||
|
* execution of it.
|
||||||
|
*
|
||||||
|
* The actual debugging runs in a dedicated thread which spawns
|
||||||
|
* the process, waits for and dispatches debug events.
|
||||||
|
*
|
||||||
|
* However, if you want to attach a remote debugger, you have to
|
||||||
|
* set the parameter local_debugger to false. Then, the debugger
|
||||||
|
* will only create the remote process and monitor it.
|
||||||
|
*
|
||||||
|
* @param local_debugger True to attach inject's local debugger,
|
||||||
|
* false to allow attaching a remote
|
||||||
|
* debugger with enhanced features.
|
||||||
|
* @param app_name Name of the application to spawn and debug.
|
||||||
|
* @param cmd_line Command line string to pass to application.
|
||||||
|
* @return true on success, false on error. On error, no remote
|
||||||
|
* application and local debugger is started.
|
||||||
|
*/
|
||||||
|
bool debugger_init(bool local_debugger, const char *app_name, char *cmd_line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject a DLL into the remote process.
|
||||||
|
*
|
||||||
|
* @param path_dll Path to the dll to inject.
|
||||||
|
* @return true if sucessful, false on error.
|
||||||
|
*/
|
||||||
|
bool debugger_inject_dll(const char *path_dll);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait/block for a remote debugger to attach to the remote process.
|
||||||
|
*
|
||||||
|
* You only need to call this if you specified local_debugger = false
|
||||||
|
* on debugger_init.
|
||||||
|
*
|
||||||
|
* @return True if successfull and a remote debugger attached, false
|
||||||
|
* on error.
|
||||||
|
*/
|
||||||
|
bool debugger_wait_for_remote_debugger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume the remote process.
|
||||||
|
*
|
||||||
|
* Make sure to call this once you are done with setting it up.
|
||||||
|
*
|
||||||
|
* @return true on success, false on error.
|
||||||
|
*/
|
||||||
|
bool debugger_resume_process();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the remote process to exit.
|
||||||
|
*/
|
||||||
|
void debugger_wait_process_exit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup the debugger.
|
||||||
|
*
|
||||||
|
* @param failure Set this to true if you have to cleanup due to
|
||||||
|
* a failure of another debugger function call.
|
||||||
|
* Otherwise, set this to false.
|
||||||
|
*/
|
||||||
|
void debugger_finit(bool failure);
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
#define LOG_MODULE "inject-logger"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "inject/logger.h"
|
||||||
|
#include "inject/version.h"
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
static FILE *log_file;
|
||||||
|
static HANDLE log_mutex;
|
||||||
|
|
||||||
|
static char logger_console_determine_color(const char *str)
|
||||||
|
{
|
||||||
|
log_assert(str);
|
||||||
|
|
||||||
|
/* Add some color to make spotting warnings/errors easier.
|
||||||
|
Based on debug output level identifier. */
|
||||||
|
|
||||||
|
/* Avoids colored output on strings like "Windows" */
|
||||||
|
if (str[1] != ':') {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (str[0]) {
|
||||||
|
/* green */
|
||||||
|
case 'M':
|
||||||
|
return 10;
|
||||||
|
/* blue */
|
||||||
|
case 'I':
|
||||||
|
return 9;
|
||||||
|
/* yellow */
|
||||||
|
case 'W':
|
||||||
|
return 14;
|
||||||
|
/* red */
|
||||||
|
case 'F':
|
||||||
|
return 12;
|
||||||
|
/* default console color */
|
||||||
|
default:
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t logger_msg_coloring_len(const char *str)
|
||||||
|
{
|
||||||
|
// Expected format example: "I:boot: my log message"
|
||||||
|
|
||||||
|
const char *ptr;
|
||||||
|
size_t len;
|
||||||
|
int colon_count;
|
||||||
|
|
||||||
|
ptr = str;
|
||||||
|
len = 0;
|
||||||
|
colon_count = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// End of string = invalid log format
|
||||||
|
if (*ptr == '\0') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*ptr == ':') {
|
||||||
|
colon_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colon_count == 2) {
|
||||||
|
// Skip current colon, next char is a space
|
||||||
|
return len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
len++;
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logger_console(void *ctx, const char *chars, size_t nchars)
|
||||||
|
{
|
||||||
|
char color;
|
||||||
|
size_t color_len;
|
||||||
|
// See "util/log.c", has to align
|
||||||
|
char buffer[65536];
|
||||||
|
char tmp;
|
||||||
|
|
||||||
|
color_len = logger_msg_coloring_len(chars);
|
||||||
|
|
||||||
|
// Check if we could detect which part to color, otherwise just write the
|
||||||
|
// whole log message without any coloring logic
|
||||||
|
if (color_len > 0) {
|
||||||
|
color = logger_console_determine_color(chars);
|
||||||
|
|
||||||
|
strcpy(buffer, chars);
|
||||||
|
|
||||||
|
// Mask start of log message for coloring
|
||||||
|
tmp = buffer[color_len];
|
||||||
|
buffer[color_len] = '\0';
|
||||||
|
|
||||||
|
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
|
||||||
|
printf("%s", buffer);
|
||||||
|
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15);
|
||||||
|
|
||||||
|
// Write actual message non colored
|
||||||
|
buffer[color_len] = tmp;
|
||||||
|
printf("%s", buffer + color_len);
|
||||||
|
} else {
|
||||||
|
printf("%s", chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logger_file(void *ctx, const char *chars, size_t nchars)
|
||||||
|
{
|
||||||
|
if (ctx) {
|
||||||
|
fwrite(chars, 1, nchars, (FILE *) ctx);
|
||||||
|
fflush((FILE *) ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logger_writer(void *ctx, const char *chars, size_t nchars)
|
||||||
|
{
|
||||||
|
// Different threads logging the same destination, e.g. debugger thread,
|
||||||
|
// main thread
|
||||||
|
|
||||||
|
WaitForSingleObject(log_mutex, INFINITE);
|
||||||
|
|
||||||
|
logger_console(ctx, chars, nchars);
|
||||||
|
logger_file(ctx, chars, nchars);
|
||||||
|
|
||||||
|
ReleaseMutex(log_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logger_log_header()
|
||||||
|
{
|
||||||
|
log_info(
|
||||||
|
"\n"
|
||||||
|
" _ _ _ \n"
|
||||||
|
" (_)_ __ (_) ___ ___| |_ \n"
|
||||||
|
" | | '_ \\ | |/ _ \\/ __| __|\n"
|
||||||
|
" | | | | || | __/ (__| |_ \n"
|
||||||
|
" |_|_| |_|/ |\\___|\\___|\\__|\n"
|
||||||
|
" |__/ ");
|
||||||
|
|
||||||
|
log_info(
|
||||||
|
"Inject build date %s, gitrev %s", inject_build_date, inject_gitrev);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool logger_init(const char *log_file_path)
|
||||||
|
{
|
||||||
|
if (log_file_path) {
|
||||||
|
log_file = fopen(log_file_path, "w+");
|
||||||
|
} else {
|
||||||
|
log_file = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_to_writer(logger_writer, log_file);
|
||||||
|
|
||||||
|
logger_log_header();
|
||||||
|
|
||||||
|
if (log_file_path) {
|
||||||
|
log_info("Log file: %s", log_file_path);
|
||||||
|
|
||||||
|
if (!log_file) {
|
||||||
|
log_error(
|
||||||
|
"Opening log file %s failed: %s",
|
||||||
|
log_file_path,
|
||||||
|
strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_mutex = CreateMutex(NULL, FALSE, NULL);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_log(const char *str)
|
||||||
|
{
|
||||||
|
logger_writer(log_file, str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_finit()
|
||||||
|
{
|
||||||
|
log_misc("Logger finit");
|
||||||
|
|
||||||
|
if (log_file) {
|
||||||
|
fclose(log_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(log_mutex);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize inject's logger backend.
|
||||||
|
*
|
||||||
|
* This takes care of hooking and merging the different log
|
||||||
|
* streams, e.g. inject's local logging and inject's debugger
|
||||||
|
* receiving remote logging events.
|
||||||
|
*
|
||||||
|
* @param log_file_path Path to the file to log to or NULL to
|
||||||
|
* disable.
|
||||||
|
*/
|
||||||
|
bool logger_init(const char *log_file_path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a message to the logging backend.
|
||||||
|
*
|
||||||
|
* This is used by inject's debugger to redirect log messages
|
||||||
|
* recevied from the remote process.
|
||||||
|
*
|
||||||
|
* @param str String to log
|
||||||
|
*/
|
||||||
|
void logger_log(const char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown and cleanup the logging backend.
|
||||||
|
*/
|
||||||
|
void logger_finit();
|
||||||
+121
-408
@@ -10,472 +10,185 @@
|
|||||||
#include "cconfig/cconfig-util.h"
|
#include "cconfig/cconfig-util.h"
|
||||||
#include "cconfig/cmd.h"
|
#include "cconfig/cmd.h"
|
||||||
|
|
||||||
|
#include "inject/debugger.h"
|
||||||
|
#include "inject/logger.h"
|
||||||
#include "inject/options.h"
|
#include "inject/options.h"
|
||||||
|
#include "inject/version.h"
|
||||||
|
|
||||||
#include "util/cmdline.h"
|
#include "util/cmdline.h"
|
||||||
|
#include "util/log.h"
|
||||||
#include "util/mem.h"
|
#include "util/mem.h"
|
||||||
|
#include "util/signal.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
|
||||||
static FILE *log_file = NULL;
|
static bool init_options(int argc, char **argv, struct options *options)
|
||||||
|
|
||||||
static bool inject_dll(PROCESS_INFORMATION pi, const char *arg_dll);
|
|
||||||
static bool debug(HANDLE process, uint32_t pid);
|
|
||||||
static bool debug_wstr(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi);
|
|
||||||
static bool debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi);
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
{
|
||||||
struct options options;
|
options_init(options);
|
||||||
char *cmd_line;
|
|
||||||
char dll_path[MAX_PATH];
|
|
||||||
DWORD dll_path_length;
|
|
||||||
PROCESS_INFORMATION pi;
|
|
||||||
STARTUPINFO si;
|
|
||||||
BOOL ok;
|
|
||||||
BOOL debug_ok;
|
|
||||||
int hooks;
|
|
||||||
int exec_arg_pos;
|
|
||||||
|
|
||||||
options_init(&options);
|
if (argc < 3 || !options_read_cmdline(options, argc, argv)) {
|
||||||
|
|
||||||
if (argc < 3 || !options_read_cmdline(&options, argc, argv)) {
|
|
||||||
options_print_usage();
|
options_print_usage();
|
||||||
goto usage_fail;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open log file for logging if parameter specified */
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (strlen(options.log_file) > 0) {
|
static bool verify_hook_dll_and_exec_args_and_count_hooks(
|
||||||
log_file = fopen(options.log_file, "w+");
|
int argc, char **argv, uint32_t *hooks, uint32_t *exec_arg_pos)
|
||||||
|
{
|
||||||
|
log_assert(argc >= 0);
|
||||||
|
log_assert(argv);
|
||||||
|
log_assert(hooks);
|
||||||
|
log_assert(exec_arg_pos);
|
||||||
|
|
||||||
if (!log_file) {
|
*hooks = 0;
|
||||||
fprintf(
|
*exec_arg_pos = 0;
|
||||||
stderr,
|
|
||||||
"Opening log file %s failed: %s\n",
|
|
||||||
options.log_file,
|
|
||||||
strerror(errno));
|
|
||||||
goto log_file_open_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Log file: %s\n", options.log_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Count hook dlls */
|
|
||||||
hooks = 0;
|
|
||||||
exec_arg_pos = 0;
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if (str_ends_with(argv[i], "dll")) {
|
if (str_ends_with(argv[i], "dll")) {
|
||||||
hooks++;
|
(*hooks)++;
|
||||||
} else if (str_ends_with(argv[i], "exe")) {
|
} else if (str_ends_with(argv[i], "exe")) {
|
||||||
exec_arg_pos = i;
|
*exec_arg_pos = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hooks) {
|
if (!(*hooks)) {
|
||||||
fprintf(stderr, "No Hook DLL(s) specified before executable\n");
|
log_error("No Hook DLL(s) specified before executable");
|
||||||
|
return false;
|
||||||
goto hook_count_fail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exec_arg_pos) {
|
if (!*exec_arg_pos) {
|
||||||
fprintf(stderr, "No executable specified\n");
|
log_error("No executable specified");
|
||||||
|
return false;
|
||||||
goto find_exec_fail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < hooks; i++) {
|
log_misc("%d hook(s) dll detected", *hooks);
|
||||||
|
log_misc("Executable: %s", argv[*exec_arg_pos]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
verify_hook_dlls_exist(int argc, char **argv, uint32_t hook_dll_count)
|
||||||
|
{
|
||||||
|
log_assert(argc >= 0);
|
||||||
|
log_assert(argv);
|
||||||
|
|
||||||
|
char dll_path[MAX_PATH];
|
||||||
|
DWORD dll_path_length;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < hook_dll_count; i++) {
|
||||||
dll_path_length =
|
dll_path_length =
|
||||||
SearchPath(NULL, argv[i + 1], NULL, MAX_PATH, dll_path, NULL);
|
SearchPath(NULL, argv[i + 1], NULL, MAX_PATH, dll_path, NULL);
|
||||||
|
|
||||||
if (dll_path_length == 0) {
|
if (dll_path_length == 0) {
|
||||||
fprintf(
|
log_error(
|
||||||
stderr,
|
"Hook DLL not found: %08x", (unsigned int) GetLastError());
|
||||||
"Hook DLL not found: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
goto search_fail;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&si, 0, sizeof(si));
|
|
||||||
si.cb = sizeof(si);
|
|
||||||
|
|
||||||
cmd_line = args_join(argc - exec_arg_pos, argv + exec_arg_pos);
|
|
||||||
|
|
||||||
ok = CreateProcess(
|
|
||||||
argv[exec_arg_pos],
|
|
||||||
cmd_line,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
FALSE,
|
|
||||||
CREATE_SUSPENDED,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
&si,
|
|
||||||
&pi);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"Failed to launch hooked EXE: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
goto start_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(cmd_line);
|
|
||||||
cmd_line = NULL;
|
|
||||||
|
|
||||||
for (int i = 0; i < hooks; i++) {
|
|
||||||
if (!inject_dll(pi, argv[i + 1])) {
|
|
||||||
goto inject_fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_ok = false;
|
|
||||||
|
|
||||||
if (options.debug && !options.remote_debugger) {
|
|
||||||
debug_ok = DebugActiveProcess(pi.dwProcessId);
|
|
||||||
|
|
||||||
if (!debug_ok) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"DebugActiveProcess failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
} else {
|
|
||||||
printf("Debug active process\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.remote_debugger) {
|
|
||||||
printf("Waiting until debugger attaches to remote process...\n");
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
BOOL res = FALSE;
|
|
||||||
|
|
||||||
if (!CheckRemoteDebuggerPresent(pi.hProcess, &res)) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"CheckRemoteDebuggerPresent failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
printf("Debugger attached, resuming\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Sleep(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Resuming remote process...\n");
|
|
||||||
|
|
||||||
if (ResumeThread(pi.hThread) == -1) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"Error restarting hooked process: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
goto restart_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseHandle(pi.hThread);
|
|
||||||
|
|
||||||
if (options.debug) {
|
|
||||||
if (!debug_ok || !debug(pi.hProcess, pi.dwProcessId)) {
|
|
||||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseHandle(pi.hProcess);
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
|
|
||||||
inject_fail:
|
|
||||||
restart_fail:
|
|
||||||
TerminateProcess(pi.hProcess, EXIT_FAILURE);
|
|
||||||
|
|
||||||
CloseHandle(pi.hThread);
|
|
||||||
CloseHandle(pi.hProcess);
|
|
||||||
|
|
||||||
start_fail:
|
|
||||||
if (cmd_line != NULL) {
|
|
||||||
free(cmd_line);
|
|
||||||
}
|
|
||||||
|
|
||||||
hook_count_fail:
|
|
||||||
find_exec_fail:
|
|
||||||
search_fail:
|
|
||||||
if (log_file) {
|
|
||||||
fclose(log_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
log_file_open_fail:
|
|
||||||
usage_fail:
|
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool inject_dll(PROCESS_INFORMATION pi, const char *arg_dll)
|
|
||||||
{
|
|
||||||
char dll_path[MAX_PATH];
|
|
||||||
DWORD dll_path_length;
|
|
||||||
void *remote_addr;
|
|
||||||
BOOL ok;
|
|
||||||
HANDLE remote_thread;
|
|
||||||
|
|
||||||
printf("Injecting: %s\n", arg_dll);
|
|
||||||
|
|
||||||
dll_path_length = SearchPath(NULL, arg_dll, NULL, MAX_PATH, dll_path, NULL);
|
|
||||||
|
|
||||||
dll_path_length++;
|
|
||||||
|
|
||||||
remote_addr = VirtualAllocEx(
|
|
||||||
pi.hProcess,
|
|
||||||
NULL,
|
|
||||||
dll_path_length,
|
|
||||||
MEM_RESERVE | MEM_COMMIT,
|
|
||||||
PAGE_READWRITE);
|
|
||||||
|
|
||||||
if (!remote_addr) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"VirtualAllocEx failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
goto alloc_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = WriteProcessMemory(
|
|
||||||
pi.hProcess, remote_addr, dll_path, dll_path_length, NULL);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"WriteProcessMemory failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
goto write_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
remote_thread = CreateRemoteThread(
|
|
||||||
pi.hProcess,
|
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
(LPTHREAD_START_ROUTINE) LoadLibrary,
|
|
||||||
remote_addr,
|
|
||||||
0,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
if (remote_thread == NULL) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"CreateRemoteThread failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
goto inject_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForSingleObject(remote_thread, INFINITE);
|
|
||||||
CloseHandle(remote_thread);
|
|
||||||
|
|
||||||
ok = VirtualFreeEx(pi.hProcess, remote_addr, 0, MEM_RELEASE);
|
|
||||||
remote_addr = NULL;
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"VirtualFreeEx failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
inject_fail:
|
|
||||||
write_fail:
|
|
||||||
if (remote_addr != NULL) {
|
|
||||||
VirtualFreeEx(pi.hProcess, remote_addr, 0, MEM_RELEASE);
|
|
||||||
}
|
|
||||||
|
|
||||||
alloc_fail:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool debug(HANDLE process, uint32_t pid)
|
static bool inject_hook_dlls(uint32_t hooks, char **argv)
|
||||||
{
|
{
|
||||||
DEBUG_EVENT de;
|
log_assert(argv);
|
||||||
BOOL ok;
|
|
||||||
|
|
||||||
for (;;) {
|
log_info("Injecting hook DLLs...");
|
||||||
ok = WaitForDebugEvent(&de, INFINITE);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"WaitForDebugEvent failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (de.dwDebugEventCode) {
|
|
||||||
case CREATE_PROCESS_DEBUG_EVENT:
|
|
||||||
CloseHandle(de.u.CreateProcessInfo.hFile);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EXIT_PROCESS_DEBUG_EVENT:
|
|
||||||
if (de.dwProcessId == pid) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LOAD_DLL_DEBUG_EVENT:
|
|
||||||
CloseHandle(de.u.LoadDll.hFile);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OUTPUT_DEBUG_STRING_EVENT:
|
|
||||||
if (de.dwProcessId == pid) {
|
|
||||||
if (de.u.DebugString.fUnicode) {
|
|
||||||
if (!debug_wstr(process, &de.u.DebugString)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!debug_str(process, &de.u.DebugString)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) {
|
|
||||||
ok =
|
|
||||||
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
|
|
||||||
} else {
|
|
||||||
ok = ContinueDebugEvent(
|
|
||||||
de.dwProcessId, de.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"ContinueDebugEvent failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
|
for (int i = 0; i < hooks; i++) {
|
||||||
|
if (!debugger_inject_dll(argv[i + 1])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char console_get_color(char *str)
|
static void signal_shutdown_handler()
|
||||||
{
|
{
|
||||||
/* Add some color to make spotting warnings/errors easier.
|
debugger_finit(true);
|
||||||
Based on debug output level identifier. */
|
logger_finit();
|
||||||
|
|
||||||
/* Avoids colored output on strings like "Windows" */
|
|
||||||
if (str[1] != ':') {
|
|
||||||
return 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (str[0]) {
|
|
||||||
/* green */
|
|
||||||
case 'M':
|
|
||||||
return 10;
|
|
||||||
/* blue */
|
|
||||||
case 'I':
|
|
||||||
return 9;
|
|
||||||
/* yellow */
|
|
||||||
case 'W':
|
|
||||||
return 14;
|
|
||||||
/* red */
|
|
||||||
case 'F':
|
|
||||||
return 12;
|
|
||||||
/* default console color */
|
|
||||||
default:
|
|
||||||
return 15;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool debug_wstr(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
char *str;
|
struct options options;
|
||||||
wchar_t *wstr;
|
uint32_t hooks;
|
||||||
uint32_t nbytes;
|
uint32_t exec_arg_pos;
|
||||||
BOOL ok;
|
char *cmd_line;
|
||||||
|
bool local_debugger;
|
||||||
|
|
||||||
nbytes = odsi->nDebugStringLength * sizeof(wchar_t);
|
if (!init_options(argc, argv, &options)) {
|
||||||
wstr = xmalloc(nbytes);
|
goto init_options_fail;
|
||||||
|
}
|
||||||
|
|
||||||
ok =
|
if (!logger_init(strlen(options.log_file) > 0 ? options.log_file : NULL)) {
|
||||||
ReadProcessMemory(process, odsi->lpDebugStringData, wstr, nbytes, NULL);
|
goto init_logger_fail;
|
||||||
|
}
|
||||||
|
|
||||||
if (ok) {
|
signal_exception_handler_init();
|
||||||
if (wstr_narrow(wstr, &str)) {
|
// Cleanup remote process on CTRL+C
|
||||||
str[odsi->nDebugStringLength - 1] = '\0';
|
signal_register_shutdown_handler(signal_shutdown_handler);
|
||||||
|
|
||||||
SetConsoleTextAttribute(
|
if (!verify_hook_dll_and_exec_args_and_count_hooks(
|
||||||
GetStdHandle(STD_OUTPUT_HANDLE), console_get_color(str));
|
argc, argv, &hooks, &exec_arg_pos)) {
|
||||||
printf("%s", str);
|
goto verify_fail;
|
||||||
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15);
|
}
|
||||||
|
|
||||||
if (log_file) {
|
if (!verify_hook_dlls_exist(argc, argv, hooks)) {
|
||||||
fprintf(log_file, "%s", str);
|
goto verify_2_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(str);
|
// buffer consumed by debugger_init
|
||||||
} else {
|
cmd_line = args_join(argc - exec_arg_pos, argv + exec_arg_pos);
|
||||||
fprintf(stderr, "OutputDebugStringW: UTF-16 conversion failed\n");
|
|
||||||
|
local_debugger = options.debug && !options.remote_debugger;
|
||||||
|
|
||||||
|
if (!debugger_init(local_debugger, argv[exec_arg_pos], cmd_line)) {
|
||||||
|
goto debugger_init_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inject_hook_dlls(hooks, argv)) {
|
||||||
|
goto inject_hook_dlls_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute this after injecting the DLLs. Some debuggers seem to crash if we
|
||||||
|
// attach the process before DLL injection (inject's local one doesn't
|
||||||
|
// crash). However, this means the remote debugger is missing out on all
|
||||||
|
// injected DLL loads, e.g. calls to DllMain
|
||||||
|
if (options.remote_debugger) {
|
||||||
|
if (!debugger_wait_for_remote_debugger()) {
|
||||||
|
goto debugger_wait_for_remote_debugger_fail;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"ReadProcessMemory failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free(wstr);
|
if (!debugger_resume_process()) {
|
||||||
|
goto debugger_resume_process_fail;
|
||||||
return (bool) ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
|
|
||||||
{
|
|
||||||
char *str;
|
|
||||||
BOOL ok;
|
|
||||||
|
|
||||||
str = xmalloc(odsi->nDebugStringLength);
|
|
||||||
|
|
||||||
ok = ReadProcessMemory(
|
|
||||||
process, odsi->lpDebugStringData, str, odsi->nDebugStringLength, NULL);
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
str[odsi->nDebugStringLength - 1] = '\0';
|
|
||||||
|
|
||||||
SetConsoleTextAttribute(
|
|
||||||
GetStdHandle(STD_OUTPUT_HANDLE), console_get_color(str));
|
|
||||||
printf("%s", str);
|
|
||||||
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15);
|
|
||||||
|
|
||||||
if (log_file) {
|
|
||||||
fprintf(log_file, "%s", str);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"ReadProcessMemory failed: %08x\n",
|
|
||||||
(unsigned int) GetLastError());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free(str);
|
debugger_wait_process_exit();
|
||||||
|
|
||||||
return (bool) ok;
|
debugger_finit(false);
|
||||||
|
|
||||||
|
logger_finit();
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
|
||||||
|
debugger_resume_process_fail:
|
||||||
|
debugger_wait_for_remote_debugger_fail:
|
||||||
|
inject_hook_dlls_fail:
|
||||||
|
debugger_finit(true);
|
||||||
|
|
||||||
|
debugger_init_fail:
|
||||||
|
verify_2_fail:
|
||||||
|
verify_fail:
|
||||||
|
logger_finit();
|
||||||
|
|
||||||
|
init_logger_fail:
|
||||||
|
init_options_fail:
|
||||||
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -48,15 +48,15 @@ bool options_read_cmdline(struct options *options, int argc, char **argv)
|
|||||||
void options_print_usage(void)
|
void options_print_usage(void)
|
||||||
{
|
{
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"inject build " __DATE__ " " __TIME__
|
"inject build " __DATE__ " " __TIME__
|
||||||
", gitrev " STRINGIFY(GITREV) "\n"
|
", gitrev " STRINGIFY(GITREV) "\n"
|
||||||
"Usage: inject hook.dll... app.exe [hooks options...]\n"
|
"Usage: inject hook.dll... app.exe [hooks options...]\n"
|
||||||
"You can specify one or multiple hook.dll files, e.g. inject.exe "
|
"You can specify one or multiple hook.dll files, e.g. inject.exe "
|
||||||
"hook1.dll hook2.dll app.exe"
|
"hook1.dll hook2.dll app.exe"
|
||||||
"\n"
|
"\n"
|
||||||
" The following options can be specified after the exe path:\n"
|
" The following options can be specified after the exe path:\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -D Enable debugging output\n"
|
" -D Enable debugging output\n"
|
||||||
" -R Halt the injected process until a debugger is attached\n"
|
" -R Halt the injected process until a debugger is attached\n"
|
||||||
" -Y [filename] Log to a file in addition to the console\n");
|
" -Y [filename] Log to a file in addition to the console\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#include "util/defs.h"
|
||||||
|
|
||||||
|
const char *inject_build_date = __DATE__ " " __TIME__;
|
||||||
|
const char *inject_gitrev = STRINGIFY(GITREV);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
extern const char *inject_build_date;
|
||||||
|
extern const char *inject_gitrev;
|
||||||
Reference in New Issue
Block a user