* Copyright (c) 2012-2021 Google, Inc. All rights reserved.
* Copyright (c) 2003-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.
*/
* inject_shared.c
*
* Facilities shared between the core library, the preinject library
* and the drinject executable.
*
* getting parameters from registry keys
* rununderdr/runall logic
* and some debugging support
*
*/
#include "configure.h"
#ifndef NOT_DYNAMORIO_CORE
# include "../globals.h"
# include <windows.h>
# include <tchar.h>
# include <stdio.h>
# include "ntdll.h"
# ifdef DEBUG
# include "../moduledb.h"
# endif
# ifdef assert
# undef assert
# endif
# define assert assert_no_good_use_ASSERT_instead
extern void
d_r_internal_error(const char *file, int line, const char *msg);
# ifdef DEBUG
extern void
display_error(char *msg);
# ifdef NOT_DYNAMORIO_CORE_PROPER
# define display_warning display_error
# ifdef ASSERT
# undef ASSERT
# endif
# ifdef INTERNAL
# define ASSERT(x) \
if (!(x)) \
d_r_internal_error(__FILE__, __LINE__, #x)
# else
# define ASSERT(x) \
if (!(x)) \
d_r_internal_error(__FILE__, __LINE__, "")
# endif
# elif !defined(NOT_DYNAMORIO_CORE)
# define display_warning SYSLOG_INTERNAL_WARNING
# endif
# else
# define ASSERT(x) ((void)0)
# define display_error(msg)
# define display_warning(msg)
# endif
# define MAX_RUNVALUE_LENGTH 12
# ifdef NOT_DYNAMORIO_CORE_PROPER
# define VERBOSE 0
# else
# define VERBOSE 0
# endif
typedef enum {
REGISTRY_DEFAULT,
REGISTRY_32,
REGISTRY_64,
} reg_platform_t;
# if defined(NOT_DYNAMORIO_CORE_PROPER) && defined(DEBUG)
bool
ignore_assert(const char *assert_stmt, const char *expr)
{
return false;
}
void
report_dynamorio_problem(dcontext_t *unused_dcontext, uint unused_dumpcore_flag,
app_pc unused_exception_addr, app_pc unused_report_ebp,
const char *unused_fmt, ...)
{
* rather its format string */
display_error("ASSERT_CURIOSITY hit - attach a debugger\n");
}
# endif
# if VERBOSE
* utils.c. Only need this to be able to get the app name for the
* display_verbose_message title, but that's nice to have.
*/
static void
notcore_mutex_lock(int *thelock)
{
while (_InterlockedExchange((LONG *)thelock, 1) != 0) {
_mm_pause();
}
}
static void
notcore_mutex_unlock(int *thelock)
{
*thelock = 0;
}
void
display_verbose_message(char *format, ...)
{
char char_msg_buf[512];
wchar_t msg_buf[512];
static bool title_set = false;
static bool title_set_in_progress = false;
static wchar_t title_buf[MAX_PATH + 64];
static int title_lock;
static uint title_sz;
wchar_t *title = title_buf;
uint msg_sz;
size_t written;
va_list ap;
va_start(ap, format);
if (!title_set) {
* reads calling this routine
*/
if (title_set_in_progress) {
title = L"<title set in progress>";
} else {
notcore_mutex_lock(&title_lock);
if (!title_set) {
title_set_in_progress = true;
title_sz = _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);
if (title_sz < 0)
title_sz = BUFFER_SIZE_ELEMENTS(title_buf) - 1;
title_set_in_progress = false;
title_set = true;
}
notcore_mutex_unlock(&title_lock);
}
}
vsnprintf(char_msg_buf, BUFFER_SIZE_ELEMENTS(char_msg_buf), format, ap);
NULL_TERMINATE_BUFFER(char_msg_buf);
msg_sz = _snwprintf(msg_buf, BUFFER_SIZE_ELEMENTS(msg_buf), L"%hs", char_msg_buf);
NULL_TERMINATE_BUFFER(msg_buf);
if (msg_sz < 0)
msg_sz = BUFFER_SIZE_ELEMENTS(msg_buf) - 1;
write_file(STDERR, title_buf, title_sz * sizeof(title_buf[0]), NULL, &written);
write_file(STDERR, msg_buf, msg_sz * sizeof(msg_buf[0]), NULL, &written);
write_file(STDERR, "\n", sizeof("\n"), NULL, &written);
nt_messagebox(msg_buf, title);
}
# endif
* or NULL if can't find */
const char *
double_strrchr(const char *string, char c1, char c2)
{
const char *ret = NULL;
while (*string != '\0') {
if (*string == c1 || *string == c2) {
ret = string;
}
string++;
}
return ret;
}
int
wchar_to_char(char *cdst, size_t buflen, PCWSTR wide_src, size_t bytelen)
{
int res;
ssize_t wlen = utf16_to_utf8_size(wide_src, buflen, NULL);
if (wlen < 0 || (size_t)wlen >= buflen) {
cdst[0] = '\0';
return 0;
}
res = snprintf(cdst, buflen, "%.*ls", wlen, wide_src);
cdst[wlen] = '\0';
ASSERT(strlen(cdst) < buflen);
return res;
}
* registry key.
* Input: keyname - fully qualified name of the registry key to
* which valuename belongs to.
* valuename - name of the value for which the data is to be set.
* value - data that is to be set for valuename.
* Output: None.
* Return value:SET_PARAMETER_SUCCESS if valuename is changed/created.
* SET_PARAMETER_FAILURE if keyname is invalid or if valuename
* can't be changed or created.
* Notes: If valuename doesn't exist, it will be created with value as
* data. Implemented as part of case 3702.
*/
static int
set_registry_parameter(const wchar_t *keyname, const wchar_t *valuename,
const char *value)
{
HANDLE hkey;
int size, res;
wchar_t wvalue[MAX_REGISTRY_PARAMETER];
size = _snwprintf(wvalue, BUFFER_SIZE_ELEMENTS(wvalue), L"%S", value);
NULL_TERMINATE_BUFFER(wvalue);
ASSERT(size >= 0 && size < BUFFER_SIZE_ELEMENTS(wvalue));
hkey = reg_open_key(keyname, KEY_SET_VALUE);
if (NULL != hkey) {
res = reg_set_key_value(hkey, valuename, wvalue);
if (res) {
* power cycle will throw out the changes. See case 4138.
*/
reg_flush_key(hkey);
reg_close_key(hkey);
return SET_PARAMETER_SUCCESS;
}
reg_close_key(hkey);
}
return SET_PARAMETER_FAILURE;
}
static int
get_registry_parameter(PCWSTR keyname, PCWSTR valuename, char *value,
int maxlen ,
reg_platform_t whichreg)
{
int retval = GET_PARAMETER_FAILURE;
KEY_VALUE_PARTIAL_INFORMATION *kvpi;
reg_query_value_result_t result;
* though would be kind of ugly for the caller */
char buf_array[sizeof(KEY_VALUE_PARTIAL_INFORMATION) +
sizeof(wchar_t) * (MAX_REGISTRY_PARAMETER + 1)];
* hash lists) use the local array; for process control hash lists use a
* dynamic buffer. This messy code was needed to handle case 9252.
*/
char *buf = (char *)&buf_array;
int alloc_size = sizeof(buf_array);
# if !defined(NOT_DYNAMORIO_CORE_PROPER) && !defined(NOT_DYNAMORIO_CORE)
* Even if core is using, can't use this till heap is initialized. */
if (maxlen > MAX_REGISTRY_PARAMETER && dynamo_heap_initialized) {
* MAX_REGISTRY_PARAMETER. Case 9252.
*/
# ifdef PROCESS_CONTROL
ASSERT(IS_PROCESS_CONTROL_ON() &&
maxlen == (int)(DYNAMO_OPTION(pc_num_hashes) * (MD5_STRING_LENGTH + 1)));
# endif
alloc_size =
sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(wchar_t) * (maxlen + 1);
buf = heap_alloc(GLOBAL_DCONTEXT, alloc_size HEAPACCT(ACCT_OTHER));
}
# else
ASSERT(maxlen <= MAX_REGISTRY_PARAMETER);
# endif
kvpi = (KEY_VALUE_PARTIAL_INFORMATION *)buf;
result = reg_query_value(
keyname, valuename, KeyValuePartialInformation, kvpi, alloc_size,
whichreg == REGISTRY_64 ? KEY_WOW64_64KEY
: (whichreg == REGISTRY_32 ? KEY_WOW64_32KEY : 0));
if (result == REG_QUERY_SUCCESS) {
snprintf(value, maxlen - 1, "%*ls", kvpi->DataLength / sizeof(wchar_t) - 1,
(wchar_t *)kvpi->Data);
value[maxlen - 1] = '\0';
# if VERBOSE
display_verbose_message("got registry value of %hs for value %ls in key %ls",
value, valuename, keyname);
# endif VERBOSE
retval = GET_PARAMETER_SUCCESS;
}
# if !defined(NOT_DYNAMORIO_CORE_PROPER) && !defined(NOT_DYNAMORIO_CORE)
else if (result == REG_QUERY_BUFFER_TOO_SMALL) {
snprintf(buf, alloc_size, "%ls - %ls", keyname, valuename);
buf[(alloc_size / sizeof(wchar_t)) - 1] = '\0';
retval = GET_PARAMETER_BUF_TOO_SMALL;
SYSLOG_NO_OPTION_SYNCH(SYSLOG_ERROR, ERROR_REGISTRY_PARAMETER_TOO_LONG, 3,
get_application_name(), get_application_pid(), buf);
}
# endif
# if VERBOSE
if (IS_GET_PARAMETER_FAILURE(retval)) {
display_verbose_message("didn't get registry value %ls in key %ls", valuename,
keyname);
}
# endif
# if !defined(NOT_DYNAMORIO_CORE_PROPER) && !defined(NOT_DYNAMORIO_CORE)
if (maxlen > MAX_REGISTRY_PARAMETER && dynamo_heap_initialized)
heap_free(GLOBAL_DCONTEXT, buf, alloc_size HEAPACCT(ACCT_OTHER));
# endif
return retval;
}
* returns
* -1 if can't read remote process,
* 0 if PEB.Ldr is NULL
* 1 if PEB.Ldr has been initialized
*/
int
get_remote_process_ldr_status(HANDLE process_handle)
{
size_t nbytes;
size_t res;
PEB peb;
LPVOID peb_base = get_peb(process_handle);
res = nt_read_virtual_memory(process_handle, (LPVOID)peb_base, &peb, sizeof(peb),
&nbytes);
if (!res) {
* FIXME - could dup the handle and retry */
return -1;
}
if (peb.LoaderData != NULL) {
return 1;
} else {
return 0;
}
}
static bool
is_windows_version_vista_plus(void);
* this assumes it will be called on process initialization, when
* the PEB apparently uses offsets in the ProcessParameters block
* instead of direct pointers.
* image name and cmdline combined into one call to reduce
* read process memory calls (whether this is actually true depends on
* usage)
* Handles both 32-bit and 64-bit remote processes.
*/
void
get_process_imgname_cmdline(HANDLE process_handle, wchar_t *image_name,
int max_image_wchars, wchar_t *command_line,
int max_cmdl_wchars)
{
size_t nbytes;
int res;
int len;
* 64-bit WOW64 PEB.
*/
uint64 peb_base = get_peb_maybe64(process_handle);
union {
uint64 params_ptr_64;
uint params_ptr_32;
} params_ptr;
bool peb_is_32 = IF_X64_ELSE(false, is_32bit_process(process_handle));
uint64 param_location;
RTL_USER_PROCESS_PARAMETERS params = { 0 };
# ifndef X64
RTL_USER_PROCESS_PARAMETERS_64 params64 = { 0 };
# endif
if (image_name != NULL)
image_name[0] = L'\0';
if (command_line != NULL)
command_line[0] = L'\0';
res = read_remote_memory_maybe64(
process_handle,
peb_base +
(peb_is_32 ? X86_PROCESS_PARAM_PEB_OFFSET : X64_PROCESS_PARAM_PEB_OFFSET),
¶ms_ptr, sizeof(params_ptr), &nbytes);
if (!res || nbytes != sizeof(params_ptr)) {
display_error("Warning: could not read process memory!");
return;
}
uint64 params_base = peb_is_32 ? params_ptr.params_ptr_32 : params_ptr.params_ptr_64;
res = read_remote_memory_maybe64(
process_handle, params_base,
IF_NOT_X64(!peb_is_32 ? (void *)¶ms64 :)(void *) & params,
IF_NOT_X64(!peb_is_32 ? sizeof(params64) :) sizeof(params), &nbytes);
if (!res || nbytes != (IF_NOT_X64(!peb_is_32 ? sizeof(params64) :) sizeof(params))) {
display_error("Warning: could not read process memory!");
return;
}
* from the beginning of the ProcessParameters structure during
* process init on os versions prior to Vista */
if (image_name) {
if (is_windows_version_vista_plus()) {
param_location = IF_NOT_X64(!peb_is_32 ? params64.ImagePathName.u.Buffer64
:)(uint64) params.ImagePathName.Buffer;
} else {
param_location = IF_NOT_X64(!peb_is_32 ? params64.ImagePathName.u.Buffer64 :)(
(uint64)params.ImagePathName.Buffer + params_base);
}
len = IF_NOT_X64(!peb_is_32 ? params64.ImagePathName.Length :)
params.ImagePathName.Length;
if (len > 2 * (max_image_wchars - 1))
len = 2 * (max_image_wchars - 1);
res = read_remote_memory_maybe64(process_handle, param_location, image_name, len,
&nbytes);
if (!res) {
len = 0;
display_warning("Warning: could not read image name from PEB");
}
image_name[len / 2] = 0;
}
if (command_line) {
if (is_windows_version_vista_plus()) {
param_location = IF_NOT_X64(!peb_is_32 ? params64.CommandLine.u.Buffer64
:)(uint64) params.CommandLine.Buffer;
} else {
param_location = IF_NOT_X64(!peb_is_32 ? params64.CommandLine.u.Buffer64 :)(
uint64)(params.CommandLine.Buffer + params_base);
}
len = IF_NOT_X64(!peb_is_32 ? params64.CommandLine.Length :)
params.CommandLine.Length;
if (len > 2 * (max_cmdl_wchars - 1))
len = 2 * (max_cmdl_wchars - 1);
res = read_remote_memory_maybe64(process_handle, param_location, command_line,
len, &nbytes);
if (!res) {
len = 0;
display_warning("Warning: could not read cmdline from PEB");
}
command_line[len / 2] = 0;
}
return;
}
static inline int
get_rununder_value(const char *runvalue)
{
easier to work on hex.
FIXME: share the logic in parse_uint() from after options.c -r 1.4
to allow both hex and decimal values
*/
return atoi(runvalue);
}
#else
# include <windows.h>
# include <globals_shared.h>
# define DIRSEP '\\'
# define ALT_DIRSEP '/'
#endif
const wchar_t *
double_wcsrchr(const wchar_t *string, wchar_t c1, wchar_t c2)
{
const wchar_t *ret = NULL;
while (*string != L'\0') {
if (*string == c1 || *string == c2) {
ret = string;
}
string++;
}
return ret;
}
const wchar_t *
w_get_short_name(const wchar_t *exename)
{
const wchar_t *exe;
exe = double_wcsrchr(exename, L_EXPAND_LEVEL(DIRSEP), L_EXPAND_LEVEL(ALT_DIRSEP));
if (exe == NULL)
exe = exename;
else
exe++;
return exe;
}
We derive an application specific name to differentiate instances
based on the canonicalized commandline of the process.
Originally we did that only for svchost, but now it is for anything
marked with RUNUNDER_COMMANDLINE_DISPATCH.
The current scheme asks for adding all alphanumeric characters from
the original commandline after skipping the executable name itself.
If no_strip (e.g. RUNUNDER_COMMANDLINE_NO_STRIP is set) then the
first argument on the commandline is not stripped. This is for
backwards compatibility where we stripped the -k argument in the
svchost groups, e.g. 'svchost -k rpcss'
max_derived_length is in number of elements, so most callers should
simply use BUFFER_SIZE_ELEMENTS on the buffer passed as derived_name
Returns 1 if group command line qualifier present
(normally should be 1 if called for matching executables,
but on an empty commandline we do return 0)
*/
bool
get_commandline_qualifier(const wchar_t *command_line, wchar_t *derived_name,
uint max_derived_length , bool no_strip)
{
wchar_t *derived_ptr = derived_name;
wchar_t *derived_end = derived_name + max_derived_length - 1;
const wchar_t *cmdptr;
if (command_line[0] == L'"') {
cmdptr = wcschr(command_line + 1, L'"');
if (cmdptr)
cmdptr++;
} else {
cmdptr = wcschr(command_line, L' ');
}
if (!cmdptr) {
*derived_name = L'\0';
return 0;
}
do {
* "svchost.exe -k netsvcs"
*/
while (*cmdptr && !iswalnum(*cmdptr))
cmdptr++;
if (!*cmdptr)
break;
if (!no_strip) {
while (*cmdptr && iswalnum(*cmdptr))
cmdptr++;
no_strip = true;
if (!*cmdptr)
break;
}
while (*cmdptr && iswalnum(*cmdptr)) {
if (derived_ptr == derived_end)
goto out;
*derived_ptr++ = *cmdptr++;
}
* "/test", since currently there is no need to be that punctual.
*/
} while (*cmdptr);
out:
*derived_ptr = L'\0';
if (derived_ptr == derived_name)
return 0;
else
return 1;
}
#ifndef NOT_DYNAMORIO_CORE
typedef enum {
QUALIFIED_FULL_NAME,
QUALIFIED_SHORT_NAME,
UNQUALIFIED_FULL_NAME,
UNQUALIFIED_SHORT_NAME,
} qualified_name_type_t;
# define NAME_TYPE_IS_UNQUALIFIED(name_type) \
(name_type == UNQUALIFIED_FULL_NAME || name_type == UNQUALIFIED_SHORT_NAME)
# define NAME_TYPE_IS_SHORT(name_type) \
(name_type == QUALIFIED_SHORT_NAME || name_type == UNQUALIFIED_SHORT_NAME)
We test the rununder_mask of a process and if it specifies that a
fully qualified name is needed we lookup at the fully qualified
location first.
See comments in get_commandline_qualifier() and case 1324 for a list
of executables which we further distinguish based on commandline.
If we hardcode only a few more entries we could then avoid this
buffer and registry access altogether, yet for generality sake
we'll do this.
FIXME[inefficiency]: The systemwide_should_inject() thus ends up
checking twice for RUNUNDERDR, but the OS is good at caching this.
*/
static uint
commandline_qualifier_needed(const wchar_t *process_short_name, reg_platform_t whichreg)
{
char runvalue[MAX_RUNVALUE_LENGTH];
uint ret_val = 0;
int res;
wchar_t app_specific_base[MAXIMUM_PATH] = DYNAMORIO_REGISTRY_BASE L"\\";
passed by get_subkey_parameter(,,QUALIFIED_SHORT_NAME) and in
fact have the same contents as we'll now prepare in a different buffer.
Other callers of get_process_qualified_name() do not have an
extra buffer information prepared. For now we'll allocate another buffer.
*/
wcsncat(app_specific_base, process_short_name, BUFFER_ROOM_LEFT_W(app_specific_base));
NULL_TERMINATE_BUFFER(app_specific_base);
res = get_registry_parameter(app_specific_base, L_DYNAMORIO_VAR_RUNUNDER, runvalue,
sizeof(runvalue), whichreg);
if (IS_GET_PARAMETER_SUCCESS(res)) {
ret_val = get_rununder_value(runvalue) &
(RUNUNDER_COMMANDLINE_DISPATCH | RUNUNDER_COMMANDLINE_NO_STRIP);
}
return ret_val;
}
* requested in name_type) into the user provided buffer. If short is requested in
* name_type only the executable name portion is added, otherwise the full path is used.
*
* max_exename_length is in number of elements, so most callers should
* simply use BUFFER_SIZE_ELEMENTS on the buffer passed as w_exename
*
* If process_handle is NULL we read from the local PEB entries. */
static void
get_process_qualified_name(HANDLE process_handle, wchar_t *w_exename,
size_t max_exename_length, qualified_name_type_t name_type,
reg_platform_t whichreg)
{
const wchar_t *full_name;
const wchar_t *short_name;
uint commandline_dispatch = 0;
PEB *own_peb;
wchar_t other_process_img_or_cmd[MAXIMUM_PATH];
data, we need to check stack depths for the follow children
case. Although not needed when reading current process this
function should be called only at startup with known stack layout.
*/
if (process_handle == NULL) {
own_peb = get_own_peb();
ASSERT(own_peb && own_peb->ProcessParameters);
ASSERT(own_peb->ProcessParameters->ImagePathName.Buffer);
full_name = get_process_param_buf(
own_peb->ProcessParameters, own_peb->ProcessParameters->ImagePathName.Buffer);
} else {
own_peb = NULL;
get_process_imgname_cmdline(
process_handle, other_process_img_or_cmd ,
BUFFER_SIZE_ELEMENTS(other_process_img_or_cmd), NULL, 0);
full_name = other_process_img_or_cmd;
}
short_name = w_get_short_name(full_name);
wcsncpy(w_exename, (NAME_TYPE_IS_SHORT(name_type)) ? short_name : full_name,
max_exename_length);
w_exename[max_exename_length - 1] = 0;
if (NAME_TYPE_IS_UNQUALIFIED(name_type)) {
ASSERT_CURIOSITY(wcslen(w_exename) < max_exename_length - 1);
return;
}
commandline_dispatch = commandline_qualifier_needed(short_name, whichreg);
if (TEST(RUNUNDER_COMMANDLINE_DISPATCH, commandline_dispatch)) {
wchar_t cmdline_qualifier[MAXIMUM_PATH] = L"-";
no other buffer is needed at all, but for the sake of
readability keeping this extra */
const wchar_t *process_commandline;
if (process_handle == NULL) {
ASSERT(own_peb->ProcessParameters->CommandLine.Buffer);
process_commandline =
get_process_param_buf(own_peb->ProcessParameters,
own_peb->ProcessParameters->CommandLine.Buffer);
} else {
get_process_imgname_cmdline(process_handle, NULL, 0, other_process_img_or_cmd,
BUFFER_SIZE_ELEMENTS(other_process_img_or_cmd));
process_commandline = other_process_img_or_cmd;
}
get_commandline_qualifier(
process_commandline, cmdline_qualifier + 1,
BUFFER_SIZE_ELEMENTS(cmdline_qualifier) - 1,
TEST(RUNUNDER_COMMANDLINE_NO_STRIP, commandline_dispatch));
* no qualifier was found (we still want the '-' to separate out the registry
* entries xref 9119) */
wcsncat(w_exename, cmdline_qualifier, max_exename_length - wcslen(w_exename) - 1);
}
w_exename[max_exename_length - 1] = 0;
ASSERT_CURIOSITY(wcslen(w_exename) < max_exename_length - 1);
}
* it might not be safe to read the process parameters later. */
* necessary */
const wchar_t *
get_own_qualified_name()
{
static wchar_t full_qualified_name[MAXIMUM_PATH];
if (full_qualified_name[0] == L'\0') {
get_process_qualified_name(NULL, full_qualified_name,
BUFFER_SIZE_ELEMENTS(full_qualified_name),
QUALIFIED_FULL_NAME, REGISTRY_DEFAULT);
ASSERT(full_qualified_name[0] != L'\0');
}
return full_qualified_name;
}
const wchar_t *
get_own_unqualified_name()
{
static wchar_t full_unqualified_name[MAXIMUM_PATH];
if (full_unqualified_name[0] == L'\0') {
get_process_qualified_name(NULL, full_unqualified_name,
BUFFER_SIZE_ELEMENTS(full_unqualified_name),
UNQUALIFIED_FULL_NAME, REGISTRY_DEFAULT);
ASSERT(full_unqualified_name[0] != L'\0');
}
return full_unqualified_name;
}
* necessary */
const wchar_t *
get_own_short_qualified_name()
{
static wchar_t short_qualified_name[MAXIMUM_PATH];
if (!short_qualified_name[0]) {
get_process_qualified_name(NULL, short_qualified_name,
BUFFER_SIZE_ELEMENTS(short_qualified_name),
QUALIFIED_SHORT_NAME, REGISTRY_DEFAULT);
ASSERT(short_qualified_name[0]);
}
return short_qualified_name;
}
const wchar_t *
get_own_short_unqualified_name()
{
static wchar_t short_unqualified_name[MAXIMUM_PATH];
if (!short_unqualified_name[0]) {
get_process_qualified_name(NULL, short_unqualified_name,
BUFFER_SIZE_ELEMENTS(short_unqualified_name),
UNQUALIFIED_SHORT_NAME, REGISTRY_DEFAULT);
ASSERT(short_unqualified_name[0]);
}
return short_unqualified_name;
}
# ifdef PARAMS_IN_REGISTRY
* but when PARAMS_IN_REGISTRY is defined we support the old registry scheme
*/
static int
get_subkey_parameter(HANDLE process_handle, const wchar_t *uname, char *value, int maxlen,
bool use_qualified, reg_platform_t whichreg)
{
int retval;
wchar_t app_specific_base[MAXIMUM_PATH] = DYNAMORIO_REGISTRY_BASE L"\\";
ASSERT(wcslen(app_specific_base) < BUFFER_SIZE_ELEMENTS(app_specific_base));
if (process_handle == NULL) {
wcsncat(app_specific_base,
use_qualified ? get_own_short_qualified_name()
: get_own_short_unqualified_name(),
BUFFER_ROOM_LEFT_W(app_specific_base));
} else {
we just append to the current one */
get_process_qualified_name(
process_handle, app_specific_base + wcslen(app_specific_base),
BUFFER_ROOM_LEFT_W(app_specific_base),
use_qualified ? QUALIFIED_SHORT_NAME : UNQUALIFIED_SHORT_NAME, whichreg);
}
NULL_TERMINATE_BUFFER(app_specific_base);
retval = get_registry_parameter(app_specific_base, uname, value, maxlen, whichreg);
# if VERBOSE
display_verbose_message("gskp: %ls -- %ls\n\"%hs\"", app_specific_base, uname,
IS_GET_PARAMETER_SUCCESS(retval) ? value : "");
# endif
if (IS_GET_PARAMETER_FAILURE(retval)) {
HANDLE hkey = reg_open_key(app_specific_base, KEY_READ);
if (hkey == NULL) {
retval = GET_PARAMETER_NOAPPSPECIFIC;
}
reg_close_key(hkey);
}
return retval;
}
* resulting value. If not successful leaves original buffer contents intact.
*
* The same parameter is looked up first in the application specific registry
* subtree and then in the global registry tree. We no longer look for
* environment variables.
*/
static int
get_process_parameter_internal(HANDLE phandle, const wchar_t *name, char *value,
int maxlen, bool use_qualified, reg_platform_t whichreg)
{
int err, err2;
# if VERBOSE
display_verbose_message("get_parameter:%ls", name);
# endif VERBOSE
err = get_subkey_parameter(phandle, name, value, maxlen, use_qualified, whichreg);
if (err != GET_PARAMETER_SUCCESS) {
err2 = get_registry_parameter(DYNAMORIO_REGISTRY_BASE, name, value, maxlen,
whichreg);
if (IS_GET_PARAMETER_SUCCESS(err2)) {
* GET_PARAMETER_NOAPPSPECIFIC; otherwise, if there's a
* global, return success. */
if (err != GET_PARAMETER_NOAPPSPECIFIC)
err = GET_PARAMETER_SUCCESS;
} else if (err == GET_PARAMETER_BUF_TOO_SMALL ||
err2 == GET_PARAMETER_BUF_TOO_SMALL) {
err = GET_PARAMETER_BUF_TOO_SMALL;
} else
err = GET_PARAMETER_FAILURE;
}
return err;
}
int
get_process_parameter(HANDLE phandle, const wchar_t *name, char *value, int maxlen)
{
return get_process_parameter_internal(phandle, name, value, maxlen, true ,
REGISTRY_DEFAULT);
}
int
d_r_get_parameter(const wchar_t *name, char *value, int maxlen)
{
return get_process_parameter_internal(NULL, name, value, maxlen, true ,
REGISTRY_DEFAULT);
}
int
get_parameter_ex(const wchar_t *name, char *value, int maxlen, bool ignore_cache)
{
return d_r_get_parameter(name, value, maxlen);
}
# ifdef X64
int
get_parameter_32(const wchar_t *name, char *value, int maxlen)
{
return get_process_parameter_internal(NULL, name, value, maxlen, true ,
REGISTRY_32);
}
# else
int
get_parameter_64(const wchar_t *name, char *value, int maxlen)
{
return get_process_parameter_internal(NULL, name, value, maxlen, true ,
REGISTRY_64);
}
# endif
* for ex. would get parameter from svchost.exe instead of svchost.exe-netsvc */
int
get_unqualified_parameter(const wchar_t *name, char *value, int maxlen)
{
return get_process_parameter_internal(NULL, name, value, maxlen, false ,
REGISTRY_DEFAULT);
}
* value name should belong to the registry key associated with the
* executable for the given process under DYNAMORIO_REGISTRY_BASE.
* Input: phandle - handle for the process whose registry value is to be
* changed.
* name - value name for the corresponding DR parameter.
* value - value to which the registry parameter should be set to.
* Output: None.
* Return value:SET_PARAMETER_SUCCESS if name is changed.
* SET_PARAMETER_FAILURE if not.
* Notes: If called with an incorrect parameter name, a value with that
* parameter name will be created in the registry for the current
* executable name under DYNAMORIO_REGISTRY_BASE. Implemented as
* part of case 3702.
*/
int
set_process_parameter(HANDLE phandle, const wchar_t *name, const char *value)
{
wchar_t app_specific_base[MAXIMUM_PATH] = DYNAMORIO_REGISTRY_BASE L"\\";
* terminate because windows compiler doesn't complain or null terminate
* the array if the constant is longer than the array size!
*/
NULL_TERMINATE_BUFFER(app_specific_base);
ASSERT(wcslen(app_specific_base) < BUFFER_SIZE_ELEMENTS(app_specific_base) - 1);
if (phandle == NULL) {
wcsncat(app_specific_base, get_own_short_qualified_name(),
BUFFER_ROOM_LEFT_W(app_specific_base));
} else {
we just append to the current one */
get_process_qualified_name(phandle, app_specific_base + wcslen(app_specific_base),
BUFFER_ROOM_LEFT_W(app_specific_base),
QUALIFIED_SHORT_NAME,
false );
}
NULL_TERMINATE_BUFFER(app_specific_base);
ASSERT(wcslen(app_specific_base) < BUFFER_SIZE_ELEMENTS(app_specific_base) - 1);
return set_registry_parameter(app_specific_base, name, value);
}
# else
int
get_parameter_from_registry(const wchar_t *name, char *value,
int maxlen )
{
return get_registry_parameter(DYNAMORIO_REGISTRY_BASE, name, value, maxlen,
REGISTRY_DEFAULT);
}
# ifndef NOT_DYNAMORIO_CORE
static int
get_process_parameter_ex(HANDLE phandle, const char *name, char *value, int maxlen,
bool consider_1config)
{
wchar_t short_unqual_name[MAXIMUM_PATH];
char appname[MAXIMUM_PATH];
bool app_specific, from_1config;
process_id_t pid;
if (phandle == NULL) {
# if !defined(NOT_DYNAMORIO_CORE) && !defined(NOT_DYNAMORIO_CORE_PROPER)
return d_r_get_parameter(name, value, maxlen);
# else
pid = process_id_from_handle(NT_CURRENT_PROCESS);
# endif
} else
pid = process_id_from_handle(phandle);
get_process_qualified_name(phandle, short_unqual_name,
BUFFER_SIZE_ELEMENTS(short_unqual_name),
UNQUALIFIED_SHORT_NAME, REGISTRY_DEFAULT);
NULL_TERMINATE_BUFFER(short_unqual_name);
snprintf(appname, BUFFER_SIZE_ELEMENTS(appname), "%ls", short_unqual_name);
NULL_TERMINATE_BUFFER(appname);
if (!get_config_val_other_app(appname, pid, DR_PLATFORM_DEFAULT, name, value, maxlen,
&app_specific, NULL, &from_1config) ||
(!consider_1config && from_1config))
return GET_PARAMETER_FAILURE;
if (!app_specific)
return GET_PARAMETER_NOAPPSPECIFIC;
return GET_PARAMETER_SUCCESS;
}
int
get_process_parameter(HANDLE phandle, const char *name, char *value, int maxlen)
{
return get_process_parameter_ex(phandle, name, value, maxlen, true);
}
# endif
# ifndef X64
int
get_parameter_64(const char *name, char *value, int maxlen)
{
return get_config_val_other_arch(name, value, maxlen, NULL, NULL, NULL);
}
# endif
# endif
static bool
is_nt_or_custom_safe_mode()
{
char start_options[MAX_REGISTRY_PARAMETER];
int retval;
retval = get_registry_parameter(L"\\Registry\\Machine\\System\\CurrentControlSet"
L"\\Control",
L"SystemStartOptions", start_options,
sizeof(start_options), REGISTRY_DEFAULT);
if (IS_GET_PARAMETER_SUCCESS(retval)) {
char safemarker_override_buf[MAX_PARAMNAME_LENGTH];
char *safemarker = "SOS";
* distinction in is_safe_mode() between MINIMAL and NETWORK
* we'd need to check for SAFEBOOT:MINIMAL, since SOS will be
* set for SAFEBOOT:NETWORK as well.
* Note: There is no app specific override for safe boot, just global.
*/
retval = get_registry_parameter(
DYNAMORIO_REGISTRY_BASE, L_DYNAMORIO_VAR_SAFEMARKER, safemarker_override_buf,
sizeof(safemarker_override_buf),
* is no wow64 */
REGISTRY_DEFAULT);
if (IS_GET_PARAMETER_SUCCESS(retval))
safemarker = safemarker_override_buf;
* converts the boot.ini options in all CAPS, so the value in
* DYNAMORIO_SAFEMARKER should always be all CAPS as well
*/
if (strstr(start_options, safemarker)) {
return true;
}
}
return false;
}
* which we cannot use in drpreinject.dll
* otherwise this should be equivalent to get_os_version() == WINDOWS_VERSION_NT
*/
static inline bool
is_windows_version_nt()
{
PEB *peb = get_own_peb();
ASSERT(peb->OSPlatformId == VER_PLATFORM_WIN32_NT);
return (peb->OSMajorVersion == 4);
}
static bool
is_windows_version_vista_plus()
{
PEB *peb = get_own_peb();
ASSERT(peb->OSPlatformId == VER_PLATFORM_WIN32_NT);
return (peb->OSMajorVersion >= 6);
}
bool
is_safe_mode()
{
char buf[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(uint)];
KEY_VALUE_PARTIAL_INFORMATION *kvpi = (KEY_VALUE_PARTIAL_INFORMATION *)buf;
enum {
MINIMAL = 1,
NETWORK = 2,
};
when running with == NETWORK, we don't want to expose network
listening services to risks, and to disable us administrators
only need the local console */
if (reg_query_value(L"\\Registry\\Machine\\System\\CurrentControlSet"
L"\\Control\\SafeBoot\\Option",
L"OptionValue", KeyValuePartialInformation, kvpi, sizeof(buf),
0) == REG_QUERY_SUCCESS) {
if (*(uint *)(kvpi->Data) == MINIMAL) {
return true;
}
}
* allow this even on Win2000 so that /DISABLESC can be passed on
* the command line.
*/
if (is_windows_version_nt()) {
return is_nt_or_custom_safe_mode();
}
return false;
}
* checks wow64; if x64, checks x64. we don't support cross-arch follow-children
* (PR 254193).
*/
bool
systemwide_inject_enabled()
{
char appinit[MAXIMUM_PATH];
int retval;
retval = get_registry_parameter(
INJECT_ALL_HIVE_L INJECT_ALL_KEY_L, INJECT_ALL_SUBKEY_L, appinit, sizeof(appinit),
REGISTRY_DEFAULT );
if (IS_GET_PARAMETER_SUCCESS(retval)) {
if ((strstr(appinit, INJECT_DLL_NAME) != NULL ||
strstr(appinit, INJECT_DLL_8_3_NAME) != NULL))
return 1;
}
return 0;
}
# ifdef PARAMS_IN_REGISTRY
* the DYNAMORIO_VAR_CMDLINE parameter.
* if callers need REGISTRY_{32,64} they should add that parameter: not needed currently.
*/
static int
check_commandline_match(HANDLE process)
{
char process_cmdline[MAX_PATH];
wchar_t w_process_cmdline[MAX_PATH];
if (process == NULL) {
PEB *peb = get_own_peb();
ASSERT(peb && peb->ProcessParameters);
ASSERT(peb->ProcessParameters->CommandLine.Buffer);
wcsncpy(w_process_cmdline, peb->ProcessParameters->CommandLine.Buffer, MAX_PATH);
w_process_cmdline[MAX_PATH - 1] = L'\0';
} else {
get_process_imgname_cmdline(process, NULL, 0, w_process_cmdline, MAX_PATH);
w_process_cmdline[MAX_PATH - 1] = L'\0';
}
wchar_to_char(process_cmdline, sizeof(process_cmdline), w_process_cmdline,
(wcslen(w_process_cmdline) + 1) * sizeof(wchar_t));
{
* stack space */
char *cmdline_to_match = (char *)w_process_cmdline;
if ((GET_PARAMETER_SUCCESS ==
get_subkey_parameter(process, L_DYNAMORIO_VAR_CMDLINE, cmdline_to_match,
MAX_PATH, true, REGISTRY_DEFAULT)) &&
NULL != strstr(process_cmdline, cmdline_to_match))
return 1;
return 0;
}
}
# endif
* if it's defined in app-specific key, check against RUNUNDER_ON
* if RUNUNDER_ON is set check against RUNUNDER_EXPLICIT
* if set return INJECT_TRUE|INJECT_EXPLICIT else return INJECT_TRUE
* if RUNUNDER_ON is not set return INJECT_EXCLUDED
*
* if no app-specific key, check global key against RUNUNDER_ALL
* if set return INJECT_TRUE else return INJECT_FALSE
*
* if no app-specific key and no global key return INJECT_FALSE
*
* if mask isn't NULL, the DYNAMORIO_RUNUNDER mask is returned in it.
*
* NOTE - if return INJECT_TRUE and !INJECT_EXPLICIT then preinjector should inject if
* systemwide_inject_enabled()
*/
static inject_setting_mask_t
systemwide_should_inject_common(HANDLE process, int *mask, reg_platform_t whichreg,
bool consider_1config)
{
char runvalue[MAX_RUNVALUE_LENGTH];
# ifdef PARAMS_IN_REGISTRY
int retval;
# endif
int rununder_mask, err;
# if VERBOSE
display_verbose_message("systemwide_should_inject");
# endif
# ifdef PARAMS_IN_REGISTRY
err = get_process_parameter_internal(process, L_DYNAMORIO_VAR_RUNUNDER, runvalue,
sizeof(runvalue), true , whichreg);
if (IS_GET_PARAMETER_FAILURE(err))
return INJECT_FALSE;
# else
* changing several get_process_parameter callers who check specific return values,
* we add a new _ex() routine that allows excluding 1config files.
* For syswide we do NOT want to inject if there is a 1config file, to avoid
* double injection.
*/
err = get_process_parameter_ex(process, DYNAMORIO_VAR_RUNUNDER, runvalue,
sizeof(runvalue), consider_1config);
if (IS_GET_PARAMETER_FAILURE(err))
return INJECT_FALSE;
# endif
rununder_mask = get_rununder_value(runvalue);
if (NULL != mask) {
if (IS_GET_PARAMETER_SUCCESS(err))
*mask = rununder_mask;
else
*mask = 0;
}
against runall */
if (err == GET_PARAMETER_NOAPPSPECIFIC) {
if (rununder_mask & RUNUNDER_ALL)
return INJECT_TRUE;
else
return INJECT_FALSE;
} else {
if (!(rununder_mask & RUNUNDER_ON))
return INJECT_EXCLUDED;
else {
* injection technique. */
int inject_mask = INJECT_FALSE;
if (rununder_mask & RUNUNDER_EXPLICIT)
inject_mask |= INJECT_EXPLICIT;
# ifdef PARAMS_IN_REGISTRY
if (rununder_mask & RUNUNDER_COMMANDLINE_MATCH) {
* if the commandline doesn't match and runall is on,
* return INJECT_TRUE
* else return INJECT_FALSE */
if (check_commandline_match(process)) {
inject_mask |= INJECT_TRUE;
} else {
retval = get_registry_parameter(DYNAMORIO_REGISTRY_BASE,
L_DYNAMORIO_VAR_RUNUNDER, runvalue,
sizeof(runvalue), whichreg);
if (IS_GET_PARAMETER_SUCCESS(retval)) {
if (RUNUNDER_ALL == get_rununder_value(runvalue))
inject_mask |= INJECT_TRUE;
}
}
return inject_mask;
} else
# endif
return (inject_mask | INJECT_TRUE);
}
}
}
# ifndef X64
inject_setting_mask_t
systemwide_should_preinject_64(HANDLE process, int *mask)
{
return systemwide_should_inject_common(process, mask, REGISTRY_64, false);
}
# endif
inject_setting_mask_t
systemwide_should_inject(HANDLE process, int *mask)
{
return systemwide_should_inject_common(process, mask, REGISTRY_DEFAULT, true);
}
inject_setting_mask_t
systemwide_should_preinject(HANDLE process, int *mask)
{
return systemwide_should_inject_common(process, mask, REGISTRY_DEFAULT, false);
}
* flag for the registry value DYNAMORIO_RUNUNDER is cleared for
* the given process, so that the application won't start under
* DR during the next invocation.
* Input: process - handle of the process whose registry value should be
* modified.
* rununder_mask - contains the process' DYNAMORIO_RUNUNDER mask.
* Output: None.
* Return value:None.
* Notes: See case 3702.
*/
void
check_for_run_once(HANDLE process, int rununder_mask)
{
# ifdef PARAMS_IN_REGISTRY
int size;
char mask_string[MAX_RUNVALUE_LENGTH];
if (TEST(rununder_mask, RUNUNDER_ONCE)) {
rununder_mask &= ~RUNUNDER_ON;
size =
snprintf(mask_string, BUFFER_SIZE_ELEMENTS(mask_string), "%d", rununder_mask);
NULL_TERMINATE_BUFFER(mask_string);
ASSERT(size >= 0 && size < BUFFER_SIZE_ELEMENTS(mask_string) - 1);
* SYSTEM and Admin users. If another user runs an executable with
* RUNUNDER_ONCE, the core won't turn of RUNUNDER_ON because registry
* write will fail. This is an EV limitation: RUNUNDER_ONCE won't work
* for non privileged processes. Will be fixed in 2.5. See case 4249.
*/
if (set_process_parameter(process, L_DYNAMORIO_VAR_RUNUNDER, mask_string) !=
SET_PARAMETER_SUCCESS) {
* be ASSERT_CURIOSITY. Defining ASSERT_CURIOSITY for core,
* drinject.exe and drpreinject.dll is an ugly redefinition hack;
* better not do it just for case 4249.
*/
display_error("Can't enforce RUNUNDER_ONCE.");
ASSERT_NOT_REACHED();
}
}
# else
# endif
}
#endif
#ifdef UNIT_TEST
int
test(char *name, char *filter, int expected)
{
int res = check_filter(name, filter);
printf("check_filter(\"%s\", \"%s\") = %s\n", name, filter, res ? "true" : "false");
if (res != expected)
printf("FAILURE!\n");
}
int
nametest(const char *commandline, const char *expect, )
{
wchar_t wcommandline[2048];
wchar_t wexpect[2048];
wchar_t derived[MAX_PATH];
int ret;
_snwprintf(wcommandline, sizeof(wcommandline), L"%S", commandline);
_snwprintf(wexpect, sizeof(wexpect), L"%S", expect);
ret = get_commandline_qualifier(wcommandline, derived, BUFFER_SIZE_ELEMENTS(derived),
true);
printf("get_commandline_qualifier(\"%s\") => \"%ls\" [%d]\n", commandline, derived,
ret);
if (wcscmp(derived, expect))
printf("FAILED!\n");
ret = get_commandline_qualifier(wcommandline, derived, BUFFER_SIZE_ELEMENTS(derived),
REGISTRY_DEFAULT);
printf("get_commandline_qualifier(\"%s\") => \"%ls\" [%d]\n", commandline, derived,
ret);
}
int
main()
{
char *filter = "calc.exe;notepad.exe";
test("notepad.exe", filter, 1);
test("calc.exe", filter, 1);
test("write.exe", filter, 0);
test("test.exe", filter, 0);
filter = NULL;
test("notepad.exe", filter, 0);
test("calc.exe", filter, 0);
test("write.exe", filter, 0);
test("test.exe", filter, 0);
nametest("C:\WINNT\System32\dllhost.exe /Processid:{"
"3D14228D-FBE1-11D0-995D-00C04FD919C1}",
"");
nametest("svchost.exe -k netsvc ", "");
nametest("system32\svchost -k rpcss ", "");
nametest("\"c:\program files\test\my test\sqlserver.exe\" -s uddi ", "");
nametest("\"c:\program files\test\my test\sqlserver\" -s uddi ", "");
nametest("c:\program files\test\my test\sqlserver.EXE -s uddi ", "");
nametest("c:\program files\test\my test\sqlserver.EXE -s uddi -u test\test ",
"");
nametest("C:\WINNT\System32\dllhost.exe "
"/Processid:{3D14228D-FBE1-11D0-995D-00C04FD919C1} "
"/Processid:{3D14228D-FBE1-11D0-995D-00C04FD919C1} "
"/Processid:{3D14228D-FBE1-11D0-995D-00C04FD919C1} "
"/Processid:{3D14228D-FBE1-11D0-995D-00C04FD919C1} "
"/Processid:{3D14228D-FBE1-11D0-995D-00C04FD919C1} "
"/Processid:{3D14228D-FBE1-11D0-995D-00C04FD919C1} "
"/Processid:{3D14228D-FBE1-11D0-995D-00C04FD919C1} "
"/Processid:{3D14228D-FBE1-11D0-995D-00C04FD919C1}",
"");
nametest("dllhost.exe ////// /P#%@#$%-k netsvc ", "");
* cases (and add vs* functions) */
{
char buf[20];
memset(buf, 0x1, sizeof(buf));
snprintf(buf, 10, "0123456789%dfoo", 555);
if (buf[10] != 0x1 || buf[9] != '5')
printf("FAILED snprintf safety check\n");
}
{
wchar_t buf[20];
memset(buf, 0x1, sizeof(buf));
snwprintf(buf, 10, "0123456789%dfoo", 555);
if (buf[10] != 0x11 || buf[9] != L'5')
printf("FAILED snprintf safety check\n");
}
return 0;
}
#endif