blob: e2af15c160ca68eb4d3bcbc6c0f21dd47c8df679 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 Google, Inc. All rights reserved.
* Copyright (c) 2009-2010 Derek Bruening 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.
*/
/*
* loader.c: custom private library loader for Windows
*
* original case: i#157
*
* unfinished/additional features:
*
* i#235: redirect more of ntdll for more transparent private libraries:
* - in particular, redirect Ldr*, or at least kernel32!*W
* - we'll redirect any additional routines as transparency issues come up
*
* i#350: no-dcontext try/except:749
* - then we can check readability of everything more easily: today
* not checking everything in the name of performance
*
* i#233: advanced loader features:
* - delay-load dlls
* - bound imports
* - import hint
* - TLS (though expect only in .exe not .dll)
*
* i#234: earliest injection:
* - use bootstrap loader w/ manual syscalls or ntdll binding to load DR
* itself with this private loader at very first APC point
*
* i#249: TLS/TEB/PEB isolation for private dll copies
* - -private_peb uses a private PEB copy, but is limited in several respects:
* * uses a shallow copy
* (we should look at the fiber API to see the full list of fields to copy)
* * does not intercept private libs/client using NtQueryInformationProcess
* but kernel seems to just use TEB pointer anyway!
* * added dr_get_app_PEB() for client to get app PEB
*
* i#1299: improved isolation of user32.dll
*/
#include "../globals.h"
#include "../module_shared.h"
#include "ntdll.h"
#include "os_private.h"
#include "diagnost.h" /* to read systemroot reg key */
#include "arch.h"
#include "instr.h"
#include "decode.h"
#include "drwinapi/drwinapi.h"
#ifdef X64
# define IMAGE_ORDINAL_FLAG IMAGE_ORDINAL_FLAG64
#else
# define IMAGE_ORDINAL_FLAG IMAGE_ORDINAL_FLAG32
#endif
/* Not persistent across code cache execution, so not protected.
* Synchronized by privload_lock.
*/
DECLARE_NEVERPROT_VAR(static char modpath[MAXIMUM_PATH], {0});
DECLARE_NEVERPROT_VAR(static char forwmodpath[MAXIMUM_PATH], {0});
/* Written during initialization only */
static char systemroot[MAXIMUM_PATH];
static bool windbg_cmds_initialized;
/* PE entry points take 3 args */
typedef BOOL (WINAPI *dllmain_t)(HANDLE, DWORD, LPVOID);
/* forward decls */
static void
privload_init_search_paths(void);
static bool
privload_get_import_descriptor(privmod_t *mod, IMAGE_IMPORT_DESCRIPTOR **imports OUT,
app_pc *imports_end OUT);
static bool
privload_process_one_import(privmod_t *mod, privmod_t *impmod,
IMAGE_THUNK_DATA *lookup, app_pc *address);
static const char *
privload_map_name(const char *impname, privmod_t *immed_dep);
static privmod_t *
privload_locate_and_load(const char *impname, privmod_t *dependent, bool reachable);
static void
privload_add_windbg_cmds_post_init(privmod_t *mod);
static app_pc
privload_redirect_imports(privmod_t *impmod, const char *name, privmod_t *importer);
static void
privload_add_windbg_cmds(void);
#ifdef CLIENT_INTERFACE
/* Isolate the app's PEB by making a copy for use by private libs (i#249) */
static PEB *private_peb;
static bool private_peb_initialized = false;
/* Isolate TEB->FlsData: for first thread we need to copy before have dcontext */
static void *pre_fls_data;
/* Isolate TEB->ReservedForNtRpc: for first thread we need to copy before have dcontext */
static void *pre_nt_rpc;
/* Isolate TEB->NlsCache: for first thread we need to copy before have dcontext */
static void *pre_nls_cache;
/* Used to handle loading windows lib later during init */
static bool swapped_to_app_peb;
/* FIXME i#875: we do not have ntdll!RtlpFlsLock isolated. Living w/ it for now. */
#endif
/* NtTickCount: not really a syscall, just reads KUSER_SHARED_DATA.
* Redirects to RtlGetTickCount on Win2003+.
* But, it's not present on XP (i#1195), so we have to dynamically
* look it up.
*/
typedef ULONG_PTR (NTAPI *ntdll_NtTickCount_t)(void);
static ntdll_NtTickCount_t ntdll_NtTickCount;
/***************************************************************************/
HANDLE WINAPI RtlCreateHeap(ULONG flags, void *base, size_t reserve_sz,
size_t commit_sz, void *lock, void *params);
BOOL WINAPI RtlDestroyHeap(HANDLE base);
void
os_loader_init_prologue(void)
{
app_pc ntdll = get_ntdll_base();
app_pc drdll = get_dynamorio_dll_start();
app_pc user32 = NULL;
privmod_t *mod;
/* FIXME i#812: need to delay this for earliest injection */
if (!dr_earliest_injected && !standalone_library) {
user32 = (app_pc) get_module_handle(L"user32.dll");
}
#ifdef CLIENT_INTERFACE
if (INTERNAL_OPTION(private_peb)) {
/* Isolate the app's PEB by making a copy for use by private libs (i#249).
* We just do a shallow copy for now until we hit an issue w/ deeper fields
* that are allocated at our init time.
* Anything allocated by libraries after our init here will of
* course get its own private deep copy.
* We also do not intercept private libs calling NtQueryInformationProcess
* to get info.PebBaseAddress: we assume they don't do that. It's not
* exposed in any WinAPI routine.
*/
GET_NTDLL(RtlInitializeCriticalSection, (OUT RTL_CRITICAL_SECTION *crit));
PEB *own_peb = get_own_peb();
/* FIXME: does it need to be page-aligned? */
private_peb = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, PEB, ACCT_OTHER, UNPROTECTED);
memcpy(private_peb, own_peb, sizeof(*private_peb));
/* We need priv libs to NOT use any locks that app code uses: else we'll
* deadlock (classic transparency violation).
* One concern here is that the real PEB points at ntdll!FastPebLock
* but we assume nobody cares.
*/
private_peb->FastPebLock = HEAP_TYPE_ALLOC
(GLOBAL_DCONTEXT, RTL_CRITICAL_SECTION, ACCT_OTHER, UNPROTECTED);
if (!dr_earliest_injected) /* FIXME i#812: need to delay this */
RtlInitializeCriticalSection(private_peb->FastPebLock);
/* We can't redirect ntdll routines allocating memory internally,
* but we can at least have them not affect the app's Heap.
* We do this after the swap in case it affects some other peb field,
* in which case it will match the RtlDestroyHeap.
*/
if (dr_earliest_injected) { /* FIXME i#812: need to delay RtlCreateHeap */
private_peb->ProcessHeap = own_peb->ProcessHeap;
} else {
private_peb->ProcessHeap = RtlCreateHeap(HEAP_GROWABLE | HEAP_CLASS_PRIVATE,
NULL, 0, 0, NULL, NULL);
if (private_peb->ProcessHeap == NULL) {
SYSLOG_INTERNAL_ERROR("private default heap creation failed");
/* fallback */
private_peb->ProcessHeap = own_peb->ProcessHeap;
}
}
if (get_os_version() >= WINDOWS_VERSION_2003) {
/* FLS is supported in WinXP-64 or later */
/* We need a custom setup for FLS structures */
ntdll_redir_fls_init(own_peb, private_peb);
}
private_peb_initialized = true;
swap_peb_pointer(NULL, true/*to priv*/);
LOG(GLOBAL, LOG_LOADER, 2, "app peb="PFX"\n", own_peb);
LOG(GLOBAL, LOG_LOADER, 2, "private peb="PFX"\n", private_peb);
pre_nls_cache = get_tls(NLS_CACHE_TIB_OFFSET);
pre_fls_data = get_tls(FLS_DATA_TIB_OFFSET);
pre_nt_rpc = get_tls(NT_RPC_TIB_OFFSET);
/* Clear state to separate priv from app.
* XXX: if we attach or something it seems possible that ntdll or user32
* or some other shared resource might set these and we want to share
* the value between app and priv. In that case we should not clear here
* and should relax the asserts in dispatch and is_using_app_peb to
* allow app==priv if both ==pre.
*/
set_tls(NLS_CACHE_TIB_OFFSET, NULL);
set_tls(FLS_DATA_TIB_OFFSET, NULL);
set_tls(NT_RPC_TIB_OFFSET, NULL);
LOG(GLOBAL, LOG_LOADER, 2, "initial thread TEB->NlsCache="PFX"\n",
pre_nls_cache);
LOG(GLOBAL, LOG_LOADER, 2, "initial thread TEB->FlsData="PFX"\n", pre_fls_data);
LOG(GLOBAL, LOG_LOADER, 2, "initial thread TEB->ReservedForNtRpc="PFX"\n",
pre_nt_rpc);
}
#endif
drwinapi_init();
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
privload_init_search_paths();
/* We count on having at least one node that's never removed so we
* don't have to unprot .data and write to modlist later
*/
snprintf(modpath, BUFFER_SIZE_ELEMENTS(modpath), "%s/system32/%s",
systemroot, "ntdll.dll");
NULL_TERMINATE_BUFFER(modpath);
mod = privload_insert(NULL, ntdll, get_allocation_size(ntdll, NULL),
"ntdll.dll", modpath);
mod->externally_loaded = true;
/* FIXME i#234: Once we have earliest injection and load DR via this private loader
* (i#234/PR 204587) we can remove this
*/
mod = privload_insert(NULL, drdll, get_allocation_size(drdll, NULL),
DYNAMORIO_LIBRARY_NAME, get_dynamorio_library_path());
mod->externally_loaded = true;
/* Sometimes a privlib calls LoadLibrary to get a handle to the executable */
mod = privload_insert(NULL, get_application_base(),
get_application_end() - get_application_base() + 1,
get_application_short_unqualified_name(),
get_application_name());
mod->externally_loaded = true;
/* FIXME i#1299: loading a private user32.dll is problematic: it registers
* callbacks that KiUserCallbackDispatcher invokes. For now we do not
* duplicate it. If the app loads it dynamically later we will end up
* duplicating but not worth checking for that.
* If client private lib loads user32 when app does not statically depend
* on it, we'll have a private copy and no app copy: this may cause
* problems later but waiting to see.
*/
if (user32 != NULL) {
snprintf(modpath, BUFFER_SIZE_ELEMENTS(modpath), "%s/system32/%s",
systemroot, "user32.dll");
NULL_TERMINATE_BUFFER(modpath);
mod = privload_insert(NULL, user32, get_allocation_size(user32, NULL),
"user32.dll", modpath);
LOG(GLOBAL, LOG_LOADER, 2, "adding app's user32.dll to privlib list\n");
mod->externally_loaded = true;
}
/* i#1195: NtTickCount is only on 2K3+. If we can't find it we use
* the old KUSER_SHARED_DATA so make sure we're on an old OS.
*/
ntdll_NtTickCount = (ntdll_NtTickCount_t)
get_proc_address(get_ntdll_base(), "NtGetTickCount");
ASSERT(ntdll_NtTickCount != NULL ||
get_os_version() <= WINDOWS_VERSION_XP);
}
void
os_loader_init_epilogue(void)
{
#ifdef CLIENT_INTERFACE
if (INTERNAL_OPTION(private_peb) && !should_swap_peb_pointer()) {
/* Not going to be swapping so restore permanently to app */
swap_peb_pointer(NULL, false/*to app*/);
swapped_to_app_peb = true;
}
#endif
#ifndef STANDALONE_UNIT_TEST
/* drmarker and the privlist are set up, so fill in the windbg commands (i#522) */
privload_add_windbg_cmds();
#endif
}
void
os_loader_exit(void)
{
drwinapi_exit();
#ifdef CLIENT_INTERFACE
if (INTERNAL_OPTION(private_peb)) {
if (should_swap_peb_pointer()) {
/* Swap back so any further peb queries (e.g., reading env var
* while reporting a leak) use a non-freed peb
*/
swap_peb_pointer(NULL, false/*to app*/);
}
/* we do have a dcontext */
ASSERT(get_thread_private_dcontext != NULL);
TRY_EXCEPT(get_thread_private_dcontext(), {
RtlDestroyHeap(private_peb->ProcessHeap);
}, {
/* shouldn't crash, but does on security-win32/sd_tester,
* probably b/c it corrupts the heap: regardless we don't
* want DR reporting a crash on an ntdll address so we ignore.
*/
});
if (get_os_version() >= WINDOWS_VERSION_2003) {
/* FLS is supported in WinXP-64 or later */
ntdll_redir_fls_exit(private_peb);
}
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, private_peb->FastPebLock,
RTL_CRITICAL_SECTION, ACCT_OTHER, UNPROTECTED);
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, private_peb, PEB, ACCT_OTHER, UNPROTECTED);
}
#endif
}
void
os_loader_thread_init_prologue(dcontext_t *dcontext)
{
#ifdef CLIENT_INTERFACE
if (INTERNAL_OPTION(private_peb) && should_swap_peb_pointer()) {
if (!dynamo_initialized) {
/* For first thread use cached pre-priv-lib value for app and
* whatever value priv libs have set for priv
*/
dcontext->priv_nls_cache = get_tls(NLS_CACHE_TIB_OFFSET);
dcontext->priv_fls_data = get_tls(FLS_DATA_TIB_OFFSET);
dcontext->priv_nt_rpc = get_tls(NT_RPC_TIB_OFFSET);
IF_X64(dcontext->app_stack_limit = get_tls(BASE_STACK_TIB_OFFSET));
dcontext->app_nls_cache = pre_nls_cache;
dcontext->app_fls_data = pre_fls_data;
dcontext->app_nt_rpc = pre_nt_rpc;
set_tls(NLS_CACHE_TIB_OFFSET, dcontext->app_nls_cache);
set_tls(FLS_DATA_TIB_OFFSET, dcontext->app_fls_data);
set_tls(NT_RPC_TIB_OFFSET, dcontext->app_nt_rpc);
} else {
/* The real value will be set by swap_peb_pointer */
IF_X64(dcontext->app_stack_limit = NULL);
dcontext->app_nls_cache = NULL;
dcontext->app_fls_data = NULL;
dcontext->app_nt_rpc = NULL;
/* We assume clearing out any non-NULL value for priv is safe */
dcontext->priv_nls_cache = NULL;
dcontext->priv_fls_data = NULL;
dcontext->priv_nt_rpc = NULL;
}
#ifdef X64
LOG(THREAD, LOG_LOADER, 2, "app stack limit="PFX"\n", dcontext->app_stack_limit);
#endif
LOG(THREAD, LOG_LOADER, 2, "app nls_cache="PFX", priv nls_cache="PFX"\n",
dcontext->app_nls_cache, dcontext->priv_nls_cache);
LOG(THREAD, LOG_LOADER, 2, "app fls="PFX", priv fls="PFX"\n",
dcontext->app_fls_data, dcontext->priv_fls_data);
LOG(THREAD, LOG_LOADER, 2, "app rpc="PFX", priv rpc="PFX"\n",
dcontext->app_nt_rpc, dcontext->priv_nt_rpc);
/* For swapping teb fields (detach, reset i#25) we'll need to
* know the teb base from another thread
*/
dcontext->teb_base = (byte *) get_tls(SELF_TIB_OFFSET);
swap_peb_pointer(dcontext, true/*to priv*/);
}
#endif
}
void
os_loader_thread_init_epilogue(dcontext_t *dcontext)
{
#ifdef CLIENT_INTERFACE
if (INTERNAL_OPTION(private_peb) && should_swap_peb_pointer()) {
/* For subsequent app threads, peb ptr will be swapped to priv
* by transfer_to_dispatch(), and w/ FlsData swap we have to
* properly nest.
*/
if (dynamo_initialized/*later thread*/ && !IS_CLIENT_THREAD(dcontext))
swap_peb_pointer(dcontext, false/*to app*/);
}
#endif
}
void
os_loader_thread_exit(dcontext_t *dcontext)
{
/* do nothing in Windows */
}
#ifdef CLIENT_INTERFACE
/* our copy of the PEB for isolation (i#249) */
PEB *
get_private_peb(void)
{
ASSERT(INTERNAL_OPTION(private_peb));
ASSERT(private_peb != NULL);
return private_peb;
}
/* For performance reasons we avoid the swap if there's no client.
* We'd like to do so if there are no private WinAPI libs
* (we assume libs not in the system dir will not write to PEB or TEB fields we
* care about (mainly Fls ones)), but kernel32 can be loaded in dr_init()
* (which is after arch_init()) via dr_enable_console_printing(); plus,
* a client could load kernel32 via dr_load_aux_library(), or a 3rd-party
* priv lib could load anything at any time. Xref i#984.
*/
bool
should_swap_peb_pointer(void)
{
return (INTERNAL_OPTION(private_peb) &&
!IS_INTERNAL_STRING_OPTION_EMPTY(client_lib));
}
static void *
get_teb_field(dcontext_t *dcontext, ushort offs)
{
if (dcontext == NULL || dcontext == GLOBAL_DCONTEXT) {
/* get our own */
return get_tls(offs);
} else {
byte *teb = dcontext->teb_base;
return *((void **)(teb + offs));
}
}
static void
set_teb_field(dcontext_t *dcontext, ushort offs, void *value)
{
if (dcontext == NULL || dcontext == GLOBAL_DCONTEXT) {
/* set our own */
set_tls(offs, value);
} else {
byte *teb = dcontext->teb_base;
ASSERT(dcontext->teb_base != NULL);
*((void **)(teb + offs)) = value;
}
}
bool
is_using_app_peb(dcontext_t *dcontext)
{
/* don't use get_own_peb() as we want what's actually pointed at by TEB */
PEB *cur_peb = get_teb_field(dcontext, PEB_TIB_OFFSET);
IF_X64(void *cur_stack_limit;)
void *cur_nls_cache;
void *cur_fls;
void *cur_rpc;
ASSERT(dcontext != NULL && dcontext != GLOBAL_DCONTEXT);
if (!INTERNAL_OPTION(private_peb) ||
!private_peb_initialized ||
!should_swap_peb_pointer())
return true;
ASSERT(cur_peb != NULL);
IF_X64(cur_stack_limit = get_teb_field(dcontext, BASE_STACK_TIB_OFFSET));
cur_nls_cache = get_teb_field(dcontext, NLS_CACHE_TIB_OFFSET);
cur_fls = get_teb_field(dcontext, FLS_DATA_TIB_OFFSET);
cur_rpc = get_teb_field(dcontext, NT_RPC_TIB_OFFSET);
if (cur_peb == get_private_peb()) {
/* won't nec equal the priv_ value since could have changed: but should
* not have the app value!
*/
IF_X64(ASSERT(!is_dynamo_address(dcontext->app_stack_limit) ||
IS_CLIENT_THREAD(dcontext)));
ASSERT(cur_nls_cache == NULL ||
cur_nls_cache != dcontext->app_nls_cache);
ASSERT(cur_fls == NULL ||
cur_fls != dcontext->app_fls_data);
ASSERT(cur_rpc == NULL ||
cur_rpc != dcontext->app_nt_rpc);
return false;
} else {
/* won't nec equal the app_ value since could have changed: but should
* not have the priv value!
*/
IF_X64(ASSERT(!is_dynamo_address(cur_stack_limit)));
ASSERT(cur_nls_cache == NULL ||
cur_nls_cache != dcontext->priv_nls_cache);
ASSERT(cur_fls == NULL ||
cur_fls != dcontext->priv_fls_data);
ASSERT(cur_rpc == NULL ||
cur_rpc != dcontext->priv_nt_rpc);
return true;
}
}
static void
swap_peb_pointer_ex(dcontext_t *dcontext, bool to_priv, dr_state_flags_t flags)
{
PEB *tgt_peb = to_priv ? get_private_peb() : get_own_peb();
ASSERT(INTERNAL_OPTION(private_peb));
ASSERT(private_peb_initialized || should_swap_peb_pointer());
ASSERT(tgt_peb != NULL);
if (TEST(DR_STATE_PEB, flags)) {
set_teb_field(dcontext, PEB_TIB_OFFSET, (void *) tgt_peb);
LOG(THREAD, LOG_LOADER, 2, "set teb->peb to "PFX"\n", tgt_peb);
}
if (dcontext != NULL && dcontext != GLOBAL_DCONTEXT) {
/* We preserve TEB->LastErrorValue and we swap TEB->FlsData,
* TEB->ReservedForNtRpc, and TEB->NlsCache.
*/
IF_X64(void *cur_stack_limit = get_teb_field(dcontext, BASE_STACK_TIB_OFFSET);)
void *cur_nls_cache = get_teb_field(dcontext, NLS_CACHE_TIB_OFFSET);
void *cur_fls = get_teb_field(dcontext, FLS_DATA_TIB_OFFSET);
void *cur_rpc = get_teb_field(dcontext, NT_RPC_TIB_OFFSET);
if (to_priv) {
#ifdef X64
if (TEST(DR_STATE_STACK_BOUNDS, flags) &&
dynamo_initialized /* on app stack until init finished */ &&
!is_dynamo_address(cur_stack_limit)) { /* handle two in a row */
dcontext->app_stack_limit = cur_stack_limit;
set_teb_field(dcontext, BASE_STACK_TIB_OFFSET,
dcontext->dstack - DYNAMORIO_STACK_SIZE);
}
#endif
if (TEST(DR_STATE_TEB_MISC, flags)) {
/* note: two calls in a row will clobber app_errno w/ wrong value! */
dcontext->app_errno = (int)(ptr_int_t)
get_teb_field(dcontext, ERRNO_TIB_OFFSET);
if (dcontext->priv_nls_cache != cur_nls_cache) { /* handle two in a row */
dcontext->app_nls_cache = cur_nls_cache;
set_teb_field(dcontext, NLS_CACHE_TIB_OFFSET,
dcontext->priv_nls_cache);
}
if (dcontext->priv_fls_data != cur_fls) { /* handle two calls in a row */
dcontext->app_fls_data = cur_fls;
set_teb_field(dcontext, FLS_DATA_TIB_OFFSET, dcontext->priv_fls_data);
}
if (dcontext->priv_nt_rpc != cur_rpc) { /* handle two calls in a row */
dcontext->app_nt_rpc = cur_rpc;
set_teb_field(dcontext, NT_RPC_TIB_OFFSET, dcontext->priv_nt_rpc);
}
}
} else {
#ifdef X64
if (TEST(DR_STATE_STACK_BOUNDS, flags) &&
is_dynamo_address(cur_stack_limit)) { /* handle two in a row */
set_teb_field(dcontext, BASE_STACK_TIB_OFFSET,
dcontext->app_stack_limit);
}
#endif
if (TEST(DR_STATE_TEB_MISC, flags)) {
/* two calls in a row should be fine */
set_teb_field(dcontext, ERRNO_TIB_OFFSET,
(void *)(ptr_int_t)dcontext->app_errno);
if (dcontext->app_nls_cache != cur_nls_cache) { /* handle two in a row */
dcontext->priv_nls_cache = cur_nls_cache;
set_teb_field(dcontext, NLS_CACHE_TIB_OFFSET,
dcontext->app_nls_cache);
}
if (dcontext->app_fls_data != cur_fls) { /* handle two calls in a row */
dcontext->priv_fls_data = cur_fls;
set_teb_field(dcontext, FLS_DATA_TIB_OFFSET, dcontext->app_fls_data);
}
if (dcontext->app_nt_rpc != cur_rpc) { /* handle two calls in a row */
dcontext->priv_nt_rpc = cur_rpc;
set_teb_field(dcontext, NT_RPC_TIB_OFFSET, dcontext->app_nt_rpc);
}
}
}
IF_X64(ASSERT(!is_dynamo_address(dcontext->app_stack_limit) ||
IS_CLIENT_THREAD(dcontext)));
ASSERT(!is_dynamo_address(dcontext->app_nls_cache));
ASSERT(!is_dynamo_address(dcontext->app_fls_data));
ASSERT(!is_dynamo_address(dcontext->app_nt_rpc));
/* Once we have earier injection we should be able to assert
* that priv_fls_data is either NULL or a DR address: but on
* notepad w/ drinject it's neither: need to investigate.
*/
#ifdef X64
LOG(THREAD, LOG_LOADER, 3, "cur stack_limit="PFX", app stack_limit="PFX"\n",
cur_stack_limit, dcontext->app_stack_limit);
#endif
LOG(THREAD, LOG_LOADER, 3,
"cur nls_cache="PFX", app nls_cache="PFX", priv nls_cache="PFX"\n",
cur_nls_cache, dcontext->app_nls_cache, dcontext->priv_nls_cache);
LOG(THREAD, LOG_LOADER, 3, "cur fls="PFX", app fls="PFX", priv fls="PFX"\n",
cur_fls, dcontext->app_fls_data, dcontext->priv_fls_data);
LOG(THREAD, LOG_LOADER, 3, "cur rpc="PFX", app rpc="PFX", priv rpc="PFX"\n",
cur_rpc, dcontext->app_nt_rpc, dcontext->priv_nt_rpc);
}
}
/* C version of preinsert_swap_peb() */
void
swap_peb_pointer(dcontext_t *dcontext, bool to_priv)
{
swap_peb_pointer_ex(dcontext, to_priv, DR_STATE_ALL);
}
/* Meant for use on detach only: restore app values and does not update
* or swap private values. Up to caller to synchronize w/ other thread.
*/
void
restore_peb_pointer_for_thread(dcontext_t *dcontext)
{
PEB *tgt_peb = get_own_peb();
ASSERT_NOT_TESTED();
ASSERT(INTERNAL_OPTION(private_peb));
ASSERT(private_peb_initialized || should_swap_peb_pointer());
ASSERT(tgt_peb != NULL);
ASSERT(dcontext != NULL && dcontext->teb_base != NULL);
set_teb_field(dcontext, PEB_TIB_OFFSET, (void *) tgt_peb);
LOG(GLOBAL, LOG_LOADER, 2, "set teb->peb to "PFX"\n", tgt_peb);
set_teb_field(dcontext, ERRNO_TIB_OFFSET, (void *)(ptr_int_t) dcontext->app_errno);
LOG(THREAD, LOG_LOADER, 3, "restored app errno to "PIFX"\n", dcontext->app_errno);
/* We also swap TEB->FlsData and TEB->ReservedForNtRpc */
set_teb_field(dcontext, NLS_CACHE_TIB_OFFSET, dcontext->app_nls_cache);
LOG(THREAD, LOG_LOADER, 3, "restored app nls_cache to "PFX"\n",
dcontext->app_nls_cache);
set_teb_field(dcontext, FLS_DATA_TIB_OFFSET, dcontext->app_fls_data);
LOG(THREAD, LOG_LOADER, 3, "restored app fls to "PFX"\n", dcontext->app_fls_data);
set_teb_field(dcontext, NT_RPC_TIB_OFFSET, dcontext->app_nt_rpc);
LOG(THREAD, LOG_LOADER, 3, "restored app fls to "PFX"\n", dcontext->app_nt_rpc);
}
#endif /* CLIENT_INTERFACE */
bool
os_should_swap_state(void)
{
return IF_CLIENT_INTERFACE_ELSE(should_swap_peb_pointer(), false);
}
bool
os_using_app_state(dcontext_t *dcontext)
{
#ifdef CLIENT_INTERFACE
if (INTERNAL_OPTION(private_peb) && should_swap_peb_pointer())
return is_using_app_peb(dcontext);
#endif
return true;
}
void
os_swap_context(dcontext_t *dcontext, bool to_app, dr_state_flags_t flags)
{
#ifdef CLIENT_INTERFACE
/* i#249: swap PEB pointers */
if (INTERNAL_OPTION(private_peb) && should_swap_peb_pointer()) {
swap_peb_pointer_ex(dcontext, !to_app/*to priv*/, flags);
}
#endif
}
void
privload_add_areas(privmod_t *privmod)
{
vmvector_add(modlist_areas, privmod->base, privmod->base + privmod->size,
(void *)privmod);
}
void
privload_remove_areas(privmod_t *privmod)
{
vmvector_remove(modlist_areas, privmod->base, privmod->base + privmod->size);
}
void
privload_unmap_file(privmod_t *mod)
{
unmap_file(mod->base, mod->size);
}
bool
privload_unload_imports(privmod_t *mod)
{
privmod_t *impmod;
IMAGE_IMPORT_DESCRIPTOR *imports;
app_pc imports_end;
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
if (!privload_get_import_descriptor(mod, &imports, &imports_end)) {
LOG(GLOBAL, LOG_LOADER, 2, "%s: error reading imports for %s\n",
__FUNCTION__, mod->name);
return false;
}
if (imports == NULL) {
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s has no imports\n", __FUNCTION__, mod->name);
return true;
}
while (imports->OriginalFirstThunk != 0) {
const char *impname = (const char *) RVA_TO_VA(mod->base, imports->Name);
impname = privload_map_name(impname, mod);
impmod = privload_lookup(impname);
/* If we hit an error in the middle of loading we may not have loaded
* all imports for mod so impmod may not be found
*/
if (impmod != NULL)
privload_unload(impmod);
imports++;
ASSERT((app_pc)(imports+1) <= imports_end);
}
/* I used to ASSERT((app_pc)(imports+1) == imports_end) but kernel32 on win2k
* has an extra 10 bytes in the dir->Size for unknown reasons so suppressing
*/
return true;
}
/* if anything fails, undoes the mapping and returns NULL */
app_pc
privload_map_and_relocate(const char *filename, size_t *size OUT, bool reachable)
{
file_t fd;
app_pc map;
app_pc pref;
byte *(*map_func)(file_t, size_t *, uint64, app_pc, uint, map_flags_t);
bool (*unmap_func)(file_t, size_t);
ASSERT(size != NULL);
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
/* On win32 OS_EXECUTE is required to create a section w/ rwx
* permissions, which is in turn required to map a view w/ rwx
*/
fd = os_open(filename, OS_OPEN_READ | OS_EXECUTE |
/* we should allow renaming (xref PR 214399) as well as
* simultaneous read while holding the file handle
*/
OS_SHARE_DELETE /* shared read is on by default */);
if (fd == INVALID_FILE) {
LOG(GLOBAL, LOG_LOADER, 1, "%s: failed to open %s\n", __FUNCTION__, filename);
return NULL;
}
/* The libs added prior to dynamo_heap_initialized are only client
* libs, which we do not want on the DR-areas list to allow them
* to have app execute from their .text. We do want other
* privately-loaded libs to be on the DR-areas list (though that
* means that if we mess up and the app executes their code, we throw
* an app exception: FIXME: should we raise a better error message?
*/
*size = 0; /* map at full size */
if (dynamo_heap_initialized) {
/* These hold the DR lock and update DR areas */
map_func = map_file;
unmap_func = unmap_file;
} else {
map_func = os_map_file;
unmap_func = os_unmap_file;
}
/* On Windows, SEC_IMAGE => the kernel sets up the different segments w/
* proper protections for us, all on this single map syscall
*/
/* First map: let kernel pick base.
* Even for a lib that needs to be reachable, we'd need to read or map
* the headers to find the preferred base anyway, and the common
* case will be a client lib with a preferred base in the low 2GB which
* will need no re-mapping.
*/
map = (*map_func)(fd, size, 0/*offs*/, NULL/*base*/,
/* Ask for max, then restrict pieces */
MEMPROT_READ|MEMPROT_WRITE|MEMPROT_EXEC,
MAP_FILE_COPY_ON_WRITE/*writes should not change file*/ |
MAP_FILE_IMAGE/*image*/);
if (map != NULL &&
IF_X64_ELSE(module_is_32bit(map), module_is_64bit(map))) {
/* XXX i#828: we may eventually support mixed-mode clients.
* Xref dr_load_aux_x64_library() and load_library_64().
*/
SYSLOG(SYSLOG_ERROR, CLIENT_LIBRARY_WRONG_BITWIDTH, 3,
get_application_name(), get_application_pid(), filename);
return NULL;
}
#ifdef X64
if (reachable) {
bool reloc = module_file_relocatable(map);
(*unmap_func)(map, *size);
map = NULL;
if (!reloc) {
os_close(fd);
return NULL; /* failed */
}
/* Re-map with MAP_FILE_REACHABLE */
map = (*map_func)(fd, size, 0/*offs*/, NULL,
MEMPROT_READ|MEMPROT_WRITE|MEMPROT_EXEC,
MAP_FILE_COPY_ON_WRITE/*writes should not change file*/ |
MAP_FILE_IMAGE/*image*/ |
MAP_FILE_REACHABLE);
DOCHECK(1, {
if (map != NULL) {
byte *region_start = NULL;
byte *region_end = NULL;
vmcode_get_reachable_region(&region_start, &region_end);
ASSERT(map >= region_start && map+*size <= region_end);
}
});
}
#endif
os_close(fd); /* no longer needed */
fd = INVALID_FILE;
if (map == NULL)
return NULL; /* failed */
pref = get_module_preferred_base(map);
if (pref != map) {
LOG(GLOBAL, LOG_LOADER, 2, "%s: relocating from "PFX" to "PFX"\n",
__FUNCTION__, pref, map);
if (!module_file_relocatable(map)) {
LOG(GLOBAL, LOG_LOADER, 1, "%s: module not relocatable\n", __FUNCTION__);
(*unmap_func)(map, *size);
return NULL;
}
if (!module_rebase(map, *size, (map - pref), true/*+w incremental*/)) {
LOG(GLOBAL, LOG_LOADER, 1, "%s: failed to relocate %s\n",
__FUNCTION__, filename);
(*unmap_func)(map, *size);
return NULL;
}
}
return map;
}
static privmod_t *
privload_lookup_locate_and_load(const char *name, privmod_t *name_dependent,
privmod_t *load_dependent, bool inc_refcnt,
bool reachable)
{
privmod_t *newmod = NULL;
const char *toload = name;
const char *sep = double_strrchr(name, DIRSEP, ALT_DIRSEP);
size_t rootlen = strlen(systemroot);
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
if (systemroot[0] != '\0' &&
strncasecmp(name, systemroot, rootlen) == 0 &&
name[rootlen] != '\0' &&
strncasecmp(name + rootlen + 1, "sys", strlen("sys")) == 0) {
/* We want to have system32 match syswow64 for WOW64.
* We get that effect via dropping the path if it's anywhere
* under systemroot/sys*.
* XXX: I'm assuming we don't need to match different paths in general that
* map to the same file via symlink or junction. Xref i#1295.
*/
LOG(GLOBAL, LOG_LOADER, 2, "%s: loading %s instead of %s\n",
__FUNCTION__, sep + 1, name);
name = sep + 1;
}
if (double_strrchr(name, DIRSEP, ALT_DIRSEP) == NULL) {
/* only do this mapping when a basename, not a full path, is specified */
toload = privload_map_name(name, name_dependent);
}
newmod = privload_lookup(toload);
if (newmod == NULL)
newmod = privload_locate_and_load(toload, load_dependent, reachable);
else if (inc_refcnt)
newmod->ref_count++;
return newmod;
}
/* Does a search for the name, whereas load_private_library() assumes you're
* passing it the whole path (i#486).
*/
app_pc
privload_load_private_library(const char *name, bool reachable)
{
privmod_t *newmod;
app_pc res = NULL;
acquire_recursive_lock(&privload_lock);
newmod = privload_lookup_locate_and_load(name, NULL, NULL, true/*inc refcnt*/,
reachable);
if (newmod != NULL)
res = newmod->base;
release_recursive_lock(&privload_lock);
return res;
}
void
privload_load_finalized(privmod_t *mod)
{
if (windbg_cmds_initialized) /* we added libs loaded at init time already */
privload_add_windbg_cmds_post_init(mod);
}
bool
privload_process_imports(privmod_t *mod)
{
privmod_t *impmod;
IMAGE_IMPORT_DESCRIPTOR *imports;
app_pc iat, imports_end;
uint orig_prot;
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
if (!privload_get_import_descriptor(mod, &imports, &imports_end)) {
LOG(GLOBAL, LOG_LOADER, 2, "%s: error reading imports for %s\n",
__FUNCTION__, mod->name);
return false;
}
if (imports == NULL) {
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s has no imports\n", __FUNCTION__, mod->name);
return true;
}
/* If we later have other uses, turn this into a general import iterator
* in module.c. For now we're the only use so not worth the effort.
*/
while (imports->OriginalFirstThunk != 0) {
IMAGE_THUNK_DATA *lookup;
IMAGE_THUNK_DATA *address;
const char *impname = (const char *) RVA_TO_VA(mod->base, imports->Name);
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s imports from %s\n", __FUNCTION__,
mod->name, impname);
/* FIXME i#233: support bound imports: for now ignoring */
if (imports->TimeDateStamp == -1) {
/* Imports are bound via "new bind": need to walk
* IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT =>
* IMAGE_BOUND_IMPORT_DESCRIPTOR
*/
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s has new bind imports\n",
__FUNCTION__, mod->name);
} else if (imports->TimeDateStamp != 0) {
/* Imports are bound via "old bind" */
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s has old bind imports\n",
__FUNCTION__, mod->name);
}
impmod = privload_lookup_locate_and_load(impname, mod, mod, true/*inc refcnt*/,
false/*=> true if in client/ext dir*/);
if (impmod == NULL) {
LOG(GLOBAL, LOG_LOADER, 1, "%s: unable to load import lib %s\n",
__FUNCTION__, impname);
return false;
}
#ifdef CLIENT_INTERFACE
/* i#852: identify all libs that import from DR as client libs */
if (impmod->base == get_dynamorio_dll_start())
mod->is_client = true;
#endif
/* walk the lookup table and address table in lockstep */
/* FIXME: should check readability: if had no-dcontext try (i#350) could just
* do try/except around whole thing
*/
lookup = (IMAGE_THUNK_DATA *) RVA_TO_VA(mod->base, imports->OriginalFirstThunk);
address = (IMAGE_THUNK_DATA *) RVA_TO_VA(mod->base, imports->FirstThunk);
iat = (app_pc) address;
if (!protect_virtual_memory((void *)PAGE_START(iat), PAGE_SIZE,
PAGE_READWRITE, &orig_prot))
return false;
while (lookup->u1.Function != 0) {
if (!privload_process_one_import(mod, impmod, lookup, (app_pc *)address)) {
LOG(GLOBAL, LOG_LOADER, 1, "%s: error processing imports\n",
__FUNCTION__);
return false;
}
lookup++;
address++;
if (PAGE_START(address) != PAGE_START(iat)) {
if (!protect_virtual_memory((void *)PAGE_START(iat), PAGE_SIZE,
orig_prot, &orig_prot))
return false;
iat = (app_pc) address;
if (!protect_virtual_memory((void *)PAGE_START(iat), PAGE_SIZE,
PAGE_READWRITE, &orig_prot))
return false;
}
}
if (!protect_virtual_memory((void *)PAGE_START(iat), PAGE_SIZE,
orig_prot, &orig_prot))
return false;
imports++;
ASSERT((app_pc)(imports+1) <= imports_end);
}
/* I used to ASSERT((app_pc)(imports+1) == imports_end) but kernel32 on win2k
* has an extra 10 bytes in the dir->Size for unknown reasons so suppressing
*/
/* FIXME i#233: support delay-load: IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT */
return true;
}
static bool
privload_get_import_descriptor(privmod_t *mod, IMAGE_IMPORT_DESCRIPTOR **imports OUT,
app_pc *imports_end OUT)
{
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *) mod->base;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *) (mod->base + dos->e_lfanew);
IMAGE_DATA_DIRECTORY *dir;
ASSERT(is_readable_pe_base(mod->base));
ASSERT(dos->e_magic == IMAGE_DOS_SIGNATURE);
ASSERT(nt != NULL && nt->Signature == IMAGE_NT_SIGNATURE);
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
ASSERT(imports != NULL);
dir = OPT_HDR(nt, DataDirectory) + IMAGE_DIRECTORY_ENTRY_IMPORT;
if (dir == NULL || dir->Size <= 0) {
*imports = NULL;
return true;
}
*imports = (IMAGE_IMPORT_DESCRIPTOR *) RVA_TO_VA(mod->base, dir->VirtualAddress);
ASSERT_CURIOSITY(dir->Size >= sizeof(IMAGE_IMPORT_DESCRIPTOR));
if (!is_readable_without_exception((app_pc)*imports, dir->Size)) {
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s has unreadable imports: partial map?\n",
__FUNCTION__, mod->name);
return false;
}
if (imports_end != NULL)
*imports_end = mod->base + dir->VirtualAddress + dir->Size;
return true;
}
static bool
privload_process_one_import(privmod_t *mod, privmod_t *impmod,
IMAGE_THUNK_DATA *lookup, app_pc *address)
{
app_pc dst = NULL;
const char *forwarder;
generic_func_t func;
/* Set to first-level names for use below in case no forwarder */
privmod_t *forwmod = impmod;
privmod_t *last_forwmod = NULL;
const char *forwfunc = NULL;
const char *impfunc = NULL;
const char *forwpath = NULL;
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
if (TEST(IMAGE_ORDINAL_FLAG, lookup->u1.Function)) {
/* XXX: for 64-bit this is a 64-bit type: should we widen through
* get_proc_address_by_ordinal()?
*/
DWORD ord = (DWORD) (lookup->u1.AddressOfData & ~(IMAGE_ORDINAL_FLAG));
func = get_proc_address_by_ordinal(impmod->base, ord, &forwarder);
impfunc = "<ordinal>";
} else {
/* import by name */
IMAGE_IMPORT_BY_NAME *name = (IMAGE_IMPORT_BY_NAME *)
RVA_TO_VA(mod->base, (lookup->u1.AddressOfData & ~(IMAGE_ORDINAL_FLAG)));
/* FIXME optimization i#233:
* - try name->Hint first
* - build hashtables for quick lookup instead of repeatedly walking
* export tables
*/
/* expensive to check is_readable for name: really we want no-dcxt try (i#350) */
func = get_proc_address_ex(impmod->base, (const char *) name->Name, &forwarder);
/* set to first-level names for use below in case no forwarder */
forwfunc = (const char *) name->Name;
impfunc = forwfunc;
}
/* loop to handle sequence of forwarders */
while (func == NULL) {
if (forwarder == NULL) {
#ifdef CLIENT_INTERFACE
/* there's a syslog in loader_init() but we want to provide the symbol */
char msg[MAXIMUM_PATH*2];
snprintf(msg, BUFFER_SIZE_ELEMENTS(msg),
"import %s not found in ", impfunc); /* name is subsequent arg */
NULL_TERMINATE_BUFFER(msg);
SYSLOG(SYSLOG_ERROR, CLIENT_LIBRARY_UNLOADABLE, 4,
get_application_name(), get_application_pid(), msg, impmod->name);
#endif
LOG(GLOBAL, LOG_LOADER, 1, "%s: import %s not found in %s\n",
__FUNCTION__, impfunc, impmod->name);
return false;
}
forwfunc = strchr(forwarder, '.') + 1;
/* XXX: forwarder string constraints are not documented and
* all I've seen look like this: "NTDLL.RtlAllocateHeap".
* so I've never seen a full filename or path.
* but there could still be extra dots somewhere: watch for them.
*/
if (forwfunc == (char *)(ptr_int_t)1 || strchr(forwfunc+1, '.') != NULL) {
CLIENT_ASSERT(false, "unexpected forwarder string");
return NULL;
}
if (forwfunc - forwarder + strlen("dll") >=
BUFFER_SIZE_ELEMENTS(forwmodpath)) {
ASSERT_NOT_REACHED();
LOG(GLOBAL, LOG_LOADER, 1, "%s: import string %s too long\n",
__FUNCTION__, forwarder);
return false;
}
/* we use static buffer: may be clobbered by recursion below */
snprintf(forwmodpath, forwfunc - forwarder, "%s", forwarder);
snprintf(forwmodpath + (forwfunc - forwarder), strlen("dll"), "dll");
forwmodpath[forwfunc - 1/*'.'*/ - forwarder + strlen(".dll")] = '\0';
LOG(GLOBAL, LOG_LOADER, 2, "\tforwarder %s => %s %s\n",
forwarder, forwmodpath, forwfunc);
last_forwmod = forwmod;
/* don't use forwmodpath past here: recursion may clobber it */
/* XXX: should inc ref count: but then need to walk individual imports
* and dec on unload. For now risking it.
*/
forwmod = privload_lookup_locate_and_load
(forwmodpath, last_forwmod == NULL ? mod : last_forwmod,
mod, false/*!inc refcnt*/, false/*=> true if in client/ext dir*/);
if (forwmod == NULL) {
LOG(GLOBAL, LOG_LOADER, 1, "%s: unable to load forworder for %s\n"
__FUNCTION__, forwarder);
return false;
}
/* should be listed as import; don't want to inc ref count on each forw */
func = get_proc_address_ex(forwmod->base, forwfunc, &forwarder);
}
/* write result into IAT */
LOG(GLOBAL, LOG_LOADER, 2, "\timport %s @ "PFX" => IAT "PFX"\n",
impfunc, func, address);
if (forwfunc != NULL) {
/* XXX i#233: support redirecting when imported by ordinal */
dst = privload_redirect_imports(forwmod, forwfunc, mod);
DOLOG(2, LOG_LOADER, {
if (dst != NULL)
LOG(GLOBAL, LOG_LOADER, 2, "\tredirect => "PFX"\n", dst);
});
}
if (dst == NULL)
dst = (app_pc) func;
*address = dst;
return true;
}
bool
privload_call_entry(privmod_t *privmod, uint reason)
{
app_pc entry = get_module_entry(privmod->base);
dcontext_t *dcontext = get_thread_private_dcontext();
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
/* get_module_entry adds base => returns base instead of NULL */
if (entry != NULL && entry != privmod->base) {
dllmain_t func = (dllmain_t) convert_data_to_function(entry);
BOOL res = FALSE;
LOG(GLOBAL, LOG_LOADER, 2, "%s: calling %s entry "PFX" for %d\n",
__FUNCTION__, privmod->name, entry, reason);
if (get_os_version() >= WINDOWS_VERSION_8 &&
str_case_prefix(privmod->name, "kernelbase")) {
/* XXX i#915: win8 kernelbase entry fails on initial csrss setup.
* Xref i#364, i#440.
* We can ignore and continue for at least small apps, and
* so far we have not seen problems on larger apps.
*/
}
TRY_EXCEPT_ALLOW_NO_DCONTEXT(dcontext, {
res = (*func)((HANDLE)privmod->base, reason, NULL);
}, { /* EXCEPT */
LOG(GLOBAL, LOG_LOADER, 1,
"%s: %s entry routine crashed!\n", __FUNCTION__, privmod->name);
res = FALSE;
});
if (!res && get_os_version() >= WINDOWS_VERSION_7 &&
str_case_prefix(privmod->name, "kernel")) {
/* i#364: win7 _BaseDllInitialize fails to initialize a new console
* (0xc0000041 (3221225537) - The NtConnectPort request is refused)
* which we ignore for now. DR always had trouble writing to the
* console anyway (xref i#261).
* Update: for i#440, this should now succeed, but we leave this
* in place just in case.
*/
LOG(GLOBAL, LOG_LOADER, 1,
"%s: ignoring failure of kernel32!_BaseDllInitialize\n", __FUNCTION__);
res = TRUE;
}
return CAST_TO_bool(res);
}
return true;
}
/* Map API-set pseudo-dlls to real dlls.
* In Windows 7, dlls now import from pseudo-dlls that split up the
* API. They are all named
* "API-MS-Win-<category>-<component>-L<layer>-<version>.dll".
* There is no such file: instead the loader uses a table in the special
* apisetschema.dll that is mapped into every process to map
* from the particular pseudo-dll to a real dll.
*/
static const char *
map_api_set_dll(const char *name, privmod_t *dependent)
{
/* Ideally we would read apisetschema.dll ourselves.
* It seems to be mapped in at 0x00040000.
* But this is simpler than trying to parse that dll's table.
* We ignore the version suffix ("-1-0", e.g.).
*/
if (str_case_prefix(name, "API-MS-Win-Core-APIQuery-L1"))
return "ntdll.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Console-L1"))
return "kernel32.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-DateTime-L1"))
return "kernel32.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-DelayLoad-L1"))
return "kernel32.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Debug-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-ErrorHandling-L1")) {
/* This one includes {,Set}UnhandledExceptionFilter which are only in
* kernel32, but kernel32 itself imports GetLastError, etc. which must come
* from kernelbase to avoid infinite loop. XXX: what does apisetschema say?
* dependent on what's imported?
*/
if (str_case_prefix(dependent->name, "kernel32"))
return "kernelbase.dll";
else
return "kernel32.dll";
} else if (str_case_prefix(name, "API-MS-Win-Core-Fibers-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-File-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Handle-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Heap-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Interlocked-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-IO-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Localization-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-LocalRegistry-L1"))
return "kernel32.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-LibraryLoader-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Memory-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Misc-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-NamedPipe-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-ProcessEnvironment-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-ProcessThreads-L1")) {
/* This one includes CreateProcessAsUserW which is only in
* kernel32, but kernel32 itself imports from here and its must come
* from kernelbase to avoid infinite loop. XXX: see above: seeming
* more and more like it depends on what's imported.
*/
if (str_case_prefix(dependent->name, "kernel32"))
return "kernelbase.dll";
else
return "kernel32.dll";
} else if (str_case_prefix(name, "API-MS-Win-Core-Profile-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-RTLSupport-L1")) {
if (get_os_version() >= WINDOWS_VERSION_8)
return "ntdll.dll";
else
return "kernel32.dll";
} else if (str_case_prefix(name, "API-MS-Win-Core-String-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Synch-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-SysInfo-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-ThreadPool-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-XState-L1"))
return "ntdll.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Util-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Security-Base-L1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Security-LSALookup-L1"))
return "sechost.dll";
else if (str_case_prefix(name, "API-MS-Win-Security-SDDL-L1"))
return "sechost.dll";
else if (str_case_prefix(name, "API-MS-Win-Service-Core-L1"))
return "sechost.dll";
else if (str_case_prefix(name, "API-MS-Win-Service-Management-L1"))
return "sechost.dll";
else if (str_case_prefix(name, "API-MS-Win-Service-Management-L2"))
return "sechost.dll";
else if (str_case_prefix(name, "API-MS-Win-Service-Winsvc-L1"))
return "sechost.dll";
/**************************************************/
/* Added in Win8 */
else if (str_case_prefix(name, "API-MS-Win-Core-Kernel32-Legacy-L1"))
return "kernel32.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-Registry-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Job-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Threadpool-Legacy-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Threadpool-Private-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Timezone-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Localization-Private-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Comm-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-WOW64-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Realtime-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-SystemTopology-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-ProcessTopology-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Namespace-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-File-L2-1") ||
str_case_prefix(name, "API-MS-Win-Core-Localization-L2-1") ||
str_case_prefix(name, "API-MS-Win-Core-Normalization-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-SideBySide-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Appcompat-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-WindowsErrorReporting-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Console-L2-1") ||
str_case_prefix(name, "API-MS-Win-Core-Psapi-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Psapi-Ansi-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Psapi-Obsolete-L1-1") ||
str_case_prefix(name, "API-MS-Win-Security-Appcontainer-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Registry-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-String-Obsolete-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Heap-Obsolete-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Timezone-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Threadpool-Legacy-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Registry-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-String-Obsolete-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Heap-Obsolete-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Timezone-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-Threadpool-Legacy-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-BEM-L1-1") ||
str_case_prefix(name, "API-MS-Win-Security-Base-Private-L1-1"))
return "kernelbase.dll";
else if (str_case_prefix(name, "API-MS-Win-Core-CRT-L1-1") ||
str_case_prefix(name, "API-MS-Win-Core-CRT-L2-1"))
return "msvcrt.dll";
else if (str_case_prefix(name, "API-MS-Win-Service-Private-L1-1") ||
str_case_prefix(name, "API-MS-Win-Security-Audit-L1-1"))
return "sechost.dll";
else if (str_case_prefix(name, "API-MS-Win-Eventing-Controller-L1-1") ||
str_case_prefix(name, "API-MS-Win-Eventing-Consumer-L1-1")) {
/* i#1528: moved to sechost.dll on win8.1 */
if (get_os_version() >= WINDOWS_VERSION_8_1)
return "sechost.dll";
else
return "kernelbase.dll";
}
/**************************************************/
/* Added in Win8.1 */
else if (str_case_prefix(name, "API-MS-Win-Core-ProcessTopology-L1-2") ||
str_case_prefix(name, "API-MS-Win-Core-XState-L2-1"))
return "kernelbase.dll";
else {
SYSLOG_INTERNAL_WARNING("unknown API-MS-Win pseudo-dll %s", name);
/* good guess */
return "kernelbase.dll";
}
}
/* If walking forwarder chain, immed_dep should be most recent walked.
* Else should be regular dependent.
*/
static const char *
privload_map_name(const char *impname, privmod_t *immed_dep)
{
/* 0) on Windows 7, the API-set pseudo-dlls map to real dlls */
if (get_os_version() >= WINDOWS_VERSION_7 &&
str_case_prefix(impname, "API-MS-Win-")) {
IF_DEBUG(const char *apiname = impname;)
/* We need immediate dependent to avoid infinite chain when hit
* kernel32 OpenProcessToken forwarder which needs to forward
* to kernelbase
*/
impname = map_api_set_dll(impname, immed_dep);
LOG(GLOBAL, LOG_LOADER, 2, "%s: mapped API-set dll %s to %s\n",
__FUNCTION__, apiname, impname);
return impname;
}
return impname;
}
static privmod_t *
privload_locate_and_load(const char *impname, privmod_t *dependent, bool reachable)
{
privmod_t *mod = NULL;
uint i;
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
/* The ntdll!Ldr loader searches in this order:
* 1) exe dir
* 2) cur dir
* 3) system dir
* 4) windows dir
* 5) dirs on PATH
* We modify "exe dir" to be "client lib dir".
* we do not support cur dir.
* we additionally support loading from the Extensions dir
* (i#277/PR 540817, added to search_paths in privload_init_search_paths()).
*/
/* We may be passed a full path. */
if (os_file_exists(impname, false/*!is_dir*/)) {
mod = privload_load(impname, dependent, reachable);
return mod; /* if fails to load, don't keep searching */
}
/* 1) client lib dir(s) and Extensions dir */
for (i = 0; i < search_paths_idx; i++) {
snprintf(modpath, BUFFER_SIZE_ELEMENTS(modpath), "%s/%s",
search_paths[i], impname);
NULL_TERMINATE_BUFFER(modpath);
LOG(GLOBAL, LOG_LOADER, 2, "%s: looking for %s\n", __FUNCTION__, modpath);
if (os_file_exists(modpath, false/*!is_dir*/)) {
mod = privload_load(modpath, dependent, true/*always reachable*/);
/* if fails to load, don't keep searching: that seems the most
* reasonable semantics. we could keep searching: then should
* relax the privload_recurse_cnt curiosity b/c won't be reset
* in between if many copies of same lib fail to load.
*/
return mod;
}
}
/* 2) cur dir: we do not support */
if (systemroot[0] != '\0') {
/* 3) system dir */
snprintf(modpath, BUFFER_SIZE_ELEMENTS(modpath), "%s/system32/%s",
systemroot, impname);
NULL_TERMINATE_BUFFER(modpath);
LOG(GLOBAL, LOG_LOADER, 2, "%s: looking for %s\n", __FUNCTION__, modpath);
if (os_file_exists(modpath, false/*!is_dir*/)) {
mod = privload_load(modpath, dependent, reachable);
return mod; /* if fails to load, don't keep searching */
}
/* 4) windows dir */
snprintf(modpath, BUFFER_SIZE_ELEMENTS(modpath), "%s/%s",
systemroot, impname);
NULL_TERMINATE_BUFFER(modpath);
LOG(GLOBAL, LOG_LOADER, 2, "%s: looking for %s\n", __FUNCTION__, modpath);
if (os_file_exists(modpath, false/*!is_dir*/)) {
mod = privload_load(modpath, dependent, reachable);
return mod; /* if fails to load, don't keep searching */
}
}
/* 5) dirs on PATH: FIXME: not supported yet */
#ifdef CLIENT_INTERFACE
if (mod == NULL) {
/* There's a SYSLOG in loader_init(), but we want the name of the missing
* library. If we end up using this loading code for cases where we
* expect failure, we could switch to a global missing_lib[] that we
* can write to and have loader_init() use to add to its message.
*/
SYSLOG(SYSLOG_ERROR, CLIENT_LIBRARY_UNLOADABLE, 4,
get_application_name(), get_application_pid(), impname,
"\n\tCannot find library");
}
#endif
return mod;
}
/* Although privload_init_paths will be called in both Linux and Windows,
* it is only called from os_loader_init_prologue, so it is ok to keep it
* local. Instead, we extract the shared code of adding ext path into
* privload_add_drext_path().
*/
static void
privload_init_search_paths(void)
{
reg_query_value_result_t value_result;
DIAGNOSTICS_KEY_VALUE_FULL_INFORMATION diagnostic_value_info;
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
privload_add_drext_path();
/* Get SystemRoot from CurrentVersion reg key */
value_result = reg_query_value(DIAGNOSTICS_OS_REG_KEY,
DIAGNOSTICS_SYSTEMROOT_REG_KEY,
KeyValueFullInformation,
&diagnostic_value_info,
sizeof(diagnostic_value_info), 0);
if (value_result == REG_QUERY_SUCCESS) {
snprintf(systemroot, BUFFER_SIZE_ELEMENTS(systemroot), "%S",
(wchar_t*)(diagnostic_value_info.NameAndData +
diagnostic_value_info.DataOffset -
DECREMENT_FOR_DATA_OFFSET));
NULL_TERMINATE_BUFFER(systemroot);
} else
ASSERT_NOT_REACHED();
}
/* i#440: win7 private kernel32 tries to init the console w/ csrss and
* not only gets back an error, but csrss then refuses any future
* console ops from either DR or the app itself!
* Our workaround here is to disable the ConnectConsoleInternal call
* from the private kernel32. We leave the rest alone, and it
* seems to work: or at least, printing to the console works for
* both the app and the private kernel32.
*/
static bool
privload_disable_console_init(privmod_t *mod)
{
app_pc pc, entry, prev_pc, protect;
instr_t instr;
dcontext_t *dcontext = GLOBAL_DCONTEXT;
bool prev_marks_call = false;
bool success = false;
app_pc push1 = NULL, push2 = NULL, push3 = NULL;
uint orig_prot, count = 0;
static const uint MAX_DECODE = IF_X64_ELSE(1200,1024);
static const uint MAX_INSTR_COUNT = 1024;
ASSERT(mod != NULL);
ASSERT(strcasecmp(mod->name, "kernel32.dll") == 0);
/* win8 does not need this fix (i#911) */
if (get_os_version() != WINDOWS_VERSION_7)
return true; /* nothing to do */
/* We want to turn the call to ConnectConsoleInternal from ConDllInitialize,
* which is called from the entry routine _BaseDllInitialize,
* into a nop. Unfortunately none of these are exported. We rely on the
* fact that the 1st param to the first ConDllInitialize call (not actually
* the one we care about, but same callee) is 2 (DLL_THREAD_ATTACH):
* kernel32!_BaseDllInitialize+0x8a:
* 53 push ebx
* 6a02 push 0x2
* e83c000000 call kernel32!ConDllInitialize (75043683)
*/
instr_init(dcontext, &instr);
entry = get_module_entry(mod->base);
for (pc = entry; pc < entry + MAX_DECODE; ) {
if (count++ > MAX_INSTR_COUNT)
break; /* bail */
instr_reset(dcontext, &instr);
prev_pc = pc;
pc = decode(dcontext, pc, &instr);
if (!instr_valid(&instr) || instr_is_return(&instr))
break; /* bail */
/* follow direct jumps. MAX_INSTR_COUNT avoids infinite loop on backward jmp. */
if (instr_is_ubr(&instr)) {
pc = opnd_get_pc(instr_get_target(&instr));
continue;
}
#ifdef X64
/* For x64 we don't have a very good way to identify. There is no
* ConDllInitialize, but the call to ConnectConsoleInternal from
* BaseDllInitialize is preceded by a rip-rel mov rax to memory.
*/
if (instr_get_opcode(&instr) == OP_mov_st &&
opnd_is_reg(instr_get_src(&instr, 0)) &&
opnd_get_reg(instr_get_src(&instr, 0)) == REG_RAX &&
opnd_is_rel_addr(instr_get_dst(&instr, 0))) {
prev_marks_call = true;
continue;
}
#else
if (instr_get_opcode(&instr) == OP_push_imm &&
opnd_get_immed_int(instr_get_src(&instr, 0)) == DLL_THREAD_ATTACH) {
prev_marks_call = true;
continue;
}
#endif
if (prev_marks_call &&
instr_get_opcode(&instr) == OP_call) {
/* For 32-bit we need to continue scanning. For 64-bit we're there. */
#ifdef X64
protect = prev_pc;
#else
app_pc tgt = opnd_get_pc(instr_get_target(&instr));
uint prev_lea = 0;
bool first_jcc = false;
/* Now we're in ConDllInitialize. The call to ConnectConsoleInternal
* has several lea's in front of it:
*
* 8d85d7f9ffff lea eax,[ebp-0x629]
* 50 push eax
* 8d85d8f9ffff lea eax,[ebp-0x628]
* 50 push eax
* 56 push esi
* e84c000000 call KERNEL32_620000!ConnectConsoleInternal (00636582)
*
* Unfortunately ConDllInitialize is not straight-line code.
* For now we follow the first je which is a little fragile.
* XXX: build full CFG.
*/
for (pc = tgt; pc < tgt + MAX_DECODE; ) {
if (count++ > MAX_INSTR_COUNT)
break; /* bail */
instr_reset(dcontext, &instr);
prev_pc = pc;
pc = decode(dcontext, pc, &instr);
if (!instr_valid(&instr) || instr_is_return(&instr))
break; /* bail */
if (!first_jcc && instr_is_cbr(&instr)) {
/* See above: a fragile hack to get to rest of routine */
tgt = opnd_get_pc(instr_get_target(&instr));
pc = tgt;
first_jcc = true;
continue;
}
/* Follow direct jumps, which is required on win7x86 (i#556c#5).
* MAX_INSTR_COUNT avoids infinite loop on backward jmp.
*/
if (instr_is_ubr(&instr)) {
pc = opnd_get_pc(instr_get_target(&instr));
continue;
}
if (instr_get_opcode(&instr) == OP_lea &&
opnd_is_base_disp(instr_get_src(&instr, 0)) &&
opnd_get_disp(instr_get_src(&instr, 0)) < -0x400) {
prev_lea++;
continue;
}
if (prev_lea >= 2 &&
instr_get_opcode(&instr) == OP_call) {
protect = push1;
#endif
/* found a call preceded by a large lea and maybe some pushes.
* replace the call:
* e84c000000 call KERNEL32_620000!ConnectConsoleInternal
* =>
* b801000000 mov eax,0x1
* and change the 3 pushes to nops (this is stdcall).
*/
/* 2 pages in case our code crosses a page */
if (!protect_virtual_memory((void *)PAGE_START(protect), PAGE_SIZE*2,
PAGE_READWRITE, &orig_prot))
break; /* bail */
if (push1 != NULL)
*push1 = RAW_OPCODE_nop;
if (push2 != NULL)
*push2 = RAW_OPCODE_nop;
if (push3 != NULL)
*push3 = RAW_OPCODE_nop;
*(prev_pc) = MOV_IMM2XAX_OPCODE;
*((uint *)(prev_pc+1)) = 1;
protect_virtual_memory((void *)PAGE_START(protect), PAGE_SIZE*2,
orig_prot, &orig_prot);
success = true;
break; /* done */
#ifndef X64
}
if (prev_lea > 0) {
if (instr_get_opcode(&instr) == OP_push) {
if (push1 != NULL) {
if (push2 != NULL)
push3 = prev_pc;
else
push2 = prev_pc;
} else
push1 = prev_pc;
} else {
push1 = push2 = push3 = NULL;
prev_lea = 0;
}
}
}
break; /* bailed, or done */
#endif
}
prev_marks_call = false;
}
instr_free(dcontext, &instr);
return success;
}
/* i#556: A process can be associated with only one console, which is why the call to
* ConnectConsoleInternal is nop'd out for win7. With the call disabled priv kernel32
* does not initialize globals needed for console support. This routine will share the
* real kernel32's ConsoleLpcHandle, ConsolePortHeap, and ConsolePortMemoryRemoteDelta
* with private kernel32. This will enable console support for 32-bit kernel and
* 64-bit apps.
*/
typedef BOOL (WINAPI *kernel32_AttachConsole_t) (IN DWORD);
static kernel32_AttachConsole_t kernel32_AttachConsole;
bool
privload_console_share(app_pc priv_kernel32, app_pc app_kernel32)
{
app_pc pc;
instr_t instr;
dcontext_t *dcontext = GLOBAL_DCONTEXT;
bool success = false;
size_t console_handle_diff, console_heap_diff, console_delta_diff;
app_pc console_handle = NULL, console_heap = NULL, console_delta = NULL;
app_pc get_console_cp;
BOOL status = false;
static const uint MAX_DECODE = 1024;
ASSERT(app_kernel32 != NULL);
/* GUI apps are initialized without a console. To enable writing to the console
* we attach to the parent's console.
* XXX: if an app attempts to create/attach to a console w/o first freeing itself
* from this console, it will fail since a process can only associate w/ one console.
* The solution here would be to monitor such attempts by the app and free the console
* that is setup here.
*/
if (get_own_peb()->ImageSubsystem != IMAGE_SUBSYSTEM_WINDOWS_CUI) {
kernel32_AttachConsole = (kernel32_AttachConsole_t)
get_proc_address(app_kernel32, "AttachConsole");
if (kernel32_AttachConsole == NULL)
return false;
status = kernel32_AttachConsole(ATTACH_PARENT_PROCESS);
if (status == 0) {
return false;
}
}
/* xref i#440: Noping out the call to ConsoleConnectInternal is enough to get console
* support for wow64. We have this check after in case of GUI app.
*/
if (is_wow64_process(NT_CURRENT_PROCESS)) {
return true;
}
/* Below here is win7-specific */
if (get_os_version() != WINDOWS_VERSION_7)
return true;
get_console_cp = (app_pc) get_proc_address(app_kernel32, "GetConsoleCP");
ASSERT(get_console_cp != NULL);
/* No exported routines directly reference the globals. The easiest and shortest
* path is through GetConsoleCP, where we look for a call to ConsoleClientCallServer
* which then references ConsoleLpcHandle and ConsolePortMemoryRemoteDelta.
* ConsolePortHeap seems to always precede the remote delta global by the size of a
* pointer in memory. Tested on win7 x86 and x64.
*/
instr_init(dcontext, &instr);
for (pc = get_console_cp; pc < get_console_cp + MAX_DECODE; ) {
instr_reset(dcontext, &instr);
pc = decode(dcontext, pc, &instr);
if (!instr_valid(&instr) || instr_is_return(&instr))
break; /* bail */
if (instr_get_opcode(&instr) == OP_call) {
app_pc tgt = opnd_get_pc(instr_get_target(&instr));
/* Now we're in ConsoleClientCallServer. ConsoleLpcHandle is referenced
* right away w/ a cmp. From there, we fall through on first je and then
* follow a jnz where ConsolePortMemoryRemoteDelta is referenced after.
*/
for (pc = tgt; pc < tgt + MAX_DECODE; ) {
instr_reset(dcontext, &instr);
pc = decode(dcontext, pc, &instr);
if (!instr_valid(&instr) || instr_is_return(&instr))
break; /* bail */
/* kernel32!ConsoleClientCallServer:
* 8bff mov edi,edi
* 55 push ebp
* 8bec mov ebp,esp
* 833da067a47500 cmp dword ptr [kernel32!ConsoleLpcHandle],0
* 0f8415a1feff je kernel32!ConsoleClientCallServer+0xe
*/
if (instr_get_opcode(&instr) == OP_cmp &&
#ifdef X64
opnd_is_rel_addr(instr_get_src(&instr, 0)) &&
#else
opnd_is_abs_addr(instr_get_src(&instr, 0)) &&
#endif
opnd_is_immed_int(instr_get_src(&instr, 1)) &&
opnd_get_immed_int(instr_get_src(&instr, 1)) == 0) {
console_handle = (app_pc) opnd_get_addr(instr_get_src(&instr, 0));
continue;
}
if (instr_get_opcode(&instr) == OP_jnz) {
pc = opnd_get_pc(instr_get_target(&instr));
/* First instruction following the jnz is a mov which references
* ConsolePortMemoryRemoteDelta as src.
*
* 85ff test edi,edi
* 0f8564710000 jne kernel32!ConsoleClientCallServer+0x49 (759dbcb4)
*
* kernel32!ConsoleClientCallServer+0x49:
* a18465a475 mov eax,dword ptr[kernel32!ConsolePortMemoryRemoteDelta]
*/
instr_reset(dcontext, &instr);
pc = decode(dcontext, pc, &instr);
if (!instr_valid(&instr))
break; /* bail */
if (instr_get_opcode(&instr) == OP_mov_ld &&
#ifdef X64
opnd_is_rel_addr(instr_get_src(&instr, 0))) {
#else
opnd_is_abs_addr(instr_get_src(&instr, 0))) {
#endif
console_delta = (app_pc) opnd_get_addr(instr_get_src(&instr, 0));
success = true;
}
break; /* done */
}
}
break; /* bailed, or done */
}
}
/* If we have successfully retrieved the addr to each global from app's kernel32, we
* now share the values with private kernel32.
* XXX: right now we calculate delta from base of kernel32 to each global. We should
* add a checksum to ensure app's kernel32 is same as private kernel32.
*/
if (success) {
console_handle_diff = console_handle - app_kernel32;
console_delta_diff = console_delta - app_kernel32;
console_heap_diff = console_delta_diff - sizeof(PVOID);
if (!safe_write_ex(priv_kernel32 + console_handle_diff, sizeof(PHANDLE),
console_handle, NULL) ||
!safe_write_ex(priv_kernel32 + console_delta_diff, sizeof(ULONG_PTR),
console_delta, NULL) ||
!safe_write_ex(priv_kernel32 + console_heap_diff, sizeof(PVOID),
app_kernel32 + console_heap_diff, NULL))
success = false;
}
instr_free(dcontext, &instr);
return success;
}
/* Rather than statically linking to real kernel32 we want to invoke
* routines in the private kernel32
*/
void
privload_redirect_setup(privmod_t *mod)
{
drwinapi_onload(mod);
if (strcasecmp(mod->name, "kernel32.dll") == 0) {
if (privload_disable_console_init(mod))
LOG(GLOBAL, LOG_LOADER, 2, "%s: fixed console setup\n", __FUNCTION__);
else /* we want to know about it: may well happen in future version of dll */
SYSLOG_INTERNAL_WARNING("unable to fix console setup");
}
}
/* XXX i#1298: we may also want to support redirecting routines via hook instead
* of only via imports, to handle cases where priv libs call into their own
* routines.
*/
static app_pc
privload_redirect_imports(privmod_t *impmod, const char *name, privmod_t *importer)
{
return drwinapi_redirect_imports(impmod, name, importer);
}
/* Handles a private-library callback called from interpreted app code.
* This should no longer happen now that we fully isolate the PEB and FLS,
* but I'm leaving the mechanism in case we need it in the future.
*/
bool
private_lib_handle_cb(dcontext_t *dcontext, app_pc pc)
{
return true;
}
/***************************************************************************
* SECURITY COOKIE
*/
#ifdef X64
# define SECURITY_COOKIE_INITIAL 0x00002B992DDFA232
#else
# define SECURITY_COOKIE_INITIAL 0xBB40E64E
#endif
#define SECURITY_COOKIE_16BIT_INITIAL 0xBB40
static ULONG_PTR
get_tick_count(void)
{
if (ntdll_NtTickCount != NULL) {
return (*ntdll_NtTickCount)();
} else {
/* Pre-win2k3, there is no ntdll!NtTickCount, and kernel32!GetTickCount
* does a simple computation from KUSER_SHARED_DATA.
*/
KUSER_SHARED_DATA *kud = (KUSER_SHARED_DATA *) KUSER_SHARED_DATA_ADDRESS;
ULONG64 val = (ULONG64)kud->TickCountLowDeprecated * kud->TickCountMultiplier;
return (ULONG_PTR)(val >> 18);
}
}
static bool
privload_set_security_cookie(privmod_t *mod)
{
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *) mod->base;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *) (mod->base + dos->e_lfanew);
IMAGE_DATA_DIRECTORY *dir;
IMAGE_LOAD_CONFIG_DIRECTORY *config;
ptr_uint_t *cookie_ptr;
ptr_uint_t cookie;
uint64 time100ns;
LARGE_INTEGER perfctr;
ASSERT(is_readable_pe_base(mod->base));
ASSERT(dos->e_magic == IMAGE_DOS_SIGNATURE);
ASSERT(nt != NULL && nt->Signature == IMAGE_NT_SIGNATURE);
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
dir = OPT_HDR(nt, DataDirectory) + IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG;
if (dir == NULL || dir->Size <= 0)
return false;
config = (IMAGE_LOAD_CONFIG_DIRECTORY *) RVA_TO_VA(mod->base, dir->VirtualAddress);
if (dir->Size < offsetof(IMAGE_LOAD_CONFIG_DIRECTORY, SecurityCookie) +
sizeof(config->SecurityCookie)) {
ASSERT_CURIOSITY(false && "IMAGE_LOAD_CONFIG_DIRECTORY too small");
return false;
}
cookie_ptr = (ptr_uint_t *) config->SecurityCookie;
if ((byte *)cookie_ptr < mod->base || (byte *)cookie_ptr >= mod->base + mod->size) {
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s has out-of-bounds cookie @"PFX"\n",
__FUNCTION__, mod->name, cookie_ptr);
return false;
}
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s dirsz="PIFX" configsz="PIFX" init cookie="PFX"\n",
__FUNCTION__, mod->name, dir->Size, config->Size, *cookie_ptr);
if (*cookie_ptr != SECURITY_COOKIE_INITIAL &&
*cookie_ptr != SECURITY_COOKIE_16BIT_INITIAL) {
/* I'm assuming a cookie should either be the magic value, or zero if
* no cookie is desired? Let's have a curiosity to find if there are
* any other values:
*/
ASSERT_CURIOSITY(*cookie_ptr == 0);
return true;
}
/* Generate a new cookie using:
* SystemTimeHigh ^ SystemTimeLow ^ ProcessId ^ ThreadId ^ TickCount ^
* PerformanceCounterHigh ^ PerformanceCounterLow
*/
time100ns = query_time_100ns();
/* 64-bit seems to sign-extend so we use ptr_int_t */
cookie = (ptr_int_t)(time100ns >> 32) ^ (ptr_int_t)time100ns;
cookie ^= get_process_id();
cookie ^= get_thread_id();
cookie ^= get_tick_count();
nt_query_performance_counter(&perfctr, NULL);
#ifdef X64
cookie ^= perfctr.QuadPart;
#else
cookie ^= perfctr.LowPart;
cookie ^= perfctr.HighPart;
#endif
if (*cookie_ptr == SECURITY_COOKIE_16BIT_INITIAL)
cookie &= 0xffff; /* only want low 16 bits */
if (cookie == SECURITY_COOKIE_INITIAL ||
cookie == SECURITY_COOKIE_16BIT_INITIAL) {
/* If it happens to match, make it not match */
cookie--;
}
#ifdef X64
/* Apparently the top 16 bits should always be 0.
* XXX: is my algorithm wrong for x64 above?
*/
cookie &= 0x0000ffffffffffff;
#endif
LOG(GLOBAL, LOG_LOADER, 2, " new cookie value: "PFX"\n", cookie);
*cookie_ptr = cookie;
return true;
}
void
privload_os_finalize(privmod_t *mod)
{
/* Libraries built with /GS require us to set
* IMAGE_LOAD_CONFIG_DIRECTORY.SecurityCookie (i#1093)
*/
privload_set_security_cookie(mod);
/* FIXME: not supporting TLS today in Windows:
* covered by i#233, but we don't expect to see it for dlls, only exes
*/
}
/***************************************************************************/
#ifdef WINDOWS
/* i#522: windbg commands for viewing symbols for private libs */
static bool
add_mod_to_drmarker(dr_marker_t *marker, const char *path, const char *modname,
byte *base, size_t *sofar)
{
const char *last_dir = double_strrchr(path, DIRSEP, ALT_DIRSEP);
int res;
if (last_dir == NULL) {
SYSLOG_INTERNAL_WARNING_ONCE("drmarker windbg cmd: invalid path");
return false;
}
/* We have to use .block{} b/c .sympath eats up all chars until the end
* of the "line", which for as /c is the entire command output, but it
* does stop at the }.
* Sample:
* .block{.sympath+ c:\src\dr\git\build_x86_dbg\api\samples\bin};
* .reload bbcount.dll=74ad0000;.echo "Loaded bbcount.dll";
*
*/
#define WINDBG_ADD_PATH ".block{.sympath+ "
if (*sofar + strlen(WINDBG_ADD_PATH) + (last_dir - path) < WINDBG_CMD_MAX_LEN) {
res = _snprintf(marker->windbg_cmds + *sofar,
strlen(WINDBG_ADD_PATH) + last_dir - path,
"%s%s", WINDBG_ADD_PATH, path);
ASSERT(res == -1);
*sofar += strlen(WINDBG_ADD_PATH) + last_dir - path;
return print_to_buffer(marker->windbg_cmds, WINDBG_CMD_MAX_LEN, sofar,
/* XXX i#631: for 64-bit, windbg fails to successfully
* load (has start==end) so we use /i as workaround
*/
"};\n.reload /i %s="PFMT";.echo \"Loaded %s\";\n",
modname, base, modname);
} else {
SYSLOG_INTERNAL_WARNING_ONCE("drmarker windbg cmds out of space");
return false;
}
}
static void
privload_add_windbg_cmds(void)
{
/* i#522: print windbg commands to locate DR and priv libs */
dr_marker_t *marker = get_drmarker();
size_t sofar;
privmod_t *mod;
sofar = strlen(marker->windbg_cmds);
/* dynamorio.dll is in the list on windows right now.
* if not we'd add with get_dynamorio_library_path(), DYNAMORIO_LIBRARY_NAME,
* and marker->dr_base_addr
*/
/* XXX: currently only adding those on the list at init time here
* and later loaded in privload_add_windbg_cmds_post_init(): ignoring
* unloaded modules.
*/
acquire_recursive_lock(&privload_lock);
for (mod = privload_first_module(); mod != NULL; mod = privload_next_module(mod)) {
/* user32 and ntdll are not private */
if (strcasecmp(mod->name, "user32.dll") != 0 &&
strcasecmp(mod->name, "ntdll.dll") != 0) {
if (!add_mod_to_drmarker(marker, mod->path, mod->name, mod->base, &sofar))
break;
}
}
windbg_cmds_initialized = true;
release_recursive_lock(&privload_lock);
}
static void
privload_add_windbg_cmds_post_init(privmod_t *mod)
{
/* i#522: print windbg commands to locate DR and priv libs */
dr_marker_t *marker = get_drmarker();
size_t sofar;
acquire_recursive_lock(&privload_lock);
/* privload_lock is our synch mechanism for drmarker windbg field */
sofar = strlen(marker->windbg_cmds);
if (dynamo_initialized) {
set_protection((byte *)marker, sizeof(*marker),
MEMPROT_READ|MEMPROT_WRITE|MEMPROT_EXEC);
}
add_mod_to_drmarker(marker, mod->path, mod->name, mod->base, &sofar);
if (dynamo_initialized) {
set_protection((byte *)marker, sizeof(*marker),
MEMPROT_READ|MEMPROT_EXEC);
}
release_recursive_lock(&privload_lock);
}
/***************************************************************************/
/* early injection bootstrapping
*
* dynamorio.dll has been mapped in by the parent but its imports are
* not set up. We do that here. We can't make any library calls
* since those require imports. We could try to share code with
* privload_get_import_descriptor(), privload_process_imports(),
* privload_process_one_import(), and get_proc_address_ex(), but IMHO
* the code duplication is worth the simplicity of not having a param
* or sthg that is checked on every LOG or ASSERT.
*/
typedef NTSTATUS (NTAPI *nt_protect_t)(IN HANDLE, IN OUT PVOID *, IN OUT PSIZE_T,
IN ULONG, OUT PULONG);
static nt_protect_t bootstrap_ProtectVirtualMemory;
/* exported for earliest_inject_init() */
bool
bootstrap_protect_virtual_memory(void *base, size_t size, uint prot, uint *old_prot)
{
NTSTATUS res;
SIZE_T sz = size;
if (bootstrap_ProtectVirtualMemory == NULL)
return false;
res = bootstrap_ProtectVirtualMemory(NT_CURRENT_PROCESS, &base, &sz,
prot, (ULONG*)old_prot);
return (NT_SUCCESS(res) && sz == size);
}
static char
bootstrap_tolower(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A' + 'a';
else
return c;
}
static int
bootstrap_strcmp(const char *s1, const char *s2, bool ignore_case)
{
char c1, c2;
while (true) {
if (*s1 == '\0') {
if (*s2 == '\0')
return 0;
return -1;
} else if (*s2 == '\0')
return 1;
c1 = (char) *s1;
c2 = (char) *s2;
if (ignore_case) {
c1 = bootstrap_tolower(c1);
c2 = bootstrap_tolower(c2);
}
if (c1 != c2) {
if (c1 < c2)
return -1;
else
return 1;
}
s1++;
s2++;
}
}
/* Does not handle forwarders! Assumed to be called on ntdll only. */
static generic_func_t
privload_bootstrap_get_export(byte *modbase, const char *name)
{
size_t exports_size;
uint i;
IMAGE_EXPORT_DIRECTORY *exports;
PULONG functions; /* array of RVAs */
PUSHORT ordinals;
PULONG fnames; /* array of RVAs */
uint ord = UINT_MAX; /* the ordinal to use */
app_pc func;
bool match = false;
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *) modbase;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *) (modbase + dos->e_lfanew);
IMAGE_DATA_DIRECTORY *expdir;
if (dos->e_magic != IMAGE_DOS_SIGNATURE ||
nt == NULL || nt->Signature != IMAGE_NT_SIGNATURE)
return NULL;
expdir = OPT_HDR(nt, DataDirectory) + IMAGE_DIRECTORY_ENTRY_EXPORT;
if (expdir == NULL || expdir->Size <= 0)
return NULL;
exports = (IMAGE_EXPORT_DIRECTORY *) (modbase + expdir->VirtualAddress);
exports_size = expdir->Size;
if (exports == NULL || exports->NumberOfNames == 0 || exports->AddressOfNames == 0)
return NULL;
functions = (PULONG)(modbase + exports->AddressOfFunctions);
ordinals = (PUSHORT)(modbase + exports->AddressOfNameOrdinals);
fnames = (PULONG)(modbase + exports->AddressOfNames);
for (i = 0; i < exports->NumberOfNames; i++) {
char *export_name = (char *)(modbase + fnames[i]);
match = (bootstrap_strcmp(name, export_name, false) == 0);
if (match) {
/* we have a match */
ord = ordinals[i];
break;
}
}
if (!match || ord >=exports->NumberOfFunctions)
return NULL;
func = (app_pc)(modbase + functions[ord]);
if (func == modbase)
return NULL;
if (func >= (app_pc)exports &&
func < (app_pc)exports + exports_size) {
/* forwarded */
return NULL;
}
/* get around warnings converting app_pc to generic_func_t */
return convert_data_to_function(func);
}
bool
privload_bootstrap_dynamorio_imports(byte *dr_base, byte *ntdll_base)
{
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *) dr_base;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *) (dr_base + dos->e_lfanew);
IMAGE_DATA_DIRECTORY *dir;
IMAGE_IMPORT_DESCRIPTOR *imports;
byte *iat, *imports_end;
uint orig_prot;
generic_func_t func;
/* first, get the one library routine we require */
bootstrap_ProtectVirtualMemory = (nt_protect_t)
privload_bootstrap_get_export(ntdll_base, "NtProtectVirtualMemory");
if (bootstrap_ProtectVirtualMemory == NULL)
return false;
/* get import descriptor (modeled on privload_get_import_descriptor()) */
if (dos->e_magic != IMAGE_DOS_SIGNATURE ||
nt == NULL || nt->Signature != IMAGE_NT_SIGNATURE)
return false;
dir = OPT_HDR(nt, DataDirectory) + IMAGE_DIRECTORY_ENTRY_IMPORT;
if (dir == NULL || dir->Size <= 0)
return false;
imports = (IMAGE_IMPORT_DESCRIPTOR *) RVA_TO_VA(dr_base, dir->VirtualAddress);
imports_end = dr_base + dir->VirtualAddress + dir->Size;
/* walk imports (modeled on privload_process_imports()) */
while (imports->OriginalFirstThunk != 0) {
IMAGE_THUNK_DATA *lookup;
IMAGE_THUNK_DATA *address;
const char *impname = (const char *) RVA_TO_VA(dr_base, imports->Name);
if (bootstrap_strcmp(impname, "ntdll.dll", true) != 0)
return false; /* should only import from ntdll */
/* DR shouldn't have bound imports so ignoring TimeDateStamp */
/* walk the lookup table and address table in lockstep */
lookup = (IMAGE_THUNK_DATA *) RVA_TO_VA(dr_base, imports->OriginalFirstThunk);
address = (IMAGE_THUNK_DATA *) RVA_TO_VA(dr_base, imports->FirstThunk);
iat = (app_pc) address;
if (!bootstrap_protect_virtual_memory((void *)PAGE_START(iat),
PAGE_SIZE, PAGE_READWRITE, &orig_prot))
return false;
while (lookup->u1.Function != 0) {
IMAGE_IMPORT_BY_NAME *name = (IMAGE_IMPORT_BY_NAME *)
RVA_TO_VA(dr_base, (lookup->u1.AddressOfData & ~(IMAGE_ORDINAL_FLAG)));
if (TEST(IMAGE_ORDINAL_FLAG, lookup->u1.Function))
return false; /* no ordinal support */
func = privload_bootstrap_get_export(ntdll_base, (const char *) name->Name);
if (func == NULL)
return false;
*(byte **)address = (byte *) func;
lookup++;
address++;
if (PAGE_START(address) != PAGE_START(iat)) {
if (!bootstrap_protect_virtual_memory((void *)PAGE_START(iat), PAGE_SIZE,
orig_prot, &orig_prot))
return false;
iat = (app_pc) address;
if (!bootstrap_protect_virtual_memory((void *)PAGE_START(iat), PAGE_SIZE,
PAGE_READWRITE, &orig_prot))
return false;
}
}
if (!bootstrap_protect_virtual_memory((void *)PAGE_START(iat),
PAGE_SIZE, orig_prot, &orig_prot))
return false;
imports++;
if ((byte *)(imports+1) > imports_end)
return false;
}
return true;
}
#endif /* WINDOWS */