| /* ********************************************************** |
| * Copyright (c) 2011-2013 Google, Inc. All rights reserved. |
| * Copyright (c) 2002-2010 VMware, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Copyright (c) 2003-2007 Determina Corp. */ |
| /* Copyright (c) 2002-2003 Massachusetts Institute of Technology */ |
| /* Copyright (c) 2002 Hewlett-Packard Company */ |
| |
| /* |
| * injector.c - standalone injector for win32 |
| */ |
| |
| /* Disable warnings from vc8 include files */ |
| /* buildtools\VC\8.0\dist\VC\PlatformSDK\Include\prsht.h(201) |
| * "nonstandard extension used : nameless struct/union" |
| */ |
| #pragma warning( disable : 4201 ) |
| /* buildtools\VC\8.0\dist\VC\PlatformSDK\Include\wintrust.h(459) |
| * "named type definition in parentheses" |
| */ |
| #pragma warning( disable : 4115 ) |
| |
| #include "../globals.h" |
| #define WIN32_LEAN_AND_MEAN |
| #include <windows.h> |
| #include <commdlg.h> |
| #include <imagehlp.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> /* memset */ |
| #include <time.h> |
| #include <tchar.h> |
| |
| #include "globals_shared.h" |
| #include "ntdll.h" |
| #include "inject_shared.h" |
| #include "os_private.h" |
| #include "dr_inject.h" |
| |
| #ifdef UNICODE |
| # error dr_inject.h and drdeploy.c not set up for unicde |
| #endif |
| |
| #define VERBOSE 0 |
| #if VERBOSE |
| # define DO_VERBOSE(x) x |
| # undef printf |
| # define VERBOSE_PRINT printf |
| #else |
| # define DO_VERBOSE(x) |
| # define VERBOSE_PRINT(...) |
| #endif |
| #define FP stderr |
| |
| /* FIXME: case 64 would like ^C to kill child process, it doesn't. |
| * also, child process seems able to read stdin but not to write |
| * to stdout or stderr (in fact it dies if it tries) |
| * I think Ctrl-C is delivered only if you have a debugger(windbg) attached. |
| */ |
| #define HANDLE_CONTROL_C 0 |
| |
| /* global vars */ |
| static int limit; /* in seconds */ |
| static BOOL showmem; |
| static BOOL showstats; |
| static BOOL inject; |
| static BOOL force_injection; |
| static BOOL use_environment = TRUE; /* FIXME : for now default to using |
| * the environment, below we check and |
| * never use the environment if using |
| * debug injection. Revisit. |
| */ |
| static const char *ops_param; |
| static double wallclock; /* in seconds */ |
| |
| /* FIXME : assert stuff, internal error, display_message duplicated from |
| * pre_inject, share? */ |
| |
| /* for asserts, copied from utils.h */ |
| #ifdef assert |
| # undef assert |
| #endif |
| /* avoid mistake of lower-case assert */ |
| #define assert assert_no_good_use_ASSERT_instead |
| void internal_error(const char *file, int line, const char *msg); |
| #undef ASSERT |
| #ifdef DEBUG |
| void display_error(char *msg); |
| # ifdef INTERNAL |
| # define ASSERT(x) if (!(x)) internal_error(__FILE__, __LINE__, #x) |
| # else |
| # define ASSERT(x) if (!(x)) internal_error(__FILE__, __LINE__, "") |
| # endif /* INTERNAL */ |
| #else |
| # define display_error(msg) ((void) 0) |
| # define ASSERT(x) ((void) 0) |
| #endif /* DEBUG */ |
| |
| /* in ntdll.c */ |
| extern char *get_application_name(void); |
| extern char *get_application_pid(void); |
| |
| static void |
| display_error_helper(wchar_t *msg) |
| { |
| wchar_t title_buf[MAX_PATH + 64]; |
| _snwprintf(title_buf, BUFFER_SIZE_ELEMENTS(title_buf), |
| L_PRODUCT_NAME L" Notice: %hs(%hs)", |
| get_application_name(), get_application_pid()); |
| NULL_TERMINATE_BUFFER(title_buf); |
| |
| /* for unit tests: assume that if a limit is set, we are in a |
| * script so it's ok to just display to stderr. avoids hangs when |
| * an error is encountered. */ |
| if (limit <= 0) |
| nt_messagebox(msg, title_buf); |
| else { |
| fprintf(FP, "\n\n%ls\n%ls\n\n", title_buf, msg); |
| fflush(FP); |
| } |
| } |
| |
| void |
| internal_error(const char *file, int line, const char *expr) |
| { |
| #ifdef INTERNAL |
| # define FILENAME_LENGTH L"" |
| #else |
| /* truncate file name to first character */ |
| # define FILENAME_LENGTH L".1" |
| #endif |
| wchar_t buf[512]; |
| _snwprintf(buf, BUFFER_SIZE_ELEMENTS(buf), |
| L"Injector Error %" FILENAME_LENGTH L"hs:%d %hs\n", |
| file, line, expr); |
| NULL_TERMINATE_BUFFER(buf); |
| display_error_helper(buf); |
| TerminateProcess(GetCurrentProcess(), (uint)-1); |
| } |
| |
| #ifdef DEBUG |
| void |
| display_error(char *msg) |
| { |
| # ifdef DISABLED /* going with msgbox always! */ |
| fprintf(FP, msg); |
| # else |
| wchar_t buf[512]; |
| _snwprintf(buf, BUFFER_SIZE_ELEMENTS(buf), L"%hs", msg); |
| NULL_TERMINATE_BUFFER(buf); |
| display_error_helper(buf); |
| # endif |
| } |
| #endif /* DEBUG */ |
| |
| #if HANDLE_CONTROL_C |
| BOOL WINAPI HandlerRoutine(DWORD dwCtrlType // control signal type |
| ) |
| { |
| printf("Inside HandlerRoutine!\n"); |
| fflush(stdout); |
| /* GenerateConsoleCtrlEvent(dwCtrlType, phandle);*/ |
| return TRUE; |
| } |
| #endif |
| |
| /*************************************************************************/ |
| |
| /* Opaque type to users, holds our state */ |
| typedef struct _dr_inject_info_t { |
| PROCESS_INFORMATION pi; |
| bool using_debugger_injection; |
| char image_name[MAXIMUM_PATH]; |
| } dr_inject_info_t; |
| |
| DYNAMORIO_EXPORT |
| char * |
| dr_inject_get_image_name(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| if (data == NULL) |
| return NULL; |
| return info->image_name; |
| } |
| |
| DYNAMORIO_EXPORT |
| HANDLE |
| dr_inject_get_process_handle(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| if (data == NULL) |
| return INVALID_HANDLE_VALUE; |
| return info->pi.hProcess; |
| } |
| |
| DYNAMORIO_EXPORT |
| process_id_t |
| dr_inject_get_process_id(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| if (data == NULL) |
| return 0; |
| return info->pi.dwProcessId; |
| } |
| |
| DYNAMORIO_EXPORT |
| bool |
| dr_inject_using_debug_key(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| if (data == NULL) |
| return false; |
| return info->using_debugger_injection; |
| } |
| |
| DYNAMORIO_EXPORT |
| void |
| dr_inject_print_stats(void *data, int elapsed_secs, bool showstats, bool showmem) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| VM_COUNTERS mem; |
| /* not in DR library - floating point use is OK */ |
| int secs = elapsed_secs; |
| |
| if (data == NULL) |
| return; |
| |
| if (!get_process_mem_stats(info->pi.hProcess, &mem)) { |
| /* failed */ |
| memset(&mem, 0, sizeof(VM_COUNTERS)); |
| } |
| |
| if (showstats) { |
| int cpu = get_process_load(info->pi.hProcess); |
| /* Elapsed real (wall clock) time. */ |
| if (secs >= 3600) { /* One hour -> h:m:s. */ |
| fprintf(FP, "%ld:%02ld:%02ldelapsed ", |
| secs / 3600, |
| (secs % 3600) / 60, |
| secs % 60); |
| } else { |
| fprintf(FP, "%ld:%02ld.%02ldelapsed ", /* -> m:s. */ |
| secs / 60, |
| secs % 60, |
| 0 /* for now */); |
| } |
| fprintf(FP, "%d%%CPU \n", cpu); |
| fprintf(FP, "(%lu tot, %lu RSS, %lu paged, %lu non, %lu swap)k\n", |
| mem.PeakVirtualSize/1024, |
| mem.PeakWorkingSetSize/1024, |
| mem.QuotaPeakPagedPoolUsage/1024, |
| mem.QuotaPeakNonPagedPoolUsage/1024, |
| mem.PeakPagefileUsage/1024); |
| } |
| if (showmem) { |
| fprintf(FP, "Process Memory Statistics:\n"); |
| fprintf(FP, "\tPeak virtual size: %6d KB\n", |
| mem.PeakVirtualSize/1024); |
| fprintf(FP, "\tPeak working set size: %6d KB\n", |
| mem.PeakWorkingSetSize/1024); |
| fprintf(FP, "\tPeak paged pool usage: %6d KB\n", |
| mem.QuotaPeakPagedPoolUsage/1024); |
| fprintf(FP, "\tPeak non-paged pool usage: %6d KB\n", |
| mem.QuotaPeakNonPagedPoolUsage/1024); |
| fprintf(FP, "\tPeak pagefile usage: %6d KB\n", |
| mem.PeakPagefileUsage/1024); |
| } |
| } |
| |
| /*************************************************************************/ |
| /* Following code handles the copying of environment variables to the |
| * registry (the -env option, default on) and unsetting them later |
| * |
| * FIXME : race conditions with someone else modifying this registry key, |
| * doesn't restore registry if -no_wait |
| * NOTE : doesn't propagate if using debug injection method (by design) |
| * |
| */ |
| |
| static BOOL created_product_reg_key; |
| static BOOL created_image_reg_key; |
| static HKEY image_name_key; |
| static HKEY product_name_key; |
| |
| /* use macros to avoid duplication */ |
| /* This check is necessary since we don't have |
| * preprocessor warnings enabled on windows */ |
| #if defined(TEMP_CMD) || defined(DO_CMD) |
| #error TEMP_CMD or DO_ENV_VARS macro already declared! |
| #endif |
| /* if need to add or remove environment variables looked for, do it here */ |
| #define DO_ENV_VARS() \ |
| TEMP_CMD(options, OPTIONS); \ |
| TEMP_CMD(logdir, LOGDIR); \ |
| TEMP_CMD(unsupported, UNSUPPORTED); \ |
| TEMP_CMD(rununder, RUNUNDER); \ |
| TEMP_CMD(autoinject, AUTOINJECT); \ |
| TEMP_CMD(cache_root, CACHE_ROOT); \ |
| TEMP_CMD(cache_shared, CACHE_SHARED) |
| |
| #define TEMP_CMD(name, NAME) \ |
| static BOOL overwrote_##name##_value; \ |
| static BOOL created_##name##_value; \ |
| static TCHAR old_##name##_value[MAX_REGISTRY_PARAMETER] |
| |
| /* Note - leading spacs is to avoid make ugly rule on zero argument function |
| * declerations not using void - xref PR 215100 */ |
| DO_ENV_VARS(); |
| |
| static void |
| set_registry_from_env(const TCHAR *image_name, const TCHAR *dll_path) |
| { |
| #undef TEMP_CMD |
| #define TEMP_CMD(name, NAME) \ |
| BOOL do_##name; \ |
| TCHAR name##_value[MAX_REGISTRY_PARAMETER] |
| |
| DO_ENV_VARS(); |
| |
| DWORD disp, size, type; |
| int res, len; |
| int rununder_int_value; |
| |
| /* get environment variable values if they are set */ |
| #undef TEMP_CMD |
| #define TEMP_CMD(name, NAME) \ |
| name##_value[0] = '\0'; /* to be pedantic */ \ |
| len = GetEnvironmentVariable(_TEXT(DYNAMORIO_VAR_##NAME), name##_value, \ |
| BUFFER_SIZE_ELEMENTS(name##_value)); \ |
| do_##name = (use_environment && \ |
| (len > 0 || \ |
| (len == 0 && GetLastError() != ERROR_ENVVAR_NOT_FOUND))); \ |
| ASSERT(len < BUFFER_SIZE_ELEMENTS(name##_value)); \ |
| VERBOSE_PRINT(("Environment var %s for %s, value = %s\n", \ |
| do_##name ? "set" : "not set", #name, name##_value)); |
| |
| DO_ENV_VARS(); |
| |
| if (ops_param != NULL) { |
| /* -ops overrides env var */ |
| strncpy(options_value, ops_param, BUFFER_SIZE_ELEMENTS(options_value)); |
| NULL_TERMINATE_BUFFER(options_value); |
| do_options = TRUE; |
| } |
| |
| /* we always want to set the rununder to make sure RUNUNDER_ON is on |
| * to support following children; we set RUNUNDER_EXPLICIT to allow |
| * injecting even when preinject is configured. */ |
| /* FIXME: we read only decimal */ |
| rununder_int_value = _ttoi(rununder_value); |
| rununder_int_value |= RUNUNDER_ON | RUNUNDER_EXPLICIT; |
| |
| do_rununder = true; |
| _itot(rununder_int_value, rununder_value, |
| 10 /* FIXME : is the radix abstracted somewhere */); |
| |
| /* for follow_children, we set DYNAMORIO_AUTOINJECT (unless |
| * overridden by env var: then child will use env value, while |
| * parent uses cmdline path) */ |
| if (!do_autoinject && dll_path != NULL) { |
| _tcsncpy(autoinject_value, dll_path, BUFFER_SIZE_ELEMENTS(autoinject_value)); |
| do_autoinject = true; |
| } |
| |
| /* FIXME : doesn't support svchost-* yet */ |
| ASSERT(_tcsicmp(_TEXT(SVCHOST_EXE_NAME), image_name)); |
| res = RegCreateKeyEx(DYNAMORIO_REGISTRY_HIVE, |
| _TEXT(DYNAMORIO_REGISTRY_KEY), 0, NULL, |
| REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY, NULL, |
| &product_name_key, &disp); |
| ASSERT(res == ERROR_SUCCESS); |
| if (disp == REG_CREATED_NEW_KEY) { |
| created_product_reg_key = TRUE; |
| } |
| res = RegCreateKeyEx(product_name_key, image_name, 0, NULL, |
| REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE|KEY_SET_VALUE, |
| NULL, &image_name_key, &disp); |
| ASSERT(res == ERROR_SUCCESS); |
| if (disp == REG_CREATED_NEW_KEY) { |
| created_image_reg_key = TRUE; |
| } |
| |
| DO_VERBOSE({ |
| printf("created product key? %s\ncreated image key? %s\n", |
| created_product_reg_key ? "yes" : "no", |
| created_image_reg_key ? "yes" : "no"); |
| fflush(stdout); |
| }); |
| |
| /* Now set values */ |
| #undef TEMP_CMD |
| #define TEMP_CMD(name, NAME) \ |
| if (do_##name) { \ |
| size = BUFFER_SIZE_BYTES(old_##name##_value); \ |
| res = RegQueryValueEx(image_name_key, _TEXT(DYNAMORIO_VAR_##NAME), \ |
| NULL, &type, (BYTE *) old_##name##_value, &size); \ |
| ASSERT(size <= BUFFER_SIZE_BYTES(old_##name##_value)); \ |
| if (res == ERROR_SUCCESS) { \ |
| overwrote_##name##_value = TRUE; \ |
| ASSERT(type == REG_SZ); \ |
| } else { \ |
| created_##name##_value = TRUE; \ |
| } \ |
| res = RegSetValueEx(image_name_key, _TEXT(DYNAMORIO_VAR_##NAME), \ |
| 0, REG_SZ, (BYTE *) name##_value, \ |
| (DWORD) (_tcslen(name##_value)+1)*sizeof(TCHAR)); \ |
| ASSERT(res == ERROR_SUCCESS); \ |
| VERBOSE_PRINT(("%s %s registry value with \"%s\" replacing \"%s\"\n", \ |
| overwrote_##name##_value ? "overwrote" : "created", #name, \ |
| name##_value, overwrote_##name##_value ? old_##name##_value : ""));\ |
| } |
| |
| DO_ENV_VARS(); |
| |
| DO_VERBOSE({fflush(stdout);}); |
| } |
| |
| static void |
| unset_registry_from_env(const TCHAR *image_name) |
| { |
| int res; |
| |
| VERBOSE_PRINT(("Restoring registry configuration\n")); |
| |
| /* restore registry values */ |
| #undef TEMP_CMD |
| #define TEMP_CMD(name, NAME) \ |
| if (overwrote_##name##_value) { \ |
| res = RegSetValueEx(image_name_key, _TEXT(DYNAMORIO_VAR_##NAME), 0, \ |
| REG_SZ /* FIXME : abstracted somewhere? */, \ |
| (BYTE *) old_##name##_value, \ |
| (DWORD)(_tcslen(old_##name##_value)+1) * sizeof(TCHAR));\ |
| ASSERT(res == ERROR_SUCCESS); \ |
| VERBOSE_PRINT(("Restored %s value to %s\n", #name, old_##name##_value));\ |
| } else if (created_##name##_value) { \ |
| res = RegDeleteValue(image_name_key, _TEXT(DYNAMORIO_VAR_##NAME)); \ |
| ASSERT(res == ERROR_SUCCESS); \ |
| VERBOSE_PRINT(("Deleted %s value\n", #name)); \ |
| } |
| |
| DO_ENV_VARS(); |
| |
| /* delete keys if created them */ |
| if (created_image_reg_key) { |
| res = RegDeleteKey(product_name_key, image_name); |
| ASSERT(res == ERROR_SUCCESS); |
| VERBOSE_PRINT(("deleted image reg key\n")); |
| } |
| if (created_product_reg_key) { |
| res = RegDeleteKey(DYNAMORIO_REGISTRY_HIVE, |
| _TEXT(DYNAMORIO_REGISTRY_KEY)); |
| ASSERT(res == ERROR_SUCCESS); |
| VERBOSE_PRINT(("deleted product reg key\n")); |
| } |
| DO_VERBOSE({fflush(stdout);}); |
| } |
| |
| #undef DO_ENV_VARS |
| #undef TEMP_CMD |
| |
| /************************** end environment->registry **********************/ |
| |
| /***************************************************************************/ |
| /* The following code handles checking for, setting and unsetting of the |
| * debug key injection method |
| * |
| * This whole section can go away once we have our own version of create |
| * process that doesn't check the debugger key FIXME |
| */ |
| |
| static HKEY debugger_key; |
| static TCHAR debugger_key_full_name[MAX_PATH]; |
| static TCHAR debugger_key_value[3*MAX_PATH]; |
| static DWORD debugger_key_value_size = BUFFER_SIZE_BYTES(debugger_key_value); |
| static BOOL (*debug_stop_function)(int); |
| |
| DYNAMORIO_EXPORT |
| bool |
| using_debugger_key_injection(const TCHAR *image_name) |
| { |
| int res; |
| |
| debug_stop_function = (BOOL (*)(int)) |
| (GetProcAddress(GetModuleHandle(TEXT("Kernel32")), |
| "DebugActiveProcessStop")); |
| |
| _sntprintf(debugger_key_full_name, |
| BUFFER_SIZE_ELEMENTS(debugger_key_full_name), _TEXT("%hs\\%s"), |
| DEBUGGER_INJECTION_KEY, image_name); |
| NULL_TERMINATE_BUFFER(debugger_key_full_name); |
| |
| VERBOSE_PRINT(("debugger key %s\n", debugger_key_full_name)); |
| |
| res = RegOpenKeyEx(DEBUGGER_INJECTION_HIVE, debugger_key_full_name, |
| 0, KEY_QUERY_VALUE + KEY_SET_VALUE, &debugger_key); |
| if (ERROR_SUCCESS != res) |
| return false; |
| res = RegQueryValueEx(debugger_key, _TEXT(DEBUGGER_INJECTION_VALUE_NAME), |
| NULL, NULL, (BYTE *) debugger_key_value, |
| &debugger_key_value_size); |
| if (ERROR_SUCCESS != res || |
| /* FIXME : it would be better to check if our commandline matched |
| * what was in the registry value, instead of looking for drinject */ |
| _tcsstr(debugger_key_value, _TEXT(DRINJECT_NAME)) == 0) { |
| RegCloseKey(debugger_key); |
| return false; |
| } |
| |
| /* since returning true, we don't close the debugger_key (it will be |
| * needed by the unset and restore functions). The restore function will |
| * close it */ |
| |
| return true; |
| } |
| |
| static |
| void unset_debugger_key_injection() |
| { |
| if (debug_stop_function == NULL) { |
| int res = RegDeleteValue(debugger_key, |
| _TEXT(DEBUGGER_INJECTION_VALUE_NAME)); |
| VERBOSE_PRINT(("Successfully deleted debugger registry value? %s\n", |
| (ERROR_SUCCESS == res) ? "yes" : "no")); |
| if (ERROR_SUCCESS != res) { |
| ASSERT(FALSE); |
| /* prevent infinite recursion, die now */ |
| abort(); |
| } |
| } |
| } |
| |
| static |
| void restore_debugger_key_injection(int id, BOOL started) |
| { |
| int res; |
| if (debug_stop_function == NULL) { |
| res = RegSetValueEx(debugger_key, |
| _TEXT(DEBUGGER_INJECTION_VALUE_NAME), 0, REG_SZ, |
| (BYTE *) debugger_key_value, debugger_key_value_size); |
| VERBOSE_PRINT(("Successfully restored debugger registry value? %s\n", |
| (ERROR_SUCCESS == res) ? "yes" : "no")); |
| } else { |
| if (started) { |
| res = (*debug_stop_function)(id); |
| VERBOSE_PRINT(("Successfully detached from debugging process? %s\n", |
| res ? "yes" : "no")); |
| } |
| } |
| /* close the global debugger key */ |
| RegCloseKey(debugger_key); |
| } |
| |
| /***************************** end debug key injection ********************/ |
| |
| /* CreateProcess will take a string up to 36K. */ |
| enum { MAX_CMDLINE = 36 * 1024 }; |
| |
| static const TCHAR * |
| get_image_name(const TCHAR *app_name) |
| { |
| const TCHAR *name_start = double_tcsrchr(app_name, _TEXT('\\'), _TEXT('/')); |
| if (name_start == NULL) |
| name_start = app_name; |
| else |
| name_start++; |
| return name_start; |
| } |
| |
| /* FIXME i#803: Until we have i#803 and support targeting cross-arch |
| * children, we require the child to match our bitwidth. |
| * module_is_64bit() takes in a base, but there's no need to map the |
| * whole thing in. Thus we have our own impl. |
| * Once we fix i#803, remove the ERROR_IMAGE_MACHINE_TYPE_MISMATCH_EXE |
| * comment in the docs for dr_inject_process_create. |
| */ |
| static bool |
| exe_is_right_bitwidth(const char *exe, int *errcode) |
| { |
| bool res = false; |
| HANDLE f; |
| DWORD offs; |
| DWORD read; |
| IMAGE_DOS_HEADER dos; |
| IMAGE_NT_HEADERS nt; |
| f = CreateFile(exe, GENERIC_READ, FILE_SHARE_READ, |
| NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| if (f == INVALID_HANDLE_VALUE) |
| goto read_nt_headers_error; |
| if (!ReadFile(f, &dos, sizeof(dos), &read, NULL) || |
| read != sizeof(dos) || |
| dos.e_magic != IMAGE_DOS_SIGNATURE) |
| goto read_nt_headers_error; |
| offs = SetFilePointer(f, dos.e_lfanew, NULL, FILE_BEGIN); |
| if (offs == INVALID_SET_FILE_POINTER) |
| goto read_nt_headers_error; |
| if (!ReadFile(f, &nt, sizeof(nt), &read, NULL) || |
| read != sizeof(nt) || |
| nt.Signature != IMAGE_NT_SIGNATURE) |
| goto read_nt_headers_error; |
| res = (nt.OptionalHeader.Magic == |
| IF_X64_ELSE(IMAGE_NT_OPTIONAL_HDR64_MAGIC, IMAGE_NT_OPTIONAL_HDR_MAGIC)); |
| CloseHandle(f); |
| if (!res) |
| *errcode = ERROR_IMAGE_MACHINE_TYPE_MISMATCH_EXE; |
| return res; |
| read_nt_headers_error: |
| *errcode = ERROR_FILE_NOT_FOUND; |
| if (f != INVALID_HANDLE_VALUE) |
| CloseHandle(f); |
| return false; |
| } |
| |
| static void |
| append_app_arg_and_space(char *buf, size_t bufsz, size_t *sofar, const char *arg) |
| { |
| size_t len = strlen(arg); |
| /* CreateProcess requires a single command-line string, so we must |
| * combine the separate args in such a way that the tokenizer on the |
| * other side produces the same array again. |
| * We assume MS C++, which will split on space or tab (but not [\n\r\v]). |
| * It requires quotes to include a space (cannot escape a space). |
| * We do not want to blindly quote all args, as although the argv[] |
| * array (or the result of CommandLineToArgvW()) will strip the outer quotes, |
| * some processes directly parse the command line (note that WinMain is not |
| * passed argv[]) and can't handle quotes (of course they have to handle |
| * quotes on args with spaces). |
| * |
| * XXX: by taking argv[], we're already losing transparency: most front-ends |
| * will pass us their main() argv[], which has already lost quotes. Thus |
| * the child process will not see the same quotes in the cmdline. |
| * But escaped quotes will still be there. |
| * This should be good enough. |
| * |
| * Here's my test case: |
| * % bin32/drrun -debug -- e:/derek/dr/test/args.exe foo"""bar wo\"xof"\ -woah_\"man -choc-o-dile\'in*\\\"fact -logdir "c:\program files\some path\or other" '-even_num_quote\\\\"there' "" x\\\\ |
| * orig command line is [E:\derek\dr\git\build_x86_dbg\bin32\drrun.exe -debug -- e:/derek/dr/test/args.exe "foobar wo\"xof -woah_\"man" "-choc-o-dile'in*\\\"fact" -logdir "c:\program files\some path\or other" "-even_num_quote\\\\\\\\\"there" "" x\\] |
| * appending [e:/derek/dr/test/args.exe] |
| * appending [foobar wo"xof -woah_"man] |
| * appending [-choc-o-dile'in*\"fact] |
| * appending [-logdir] |
| * appending [c:\program files\some path\or other] |
| * appending [-even_num_quote\\\\"there] |
| * appending [] |
| * appending [x\\] |
| * arg 0: [e:/derek/dr/test/args.exe] |
| * arg 1: [foobar wo"xof -woah_"man] |
| * arg 2: [-choc-o-dile'in*\"fact] |
| * arg 3: [-logdir] |
| * arg 4: [c:\program files\some path\or other] |
| * arg 5: [-even_num_quote\\\\"there] |
| * arg 6: [] |
| * arg 7: [x\\] |
| * command line is [e:/derek/dr/test/args.exe "foobar wo\"xof -woah_\"man" "-choc-o-dile'in*\\\"fact" -logdir "c:\program files\some path\or other" "-even_num_quote\\\\\\\\\"there" "" x\\] |
| */ |
| size_t span = strcspn(arg, " \t\""); |
| VERBOSE_PRINT("appending [%s]\n", arg); |
| if (len > 0 && span == len) |
| print_to_buffer(buf, bufsz, sofar, "%s ", arg); |
| else { |
| const char *a; |
| print_to_buffer(buf, bufsz, sofar, "\""); |
| for (a = arg; *a != '\0'; a++) { |
| /* MS C++ collapses sequences of backslashes before a quote, so we |
| * have to walk any sequence and see what's after it. |
| */ |
| uint i, backslashes = 0; |
| while (*a == '\\') { |
| a++; |
| backslashes++; |
| } |
| if (*a == '\"' || *a == '\0') { |
| /* MS C++ collapses backslashes before a quote, so we need to |
| * escape them if the arg already has a quote or if it ends in |
| * backslashes (and will end in a quote once we add it). |
| */ |
| for (i = 0; i < backslashes * 2; i++) |
| print_to_buffer(buf, bufsz, sofar, "\\"); |
| /* Escape any literal double-quotes */ |
| if (*a != '\0') |
| print_to_buffer(buf, bufsz, sofar, "\\\""); |
| } else { |
| /* No need to escape as these will be treated as literals already */ |
| for (i = 0; i < backslashes; i++) |
| print_to_buffer(buf, bufsz, sofar, "\\"); |
| print_to_buffer(buf, bufsz, sofar, "%c", *a); |
| } |
| } |
| print_to_buffer(buf, bufsz, sofar, "\" "); |
| } |
| } |
| |
| /* Returns 0 on success. |
| * On failure, returns a Windows API error code. |
| */ |
| DYNAMORIO_EXPORT |
| int |
| dr_inject_process_create(const char *app_name, const char **argv, |
| void **data OUT) |
| { |
| dr_inject_info_t *info = HeapAlloc(GetProcessHeap(), 0, sizeof(*info)); |
| STARTUPINFO si; |
| int errcode = 0; |
| BOOL res; |
| char *app_cmdline; |
| size_t sofar = 0; |
| int i; |
| |
| if (data == NULL) |
| return ERROR_INVALID_PARAMETER; |
| |
| if (!exe_is_right_bitwidth(app_name, &errcode) && |
| /* don't return here if couldn't find app: get appropriate errcode below */ |
| errcode == ERROR_IMAGE_MACHINE_TYPE_MISMATCH_EXE) { |
| /* Rather than return ERROR_IMAGE_MACHINE_TYPE_MISMATCH_EXE, we give the |
| * caller the decision over what to do. We go ahead and create the |
| * process, which the caller can destroy if it wants a fatal error here. |
| * This gives flexibility for corner cases like i#1224 where a PE32 |
| * image is turned into a PE32+ image by the kernel! |
| * If there's no other error below, this errcode will remain on return. |
| */ |
| } |
| |
| /* Quote and concatenate the array of strings to pass to CreateProcess. */ |
| app_cmdline = malloc(MAX_CMDLINE); |
| if (!app_cmdline) |
| return GetLastError(); |
| VERBOSE_PRINT("orig command line is [%s]\n", GetCommandLine()); |
| for (i = 0; argv[i] != NULL; i++) { |
| append_app_arg_and_space(app_cmdline, MAX_CMDLINE, &sofar, argv[i]); |
| } |
| app_cmdline[sofar-1] = '\0'; /* Trim the trailing space. */ |
| |
| /* Launch the application process. */ |
| ZeroMemory(&si, sizeof(si)); |
| si.cb = sizeof(si); |
| /* My old drinject code set dwFlags to STARTF_USESTDHANDLES and |
| * used GetStartupInfo to get values for hStd{Output,Error} but that |
| * ends up not working: perhaps that was before I had bInheritHandles |
| * set to true? Xref PR 208715, i#261, i#142. |
| */ |
| |
| strncpy(info->image_name, get_image_name(app_name), |
| BUFFER_SIZE_ELEMENTS(info->image_name)); |
| |
| /* FIXME, won't need to check this, or unset/restore debugger_key_injection |
| * if we have our own version of CreateProcess that doesn't check the |
| * debugger key */ |
| info->using_debugger_injection = using_debugger_key_injection(info->image_name); |
| |
| if (info->using_debugger_injection) { |
| unset_debugger_key_injection(); |
| } |
| |
| /* Must specify TRUE for bInheritHandles so child inherits stdin! */ |
| res = CreateProcess(app_name, app_cmdline, NULL, NULL, TRUE, |
| CREATE_SUSPENDED | |
| ((debug_stop_function && info->using_debugger_injection) ? |
| DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS : 0), |
| NULL, NULL, &si, &info->pi); |
| if (!res) |
| errcode = GetLastError(); |
| free(app_cmdline); |
| |
| if (info->using_debugger_injection) { |
| restore_debugger_key_injection(info->pi.dwProcessId, res); |
| } |
| |
| *data = (void *) info; |
| return errcode; |
| } |
| |
| DYNAMORIO_EXPORT |
| bool |
| dr_inject_process_inject(void *data, bool force_injection, |
| const char *library_path) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| CONTEXT cxt; |
| bool inject = true; |
| char library_path_buf[MAXIMUM_PATH]; |
| |
| /* force_injection prevents overriding of inject based on registry */ |
| if (!force_injection) { |
| int inject_flags = systemwide_should_inject(info->pi.hProcess, NULL); |
| bool syswide_will_inject = systemwide_inject_enabled() && |
| TEST(INJECT_TRUE, inject_flags) && |
| !TEST(INJECT_EXPLICIT, inject_flags); |
| bool should_not_takeover = TEST(INJECT_EXCLUDED, inject_flags) && |
| info->using_debugger_injection; |
| /* case 10794: to support follow_children we inject even if |
| * syswide_will_inject. we use RUNUNDER_EXPLICIT to avoid |
| * user32 injection from happening, to get consistent injection. |
| * (if we didn't things would work but we'd have |
| * a warning "<Blocking load of module dynamorio.dll>" on the 2nd |
| * injection) |
| */ |
| inject = !should_not_takeover; |
| if (!inject) { |
| /* we should always be injecting (we set the registry above) |
| * unless we are using debugger_key_injection, in which |
| * case we use what is in the registry (whoever wrote the registry |
| * should take care of late or nonexistent user32 loading in the |
| * rununder value) */ |
| ASSERT(info->using_debugger_injection); |
| display_error("application is excluded from injection\n"); |
| } else { |
| /* Don't inject if in safe mode */ |
| if (is_safe_mode()) { |
| inject = false; |
| display_error("System is in safe mode, not injecting\n"); |
| } |
| } |
| } |
| |
| if (!inject) |
| return false; |
| |
| if (library_path == NULL) { |
| int err; |
| err = get_process_parameter(info->pi.hProcess, |
| PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), |
| library_path_buf, sizeof(library_path_buf)); |
| if (err != GET_PARAMETER_SUCCESS && err != GET_PARAMETER_NOAPPSPECIFIC) { |
| inject = false; |
| display_error("WARNING: this application is not configured to run under DynamoRIO!\n" |
| "Use drconfig.exe or drrun.exe to configure."); |
| } |
| NULL_TERMINATE_BUFFER(library_path_buf); |
| library_path = library_path_buf; |
| } |
| |
| #ifdef PARAMS_IN_REGISTRY |
| /* don't set registry from environment if using debug key */ |
| if (!info->using_debugger_injection) { |
| set_registry_from_env(info->image_name, library_path); |
| } |
| #endif |
| |
| inject_init(); |
| /* FIXME PR 211367: use early_inject instead of this late injection! |
| * but non-trivial to gather the relevant addresses: so wait for |
| * earliest injection => i#234/PR 204587 prereq? |
| */ |
| if (!inject_into_thread(info->pi.hProcess, &cxt, info->pi.hThread, |
| (char *)library_path)) { |
| close_handle(info->pi.hProcess); |
| TerminateProcess(info->pi.hProcess, 0); |
| return false; |
| } |
| return true; |
| } |
| |
| DYNAMORIO_EXPORT |
| bool |
| dr_inject_process_run(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| /* resume the suspended app process so its main thread can run */ |
| ResumeThread(info->pi.hThread); |
| close_handle(info->pi.hThread); |
| |
| return true; |
| } |
| |
| DYNAMORIO_EXPORT |
| bool |
| dr_inject_wait_for_child(void *data, uint64 timeout_millis) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| wait_status_t wait_result; |
| if (timeout_millis == 0) |
| timeout_millis = INFINITE; |
| /* We use the Nt version to avoid loss of precision. */ |
| wait_result = os_wait_handle(info->pi.hProcess, timeout_millis); |
| return (wait_result == WAIT_SIGNALED); |
| } |
| |
| DYNAMORIO_EXPORT |
| int |
| dr_inject_process_exit(void *data, bool terminate) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| int exitcode = -1; |
| #ifdef PARAMS_IN_REGISTRY |
| if (!info->using_debugger_injection) { |
| unset_registry_from_env(info->image_name); |
| } |
| #endif |
| if (terminate) { |
| TerminateProcess(info->pi.hProcess, 0); |
| } |
| GetExitCodeProcess(info->pi.hProcess, (LPDWORD) &exitcode); |
| close_handle(info->pi.hProcess); |
| HeapFree(GetProcessHeap(), 0, info); |
| return exitcode; |
| } |
| |