static int parameters_present(IF_NOT_X64(bool x64_in_wow64)) { char path[MAX_PATH]; int retval; /* We should do some sanity checking on our parameters, to make sure we can really inject in applications. War story: When renaming the product from DynamoRIO to SecureCore we'd start injecting and then failing to load a dll for all apps. */ #ifndef X64 if (x64_in_wow64) { ASSERT(is_wow64_process(NT_CURRENT_PROCESS)); retval = get_parameter_64(PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), path, MAX_PATH); } else #endif retval = get_parameter(PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), path, MAX_PATH); if (IS_GET_PARAMETER_SUCCESS(retval)) { return 1; } else { return 0; } }
bool process_attach() { int rununder_mask; int should_inject; bool takeover = true; #if VERBOSE int len; char exename[MAX_PATH]; #endif /* FIXME: append to event log to indicate we're in the address space */ VERBOSE_MESSAGE("inside preinject dll\n"); ntdll_init(); #ifndef PARAMS_IN_REGISTRY /* i#85/PR 212034: use config files */ config_init(); #endif #if VERBOSE len = GetModuleFileName(NULL, exename, MAX_PATH); ASSERT(len > 0); #endif #if 0 /* PR 314367: re-enable once it all works */ # ifndef X64 /* PR 253431: one method of injecting 64-bit DR into a WOW64 process is * via 32-bit AppInit drpreinject. * x64 configuration takes precedence over wow64. */ if (is_wow64_process(NT_CURRENT_PROCESS)) { should_inject = systemwide_should_preinject_64(NULL, &rununder_mask); if (((INJECT_TRUE & should_inject) != 0) && ((INJECT_EXPLICIT & should_inject) == 0) && !is_safe_mode() && parameters_present(true)) { VERBOSE_MESSAGE("<"PRODUCT_NAME" is taking over process %d (%s) as x64>\n", GetCurrentProcessId(), exename); check_for_run_once(NULL, rununder_mask); /* we commit to x64 takeover based on there being a positive * rununder setting and an AUTOINJECT entry. if the AUTOINJECT * turns out to be invalid, we'll try the 32-bit. */ takeover = !load_dynamorio_lib(true); } } # endif #endif /* 0 */ if (takeover) { should_inject = systemwide_should_preinject(NULL, &rununder_mask); if (((INJECT_TRUE & should_inject) == 0) || ((INJECT_EXPLICIT & should_inject) != 0) || is_safe_mode() || !parameters_present(IF_NOT_X64(false))) { /* not taking over */ VERBOSE_MESSAGE(PRODUCT_NAME " is NOT taking over process %d (%s)\n", GetCurrentProcessId(), exename); } else { /* yes, load in dynamo to take over! */ VERBOSE_MESSAGE("<" PRODUCT_NAME " is taking over process %d (%s)>\n", GetCurrentProcessId(), exename); check_for_run_once(NULL, rununder_mask); load_dynamorio_lib(IF_NOT_X64(false)); } } ntdll_exit(); /* i#1522: self-unloading messes up the win8+ loader so we return false instead */ if (running_on_win8_or_later()) return false; else return true; }
static bool load_dynamorio_lib(IF_NOT_X64(bool x64_in_wow64)) { HMODULE dll = NULL; char path[MAX_PATH]; #ifdef DEBUG char msg[3 * MAX_PATH]; #endif int retval = -1; /* failure */ #ifndef X64 bool wow64 = is_wow64_process(NT_CURRENT_PROCESS); if (x64_in_wow64) { ASSERT(wow64); retval = get_parameter_64(PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), path, MAX_PATH); } else #endif retval = get_parameter(PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), path, MAX_PATH); if (IS_GET_PARAMETER_SUCCESS(retval)) { dr_marker_t mark; VERBOSE_MESSAGE("Loading \"%hs\"", path); /* The read_and_verify_dr_marker is the canonical check for dr in a * process, we double check against GetModuleHandle here just to be * extra safe (in case dr failed to initialize before). Note that * GetModuleHandle won't find dr's dll if we implement certian -hide * or early_injection proposals. */ if (read_and_verify_dr_marker(GetCurrentProcess(), &mark) != DR_MARKER_FOUND && GetModuleHandle(DYNAMORIO_LIBRARY_NAME) == NULL #ifndef X64 /* these ifdefs are rather ugly: just export all routines in x64 builds? */ && /* check for 64-bit as well */ (!wow64 || read_and_verify_dr_marker_64(GetCurrentProcess(), &mark) != DR_MARKER_FOUND) /* FIXME PR 251677: need 64-bit early injection to fully test * read_and_verify_dr_marker_64 */ #endif ) { /* OK really going to load dr now, verify that we are injecting * early enough (i.e. user32.dll is statically linked). This * presumes preinject is only used with app_init injection which is * currently the case. FIXME - should we also check_sole_thread * here? We can't really handle more then one thread when dr is * loading, but this can happen with early remote injected threads * many of which (CTRL) are relatively harmless. */ LDR_MODULE *mod = get_ldr_module_by_name(L"user32.dll"); ASSERT(mod != NULL); if (ldr_module_statically_linked(mod)) { #ifndef X64 if (x64_in_wow64) dll = load_library_64(path); else #endif dll = LoadLibrary(path); } else { /* FIXME - would be really nice to communicate this back to * the controller. */ #ifdef DEBUG _snprintf(msg, BUFFER_SIZE_ELEMENTS(msg), PRODUCT_NAME " Error: improper injection - " PRODUCT_NAME " (%s) can't inject into process %s (%s) (user32.dll " "not statically linked)\n", path, get_application_name(), get_application_pid()); NULL_TERMINATE_BUFFER(msg); display_error(msg); #endif } } else { /* notify failure only in debug builds, otherwise just return */ #ifdef DEBUG /* with early injection this becomes even more likely */ if (read_and_verify_dr_marker(GetCurrentProcess(), &mark) == DR_MARKER_FOUND # ifndef X64 || (wow64 && read_and_verify_dr_marker_64(GetCurrentProcess(), &mark) == DR_MARKER_FOUND) # endif ) { /* ok, early injection should always beat this */ # if VERBOSE /* can't readily tell what was expected */ _snprintf(msg, BUFFER_SIZE_ELEMENTS(msg), PRODUCT_NAME " ok if early injection, otherwise ERROR: " "double injection, " PRODUCT_NAME " (%s) is already loaded " "in process %s (%s), continuing\n", path, get_application_name(), get_application_pid()); NULL_TERMINATE_BUFFER(msg); display_error(msg); # endif /* VERBOSE */ } else { /* if GetModuleHandle finds us but we don't have a marker * we may have failed somehow */ _snprintf(msg, BUFFER_SIZE_ELEMENTS(msg), PRODUCT_NAME " Error: failed injection, " PRODUCT_NAME " (%s) is " "loaded but not initialized in process %s (%s), continuing\n", path, get_application_name(), get_application_pid()); NULL_TERMINATE_BUFFER(msg); display_error(msg); } #endif /* DEBUG */ return false; } } else path[0] = 0; if (dll == NULL) { #ifdef DEBUG int err = GetLastError(); _snprintf(msg, BUFFER_SIZE_ELEMENTS(msg), PRODUCT_NAME " Error %d loading %s\n", err, path); NULL_TERMINATE_BUFFER(msg); display_error(msg); #endif return false; } else { int_func_t init_func; void_func_t take_over_func; int res; #ifndef X64 if (x64_in_wow64) { init_func = (int_func_t)(ptr_uint_t) /*we know <4GB*/ get_proc_address_64((uint64)dll, "dynamorio_app_init"); take_over_func = (void_func_t)(ptr_uint_t) /*we know <4GB*/ get_proc_address_64((uint64)dll, "dynamorio_app_take_over"); VERBOSE_MESSAGE("dynamorio_app_init: 0x%08x; dynamorio_app_take_over: " "0x%08x\n", init_func, take_over_func); } else { #endif init_func = (int_func_t)GetProcAddress(dll, "dynamorio_app_init"); take_over_func = (void_func_t)GetProcAddress(dll, "dynamorio_app_take_over"); #ifndef X64 } #endif if (init_func == NULL || take_over_func == NULL) { /* unload the library so that it's clear DR is not in control * (o/w the DR library is in the process and it's not clear * what's going on) */ #ifndef X64 if (x64_in_wow64) { # ifdef DEBUG bool ok = # endif free_library_64(dll); ASSERT(ok); } else #endif FreeLibrary(dll); #ifdef DEBUG display_error("Error getting " PRODUCT_NAME " functions\n"); #endif return false; } VERBOSE_MESSAGE("about to inject dynamorio"); #ifndef X64 if (x64_in_wow64) res = switch_modes_and_call(init_func, NULL, NULL, NULL); else #endif res = (*init_func)(); VERBOSE_MESSAGE("dynamorio_app_init() returned %d\n", res); #ifndef X64 if (x64_in_wow64) switch_modes_and_call(take_over_func, NULL, NULL, NULL); else #endif (*take_over_func)(); VERBOSE_MESSAGE("inside " PRODUCT_NAME " now\n"); } return true; }
/* For 32-bit build, supports looking for x64 marker (in WOW64 process). * For 64-bit build, only supports looking for x64 marker. */ static int read_and_verify_dr_marker_common(HANDLE process, dr_marker_t *marker, bool x64) { byte buf[8]; /* only needs to be 5, but dword pad just in case */ size_t res; void *target = NULL; #if !defined(NOT_DYNAMORIO_CORE) && !defined(NOT_DYNAMORIO_CORE_PROPER) GET_NTDLL(DR_MARKER_HOOKED_FUNCTION, DR_MARKER_HOOKED_FUNCTION_ARGS); void *hook_func = (void *)DR_MARKER_HOOKED_FUNCTION; #else if (IF_X64_ELSE(!x64, x64 && !is_wow64_process(NT_CURRENT_PROCESS))) return DR_MARKER_ERROR; if (x64) { # ifndef X64 uint64 hook_func = get_proc_address_64 (get_module_handle_64(L_DR_MARKER_HOOKED_DLL), DR_MARKER_HOOKED_FUNCTION_STRING); uint64 landing_pad = 0; if (hook_func == 0) return DR_MARKER_ERROR; if (!NT_SUCCESS(nt_wow64_read_virtual_memory64(process, hook_func, buf, 5, &res)) || res != 5) { return DR_MARKER_ERROR; } if (buf[0] != OP_jmp_byte) return DR_MARKER_NOT_FOUND; /* jmp offset + EIP (after jmp = hook_func + size of jmp (5 bytes)) */ /* for 64-bit, the target is stored in front of the trampoline */ landing_pad = *(int *)&buf[1] + hook_func + 5 - 8; if (!NT_SUCCESS(nt_wow64_read_virtual_memory64(process, landing_pad, buf, 8, &res)) || res != 8U) return DR_MARKER_ERROR; /* trampoline address is stored at the top of the landing pad for 64-bit */ target = (void *)PAGE_START(*(ptr_int_t *)buf); } else { # endif /* !X64 */ void *hook_func = (void *)GetProcAddress(GetModuleHandle(DR_MARKER_HOOKED_DLL), DR_MARKER_HOOKED_FUNCTION_STRING); #endif void *landing_pad; if (hook_func == NULL) return DR_MARKER_ERROR; if (!READ_FUNC(process, hook_func, buf, 5, &res) || res != 5) return DR_MARKER_ERROR; if (buf[0] != OP_jmp_byte) return DR_MARKER_NOT_FOUND; /* jmp offset + EIP (after jmp = hook_func + size of jmp (5 bytes)) */ landing_pad = (void *)(*(int *)&buf[1] + (ptr_int_t)hook_func + 5); /* for 64-bit, the target is stored in front of the trampoline */ if (x64) landing_pad = (byte *)landing_pad - 8; /* see emit_landing_pad_code() for layout of landing pad */ if (!READ_FUNC(process, landing_pad, buf, (x64 ? 8 : 5), &res) || res != (x64 ? 8U : 5U)) return DR_MARKER_ERROR; if (x64) { /* trampoline address is stored at the top of the landing pad for 64-bit */ target = (void *)PAGE_START(*(ptr_int_t *)buf); } else { /* jmp offset + EIP (after jmp = landing_pad + size of jmp (5 bytes)) */ target = (void *)PAGE_START(*(int *)&buf[1] + (ptr_int_t)landing_pad + 5); } #if defined(NOT_DYNAMORIO_CORE) || defined(NOT_DYNAMORIO_CORE_PROPER) } #endif if (target == NULL) return DR_MARKER_ERROR; if (!READ_FUNC(process, target, marker, sizeof(dr_marker_t), &res) || res != sizeof(dr_marker_t)) { return DR_MARKER_NOT_FOUND; } if (dr_marker_verify(process, marker)) { return DR_MARKER_FOUND; } return DR_MARKER_NOT_FOUND; /* probably some other hooker */ }
/* pass non-NULL for thandle if you want this routine to use * Get/SetThreadContext to get the context -- you must still pass * in a pointer to a cxt */ BOOL inject_into_thread(HANDLE phandle, CONTEXT *cxt, HANDLE thandle, char *dynamo_path) { size_t nbytes; BOOL success = FALSE; ptr_uint_t dynamo_entry_esp; ptr_uint_t dynamo_path_esp; LPVOID load_dynamo_code = NULL; /* = base of code allocation */ ptr_uint_t addr; reg_t *bufptr; char buf[MAX_PATH]; uint old_prot; ASSERT(cxt != NULL); #ifndef NOT_DYNAMORIO_CORE_PROPER /* FIXME - if we were early injected we couldn't call inject_init during * startup because kernel32 wasn't loaded yet, so we call it here which * isn't safe because it uses app locks. If we want to support a mix * of early and late follow children injection we should change load_dynamo * to use Nt functions (which we can link) rather then kernel32 functions * (which we have to look up). We could also use module.c code to safely * walk the exports of kernel32.dll (we can cache its mod handle when it * is loaded). */ if (!inject_initialized) { SYSLOG_INTERNAL_WARNING("Using late inject follow children from early injected process, unsafe LdrLock usage"); SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT); inject_init(); SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); } #else ASSERT(inject_initialized); #endif /* soon we'll start using alternative injection with case 102 - leaving block */ { reg_t app_xsp; if (thandle != NULL) { /* grab the context of the app's main thread */ cxt->ContextFlags = CONTEXT_DR_STATE; if (!NT_SUCCESS(nt_get_context(thandle, cxt))) { display_error("GetThreadContext failed"); goto error; } } app_xsp = cxt->CXT_XSP; /* copy load_dynamo() into the address space of the new process */ ASSERT(BUFFER_SIZE_BYTES(buf) > SIZE_OF_LOAD_DYNAMO); memcpy(buf, (char*)load_dynamo, SIZE_OF_LOAD_DYNAMO); /* R-X protection is adequate for our non-self modifying code, * and we'll update that after we're done with * nt_write_virtual_memory() calls */ /* get allocation, this will be freed by os_heap_free, so make sure * is compatible allocation method */ if (!NT_SUCCESS(nt_remote_allocate_virtual_memory(phandle, &load_dynamo_code, SIZE_OF_LOAD_DYNAMO, PAGE_EXECUTE_READWRITE, MEMORY_COMMIT))) { display_error("Failed to allocate memory for injection code"); goto error; } if (!nt_write_virtual_memory(phandle, load_dynamo_code, buf, SIZE_OF_LOAD_DYNAMO, &nbytes)) { display_error("WriteMemory failed"); goto error; } /* Xref PR 252745 & PR 252008 - we can use the app's stack to hold our data * even on WOW64 and 64-bit since we're using set context to set xsp. */ /* copy the DYNAMORIO_ENTRY string to the app's stack */ _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%s", DYNAMORIO_ENTRY); NULL_TERMINATE_BUFFER(buf); nbytes = strlen(buf) + 1; // include the trailing '\0' /* keep esp at pointer-sized alignment */ cxt->CXT_XSP -= ALIGN_FORWARD(nbytes, XSP_SZ); dynamo_entry_esp = cxt->CXT_XSP; if (!nt_write_virtual_memory(phandle, (LPVOID)cxt->CXT_XSP, buf, nbytes, &nbytes)) { display_error("WriteMemory failed"); goto error; } /* copy the dynamorio_path string to the app's stack */ _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%s", dynamo_path); NULL_TERMINATE_BUFFER(buf); nbytes = strlen(buf) + 1; // include the trailing '\0' /* keep esp at pointer-sized byte alignment */ cxt->CXT_XSP -= ALIGN_FORWARD(nbytes, XSP_SZ); dynamo_path_esp = cxt->CXT_XSP; if (!nt_write_virtual_memory(phandle, (LPVOID)cxt->CXT_XSP, buf, nbytes, &nbytes)) { display_error("WriteMemory failed"); goto error; } /* copy the current context to the app's stack. Only need the * control registers, so we use a dr_mcontext_t layout. */ bufptr = (reg_t*) buf; *bufptr++ = cxt->CXT_XDI; *bufptr++ = cxt->CXT_XSI; *bufptr++ = cxt->CXT_XBP; *bufptr++ = app_xsp; *bufptr++ = cxt->CXT_XBX; *bufptr++ = cxt->CXT_XDX; *bufptr++ = cxt->CXT_XCX; *bufptr++ = cxt->CXT_XAX; #ifdef X64 *bufptr++ = cxt->R8; *bufptr++ = cxt->R9; *bufptr++ = cxt->R10; *bufptr++ = cxt->R11; *bufptr++ = cxt->R12; *bufptr++ = cxt->R13; *bufptr++ = cxt->R14; *bufptr++ = cxt->R15; #endif /* It would be nice to use preserve_xmm_caller_saved(), but we'd need to * link proc.c and deal w/ messy dependencies to get it into arch_exports.h, * so we do our own check. We go ahead and put in the xmm slots even * if the underlying processor has no xmm support: no harm done. */ if (IF_X64_ELSE(true, is_wow64_process(NT_CURRENT_PROCESS))) { /* PR 264138: preserve xmm0-5. We fill in all slots even though * for 32-bit we don't use them (PR 306394). */ int i, j; for (i = 0; i < NUM_XMM_SLOTS; i++) { for (j = 0; j < IF_X64_ELSE(2,4); j++) { *bufptr++ = CXT_XMM(cxt, i)->reg[j]; } } } else { /* skip xmm slots */ bufptr += XMM_SLOTS_SIZE/sizeof(*bufptr); } *bufptr++ = cxt->CXT_XFLAGS; *bufptr++ = cxt->CXT_XIP; ASSERT((char *)bufptr - (char *)buf == sizeof(dr_mcontext_t)); *bufptr++ = (ptr_uint_t)load_dynamo_code; *bufptr++ = SIZE_OF_LOAD_DYNAMO; nbytes = sizeof(dr_mcontext_t) + 2*sizeof(reg_t); cxt->CXT_XSP -= nbytes; #ifdef X64 /* We need xsp to be aligned prior to each call, but we can only pad * before the context as all later users assume the info they need is * at TOS. */ cxt->CXT_XSP = ALIGN_BACKWARD(cxt->CXT_XSP, XMM_ALIGN); #endif if (!nt_write_virtual_memory(phandle, (LPVOID)cxt->CXT_XSP, buf, nbytes, &nbytes)) { display_error("WriteMemory failed"); goto error; } /* push the address of the DYNAMORIO_ENTRY string on the app's stack */ cxt->CXT_XSP -= XSP_SZ; if (!nt_write_virtual_memory(phandle, (LPVOID)cxt->CXT_XSP, &dynamo_entry_esp, sizeof(dynamo_entry_esp), &nbytes)) { display_error("WriteMemory failed"); goto error; } /* push the address of GetProcAddress on the app's stack */ ASSERT(addr_getprocaddr); addr = addr_getprocaddr; cxt->CXT_XSP -= XSP_SZ; if (!nt_write_virtual_memory(phandle, (LPVOID)cxt->CXT_XSP, &addr, sizeof(addr), &nbytes)) { display_error("WriteMemory failed"); goto error; } /* push the address of the dynamorio_path string on the app's stack */ cxt->CXT_XSP -= XSP_SZ; if (!nt_write_virtual_memory(phandle, (LPVOID)cxt->CXT_XSP, &dynamo_path_esp, sizeof(dynamo_path_esp), &nbytes)) { display_error("WriteMemory failed"); goto error; } /* push the address of LoadLibraryA on the app's stack */ ASSERT(addr_loadlibrarya); addr = addr_loadlibrarya; cxt->CXT_XSP -= XSP_SZ; if (!nt_write_virtual_memory(phandle, (LPVOID)cxt->CXT_XSP, &addr, sizeof(addr), &nbytes)) { display_error("WriteMemory failed"); goto error; } #ifdef LOAD_DYNAMO_DEBUGBREAK /* push the address of DebugBreak on the app's stack */ ASSERT(addr_debugbreak); addr = addr_debugbreak; cxt->CXT_XSP -= XSP_SZ; if (!nt_write_virtual_memory(phandle, (LPVOID)cxt->CXT_XSP, &addr, sizeof(addr), &nbytes)) { display_error("WriteMemory failed"); goto error; } #endif /* make the code R-X now */ if (!nt_remote_protect_virtual_memory(phandle, load_dynamo_code, SIZE_OF_LOAD_DYNAMO, PAGE_EXECUTE_READ, &old_prot)) { display_error("Failed to make injection code R-X"); goto error; } ASSERT(old_prot == PAGE_EXECUTE_READWRITE); /* now change Eip to point to the entry point of load_dynamo(), so that when we resume, load_dynamo is invoked automatically */ cxt->CXT_XIP = (ptr_uint_t)load_dynamo_code; cxt->CXT_XFLAGS = 0; if (thandle != NULL) { if (!NT_SUCCESS(nt_set_context(thandle, cxt))) { display_error("SetThreadContext failed"); goto error; } } success = TRUE; } error: /* we do not recover any changes in the child's address space */ return success; }