mirror of
https://github.com/djhackersdev/bemanitools.git
synced 2026-04-22 01:27:34 -05:00
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:
parent
c965011788
commit
73da986d0b
|
|
@ -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 \
|
||||
|
||||
|
|
|
|||
29
src/main/inject/debug-config.c
Normal file
29
src/main/inject/debug-config.c
Normal 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);
|
||||
}
|
||||
18
src/main/inject/debug-config.h
Normal file
18
src/main/inject/debug-config.h
Normal 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
|
||||
52
src/main/inject/debugger-config.c
Normal file
52
src/main/inject/debugger-config.c
Normal 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);
|
||||
}
|
||||
29
src/main/inject/debugger-config.h
Normal file
29
src/main/inject/debugger-config.h
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
226
src/main/inject/hooks-config.c
Normal file
226
src/main/inject/hooks-config.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/main/inject/hooks-config.h
Normal file
39
src/main/inject/hooks-config.h
Normal 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
|
||||
77
src/main/inject/inject-config.c
Normal file
77
src/main/inject/inject-config.c
Normal 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);
|
||||
}
|
||||
26
src/main/inject/inject-config.h
Normal file
26
src/main/inject/inject-config.h
Normal 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
135
src/main/inject/inject.c
Normal 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
8
src/main/inject/inject.h
Normal 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
|
||||
140
src/main/inject/logger-config.c
Normal file
140
src/main/inject/logger-config.c
Normal 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);
|
||||
}
|
||||
43
src/main/inject/logger-config.h
Normal file
43
src/main/inject/logger-config.h
Normal 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
123
src/main/inject/logger.c
Normal 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
10
src/main/inject/logger.h
Normal 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
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user