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