refactor: Inject

- Split into modules similar to launcher to improve overall structure
- Use property (node) API for configuration

Remark: "MVP" without IAT hooking working, command line arguments and overriding. Will be added (again) later
This commit is contained in:
icex2 2024-08-15 11:34:31 +02:00
parent c965011788
commit 73da986d0b
18 changed files with 1125 additions and 374 deletions

View File

@ -14,8 +14,15 @@ libs_inject := \
mxml \
src_inject := \
main.c \
debug-config.c \
debugger-config.c \
debugger.c \
hooks-config.c \
inject-config.c \
inject.c \
logger-config.c \
logger.c \
main.c \
options.c \
version.c \

View File

@ -0,0 +1,29 @@
#define LOG_MODULE "inject-debug-config"
#include "core/property-node-ext.h"
#include "iface-core/log.h"
#include "inject/debug-config.h"
void debug_config_init(debug_config_t *config)
{
log_assert(config);
config->property_configs_log = false;
}
void debug_config_load(
const core_property_node_t *node, debug_config_t *config)
{
core_property_node_result_t result;
log_assert(node);
log_assert(config);
result = core_property_node_ext_bool_read(node, "property_configs_log", &config->property_configs_log);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_bool_read(node, "property_api_trace_log", &config->property_configs_log);
core_property_node_fatal_on_error(result);
}

View File

@ -0,0 +1,18 @@
#ifndef INJECT_DEBUG_CONFIG_H
#define INJECT_DEBUG_CONFIG_H
#include <stdbool.h>
#include "core/property-node.h"
typedef struct debug_config {
bool property_configs_log;
bool property_api_trace_log;
} debug_config_t;
void debug_config_init(debug_config_t *config);
void debug_config_load(
const core_property_node_t *node, debug_config_t *config);
#endif

View File

@ -0,0 +1,52 @@
#define LOG_MODULE "inject-debugger-config"
#include "core/property-node-ext.h"
#include "iface-core/log.h"
#include "inject/debugger-config.h"
#include "util/str.h"
static debugger_attach_type_t _debugger_config_str_to_attachtype(const char *str)
{
log_assert(str);
if (str_eq(str, "none")) {
return DEBUGGER_ATTACH_TYPE_NONE;
} else if (str_eq(str, "inject")) {
return DEBUGGER_ATTACH_TYPE_INJECT;
} else if (str_eq(str, "external")) {
return DEBUGGER_ATTACH_TYPE_EXTERNAL;
} else {
log_fatal("Invalid debugger attach type for debugger config: %s", str);
}
}
void debugger_config_init(debugger_config_t *config)
{
log_assert(config);
memset(config, 0, sizeof(debugger_config_t));
}
void debugger_config_load(
const core_property_node_t *node, debugger_config_t *config)
{
core_property_node_result_t result;
char buffer[16];
log_assert(node);
log_assert(config);
result = core_property_node_ext_str_read(node, "app/path", config->app.path, sizeof(config->app.path));
core_property_node_fatal_on_error(result);
result = core_property_node_ext_str_read_or_default(
node, "app/args", config->app.args, sizeof(config->app.args), "");
core_property_node_fatal_on_error(result);
result = core_property_node_ext_str_read(node, "attach_type", buffer, sizeof(buffer));
core_property_node_fatal_on_error(result);
config->attach_type = _debugger_config_str_to_attachtype(buffer);
}

View File

@ -0,0 +1,29 @@
#ifndef INJECT_DEBUGGER_CONFIG_H
#define INJECT_DEBUGGER_CONFIG_H
#include <windows.h>
#include <stdbool.h>
#include "core/property-node.h"
#include "inject/debugger.h"
// https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/command-line-string-limitation
#define WINDOWS_CMD_LINE_ARGS_MAX_LEN 8192
typedef struct debugger_config {
struct debugger_app_config {
char path[MAX_PATH];
char args[WINDOWS_CMD_LINE_ARGS_MAX_LEN];
} app;
debugger_attach_type_t attach_type;
} debugger_config_t;
void debugger_config_init(debugger_config_t *config);
void debugger_config_load(
const core_property_node_t *node, debugger_config_t *config);
#endif

View File

@ -9,6 +9,7 @@
#include <stdlib.h>
#include "core/log-bt.h"
#include "core/log-ext.h"
#include "iface-core/log.h"
@ -23,10 +24,11 @@
struct debugger_thread_params {
const char *app_name;
char *cmd_line;
bool local_debugger;
const char *cmd_line;
};
static debugger_attach_type_t debugger_attach_type;
static HANDLE debugger_thread_handle;
static HANDLE debugger_ready_event;
@ -134,7 +136,7 @@ read_debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
free(str);
log_warning(
"ERROR: ReadProcessMemory for debug string failed: %08x",
"ReadProcessMemory for debug string failed: %08x",
(unsigned int) GetLastError());
str = NULL;
}
@ -160,12 +162,12 @@ read_debug_wstr(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
if (wstr_narrow(wstr, &str)) {
str[odsi->nDebugStringLength - 1] = '\0';
} else {
log_warning("ERROR: OutputDebugStringW: UTF-16 conversion failed");
log_warning("OutputDebugStringW: UTF-16 conversion failed");
str = NULL;
}
} else {
log_warning(
"ERROR: ReadProcessMemory for debug string failed: %08x",
"ReadProcessMemory for debug string failed: %08x",
(unsigned int) GetLastError());
str = NULL;
}
@ -206,9 +208,10 @@ static bool log_debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi)
}
}
static bool debugger_create_process(
bool local_debugger, const char *app_name, char *cmd_line)
static bool debugger_create_process(const char *app_name, const char *cmd_line)
{
char cmd_line_cpy[8192];
log_assert(app_name);
log_assert(cmd_line);
@ -222,10 +225,10 @@ static bool debugger_create_process(
flags = 0;
// CREATE_SUSPENDED that we have plenty of time to set up the debugger and
// theemote process environment with hook dlls.
// the remote process environment with hook dlls.
flags |= CREATE_SUSPENDED;
if (local_debugger) {
if (debugger_attach_type == DEBUGGER_ATTACH_TYPE_INJECT) {
// 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
@ -241,27 +244,25 @@ static bool debugger_create_process(
log_misc("Creating remote process %s...", app_name);
log_misc("Remote process cmd_line: %s", cmd_line);
str_cpy(cmd_line_cpy, sizeof(cmd_line_cpy), cmd_line);
ok = CreateProcess(
app_name, cmd_line, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi);
app_name, cmd_line_cpy, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi);
if (!ok) {
log_warning(
"ERROR: Failed to launch hooked EXE: %08x",
"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 uint32_t debugger_loop()
static uint32_t _debugger_thread_loop()
{
DEBUG_EVENT de;
DWORD continue_status;
@ -272,7 +273,7 @@ static uint32_t debugger_loop()
for (;;) {
if (!WaitForDebugEvent(&de, INFINITE)) {
log_warning(
"ERROR: WaitForDebugEvent failed: %08x",
"WaitForDebugEvent failed: %08x",
(unsigned int) GetLastError());
return 1;
}
@ -391,14 +392,14 @@ static uint32_t debugger_loop()
if (!ContinueDebugEvent(
de.dwProcessId, de.dwThreadId, continue_status)) {
log_warning(
"ERROR: ContinueDebugEvent failed: %08x",
"ContinueDebugEvent failed: %08x",
(unsigned int) GetLastError());
return 1;
}
}
}
static DWORD WINAPI debugger_proc(LPVOID param)
static DWORD WINAPI _debugger_thread_proc(LPVOID param)
{
uint32_t debugger_loop_exit_code;
@ -406,11 +407,9 @@ static DWORD WINAPI debugger_proc(LPVOID param)
params = (struct debugger_thread_params *) param;
log_misc(
"Debugger thread start (local debugger: %d)", params->local_debugger);
log_misc("Debugger thread start");
if (!debugger_create_process(
params->local_debugger, params->app_name, params->cmd_line)) {
if (!debugger_create_process(params->app_name, params->cmd_line)) {
return 0;
}
@ -418,8 +417,8 @@ static DWORD WINAPI debugger_proc(LPVOID param)
// 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_exit_code = debugger_loop();
if (debugger_attach_type == DEBUGGER_ATTACH_TYPE_INJECT) {
debugger_loop_exit_code = _debugger_thread_loop();
free(params);
@ -439,49 +438,7 @@ static DWORD WINAPI debugger_proc(LPVOID param)
}
}
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_warning(
"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_warning(
"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()
void _debugger_wait_for_remote_debugger()
{
BOOL res;
@ -491,10 +448,10 @@ bool debugger_wait_for_remote_debugger()
res = FALSE;
if (!CheckRemoteDebuggerPresent(pi.hProcess, &res)) {
log_warning(
"ERROR: CheckRemoteDebuggerPresent failed: %08x",
log_fatal(
"CheckRemoteDebuggerPresent failed: %08x",
(unsigned int) GetLastError());
return false;
return;
}
if (res) {
@ -504,8 +461,72 @@ bool debugger_wait_for_remote_debugger()
Sleep(1000);
}
}
return true;
void _debugger_resume_process()
{
log_info("Resuming remote process...");
if (ResumeThread(pi.hThread) == -1) {
log_fatal(
"Resuming remote process failed: %08x",
(unsigned int) GetLastError());
}
CloseHandle(pi.hThread);
}
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_init(debugger_attach_type_t attach_type, const char *app_name, const char *cmd_line)
{
struct debugger_thread_params *thread_params;
log_assert(app_name);
log_assert(cmd_line);
debugger_attach_type = attach_type;
debugger_ready_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!debugger_ready_event) {
log_fatal(
"Creating event object failed: %08x",
(unsigned int) GetLastError());
}
// 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;
debugger_thread_handle =
CreateThread(NULL, 0, _debugger_thread_proc, thread_params, 0, 0);
if (!debugger_thread_handle) {
free(thread_params);
log_fatal(
"Creating debugger thread failed: %08x",
(unsigned int) GetLastError());
}
WaitForSingleObject(debugger_ready_event, INFINITE);
log_misc("Initialized, attach type: %d", debugger_attach_type);
}
bool debugger_inject_dll(const char *path_dll)
@ -523,6 +544,10 @@ bool debugger_inject_dll(const char *path_dll)
dll_path_length =
SearchPath(NULL, path_dll, NULL, MAX_PATH, dll_path, NULL);
if (dll_path_length == 0) {
log_fatal_on_win_last_error("Determining path for dll %s failed", path_dll);
}
dll_path_length++;
remote_addr = VirtualAllocEx(
@ -534,7 +559,7 @@ bool debugger_inject_dll(const char *path_dll)
if (!remote_addr) {
log_warning(
"ERROR: VirtualAllocEx failed: %08x",
"VirtualAllocEx failed: %08x",
(unsigned int) GetLastError());
goto alloc_fail;
@ -545,7 +570,7 @@ bool debugger_inject_dll(const char *path_dll)
if (!ok) {
log_warning(
"ERROR: WriteProcessMemory failed: %08x",
"WriteProcessMemory failed: %08x",
(unsigned int) GetLastError());
goto write_fail;
@ -562,7 +587,7 @@ bool debugger_inject_dll(const char *path_dll)
if (remote_thread == NULL) {
log_warning(
"ERROR: CreateRemoteThread failed: %08x",
"CreateRemoteThread failed: %08x",
(unsigned int) GetLastError());
goto inject_fail;
@ -576,9 +601,11 @@ bool debugger_inject_dll(const char *path_dll)
if (!ok) {
log_warning(
"ERROR: VirtualFreeEx failed: %08x", (unsigned int) GetLastError());
"VirtualFreeEx failed: %08x", (unsigned int) GetLastError());
}
log_misc("Injecting success: %s", path_dll);
return true;
inject_fail:
@ -761,34 +788,19 @@ inject_fail:
return false;
}
bool debugger_resume_process()
void debugger_run()
{
log_info("Resuming remote process...");
if (ResumeThread(pi.hThread) == -1) {
log_warning(
"ERROR: Resuming remote process: %08x",
(unsigned int) GetLastError());
return false;
// 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 (debugger_attach_type == DEBUGGER_ATTACH_TYPE_EXTERNAL) {
_debugger_wait_for_remote_debugger();
}
CloseHandle(pi.hThread);
_debugger_resume_process();
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");
_debugger_wait_process_exit();
}
void debugger_finit(bool failure)

View File

@ -2,16 +2,11 @@
#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.
*/
typedef enum debugger_attach_type {
DEBUGGER_ATTACH_TYPE_NONE = 0,
DEBUGGER_ATTACH_TYPE_INJECT = 1,
DEBUGGER_ATTACH_TYPE_EXTERNAL = 2,
} debugger_attach_type_t;
/**
* Initialize the debugger.
@ -30,15 +25,11 @@
* 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 attach_type Setup the debugger to attach with the given type of debugger
* @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);
void debugger_init(debugger_attach_type_t attach_type, const char *app_name, const char *cmd_line);
/**
* Inject a DLL into the remote process.
@ -48,6 +39,8 @@ bool debugger_init(bool local_debugger, const char *app_name, char *cmd_line);
*/
bool debugger_inject_dll(const char *path_dll);
void debugger_run();
/**
* Inject a DLL into the remote process by replacing its reference in
* the import table.

View File

@ -0,0 +1,226 @@
#define LOG_MODULE "inject-hooks-config"
#include "core/property-ext.h"
#include "core/property-node-ext.h"
#include "iface-core/log.h"
#include "inject/hooks-config.h"
#define HOOKS_CONFIG_MAX_LAYER_CONFIG_NODES 8
static core_property_t *
_hooks_config_layered_config_nodes_load(const core_property_node_t *node)
{
char kind[64];
char file[MAX_PATH];
int cnt;
core_property_node_t cur;
core_property_node_t tmp;
core_property_t *config_property[HOOKS_CONFIG_MAX_LAYER_CONFIG_NODES];
core_property_t *merged_property;
core_property_node_result_t result;
core_property_result_t prop_result;
log_assert(node);
cnt = 0;
result = core_property_node_search(node, "config", &cur);
if (result != CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) {
core_property_node_fatal_on_error(result);
}
while (result != CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) {
if (cnt >= HOOKS_CONFIG_MAX_LAYER_CONFIG_NODES) {
log_fatal(
"Exceeding max supported config nodes for layering, max is %d",
HOOKS_CONFIG_MAX_LAYER_CONFIG_NODES);
}
result =
core_property_node_attr_read(&cur, "kind", kind, sizeof(kind));
if (CORE_PROPERTY_NODE_RESULT_IS_ERROR(result)) {
log_fatal("Failed reading 'kind' attribute value of config node");
}
if (!strcmp(kind, "file")) {
core_property_node_str_read(&cur, file, sizeof(file));
prop_result = core_property_file_load(file, &config_property[cnt]);
core_property_fatal_on_error(prop_result);
} else if (!strcmp(kind, "inline")) {
// The nested child is the actual root of the inline, not the outer
// <config> node
result = core_property_node_child_get(&cur, &tmp);
memcpy(&cur, &tmp, sizeof(core_property_node_t));
if (result != CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) {
core_property_node_fatal_on_error(result);
}
result =
core_property_node_ext_extract(&cur, &config_property[cnt]);
core_property_node_fatal_on_error(result);
} else {
log_fatal(
"Unsupported 'kind' attribute value '%s' of config node", kind);
}
cnt++;
result = core_property_node_next_result_search(&cur, &tmp);
memcpy(&cur, &tmp, sizeof(core_property_node_t));
if (result != CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) {
core_property_node_fatal_on_error(result);
}
}
if (cnt == 0) {
prop_result = core_property_str_load("<hook></hook>", &merged_property);
core_property_fatal_on_error(prop_result);
} else {
prop_result =
core_property_ext_many_merge(config_property, cnt, &merged_property);
core_property_fatal_on_error(prop_result);
for (int i = 0; i < cnt; i++) {
core_property_free(&config_property[i]);
}
}
return merged_property;
}
static void _hooks_config_hooks_load(
const core_property_node_t *node,
struct hooks_hook_config *configs)
{
core_property_node_result_t result;
core_property_node_t child;
core_property_node_t tmp;
uint8_t processed_hooks;
processed_hooks = 0;
result = core_property_node_search(node, "hook", &child);
do {
if (processed_hooks >= HOOKS_CONFIG_MAX_HOOKS) {
log_fatal("Cannot load more hooks, max supported capacity reached");
}
if (result == CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) {
return;
} else {
core_property_node_fatal_on_error(result);
}
result = core_property_node_ext_bool_read(&child, "enable", &configs[processed_hooks].enable);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_str_read(&child, "path", configs[processed_hooks].path, sizeof(configs[processed_hooks].path));
core_property_node_fatal_on_error(result);
configs[processed_hooks].config = _hooks_config_layered_config_nodes_load(&child);
result = core_property_node_next_result_search(&child, &tmp);
memcpy(&child, &tmp, sizeof(core_property_node_t));
processed_hooks++;
} while (true);
}
static void _hooks_config_iats_load(
const core_property_node_t *node,
struct hooks_iat_config *configs)
{
core_property_node_result_t result;
core_property_node_t child;
core_property_node_t tmp;
uint8_t processed_hooks;
processed_hooks = 0;
result = core_property_node_search(node, "iat", &child);
do {
if (processed_hooks >= HOOKS_CONFIG_MAX_HOOKS) {
log_fatal("Cannot load more hooks, max supported capacity reached");
}
if (result == CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) {
return;
} else {
core_property_node_fatal_on_error(result);
}
result = core_property_node_ext_bool_read(&child, "enable", &configs[processed_hooks].enable);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_str_read(&child, "source_name", configs[processed_hooks].source_name, sizeof(configs[processed_hooks].source_name));
core_property_node_fatal_on_error(result);
result = core_property_node_ext_str_read(&child, "path", configs[processed_hooks].path, sizeof(configs[processed_hooks].path));
core_property_node_fatal_on_error(result);
configs[processed_hooks].config = _hooks_config_layered_config_nodes_load(&child);
result = core_property_node_next_result_search(&child, &tmp);
memcpy(&child, &tmp, sizeof(core_property_node_t));
processed_hooks++;
} while (true);
}
void hooks_config_init(hooks_config_t *config)
{
log_assert(config);
memset(config, 0, sizeof(hooks_config_t));
}
void hooks_config_load(
const core_property_node_t *node, hooks_config_t *config)
{
log_assert(node);
log_assert(config);
_hooks_config_hooks_load(node, config->hooks);
_hooks_config_iats_load(node, config->iats);
}
bool hooks_config_hook_is_valid(const struct hooks_hook_config *hook)
{
log_assert(hook);
return strlen(hook->path) > 0;
}
bool hooks_config_iat_is_valid(const struct hooks_iat_config *hook)
{
log_assert(hook);
return strlen(hook->path) > 0;
}
void hooks_config_fini(hooks_config_t *config)
{
uint32_t i;
log_assert(config);
for (i = 0; i < HOOKS_CONFIG_MAX_HOOKS; i++) {
if (hooks_config_hook_is_valid(&config->hooks[i])) {
core_property_free(&config->hooks[i].config);
}
}
for (i = 0; i < HOOKS_CONFIG_MAX_HOOKS; i++) {
if (hooks_config_iat_is_valid(&config->iats[i])) {
core_property_free(&config->iats[i].config);
}
}
}

View File

@ -0,0 +1,39 @@
#ifndef INJECT_HOOKS_CONFIG_H
#define INJECT_HOOKS_CONFIG_H
#include <windows.h>
#include <stdbool.h>
#include "core/property.h"
#include "core/property-node.h"
#define HOOKS_CONFIG_MAX_HOOKS 16
typedef struct hooks_config {
struct hooks_hook_config {
bool enable;
char path[MAX_PATH];
core_property_t *config;
} hooks[HOOKS_CONFIG_MAX_HOOKS];
struct hooks_iat_config {
bool enable;
char source_name[MAX_PATH];
char path[MAX_PATH];
core_property_t *config;
} iats[HOOKS_CONFIG_MAX_HOOKS];
} hooks_config_t;
void hooks_config_init(hooks_config_t *config);
void hooks_config_load(
const core_property_node_t *node, hooks_config_t *config);
bool hooks_config_hook_is_valid(const struct hooks_hook_config *hook);
bool hooks_config_iat_is_valid(const struct hooks_iat_config *hook);
void hooks_config_fini(hooks_config_t *config);
#endif

View File

@ -0,0 +1,77 @@
#define LOG_MODULE "inject-config"
#include "core/property-ext.h"
#include "core/property-node.h"
#include "core/property.h"
#include "core/property-mxml-internal.h"
#include "core/property-node-ext.h"
#include "iface-core/log.h"
#include "inject/debug-config.h"
#include "inject/debugger-config.h"
#include "inject/hooks-config.h"
#include "inject/inject-config.h"
#include "inject/logger-config.h"
void inject_config_init(struct inject_config *config)
{
log_assert(config);
config->version = 1;
debug_config_init(&config->debug);
debugger_config_init(&config->debugger);
hooks_config_init(&config->hooks);
logger_config_init(&config->logger);
}
void inject_config_file_load(const char *path, inject_config_t *config)
{
core_property_result_t result_prop;
core_property_node_result_t result;
core_property_t *property;
core_property_node_t root_node;
core_property_node_t child_node;
log_info("Loading configuration file: %s", path);
result_prop = core_property_file_load(path, &property);
core_property_fatal_on_error(result_prop);
result = core_property_root_node_get(property, &root_node);
core_property_node_fatal_on_error(result);
result = core_property_node_search(&root_node, "debug", &child_node);
core_property_node_fatal_on_error(result);
debug_config_load(&child_node, &config->debug);
result = core_property_node_search(&root_node, "debugger", &child_node);
core_property_node_fatal_on_error(result);
debugger_config_load(&child_node, &config->debugger);
result = core_property_node_search(&root_node, "hooks", &child_node);
core_property_node_fatal_on_error(result);
hooks_config_load(&child_node, &config->hooks);
result = core_property_node_search(&root_node, "logger", &child_node);
core_property_node_fatal_on_error(result);
logger_config_load(&child_node, &config->logger);
if (config->debug.property_configs_log) {
core_property_ext_log(property, log_misc_func);
}
core_property_free(&property);
log_misc("Loading done");
}
void inject_config_fini(inject_config_t *config)
{
log_assert(config);
// Other configs don't have a fini
hooks_config_fini(&config->hooks);
}

View File

@ -0,0 +1,26 @@
#ifndef INJECT_CONFIG_H
#define INJECT_CONFIG_H
#include <windows.h>
#include "inject/debug-config.h"
#include "inject/debugger-config.h"
#include "inject/hooks-config.h"
#include "inject/logger-config.h"
typedef struct inject_config {
uint32_t version;
hooks_config_t hooks;
logger_config_t logger;
debugger_config_t debugger;
debug_config_t debug;
} inject_config_t;
void inject_config_init(struct inject_config *config);
void inject_config_file_load(const char *path, inject_config_t *config);
void inject_config_fini(inject_config_t *config);
#endif

135
src/main/inject/inject.c Normal file
View File

@ -0,0 +1,135 @@
#define LOG_MODULE "inject"
#include "iface-core/log.h"
#include "inject/debugger.h"
#include "inject/inject-config.h"
#include "inject/logger.h"
#include "inject/version.h"
#include "util/os.h"
#include "util/signal.h"
static void _inject_fini();
static void _inject_signal_handler_shutdown()
{
_inject_fini();
exit(0);
}
static void _inject_header_log()
{
log_info(
"\n"
" _ _ _ \n"
" (_)_ __ (_) ___ ___| |_ \n"
" | | '_ \\ | |/ _ \\/ __| __|\n"
" | | | | || | __/ (__| |_ \n"
" |_|_| |_|/ |\\___|\\___|\\__|\n"
" |__/ ");
log_info(
"build date %s, gitrev %s", inject_build_date, inject_gitrev);
}
static void _inject_iat_hook_dlls(const struct hooks_iat_config *configs)
{
uint32_t i;
log_assert(configs);
log_info("Injecting IAT hook DLLs...");
for (i = 0; i < HOOKS_CONFIG_MAX_HOOKS; i++) {
if (hooks_config_iat_is_valid(&configs[i])) {
if (configs[i].enable) {
if (!debugger_replace_dll_iat(configs[i].source_name, configs[i].path)) {
log_fatal("Injecting iat hook failed: %s=%s", configs[i].source_name, configs[i].path);
}
} else {
log_warning("iat hook disabled: %s=%s", configs[i].source_name, configs[i].path);
}
}
}
}
static void _inject_hook_dlls(const struct hooks_hook_config *configs)
{
uint32_t i;
log_assert(configs);
log_info("Injecting hook DLLs...");
for (i = 0; i < HOOKS_CONFIG_MAX_HOOKS; i++) {
if (hooks_config_hook_is_valid(&configs[i])) {
if (configs[i].enable) {
if (!debugger_inject_dll(configs[i].path)) {
log_fatal("Injecting hook failed: %s", configs[i].path);
}
} else {
log_warning("Hook disabled: %s", configs[i].path);
}
}
}
}
static void _inject_init(const inject_config_t *config)
{
log_assert(config);
logger_init(&config->logger);
_inject_header_log();
os_version_log();
signal_exception_handler_init();
// Cleanup remote process on CTRL+C
signal_register_shutdown_handler(_inject_signal_handler_shutdown);
debugger_init(
config->debugger.attach_type,
config->debugger.app.path,
config->debugger.app.args);
log_misc("<<< init");
}
static void _inject_run(const inject_config_t *config)
{
log_assert(config);
log_misc(">>> run");
_inject_hook_dlls(config->hooks.hooks);
_inject_iat_hook_dlls(config->hooks.iats);
debugger_run();
log_misc("<<< run");
}
static void _inject_fini()
{
log_misc(">>> fini");
debugger_finit(false);
logger_fini();
}
// TODO run inject module only with inject configuration
// config must be bootstrapped using an early env
// in main with a early logger setup etc.
// apply the same to launcher
void inject_main(const inject_config_t *config)
{
log_assert(config);
log_misc(">>> main");
_inject_init(config);
_inject_run(config);
_inject_fini();
}

8
src/main/inject/inject.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef INJECT_INJECT_H
#define INJECT_INJECT_H
#include "inject/inject-config.h"
void inject_main(const inject_config_t *config);
#endif

View File

@ -0,0 +1,140 @@
#define LOG_MODULE "inject-logger-config"
#include "core/property-node-ext.h"
#include "iface-core/log.h"
#include "inject/logger-config.h"
#include "util/str.h"
static enum core_log_bt_log_level _logger_config_str_to_loglevel(const char *str)
{
log_assert(str);
if (str_eq(str, "off")) {
return CORE_LOG_BT_LOG_LEVEL_OFF;
} else if (str_eq(str, "fatal")) {
return CORE_LOG_BT_LOG_LEVEL_FATAL;
} else if (str_eq(str, "warning")) {
return CORE_LOG_BT_LOG_LEVEL_WARNING;
} else if (str_eq(str, "info")) {
return CORE_LOG_BT_LOG_LEVEL_INFO;
} else if (str_eq(str, "misc")) {
return CORE_LOG_BT_LOG_LEVEL_MISC;
} else {
log_fatal("Invalid log level string in config: %s", str);
}
}
static enum core_log_sink_async_overflow_policy _logger_config_str_to_overflowpolicy(const char *str)
{
log_assert(str);
if (str_eq(str, "discard_new")) {
return CORE_LOG_SINK_ASYNC_OVERFLOW_POLICY_DISCARD_NEW;
} else if (str_eq(str, "block")) {
return CORE_LOG_SINK_ASYNC_OVERFLOW_POLICY_BLOCK;
} else {
log_fatal("Invalid overflow policy string in config: %s", str);
}
}
static void _logger_config_sink_async_load(const core_property_node_t *node, struct logger_sink_async_config *config)
{
core_property_node_result_t result;
core_property_node_t child;
char buffer[16];
log_assert(node);
log_assert(config);
result = core_property_node_search(node, "sinks/async", &child);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_bool_read(&child, "enable", &config->enable);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_u8_read(&child, "queue_length", &config->queue_length);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_str_read(&child, "overflow_policy", buffer, sizeof(buffer));
core_property_node_fatal_on_error(result);
config->overflow_policy = _logger_config_str_to_overflowpolicy(buffer);
}
static void _logger_config_sink_console_load(const core_property_node_t *node, struct logger_sink_console_config *config)
{
core_property_node_result_t result;
core_property_node_t child;
log_assert(node);
log_assert(config);
result = core_property_node_search(node, "sinks/console", &child);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_bool_read(&child, "enable", &config->enable);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_bool_read(&child, "color", &config->color);
core_property_node_fatal_on_error(result);
}
static void _logger_config_sink_file_load(const core_property_node_t *node, struct logger_sink_file_config *config)
{
core_property_node_result_t result;
core_property_node_t child;
log_assert(node);
log_assert(config);
result = core_property_node_search(node, "sinks/file", &child);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_bool_read(&child, "enable", &config->enable);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_str_read(&child, "path", config->path, sizeof(config->path));
core_property_node_fatal_on_error(result);
result = core_property_node_ext_bool_read(&child, "append", &config->append);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_bool_read(&child, "rotate", &config->rotate);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_u8_read(&child, "max_rotations", &config->max_rotations);
core_property_node_fatal_on_error(result);
}
void logger_config_init(logger_config_t *config)
{
log_assert(config);
memset(config, 0, sizeof(logger_config_t));
}
void logger_config_load(
const core_property_node_t *node, logger_config_t *config)
{
core_property_node_result_t result;
char buffer[16];
log_assert(node);
log_assert(config);
result = core_property_node_ext_bool_read(node, "enable", &config->enable);
core_property_node_fatal_on_error(result);
result = core_property_node_ext_str_read(node, "level", buffer, sizeof(buffer));
core_property_node_fatal_on_error(result);
config->level = _logger_config_str_to_loglevel(buffer);
result = core_property_node_ext_u32_read(node, "msg_buffer_size_bytes", &config->msg_buffer_size_bytes);
core_property_node_fatal_on_error(result);
_logger_config_sink_async_load(node, &config->sink_async);
_logger_config_sink_console_load(node, &config->sink_console);
_logger_config_sink_file_load(node, &config->sink_file);
}

View File

@ -0,0 +1,43 @@
#ifndef INJECT_LOGGER_CONFIG_H
#define INJECT_LOGGER_CONFIG_H
#include <stdbool.h>
#include <stdint.h>
#include <windows.h>
#include "core/log-bt.h"
#include "core/log-sink-async.h"
#include "core/property-node.h"
typedef struct logger_config {
bool enable;
enum core_log_bt_log_level level;
uint32_t msg_buffer_size_bytes;
struct logger_sink_async_config {
bool enable;
uint8_t queue_length;
enum core_log_sink_async_overflow_policy overflow_policy;
} sink_async;
struct logger_sink_console_config {
bool enable;
bool color;
} sink_console;
struct logger_sink_file_config {
bool enable;
char path[MAX_PATH];
bool append;
bool rotate;
uint8_t max_rotations;
} sink_file;
} logger_config_t;
void logger_config_init(logger_config_t *config);
void logger_config_load(
const core_property_node_t *node, logger_config_t *config);
#endif

123
src/main/inject/logger.c Normal file
View File

@ -0,0 +1,123 @@
#define LOG_MODULE "inject-logger"
#include "core/log-bt.h"
#include "core/log-sink-async.h"
#include "core/log-sink-file.h"
#include "core/log-sink-list.h"
#include "core/log-sink-null.h"
#include "core/log-sink-std.h"
#include "iface-core/log.h"
#include "inject/logger-config.h"
static void _logger_null_sink_init()
{
core_log_sink_t sink;
core_log_sink_null_open(&sink);
// Size doesn't matter (but must be valid)
// logger is entirely disabled
core_log_bt_init(1024, &sink);
core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_OFF);
}
static bool _logger_sinks_create(
const logger_config_t *config,
core_log_sink_t *root_sink)
{
core_log_sink_t target_sinks[2];
core_log_sink_t list_sink;
uint8_t target_sink_count;
log_assert(config);
log_assert(root_sink);
target_sink_count = 0;
// Fixed order to ensure logger's first sink to write to is
// async and async sinks to console and file
if (config->sink_console.enable) {
core_log_sink_std_err_open(
config->sink_console.color,
&target_sinks[target_sink_count]);
target_sink_count++;
}
if (config->sink_file.enable) {
core_log_sink_file_open(
config->sink_file.path,
config->sink_file.append,
config->sink_file.rotate,
config->sink_file.max_rotations,
&target_sinks[target_sink_count]);
target_sink_count++;
}
if (target_sink_count > 0) {
// Compose to single sink
core_log_sink_list_open(
target_sinks,
target_sink_count,
&list_sink);
// Async sink only makes sense if at least one other
// sink is enabled
if (config->sink_async.enable) {
core_log_sink_async_open(
config->msg_buffer_size_bytes,
config->sink_async.queue_length,
config->sink_async.overflow_policy,
&list_sink,
root_sink);
} else {
// "Sync" with list of sinks
memcpy(root_sink, &list_sink, sizeof(core_log_sink_t));
}
return true;
} else {
memset(root_sink, 0, sizeof(core_log_sink_t));
return false;
}
}
static void _logger_with_sinks_init(const logger_config_t *config)
{
core_log_sink_t sink;
bool has_sinks;
log_assert(config);
has_sinks = _logger_sinks_create(config, &sink);
if (has_sinks) {
core_log_bt_init(config->msg_buffer_size_bytes, &sink);
core_log_bt_level_set(config->level);
} else {
// Consider this equivalent to disabling logging entirely
_logger_null_sink_init();
}
}
void logger_init(const logger_config_t *config)
{
log_assert(config);
if (!config->enable) {
_logger_null_sink_init();
} else {
_logger_with_sinks_init(config);
}
core_log_bt_core_api_set();
}
void logger_fini()
{
core_log_bt_fini();
}

10
src/main/inject/logger.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef INJECT_LOGGER_H
#define INJECT_LOGGER_H
#include "inject/logger-config.h"
void logger_init(const logger_config_t *config);
void logger_fini();
#endif

View File

@ -1,281 +1,65 @@
#define LOG_MODULE "inject"
#define LOG_MODULE "main"
#include <windows.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cconfig/cconfig-util.h"
#include "cconfig/cmd.h"
#include "core/boot.h"
#include "core/log-bt-ext.h"
#include "core/log-bt.h"
#include "core/log-sink-file.h"
#include "core/log-sink-list.h"
#include "core/log-sink-mutex.h"
#include "core/log-sink-std.h"
#include "core/thread-crt.h"
#include "iface-core/log.h"
#include "iface-core/thread.h"
#include "inject/inject-config.h"
#include "inject/inject.h"
#include "inject/debugger.h"
#include "inject/options.h"
#include "inject/version.h"
#include "util/cmdline.h"
#include "util/debug.h"
#include "util/mem.h"
#include "util/os.h"
#include "util/signal.h"
#include "util/str.h"
static void _inject_log_header()
static void _bootstrap_options(int argc, char **argv)
{
log_info(
"\n"
" _ _ _ \n"
" (_)_ __ (_) ___ ___| |_ \n"
" | | '_ \\ | |/ _ \\/ __| __|\n"
" | | | | || | __/ (__| |_ \n"
" |_|_| |_|/ |\\___|\\___|\\__|\n"
" |__/ ");
log_info(
"inject build date %s, gitrev %s", inject_build_date, inject_gitrev);
// TODO use options here
if (argc < 2) {
printf("Not enough args\n");
exit(1);
}
}
void _inject_log_init(
const char *log_file_path, enum core_log_bt_log_level level)
static void _bootstrap_config(const char *path, inject_config_t *config)
{
if (log_file_path) {
core_log_bt_ext_init_async_with_stderr_and_file(log_file_path, false, true, 10);
} else {
core_log_bt_ext_init_async_with_stderr();
}
core_log_bt_core_api_set();
core_log_bt_level_set(level);
}
static bool init_options(int argc, char **argv, struct options *options)
{
options_init(options);
if (argc < 3 || !options_read_cmdline(options, argc, argv)) {
options_print_usage();
return false;
}
return true;
}
static bool verify_hook_dll_and_exec_args_and_count_hooks(
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);
*hooks = 0;
*exec_arg_pos = 0;
for (int i = 1; i < argc; i++) {
if (str_ends_with(argv[i], "dll")) {
(*hooks)++;
} else if (str_ends_with(argv[i], "exe")) {
*exec_arg_pos = i;
break;
}
}
if (!(*hooks)) {
log_warning("ERROR: No Hook DLL(s) specified before executable");
return false;
}
if (!*exec_arg_pos) {
log_warning("ERROR: No executable specified");
return false;
}
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++) {
char *iat_hook = strstr(argv[i + 1], "=");
if (iat_hook) {
dll_path_length =
SearchPath(NULL, iat_hook + 1, NULL, MAX_PATH, dll_path, NULL);
} else {
dll_path_length =
SearchPath(NULL, argv[i + 1], NULL, MAX_PATH, dll_path, NULL);
}
if (dll_path_length == 0) {
log_warning(
"ERROR: Hook DLL not found: %08x",
(unsigned int) GetLastError());
return false;
}
}
return true;
}
static bool inject_iat_hook_dlls(uint32_t hooks, char **argv)
{
log_assert(argv);
log_info("Injecting IAT hook DLLs...");
for (int i = 0; i < hooks; i++) {
char *iat_hook = strstr(argv[i + 1], "=");
if (!iat_hook)
continue;
*iat_hook = '\0';
debugger_replace_dll_iat(argv[i + 1], iat_hook + 1);
*iat_hook = '=';
}
return true;
}
static bool inject_hook_dlls(uint32_t hooks, char **argv)
{
log_assert(argv);
log_info("Injecting hook DLLs...");
for (int i = 0; i < hooks; i++) {
char *iat_hook = strstr(argv[i + 1], "=");
if (iat_hook)
continue;
if (!debugger_inject_dll(argv[i + 1])) {
return false;
}
}
return true;
}
static void signal_shutdown_handler()
{
debugger_finit(true);
core_log_bt_fini();
inject_config_init(config);
inject_config_file_load(path, config);
}
int main(int argc, char **argv)
{
struct options options;
uint32_t hooks;
uint32_t exec_arg_pos;
char *cmd_line;
bool local_debugger;
const char *config_path;
inject_config_t config;
core_boot("inject");
if (!init_options(argc, argv, &options)) {
goto init_options_fail;
}
config_path = argv[1];
core_thread_crt_core_api_set();
// TODO make configurable
// core_property_trace_log_enable(true);
// core_property_node_trace_log_enable(true);
// TODO expose log level
_inject_log_init(
strlen(options.log_file) > 0 ? options.log_file : NULL,
CORE_LOG_BT_LOG_LEVEL_MISC);
_bootstrap_options(argc, argv);
_bootstrap_config(config_path, &config);
_inject_log_header();
os_version_log();
inject_main(&config);
signal_exception_handler_init();
// Cleanup remote process on CTRL+C
signal_register_shutdown_handler(signal_shutdown_handler);
return 0;
if (!verify_hook_dll_and_exec_args_and_count_hooks(
argc, argv, &hooks, &exec_arg_pos)) {
goto verify_fail;
}
if (!verify_hook_dlls_exist(argc, argv, hooks)) {
goto verify_2_fail;
}
// inject
// general configuration stuff
// --config (-c) inject-09.xml
// override any parameters with key-value params
// --param (-p) logger.level=asdf
// some shortcuts for params commonly used
// --loglevel (-l)
// --logfile (-y)
// --remotedebugger (-r)
// --debugger (-d)
// --configslog (-s)
// -- hook.dll... app.exe [hooks options...]
// buffer consumed by debugger_init
cmd_line = args_join(argc - exec_arg_pos, argv + exec_arg_pos);
local_debugger = options.debug && !options.remote_debugger;
if (!debugger_init(local_debugger, argv[exec_arg_pos], cmd_line)) {
goto debugger_init_fail;
}
if (!inject_iat_hook_dlls(hooks, argv)) {
goto inject_hook_dlls_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;
}
}
if (!debugger_resume_process()) {
goto debugger_resume_process_fail;
}
debugger_wait_process_exit();
debugger_finit(false);
core_log_bt_fini();
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:
core_log_bt_fini();
init_options_fail:
return EXIT_FAILURE;
}
// TODO
// - options
// - load config
// - apply overrides to config, hook dlls are added
}