blob: 506a3380c8b831fd70a20f461503d375607aceb3 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 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.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* module.c - maintains information about modules (dll or executable images) */
#include "../globals.h"
#include "ntdll.h"
#include <stddef.h> /* for offsetof */
#ifdef RCT_IND_BRANCH
# include "../rct.h"
#endif
#include "../utils.h"
#include "os_private.h"
#include "aslr.h"
#include "instrument.h"
#include "../perscache.h" /* for coarse_info_t.rct_loaded */
#include "../hashtable.h" /* section2file table */
#include "instr.h"
#include "decode.h"
/* used to hold the version information we get from the .rsrc section */
typedef struct _version_info_t {
version_number_t file_version;
version_number_t product_version;
wchar_t *company_name;
wchar_t *product_name;
wchar_t *original_filename;
} version_info_t;
static const char *
get_module_original_filename(app_pc mod_base, version_info_t *in_info /*OPTIONAL IN*/
HEAPACCT(which_heap_t which));
static bool
module_area_free_IAT(module_area_t *ma);
/* NOTE the strings returned in info_out are pointing to the .rsrc version
* directory and as such they're only valid while the module is loaded. */
static bool
get_module_resource_version_info(app_pc mod_base, version_info_t *info_out);
#ifdef CLIENT_INTERFACE
typedef struct _pe_module_import_iterator_t {
dr_module_import_t module_import; /* module import returned by next() */
byte *mod_base;
size_t mod_size;
/* Points into an array of IMAGE_IMPORT_DESCRIPTOR structs. The last
* element of the array is zeroed.
*/
IMAGE_IMPORT_DESCRIPTOR *cur_module;
IMAGE_IMPORT_DESCRIPTOR safe_module; /* safe_read copy of cur_module */
byte *imports_end; /* end of the import descriptors */
bool hasnext; /* set to false on error or end */
} pe_module_import_iterator_t;
typedef struct _pe_symbol_import_iterator_t {
dr_symbol_import_t symbol_import; /* symbol import returned by next() */
dr_symbol_import_t next_symbol; /* next symbol import */
byte *mod_base;
dr_module_import_iterator_t *mod_iter; /* only for iterating all modules */
IMAGE_IMPORT_DESCRIPTOR *cur_module; /* always valid */
/* Points into the OriginalFirstThunk array of mod_iter->cur_module. */
IMAGE_THUNK_DATA *cur_thunk;
bool hasnext; /* set to false on error or end */
} pe_symbol_import_iterator_t;
#endif /* CLIENT_INTERFACE */
/****************************************************************************
* Section-to-file table for i#138 and PR 213463 (case 9028)
*/
static generic_table_t *section2file_table;
#define INIT_HTABLE_SIZE_SECTION 6 /* should remain small */
typedef struct _section_to_file_t {
HANDLE section_handle;
const char *file_path; /* dr_strdup-ed */
} section_to_file_t;
static void
section_to_file_free(section_to_file_t *s2f)
{
dr_strfree(s2f->file_path HEAPACCT(ACCT_VMAREAS));
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, s2f, section_to_file_t, ACCT_VMAREAS, PROTECTED);
}
/* Returns a dr_strdup-ed string which caller must dr_strfree w/ ACCT_VMAREAS */
const char *
section_to_file_lookup(HANDLE section_handle)
{
section_to_file_t *s2f;
const char *file = NULL;
TABLE_RWLOCK(section2file_table, read, lock);
s2f = generic_hash_lookup(GLOBAL_DCONTEXT, section2file_table,
(ptr_uint_t)section_handle);
if (s2f != NULL)
file = dr_strdup(s2f->file_path HEAPACCT(ACCT_VMAREAS));
TABLE_RWLOCK(section2file_table, read, unlock);
return file;
}
static bool
section_to_file_add_common(HANDLE section_handle, const char *filepath_dup)
{
section_to_file_t *s2f;
bool added = false;
TABLE_RWLOCK(section2file_table, write, lock);
s2f = generic_hash_lookup(GLOBAL_DCONTEXT, section2file_table,
(ptr_uint_t)section_handle);
if (s2f != NULL) {
/* update */
dr_strfree(s2f->file_path HEAPACCT(ACCT_VMAREAS));
} else {
added = true;
s2f = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, section_to_file_t, ACCT_VMAREAS, PROTECTED);
s2f->section_handle = section_handle;
generic_hash_add(GLOBAL_DCONTEXT, section2file_table,
(ptr_uint_t)s2f->section_handle, (void *)s2f);
}
s2f->file_path = filepath_dup;
LOG(GLOBAL, LOG_VMAREAS, 2,
"section_to_file: section "PFX" => %s\n", section_handle, s2f->file_path);
TABLE_RWLOCK(section2file_table, write, unlock);
return added;
}
bool
section_to_file_add_wide(HANDLE section_handle, const wchar_t *file_path)
{
return section_to_file_add_common(section_handle,
dr_wstrdup(file_path HEAPACCT(ACCT_VMAREAS)));
}
bool
section_to_file_add(HANDLE section_handle, const char *file_path)
{
return section_to_file_add_common(section_handle,
dr_strdup(file_path HEAPACCT(ACCT_VMAREAS)));
}
bool
section_to_file_remove(HANDLE section_handle)
{
bool found = false;
TABLE_RWLOCK(section2file_table, write, lock);
found = generic_hash_remove(GLOBAL_DCONTEXT, section2file_table,
(ptr_uint_t)section_handle);
TABLE_RWLOCK(section2file_table, write, unlock);
DODEBUG({
if (found) {
LOG(GLOBAL, LOG_VMAREAS, 2,
"section_to_file: removed section "PFX"\n", section_handle);
}
});
return found;
}
/****************************************************************************/
#ifdef DEBUG /* around symbols related part of the file */
#include <windows.h>
/* Currently only uses dll export functions for DEBUG symbol information */
/* Private data types */
typedef struct export_entry_t {
app_pc entry_point; /* exported function entry point */
char *export_name;
} export_entry_t;
/* FIXME: a module can have multiple code sections and each should
* have a separate searchable entry, yet all relevant per-module
* structures should be thrown away when a module is unloaded.
* Caution with data sections (or other invalidated vmareas) that may
* be within the module region.
*/
/* use module_area_t for any release build needs */
typedef struct module_info_t {
app_pc start;
app_pc end; /* open end interval */
char * module_name;
size_t exports_size; /* initial export table length */
uint exports_num; /* number of unique exports */
export_entry_t *exports_table; /* sorted array to allow range searches */
/* FIXME: this is necessary only for debug symbol lookup, if
* implementing export restrictions (case 286), we only need
* membership tests so we only have to populate the entry points
* into a hashtable
*/
/* FIXME: case 3927
* It will be quite useful for some debugging sessions to
* use real symbols from PDB's. It maybe easier if they are
* already preprocessed with windbg -c 'x module!*' or some other
* PDB parser. Then we'll have an easier time debugging and
* reverse engineering some external components. We could even
* think about security policies (or constraints) that would
* require symbols, since at least M$ has at least public PDB's
* (e.g. not private with variable names) but a good starting
* point.
*/
} module_info_t;
/* This structure is parallel to the one kept by the loader -
* yet may be safer to use. See comment in vm_area_vector_t.
*
* FIXME: We may want to abstract this and vm_area_vector_t into a common data structure
* with polymorphic elements with a start,end
*/
/* The module vector is kept sorted by area. Since there are no overlaps allowed
* among areas in the same vector, sorting by start_pc or by end_pc produce
* identical results.
*/
typedef struct module_info_vector_t {
struct module_info_t *buf;
int capacity;
int length;
/* thread-shared, so needs a lock */
/* FIXME: make this a read/write lock if readers contend too often */
mutex_t lock;
} module_info_vector_t;
/* debug-only so we don't need to efficiently protect it */
DECLARE_CXTSWPROT_VAR(static module_info_vector_t process_module_vector,
{NULL, 0, 0, INIT_LOCK_FREE(process_module_vector_lock)});
static void
print_module_list(module_info_vector_t *v)
{
int i;
LOG(GLOBAL, LOG_SYMBOLS, 4,
"print_module_list("PFX") capacity=%d, length=%d, lock=%d, buf="PFX,
v, v->capacity, v->length, v->lock, v->buf);
mutex_lock(&v->lock);
for(i = 0; i < v->length; i++) {
LOG(GLOBAL, LOG_SYMBOLS, 3, " "PFX"-"PFX" %s, %d exports ["SZFMT" size]\n",
v->buf[i].start,
v->buf[i].end,
v->buf[i].module_name,
v->buf[i].exports_num,
v->buf[i].exports_size);
}
mutex_unlock(&v->lock);
}
/* For binary search */
int
module_info_compare(const void *vkey, const void *vel)
{
module_info_t *key = ((module_info_t *)vkey);
module_info_t *el = ((module_info_t *)vel);
if (key->end <= el->start)
return (-1); /* key less than element */
if (key->start >= el->end)
return (1); /* key greater than element */
return (0); /* key equals (overlaps) element */
}
/* lookup a module by address,
assumes the v->lock is held by caller!
returned module_info_t *should not be used after releasing lock
returns NULL if no module found
*/
static module_info_t*
lookup_module_info(module_info_vector_t *v, app_pc addr)
{
/* BINARY SEARCH -- assumes the vector is kept sorted by add & remove! */
module_info_t key = {addr, addr+1}; /* end is open */
#ifdef NOLIBC
/* FIXME : copied from find_predecessor(), would be nice to share with
* that routine and with binary range search (w/linear backsearch) in
* vmareas.c */
int min = 0;
int max = v->length - 1;
/* binary search */
while (max >= min) {
int i = (min + max) / 2;
int cmp = module_info_compare(&key, &(v->buf[i]));
if (cmp < 0)
max = i - 1;
else if (cmp > 0)
min = i + 1;
else {
return &(v->buf[i]);
}
}
return NULL;
#else
return bsearch(&key, v->buf, v->length,
sizeof(module_info_t), module_info_compare);
#endif
}
#define INITIAL_MODULE_NUMBER 4
/* Creates a new module info, allocates its exports table, and adds to module vector,
* module_name is caller allocated (module_name is from the exports section for PE dlls)
*
* Returns a pointer to the module's export table
*/
static
export_entry_t *
module_info_create(module_info_vector_t *v, app_pc start, app_pc end,
char *module_name, uint exports_num)
{
struct module_info_t new_module = {start, end, module_name, exports_num, exports_num, 0 /* table */};
int i,j;
if (exports_num > 0) {
new_module.exports_table = global_heap_alloc(exports_num * sizeof(export_entry_t)
HEAPACCT(ACCT_SYMBOLS));
} else {
new_module.exports_table = NULL;
}
mutex_lock(&v->lock);
/* FIXME: the question is what to do when an overlap occurs.
* If we assume that we should have removed the references from an old DLL.
* A possibly new DLL overlapping the same range should not show up,
* this indeed would be an error worth investigating.
*/
/* FIXME: need a real overlap check */
if (lookup_module_info(v, start) != NULL) {
ASSERT_NOT_REACHED();
return NULL;
}
for (i = 0; i < v->length; i++) {
if (end <= v->buf[i].start)
break;
}
/* check if at full capacity */
if (v->capacity == v->length){
int new_size = v->capacity ? v->capacity * 2 : INITIAL_MODULE_NUMBER;
v->buf = global_heap_realloc(v->buf, v->capacity, new_size,
sizeof(struct module_info_t) HEAPACCT(ACCT_SYMBOLS));
v->capacity = new_size;
ASSERT(v->buf);
}
/* shift subsequent to i entries */
for (j = v->length; j > i; j--)
v->buf[j] = v->buf[j-1];
v->buf[i] = new_module;
v->length++;
mutex_unlock(&v->lock);
DOLOG(3, LOG_SYMBOLS, { print_module_list(v); });
/* we can not return &v->buf[i] since buf may get realloc-ed, or buf[i] may be shifted */
return new_module.exports_table;
}
/* remove from module vector and free up memory */
static
void
remove_module_info_vector(module_info_vector_t *v, app_pc start, app_pc end)
{
int i,j;
export_entry_t *exports_table = NULL;
size_t exports_size = 0;
mutex_lock(&v->lock);
/* linear search, we don't have a find_predecessor on module_info_t's to get i */
for (i = 0; i < v->length; i++) {
if (start == v->buf[i].start && end == v->buf[i].end) {
exports_table = v->buf[i].exports_table;
exports_size = v->buf[i].exports_size;
break;
}
}
LOG(GLOBAL, LOG_SYMBOLS, 2, "remove_module_info_vector("PFX","PFX") dll=%s\n",
start, end, v->buf[i].module_name);
ASSERT_CURIOSITY(exports_table); /* curiosity */
if (!exports_table) {
/* it could have disappeared since we last checked */
mutex_unlock(&v->lock);
return;
}
/* shift subsequent to i entries over */
for (j = i+1; j < v->length; j++)
v->buf[j-1] = v->buf[j];
v->length--;
mutex_unlock(&v->lock);
if (exports_size > 0) {
global_heap_free(exports_table, exports_size * sizeof(export_entry_t)
HEAPACCT(ACCT_SYMBOLS));
}
DOLOG(3, LOG_SYMBOLS, { print_module_list(v); });
}
/* remove internal bookkeeping for unloaded module
returns 1 if the range is a known module, and that is removed,
0 otherwise
*/
int
remove_module_info(app_pc start, size_t size)
{
module_info_t *pmod;
mutex_lock(&process_module_vector.lock);
pmod = lookup_module_info(&process_module_vector, start);
mutex_unlock(&process_module_vector.lock);
if (!pmod) { /* FIXME: need a real overlap check */
LOG(GLOBAL, LOG_SYMBOLS, 2,
"WARNING:remove_module_info called on unknown module "PFX", size "PIFX"\n",
start, size);
/* my assert_curiosity was triggered, yet unexplained */
return 0;
}
remove_module_info_vector(&process_module_vector, (app_pc)start, (app_pc)(start + size));
return 1;
}
void
module_cleanup(void)
{
module_info_vector_t *v = &process_module_vector;
int i;
export_entry_t *exports_table = NULL;
size_t exports_size = 0;
mutex_lock(&v->lock);
/* linear search, we don't have a find_predecessor on module_info_t's to get i */
for (i = 0; i < v->length; i++) {
exports_table = v->buf[i].exports_table;
exports_size = v->buf[i].exports_size;
if (exports_size > 0) {
global_heap_free(exports_table, exports_size * sizeof(export_entry_t)
HEAPACCT(ACCT_SYMBOLS));
}
}
if (v->buf != NULL)
global_heap_free(v->buf, v->capacity * sizeof(struct module_info_t)
HEAPACCT(ACCT_SYMBOLS));
v->buf = NULL;
v->capacity = 0;
v->length = 0;
mutex_unlock(&v->lock);
}
void
module_info_exit()
{
module_cleanup();
DELETE_LOCK(process_module_vector.lock);
}
int
export_entry_compare(const void *vkey, const void *vel)
{
/* used for qsort so only care about sign; truncation is ok */
return (int)(((export_entry_t*)vkey)->entry_point
- ((export_entry_t*)vel)->entry_point);
}
/* Returns the offset within table[] of the last element equal or smaller than key,
table[] must be sorted in ascending order.
Returns -1 when smaller than the first element, or array empty
*/
int
find_predecessor(export_entry_t table[], int n, app_pc tag)
{
int min = 0;
int max = n - 1;
/* binary search */
while (max >= min) {
int i = (min + max) / 2;
if (tag < table[i].entry_point)
max = i - 1;
else if (tag > table[i].entry_point)
min = i + 1;
else {
return i;
}
}
/* now max < min */
return max; /* may be -1 */
}
/* remove duplicate export entries,
returns number of unique entry points
(assumes table is ordered by address)
*/
int
remove_export_duplicates(export_entry_t table[], int n)
{
int i=0, j=1;
if (n < 2)
return n;
while (j < n) {
if (table[i].entry_point == table[j].entry_point) {
LOG(GLOBAL, LOG_SYMBOLS, 3,
"Export alias %s == %s\n", table[i].export_name, table[j].export_name);
} else {
i++;
table[i] = table[j];
}
j++;
}
i++;
return i;
}
/* prints a symbolic name, or best guess of it into a caller provided buffer */
void
print_symbolic_address(app_pc tag, char *buf, int max_chars, bool exact_only)
{
module_info_t *pmod; /* volatile pointer */
module_info_t mod = {0}; /* copy of module info */
/* FIXME: cannot grab this lock under internal_exception_lock */
if (under_internal_exception()) {
pmod = NULL;
} else {
mutex_lock(&process_module_vector.lock); /* FIXME: this can be a shared read lock */
{
pmod = lookup_module_info(&process_module_vector, tag);
if (pmod) {
mod = *pmod; /* keep a copy in case of reallocations */
/* the data will be invalid only in a race condition,
where some other thread frees the library */
}
}
mutex_unlock(&process_module_vector.lock);
}
buf[0]='\0';
if (pmod != NULL) {
int i = find_predecessor(mod.exports_table, mod.exports_num, tag);
if (i<0) { /* tag smaller than first exported function */
/* convert to offset from base */
if (!exact_only) {
snprintf(buf, max_chars, "[%s~%s+"PIFX"]",
mod.module_name, ".begin",
tag - mod.start);
}
} else {
if (mod.exports_table[i].entry_point == tag) {
/* tag exactly matches an export, i.e. <ntdll!CsrIdentifyAlertableThread> */
snprintf(buf, max_chars, "[%s!%s]", mod.module_name, mod.exports_table[i].export_name);
} else if (!exact_only) {
uint prev = (uint)i;
uint next = (uint)i+1;
ASSERT((uint)i < mod.exports_num);
/* <KERNEL32.dll~CreateProcessW+0x1564,~RegisterWaitForInputIdle-0x9e> */
snprintf(buf, max_chars, "[%s~%s+"PIFX",~%s-"PIFX"]",
mod.module_name, mod.exports_table[prev].export_name,
tag - (ptr_uint_t)mod.exports_table[prev].entry_point,
next < mod.exports_num ?
mod.exports_table[next].export_name : ".end",
(next < mod.exports_num ?
mod.exports_table[next].entry_point :
mod.end) - (ptr_uint_t)tag);
}
}
} else {
char modname_buf[MAX_MODNAME_INTERNAL];
const char *short_name = NULL;
if (under_internal_exception()) {
/* We're called in fragile situations so we explicitly check here.
* Will get lock rank order in accessing module_data_lock so just
* use PE name. This is for debugging only anyway.
*/
app_pc base = get_allocation_base(tag);
if (base != NULL && is_readable_pe_base(base))
short_name = get_dll_short_name(base);
if (short_name == NULL)
short_name = "";
} else {
os_get_module_name_buf(tag, modname_buf, BUFFER_SIZE_ELEMENTS(modname_buf));
short_name = modname_buf;
}
/* since currently we aren't working well w/ dynamically loaded dlls, and
* certain things are disabled at lower loglevels, fall back to the short name
*/
DODEBUG({
get_module_name(tag, buf, max_chars);
/* check if we get the same name */
if (strcasecmp(get_short_name(buf), short_name) != 0 && buf[0]!='\0') {
/* after a module is off the module list some code from it still
* gets executed */
/* In addition there are modules with different file names,
e.g. wdmaud.drv != wdmaud.dll (export section name) */
LOG(GLOBAL, LOG_SYMBOLS, 3,
"WARNING: print_symbolic_address("PFX"): ldr name='%s' "
"pe name='%s'\n", tag, get_short_name(buf), short_name);
}
});
snprintf(buf, max_chars, "[%s]", short_name);
}
buf[max_chars-1]='\0'; /* to make sure */
LOG(GLOBAL, LOG_SYMBOLS, 5, "print_symbolic_address("PFX")='%s'\n", tag, buf);
}
/* adds a module to the module_info_t list, and parses its exports table,
this can be done as soon as the module is mapped in the address space */
/* returns 1 if successfully added
0 if address range is not a PE file */
int
add_module_info(app_pc base_addr, size_t image_size)
{
size_t size;
IMAGE_EXPORT_DIRECTORY *exports =
get_module_exports_directory_check(base_addr, &size, true);
if (exports != NULL) {
PULONG functions = (PULONG) (base_addr + exports->AddressOfFunctions);
PUSHORT ordinals = (PUSHORT) (base_addr + exports->AddressOfNameOrdinals);
PULONG fnames = (PULONG) (base_addr + exports->AddressOfNames);
char *dll_name = (char*) (base_addr + exports->Name);
uint exports_num = 0;
uint i;
export_entry_t *exports_table;
LOG(GLOBAL, LOG_SYMBOLS, 4, "\tnumnames=%d numfunc=%d",
exports->NumberOfNames, exports->NumberOfFunctions);
if (exports->NumberOfFunctions != exports->NumberOfNames) {
/* TODO: we should also use the knowledge about the noname [ordinal] entry points */
/* shlwapi.dll or winspool.drv are good examples
* find more with dumpbin /exports shlwapi.dll | grep "number of"
*/
/* These are in fact much more important for
* rct_add_exports() where we traverse functions otherwise
* we'd have a .E in shlwapi on a noname export
* SHLWAPI!Ordinal80
*/
LOG(GLOBAL, LOG_SYMBOLS, 2,
"add_module_info: %s functions %d != %d names\n", dll_name,
exports->NumberOfFunctions, exports->NumberOfNames);
}
/* FIXME: Once we do use noname entry points this if should change to
* check NumberOfFunctions, but for now we only look at names
*/
if (exports->NumberOfNames == 0) {
/* riched32.dll from mmc.exe actually has NumberOfFunctions==0 */
LOG(GLOBAL, LOG_SYMBOLS, 1,
"dll_name=%s has no exported symbols\n", dll_name);
return 1;
}
/* FIXME: old comment, should work now.
* Dynamically loading modules is still not working very
* well, e.g. the itss.dll when loaded in notepad.exe on
* F1 seems to point to a zero page for its exports
* directory.. We are reading the right exports address
* but there is nothing there. There may be some VA/RVA
* issue, and note we try to do this on a MapViewOfSection
* before the loader has done anything to the file */
LOG(GLOBAL, LOG_SYMBOLS, 3,
"dll_name=%s exports="PFX" functions="PFX" ordinals="PFX" fnames="PFX
" numnames=%d numfunc=%d %s"
"baseord=%d\n", /* the linker adds this to the ords we see, safe to ignore */
dll_name,
exports, functions, ordinals, fnames,
exports->NumberOfNames,
exports->NumberOfFunctions,
(exports->NumberOfFunctions == exports->NumberOfNames) ? "" : "NONAMES ",
exports->Base);
DOLOG(6, LOG_SYMBOLS, {
dump_buffer_as_bytes(GLOBAL, exports, size, 16);
});
exports_table = module_info_create(&process_module_vector,
(app_pc)base_addr, (app_pc)(base_addr + image_size),
dll_name, exports->NumberOfNames);
/* FIXME: for a security policy to restrict transfers to exports only,
we actually need all functions and they simply need to be put in a hash table */
/* FIXME: for RCT_IND_BRANCH we don't need to travel
* through the string names or forwarders - we should only
* scan through all functions[] instead of
* functions[ordinals[i]]
*/
ASSERT(exports_table != NULL);
for (i = 0; i < exports->NumberOfNames; i++) {
PSTR name = (PSTR) (base_addr + fnames[i]);
ULONG ord = ordinals[i];
app_pc func = (app_pc) (base_addr + functions[ord]); /* note ord here, not i */
/* check if it points within the exports section in real address space, not RVA */
if (func < (app_pc)exports || func >= (app_pc)exports + size) {
LOG(GLOBAL, LOG_SYMBOLS, 3, "\t%s -> "PFX"\n", name, func);
/* insert in exports table, coming sorted by name order */
exports_table[exports_num].export_name = name;
exports_table[exports_num].entry_point = func;
exports_num++;
} else {
char *forwardto = (char*)(functions[ord] + base_addr);
// skip forwarded function if it forwards to a named import
// i.e. NTDLL.RtlAllocateHeap will be reported instead of HeapAlloc
LOG(GLOBAL, LOG_SYMBOLS, 3,
"Forward found for %s -> "PFX" %s. Skipping...\n", name,
functions[ord], forwardto);
// FIXME: Report the name under which it should show up if it is an ordinal import
// if it is referenced as ordinal DLLNAME.#232, then we'll get more from the current name.
// The problem though is that now the address range of the forwarded function
// is not going to give us the module name... haven't seen any so far
}
}
/* FIXME: take this post processing step out of this function */
/* the exports_table now needs to be sorted by function address instead of name */
qsort(exports_table, exports_num, /* non skipped entries only */
sizeof(export_entry_t), export_entry_compare);
/* need to remove duplicates and update entry in process_module_vector */
mutex_lock(&process_module_vector.lock);
{
module_info_t *pmod;
int unique_num = remove_export_duplicates(exports_table, exports_num);
pmod = lookup_module_info(&process_module_vector, (app_pc)base_addr); /* FIXME: need a real overlap check */
ASSERT(pmod);
pmod->exports_num = unique_num;
}
mutex_unlock(&process_module_vector.lock);
return 1;
} else {
DOLOG(SYMBOLS_LOGLEVEL, LOG_SYMBOLS, {
char short_name[MAX_MODNAME_INTERNAL];
os_get_module_name_buf(base_addr, short_name,
BUFFER_SIZE_ELEMENTS(short_name));
/* the executable itself is OK */
if (base_addr != get_own_peb()->ImageBaseAddress) {
if (short_name)
LOG(GLOBAL, LOG_SYMBOLS, 2, "No exports %s\n", short_name);
else
LOG(GLOBAL, LOG_SYMBOLS, 2, "Not a PE at "PFX"\n", base_addr);
}
});
return 0;
}
}
/* The following functions depend on traversing loader data */
/* This routine is here so we know how to walk all 3 loader lists */
static void
print_ldr_data()
{
PEB *peb = get_own_peb();
PEB_LDR_DATA *ldr = peb->LoaderData;
LIST_ENTRY *e, *mark;
LDR_MODULE *mod;
int i;
LOG(GLOBAL, LOG_ALL, 1, "PEB LoaderData:\n");
LOG(GLOBAL, LOG_ALL, 1, "\tLength = %d\n", ldr->Length);
LOG(GLOBAL, LOG_ALL, 1, "\tInitialized = %d\n", ldr->Initialized);
LOG(GLOBAL, LOG_ALL, 1, "\tSsHandle = "PFX"\n", ldr->SsHandle);
LOG(GLOBAL, LOG_ALL, 1, "InLoadOrder:\n");
mark = &ldr->InLoadOrderModuleList;
for (i = 0, e = mark->Flink; e != mark; i++, e = e->Flink) {
LOG(GLOBAL, LOG_ALL, 5,
" %d e="PFX" => "PFX" "PFX" "PFX" "PFX" "PFX" "PFX"\n", i,
e, *((ptr_uint_t *)e), *((ptr_uint_t *)e+1), *((ptr_uint_t *)e+2),
*((ptr_uint_t *)e+3), *((ptr_uint_t *)e+4), *((ptr_uint_t *)e+5));
mod = (LDR_MODULE *) e;
LOG(GLOBAL, LOG_ALL, 1, "\t%d "PFX" "PFX" 0x%x %S %S\n", i,
mod->BaseAddress, mod->EntryPoint, mod->SizeOfImage,
mod->FullDllName.Buffer, mod->BaseDllName.Buffer);
if (i > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
SYSLOG_INTERNAL_WARNING_ONCE("print_ldr_data: too many modules, maybe "
"in a race");
break;
}
}
LOG(GLOBAL, LOG_ALL, 1, "InMemoryOrder:\n");
/* FIXME: why doesn't this turn out to be in memory order? */
mark = &ldr->InMemoryOrderModuleList;
for (i = 0, e = mark->Flink; e != mark; i++, e = e->Flink) {
LOG(GLOBAL, LOG_ALL, 5,
" %d e="PFX" => "PFX" "PFX" "PFX" "PFX" "PFX" "PFX"\n", i,
e, *((ptr_uint_t *)e), *((ptr_uint_t *)e+1), *((ptr_uint_t *)e+2),
*((ptr_uint_t *)e+3), *((ptr_uint_t *)e+4), *((ptr_uint_t *)e+5));
mod = (LDR_MODULE *) ((char *)e - offsetof(LDR_MODULE, InMemoryOrderModuleList));
LOG(GLOBAL, LOG_ALL, 1, "\t%d "PFX" "PFX" 0x%x %S %S\n", i,
mod->BaseAddress, mod->EntryPoint, mod->SizeOfImage,
mod->FullDllName.Buffer, mod->BaseDllName.Buffer);
if (i > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
SYSLOG_INTERNAL_WARNING_ONCE("print_ldr_data: too many modules, maybe "
"in a race");
break;
}
}
LOG(GLOBAL, LOG_ALL, 1, "InInitOrder:\n");
mark = &ldr->InInitializationOrderModuleList;
for (i = 0, e = mark->Flink; e != mark; i++, e = e->Flink) {
LOG(GLOBAL, LOG_ALL, 5,
" %d e="PFX" => "PFX" "PFX" "PFX" "PFX" "PFX" "PFX"\n", i,
e, *((ptr_uint_t *)e), *((ptr_uint_t *)e+1), *((ptr_uint_t *)e+2),
*((ptr_uint_t *)e+3), *((ptr_uint_t *)e+4), *((ptr_uint_t *)e+5));
mod = (LDR_MODULE *)
((char *)e - offsetof(LDR_MODULE, InInitializationOrderModuleList));
LOG(GLOBAL, LOG_ALL, 1, "\t%d "PFX" "PFX" 0x%x %S %S\n", i,
mod->BaseAddress, mod->EntryPoint, mod->SizeOfImage,
mod->FullDllName.Buffer, mod->BaseDllName.Buffer);
if (i > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
SYSLOG_INTERNAL_WARNING_ONCE("print_ldr_data: too many modules, maybe "
"in a race");
break;
}
}
}
#endif /* DEBUG */
/****************************************************************************/
/* release build routines */
/* remember our struct in case we want to put it back */
static LDR_MODULE *DR_module;
static RTL_RB_TREE *
find_ntdll_mod_rbtree(module_handle_t ntdllh, RTL_RB_TREE *tomatch)
{
/* Several internal routines reference ntdll!LdrpModuleBaseAddressIndex like so:
* mov rax,qword ptr [ntdll!LdrpModuleBaseAddressIndex (000007ff`7995eaa0)]
* On Win8, but not Win8.1, the exported LdrGetProcedureAddressForCaller does.
* On both Win8 and Win8.1, the exported LdrDisableThreadCalloutsForDll calls
* the internal LdrpFindLoadedDllByHandle which then has the ref we want.
*/
# define RBTREE_MAX_DECODE 0x180 /* it's at +0xe1 on win8 */
RTL_RB_TREE *found = NULL;
instr_t inst;
bool found_call = false;
byte *pc;
byte *start = (byte *) get_proc_address(ntdllh, "LdrDisableThreadCalloutsForDll");
if (start == NULL)
return NULL;
instr_init(GLOBAL_DCONTEXT, &inst);
for (pc = start; pc < start + RBTREE_MAX_DECODE; ) {
instr_reset(GLOBAL_DCONTEXT, &inst);
pc = decode(GLOBAL_DCONTEXT, pc, &inst);
if (!instr_valid(&inst) || instr_is_return(&inst))
break;
if (!found_call && instr_get_opcode(&inst) == OP_call) {
/* We assume the first call is the one to the internal routine.
* Switch to that routine.
*/
found_call = true;
pc = opnd_get_pc(instr_get_target(&inst));
} else if (instr_get_opcode(&inst) == OP_mov_ld) {
opnd_t src = instr_get_src(&inst, 0);
if (opnd_is_abs_addr(src) IF_X64(|| opnd_is_rel_addr(src))) {
byte *addr = opnd_get_addr(src);
if (is_in_ntdll(addr)) {
RTL_RB_TREE local;
if (safe_read(addr, sizeof(local), &local) &&
local.Root == tomatch->Root &&
local.Min == tomatch->Min) {
LOG(GLOBAL, LOG_ALL, 2,
"Found LdrpModuleBaseAddressIndex @"PFX"\n", addr);
found = (RTL_RB_TREE *) addr;
break;
}
}
}
}
}
instr_free(GLOBAL_DCONTEXT, &inst);
return found;
}
/* i#934: remove from the rbtree added in Win8.
* Our strategy is to call RtlRbRemoveNode and pass in either a fake rbtree
* (if our dll is not the root or min node) or go and decode a routine
* to find the real rbtree (ntdll!LdrpModuleBaseAddressIndex) to pass in.
*/
static void
hide_from_rbtree(LDR_MODULE *mod)
{
RTL_RB_TREE *tree;
RTL_RB_TREE tree_local;
RTL_BALANCED_NODE *node;
typedef VOID (NTAPI *RtlRbRemoveNode_t)
(IN PRTL_RB_TREE Tree, IN PRTL_BALANCED_NODE Node);
RtlRbRemoveNode_t RtlRbRemoveNode;
module_handle_t ntdllh;
if (get_os_version() < WINDOWS_VERSION_8)
return;
LOG(GLOBAL, LOG_ALL, 2, "Attempting to remove dll from rbtree\n");
ntdllh = get_ntdll_base();
RtlRbRemoveNode = (RtlRbRemoveNode_t) get_proc_address(ntdllh, "RtlRbRemoveNode");
if (RtlRbRemoveNode == NULL) {
SYSLOG_INTERNAL_WARNING("cannot remove dll from rbtree: no RtlRbRemoveNode");
return;
}
tree = &tree_local;
node = &mod->BaseAddressIndexNode;
while (RTL_BALANCED_NODE_PARENT_VALUE(node) != NULL)
node = RTL_BALANCED_NODE_PARENT_VALUE(node);
tree->Root = node;
node = node->Left;
while (node->Left != NULL)
node = node->Left;
tree->Min = node;
if (&mod->BaseAddressIndexNode == tree->Root ||
&mod->BaseAddressIndexNode == tree->Min) {
/* We decode a routine known to deref ntdll!LdrpModuleBaseAddressIndex.
* An alternative could be to scan ntdll's data sec looking for root and min?
*/
tree = find_ntdll_mod_rbtree(ntdllh, tree);
if (tree == NULL) {
SYSLOG_INTERNAL_WARNING("cannot remove dll from rbtree: at root/min + "
"can't find real tree");
return;
}
}
/* Strangely this seems to have no return value so we don't know whether it
* succeeded.
*/
RtlRbRemoveNode(tree, &mod->BaseAddressIndexNode);
LOG(GLOBAL, LOG_ALL, 2, "Removed dll from rbtree\n");
}
/* FIXME : to cleanly detach we need to add ourselves back on to the module
* list so we can free library NYI, right now is memory leak but not a big
* deal since vmmheap is already leaking a lot more then that */
/* NOTE We are walking the loader lists without holding the lock which is
* potentially dangerous, however we are doing this at init time where we
* expect to be single threaded and to be in a clean app state, is more of
* a problem for unhide when it is implemented */
/* NOTE the loader lists appear to be doubly linked circular lists with
* each element being a LDR_MODULE (cast to LIST_ENTRYs at various offsets
* for actual inclusion on the lists) except the initial list entry in the
* PEB_LDR_DATA. NOTE, we assume here (and everywhere else) that the forward
* links are circularly linked for our iteration loops, we ASSERT that the
* backwards pointer is valid before updating below, FIXME should we
* be checking the forward pointers here and elsewhere for the loop? */
/* FIXME : also where is the unloaded module list kept? Might be nice to
* remove our pre-inject dll from that. */
static void
hide_from_module_lists(void)
{
/* remove us from the module lists! */
PEB *peb = get_own_peb();
PEB_LDR_DATA *ldr = peb->LoaderData;
LIST_ENTRY *e, *mark;
LDR_MODULE *mod;
int i;
app_pc DRbase;
MEMORY_BASIC_INFORMATION mbi;
size_t len;
/* FIXME: have os find DR bounds earlier so we don't duplicate work */
len = query_virtual_memory((app_pc) hide_from_module_lists, &mbi, sizeof(mbi));
ASSERT(len == sizeof(mbi));
ASSERT(mbi.State != MEM_FREE);
DRbase = (app_pc) mbi.AllocationBase;
LOG(GLOBAL, LOG_TOP, 1, "DR dll base = "PFX"\n", DRbase);
/* FIXME: build iterator so all loopers aren't duplicating all this code */
mark = &ldr->InLoadOrderModuleList;
ASSERT(mark->Flink != NULL && mark->Blink != NULL); /* sanity check */
ASSERT(offsetof(LDR_MODULE, InLoadOrderModuleList) == 0);
for (i = 0, e = mark->Flink; e != mark; i++, e = e->Flink) {
mod = (LDR_MODULE *) e;
/* sanity check */
ASSERT(e->Flink != NULL && e->Blink != NULL &&
e->Flink->Blink != NULL && e->Blink->Flink != NULL);
if ((app_pc) mod->BaseAddress == DRbase) {
/* we store the LDR_MODULE struct and do not attempt to de-allocate it,
* in case we want to put it back
*/
DR_module = mod;
LOG(GLOBAL, LOG_ALL, 1, "Removing "PFX" %S from load order module list\n",
mod->BaseAddress, mod->FullDllName.Buffer);
/* doubly linked circular list */
e->Flink->Blink = e->Blink;
e->Blink->Flink = e->Flink;
if (get_os_version() >= WINDOWS_VERSION_8) {
/* i#934: remove from the rbtree added in Win8 */
hide_from_rbtree(mod);
}
break;
}
if (i > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
SYSLOG_INTERNAL_WARNING_ONCE("modules_init: too many modules, maybe "
"in a race");
break;
}
}
mark = &ldr->InMemoryOrderModuleList;
ASSERT(mark->Flink != NULL && mark->Blink != NULL); /* sanity check */
for (i = 0, e = mark->Flink; e != mark; i++, e = e->Flink) {
mod = (LDR_MODULE *) ((char *)e - offsetof(LDR_MODULE, InMemoryOrderModuleList));
/* sanity check */
ASSERT(e->Flink != NULL && e->Blink != NULL &&
e->Flink->Blink != NULL && e->Blink->Flink != NULL);
if ((app_pc) mod->BaseAddress == DRbase) {
ASSERT(mod == DR_module);
LOG(GLOBAL, LOG_ALL, 1, "Removing "PFX" %S from memory order module list\n",
mod->BaseAddress, mod->FullDllName.Buffer);
/* doubly linked circular list */
e->Flink->Blink = e->Blink;
e->Blink->Flink = e->Flink;
break;
}
if (i > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
SYSLOG_INTERNAL_WARNING_ONCE("modules_init: too many modules, maybe "
"in a race");
break;
}
}
mark = &ldr->InInitializationOrderModuleList;
ASSERT(mark->Flink != NULL && mark->Blink != NULL); /* sanity check */
for (i = 0, e = mark->Flink; e != mark; i++, e = e->Flink) {
mod = (LDR_MODULE *) ((char *)e - offsetof(LDR_MODULE, InInitializationOrderModuleList));
/* sanity check */
ASSERT(e->Flink != NULL && e->Blink != NULL &&
e->Flink->Blink != NULL && e->Blink->Flink != NULL);
if ((app_pc) mod->BaseAddress == DRbase) {
ASSERT(mod == DR_module);
LOG(GLOBAL, LOG_ALL, 1, "Removing "PFX" %S from init order module list\n",
mod->BaseAddress, mod->FullDllName.Buffer);
/* doubly linked circular list */
e->Flink->Blink = e->Blink;
e->Blink->Flink = e->Flink;
break;
}
if (i > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
SYSLOG_INTERNAL_WARNING_ONCE("modules_init: too many modules, maybe "
"in a race");
break;
}
}
LOG(GLOBAL, LOG_ALL, 2, "After removing, module lists are:\n");
DOLOG(2, LOG_ALL, { print_ldr_data(); });
/* FIXME i#1429: also remove from hashtable used by GetModuleHandle */
}
/* N.B.: walking loader data structures at random times is dangerous!
* Do not call this for non-debug reasons if you can help it!
* See is_module_being_initialized for a safer approach to walking loader structs.
* FIXME: other routines use the same iteration, should we abstract it out?
* Other routines include check_for_unsupported_modules, loaded_modules_exports,
* get_ldr_module_by_pc, and (in ntdll.c since used by pre-inject)
* get_ldr_module_by_name (though the last two use the memory-order list)
*/
void
print_modules(file_t f, bool dump_xml) {
print_modules_ldrlist_and_ourlist(f, dump_xml, false /*not conservative*/);
}
/* conservative flag indicates we may have come here from a crash. Print
* information that do not need any allocations or lock acquisitions */
void
print_modules_ldrlist_and_ourlist(file_t f, bool dump_xml, bool conservative)
{
/* We used to walk through every block in memory and call GetModuleFileName
* That's not re-entrant, so instead we walk the loader's data structures in the PEB
*/
PEB *peb = get_own_peb();
PEB_LDR_DATA *ldr = peb->LoaderData;
LIST_ENTRY *e, *mark;
LDR_MODULE *mod;
uint traversed = 0;
#ifdef DEBUG
RTL_CRITICAL_SECTION *lock;
thread_id_t owner;
#endif
if (ldr == NULL) {
ASSERT(dr_earliest_injected);
return;
}
#ifdef DEBUG
lock = (RTL_CRITICAL_SECTION *) peb->LoaderLock;
owner = (thread_id_t) lock->OwningThread;
LOG(GLOBAL, LOG_ALL, 2, "LoaderLock owned by %d\n", owner);
if (owner != 0 && owner != get_thread_id()) {
LOG(GLOBAL, LOG_ALL, 1, "WARNING: print_modules called w/o holding LoaderLock\n");
DOLOG_ONCE(2, LOG_ALL, {
SYSLOG_INTERNAL_WARNING("print_modules w/o holding LoaderLock");
});
}
#endif
print_file(f, dump_xml ? "<loaded-modules>\n" : "\nLoaded modules:\n");
/* We use the memory order list instead of the init order list, as
* it includes the .exe, and is updated first upon loading a new dll
*/
mark = &ldr->InMemoryOrderModuleList;
for (e = mark->Flink; e != mark; e = e->Flink) {
uint checksum = 0;
char *pe_name = NULL;
app_pc preferred_base;
version_info_t info;
mod = (LDR_MODULE *) ((char *)e - offsetof(LDR_MODULE, InMemoryOrderModuleList));
get_module_info_pe(mod->BaseAddress, &checksum, NULL, NULL, &pe_name, NULL);
preferred_base = get_module_preferred_base(mod->BaseAddress);
print_file(f, dump_xml ?
"\t<dll range=\""PFX"-"PFX"\" name=\"%ls\" "
"entry=\""PFX"\" count=\"%-3d\"\n"
"\t flags=\"0x%08x\" "
"timestamp=\"0x%08x\" checksum=\"0x%08x\" pe_name=\"%s\"\n"
"\t path=\"%ls\" preferred_base=\""PFX"\"\n"
"\t dll_relocated=\"%s\" "
:
" "PFX"-"PFX" %-13ls entry="PFX" count=%-3d\n"
"\tflags=0x%08x timestamp=0x%08x checksum=0x%08x\n"
"\tpe_name=%s %ls\n\tpreferred_base="PFX"\n"
"\tdll_relocated=%s\n",
mod->BaseAddress,
(char *)mod->BaseAddress + mod->SizeOfImage - 1,
mod->BaseDllName.Buffer, mod->EntryPoint, mod->LoadCount,
mod->Flags, mod->TimeDateStamp, checksum,
pe_name == NULL ? "(null)" : pe_name,
mod->FullDllName.Buffer,
preferred_base,
preferred_base == (app_pc) mod->BaseAddress ? "no" : "yes"
);
if (get_module_resource_version_info(mod->BaseAddress, &info)) {
print_file(f, dump_xml ?
"file_version=\"%d.%d.%d.%d\" product_version=\"%d.%d.%d.%d\"\n"
"\t original_filename=\"%S\" company_name=\"%S\"\n"
"\t product_name=\"%S\" "
:
"\tfile_version=%d.%d.%d.%d product_version=%d.%d.%d.%d"
"\toriginal_filename=%S\n\tcompany_name=%S"
" product_name=%S\n",
info.file_version.version_parts.p1,
info.file_version.version_parts.p2,
info.file_version.version_parts.p3,
info.file_version.version_parts.p4,
info.product_version.version_parts.p1,
info.product_version.version_parts.p2,
info.product_version.version_parts.p3,
info.product_version.version_parts.p4,
info.original_filename == NULL ? L"none" : info.original_filename,
info.company_name == NULL ? L"none" : info.company_name,
info.product_name == NULL ? L"none" : info.product_name);
} else {
print_file(f, dump_xml ?
"no_version_information=\"true\" " :
"\tmodule_has_no_version_information\n");
}
if (dump_xml) {
print_file(f, "/> \n");
}
if (traversed++ > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
SYSLOG_INTERNAL_WARNING_ONCE("print_modules: too many modules");
break;
}
}
if (dump_xml)
print_file(f, "</loaded-modules>\n");
else
print_file(f, "\n");
/* FIXME: currently updated only under aslr_action */
if (TEST(ASLR_DLL, DYNAMO_OPTION(aslr)) &&
TEST(ASLR_TRACK_AREAS, DYNAMO_OPTION(aslr_action)) &&
/* FIXME: xref case 10750: could print w/o lock inside a TRY */
!conservative) {
print_file(f, "<print_modules_safe/>\n");
if (is_module_list_initialized()) {
print_modules_safe(f, dump_xml);
}
}
}
void
print_modules_safe(file_t f, bool dump_xml)
{
module_iterator_t *mi;
/* we walk our own module list that is populated on an initial walk through memory,
* and further kept consistent on memory mappings of likely DLLs */
print_file(f, dump_xml ? "<loaded-modules>\n" : "\nLoaded modules:\n");
mi = module_iterator_start();
while (module_iterator_hasnext(mi)) {
module_area_t *ma = module_iterator_next(mi);
print_file(f, dump_xml ?
"\t<dll range=\""PFX"-"PFX"\" name=\"%ls\" "
"entry=\""PFX"\" count=\"%-3d\"\n"
"\t flags=\"0x%08x\" "
"timestamp=\"0x%08x\" checksum=\"0x%08x\" pe_name=\"%s\"\n"
"\t path=\"%ls\" preferred_base=\""PFX"\" />\n"
:
" "PFX"-"PFX" %-13ls entry="PFX" count=%-3d\n"
"\tflags=0x%08x timestamp=0x%08x checksum=0x%08x\n"
"\tpe_name=%s %ls\n\tpreferred_base="PFX"\n",
ma->start, ma->end - 1, /* inclusive */
L"name", /* FIXME: dll name is often quite useful */
ma->entry_point,
0 /* no LoadCount */,
0 /* no Flags */,
ma->os_data.timestamp, ma->os_data.checksum,
GET_MODULE_NAME(&ma->names) == NULL ?
"(null)" : GET_MODULE_NAME(&ma->names),
L"path", /* FIXME: path is often quite useful */
ma->os_data.preferred_base);
}
module_iterator_stop(mi);
if (dump_xml)
print_file(f, "</loaded-modules>\n");
else
print_file(f, "\n");
}
/* N.B.: see comments on print_modules about why this is a
* dangerous routine, especially on a critical path like diagnostics...
* FIXME! Returns true if found an unsupported module, false otherwise
*/
bool
check_for_unsupported_modules()
{
PEB *peb = get_own_peb();
PEB_LDR_DATA *ldr = peb->LoaderData;
LIST_ENTRY *e, *mark;
LDR_MODULE *mod;
char filter[MAXIMUM_PATH];
char dllname[MAXIMUM_PATH];
const char *short_name;
uint traversed = 0;
int retval = get_parameter(PARAM_STR(DYNAMORIO_VAR_UNSUPPORTED),
filter, sizeof(filter));
if (IS_GET_PARAMETER_FAILURE(retval) ||
filter[0] == 0 /* empty UNSUPPORTED list */) {
/* no unsupported list, so nothing to look for */
return false;
}
LOG(GLOBAL, LOG_ALL, 4, "check_for_unsupported_modules: %s\n", filter);
/* FIXME: check peb->LoaderLock? */
/* FIXME: share iteration w/ the other routines that do this? */
mark = &ldr->InInitializationOrderModuleList;
for (e = mark->Flink; e != mark; e = e->Flink) {
mod = (LDR_MODULE *) ((char *)e - offsetof(LDR_MODULE, InInitializationOrderModuleList));
wchar_to_char(dllname, MAXIMUM_PATH, mod->FullDllName.Buffer,
/* Length is size in bytes not counting final 0 */
mod->FullDllName.Length);
short_name = get_short_name(dllname);
LOG(GLOBAL, LOG_ALL, 4, "\tchecking %s => %s\n", dllname, short_name);
if (check_filter(filter, short_name)) {
/* critical since it's unrecoverable and to distinguish from attacks */
/* dumpcore if warranted and not already dumped at the security
* violation, options are already synchronized at the security
* violation */
SYSLOG(SYSLOG_CRITICAL, UNSUPPORTED_APPLICATION, 3,
get_application_name(), get_application_pid(), dllname);
return true;
}
if (traversed++ > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
SYSLOG_INTERNAL_WARNING_ONCE("check_for_unsupported_modules: "
"too many modules");
break;
}
}
return false;
}
/* non-DEBUG routines for parsing PE files */
/* FIXME: make this a static inline function get_nt_header
* that verifies and returns nt header */
#define DOS_HEADER(base) ((IMAGE_DOS_HEADER *)(base))
#define NT_HEADER(base) ((IMAGE_NT_HEADERS *)((ptr_uint_t)(base) + \
DOS_HEADER(base)->e_lfanew))
#define VERIFY_DOS_HEADER(base) { \
DEBUG_DECLARE(IMAGE_DOS_HEADER *dos = DOS_HEADER(base)); \
ASSERT(dos->e_magic == IMAGE_DOS_SIGNATURE); \
}
#define VERIFY_NT_HEADER(base) { \
DEBUG_DECLARE(IMAGE_NT_HEADERS *nt = NT_HEADER(base)); \
VERIFY_DOS_HEADER(base); \
ASSERT(nt != NULL && nt->Signature == IMAGE_NT_SIGNATURE); \
ASSERT_CURIOSITY(nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC || \
nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC); \
}
/* returns true iff [start2, start2+size2] covers the same or a subset of the pages
* covered by [start1, start1+size1]
*/
static inline bool
on_subset_of_pages(app_pc start1, size_t size1, app_pc start2, size_t size2)
{
return (PAGE_START(start1) <= PAGE_START(start2) &&
PAGE_START(start1 + size1) >= PAGE_START(start2 + size2));
}
bool
is_readable_pe_base(app_pc base)
{
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *) base;
IMAGE_NT_HEADERS *nt;
size_t size;
/* would be nice to batch the is_readables into one, but we need
* to dereference in turn...
*/
if (!is_readable_without_exception((app_pc)dos, sizeof(*dos)) ||
dos->e_magic != IMAGE_DOS_SIGNATURE)
return false;
nt = (IMAGE_NT_HEADERS *) (((ptr_uint_t)dos) + dos->e_lfanew);
if (nt == NULL ||
/* optimization: reduce number of system calls for safe reads */
(!on_subset_of_pages((app_pc)dos, sizeof(*dos), (app_pc)nt, sizeof(*nt)) &&
!is_readable_without_exception((app_pc)nt, sizeof(*nt))) ||
nt->Signature != IMAGE_NT_SIGNATURE)
return false;
/* make sure section headers are readable */
size = nt->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
if (/* optimization: reduce number of system calls for safe reads */
!on_subset_of_pages((app_pc)dos, sizeof(*dos),
(app_pc) IMAGE_FIRST_SECTION(nt), size) &&
!is_readable_without_exception((app_pc) IMAGE_FIRST_SECTION(nt), size))
return false;
return true;
}
/* Returns the size of the image section when loaded not counting alignment bytes
* added by the image loader. */
static inline size_t
get_image_section_unpadded_size(IMAGE_SECTION_HEADER *sec
_IF_DEBUG(IMAGE_NT_HEADERS *nt))
{
ASSERT(sec != NULL && nt != NULL);
/* Curiosity if VirtualSize/SizeOfRawData relationship doesn't match one of the
* cases we've seen. Note that this will fire for the (experimentally legal, but
* never seen in practice) case of raw data much larger than virtual size (as in
* past the next file alignment), though the various size routines here will handle
* that correctly (see 5355, 9053). */
ASSERT_CURIOSITY(sec->Misc.VirtualSize > sec->SizeOfRawData /* case 5355 */ ||
sec->Misc.VirtualSize == 0 /* case 10501 */ ||
ALIGN_FORWARD(sec->Misc.VirtualSize,
nt->OptionalHeader.FileAlignment) ==
ALIGN_FORWARD(sec->SizeOfRawData, /* case 8868 not always aligned */
nt->OptionalHeader.FileAlignment));
ASSERT_CURIOSITY(sec->Misc.VirtualSize != 0 || sec->SizeOfRawData != 0);
if (sec->Misc.VirtualSize == 0) /* case 10501 */
return sec->SizeOfRawData;
return sec->Misc.VirtualSize; /* case 5355 */
}
/* Returns the size in bytes of the image section when loaded, including image loader
* allocated alignment/padding bytes. To include non-allocated (MEM_RESERVE) padding
* bytes align this value forward to nt->OptionalHeader.SectionAlignment (only comes
* into play when SectionAlignment is > PAGE_SIZE). */
static inline size_t
get_image_section_size(IMAGE_SECTION_HEADER *sec, IMAGE_NT_HEADERS *nt)
{
/* Xref case 9797, drivers (which we've seen mapped in on Vista) don't
* usually use page size section alignment (use 0x80 alignment instead). */
size_t unpadded_size = get_image_section_unpadded_size(sec _IF_DEBUG(nt));
uint alignment = MIN(PAGE_SIZE, nt->OptionalHeader.SectionAlignment);
return ALIGN_FORWARD(unpadded_size, alignment);
}
/* Returns the size of the portion of the image file that's mapped into the image section
* when it's loaded. */
static inline size_t
get_image_section_map_size(IMAGE_SECTION_HEADER *sec, IMAGE_NT_HEADERS *nt)
{
/* Xref case 5355 - this is mapped in irregardless of sec->Characteristics flags
* (including the UNINITIALIZED_DATA flag) so can ignore them. */
size_t virtual_size = get_image_section_size(sec, nt);
/* FileAlignment - the alignment factor (in bytes) that is used to align the raw data
* of sections in the image file. The value should be a power of 2 between 512
* (though note the lower bound is not enforced, xref 9798) and 64 K, inclusive. The
* default is 512. If the SectionAlignment is less than the architecture's page size,
* then FileAlignment must match SectionAlignment. */
size_t raw_data_size =
ALIGN_FORWARD(sec->SizeOfRawData, nt->OptionalHeader.FileAlignment);
/* Xref 5355, the size of the mapping is the lesser of the virtual size (as
* determined above) and the FileAlignment aligned size of SizeOfRawData. Any
* extra space up to virtual size is 0 filled. */
return MIN(virtual_size, raw_data_size);
}
/* returns the offset into the PE file at which the mapping for section sec starts */
static inline size_t
get_image_section_file_offs(IMAGE_SECTION_HEADER *sec, IMAGE_NT_HEADERS *nt)
{
ASSERT(sec != NULL && nt != NULL);
/* Xref 5355, despite PE specifications it appears that PointerToRawData is not
* required to be aligned (the image loader apparently back aligns it before use). */
return ALIGN_BACKWARD(sec->PointerToRawData, nt->OptionalHeader.FileAlignment);
}
void
print_module_section_info(file_t file, app_pc addr)
{
IMAGE_DOS_HEADER *dos;
IMAGE_NT_HEADERS *nt;
IMAGE_SECTION_HEADER *sec;
uint i;
app_pc module_base = get_module_base(addr);
if (module_base == NULL)
return;
dos = (IMAGE_DOS_HEADER *) module_base;
nt = (IMAGE_NT_HEADERS *) (((ptr_uint_t)dos) + dos->e_lfanew);
sec = IMAGE_FIRST_SECTION(nt);
/* FIXME : can we share this loop with is_in_executable_file_section? */
for (i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) {
app_pc sec_start = module_base + sec->VirtualAddress;
app_pc sec_end = module_base + sec->VirtualAddress +
get_image_section_size(sec, nt);
/* xref case 6799, section is [start, end) */
if (sec_start <= addr && addr < sec_end) {
print_file(file,
"\t\tmod_base= \""PFX"\"\n"
"\t\tsec_name= \"%.*s\"\n"
"\t\tsec_start= \""PFX"\"\n"
"\t\tsec_end= \""PFX"\"\n"
"\t\tVirtualSize= \"0x%08x\"\n"
"\t\tSizeOfRawData= \"0x%08x\"\n"
"\t\tsec_characteristics= \"0x%08x\"\n",
module_base, IMAGE_SIZEOF_SHORT_NAME, sec->Name,
sec_start, sec_end, sec->Misc.VirtualSize, sec->SizeOfRawData,
sec->Characteristics);
}
}
}
/* Looks for a section or (if merge) group of sections that satisfies the following
* criteria:
* - if start_pc != NULL, that contains [start_pc, end_pc);
* - if sec_characteristics_match != 0, that matches ANY of sec_characteristics_match;
* - if name != NULL, that matches name.
* - if nth > -1, the nth section, or nth segment if merge=true
*
* If a section or (if merge) group of sections are found that satisfy the above,
* then returns the bounds of the section(s) in sec_start_out and sec_end_out
* and sec_end_unpad_out (end w/o padding for alignment) (all 3 are
* optional) and returns true. If no matching section(s) are found returns false.
* If !merge the actual characteristics are returned in sec_characteristics_out,
* which is optional and must be NULL if merge.
* If map_size, *sec_end_out will be the portion of the file that is mapped
* (but sec_end_nopad_out will be unchanged).
*
* FIXME - with case 10526 fix letting the exemption polices trim to section boundaries
* is there any reason we still need merging support?
*/
static bool
is_in_executable_file_section(app_pc module_base, app_pc start_pc, app_pc end_pc,
app_pc *sec_start_out /* OPTIONAL OUT */,
app_pc *sec_end_out /* OPTIONAL OUT */,
app_pc *sec_end_nopad_out /* OPTIONAL OUT */,
uint *sec_characteristics_out /* OPTIONAL OUT */,
IMAGE_SECTION_HEADER *sec_header_out /* OPTIONAL OUT */,
uint sec_characteristics_match /* TESTANY, 0 to ignore */,
const char *name /* OPTIONAL */, bool merge,
int nth /* -1 to ignore */, bool map_size)
{
IMAGE_DOS_HEADER *dos;
IMAGE_NT_HEADERS *nt;
IMAGE_SECTION_HEADER *sec;
uint i, seg_num = 0, prev_chars = 0;
bool prev_sec_same_chars = false, result = false, stop_at_next_non_matching = false;
app_pc sec_start = NULL, sec_end = NULL, sec_end_nopad = NULL;
/* See case 7998 where a NULL base was passed. */
ASSERT_CURIOSITY(module_base != NULL);
if (module_base == NULL)
return false;
dos = (IMAGE_DOS_HEADER *) module_base;
nt = (IMAGE_NT_HEADERS *) (((ptr_uint_t)dos) + dos->e_lfanew);
if (dos->e_magic != IMAGE_DOS_SIGNATURE ||
nt == NULL || nt->Signature != IMAGE_NT_SIGNATURE)
return false;
/* must specify some criteria */
ASSERT(start_pc != NULL || sec_characteristics_match != 0 || name != NULL
|| nth > -1);
ASSERT(start_pc == NULL || start_pc < end_pc);
/* sec_characteristics_out & section_header_out only makes sense if !merge,
* unless doing nth segment
*/
ASSERT(sec_characteristics_out == NULL || !merge || nth > -1);
ASSERT(sec_header_out == NULL || !merge);
/* We cannot use the OptionalHeader fields BaseOfCode or SizeOfCode or SizeOfData
* since for multiple sections the SizeOfCode is the sum of the
* non-page-align-expanded sizes, and sections need not be contiguous!
* Instead we walk all sections for ones that match our criteria. */
LOG(GLOBAL, LOG_VMAREAS, 4, "module @ "PFX":\n", module_base);
sec = IMAGE_FIRST_SECTION(nt);
for (i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) {
LOG(GLOBAL, LOG_VMAREAS, 4, "\tName = %.*s\n", IMAGE_SIZEOF_SHORT_NAME,
sec->Name);
LOG(GLOBAL, LOG_VMAREAS, 4, "\tVirtualSize = "PFX"\n", sec->Misc.VirtualSize);
LOG(GLOBAL, LOG_VMAREAS, 4, "\tVirtualAddress = "PFX"\n", sec->VirtualAddress);
LOG(GLOBAL, LOG_VMAREAS, 4, "\tSizeOfRawData = 0x%08x\n", sec->SizeOfRawData);
LOG(GLOBAL, LOG_VMAREAS, 4, "\tCharacteristics= 0x%08x\n", sec->Characteristics);
if ((sec_characteristics_match == 0 ||
TESTANY(sec_characteristics_match, sec->Characteristics)) &&
(name == NULL ||
(sec->Name != NULL && strncmp((const char *)sec->Name,
name, strlen(name)) == 0)) &&
(nth == -1 || nth == (int)seg_num)) {
app_pc new_start = module_base + sec->VirtualAddress;
if (prev_sec_same_chars && sec_end == new_start &&
(nth == -1 || prev_chars == sec->Characteristics)) {
/* os will merge adjacent regions w/ same privileges, so consider
* these one region by leaving sec_start at its old value if merge */
ASSERT(merge);
LOG(GLOBAL, LOG_VMAREAS, 2,
"is_in_executable_file_section: adjacent sections @"PFX" and "PFX"\n",
sec_start, new_start);
} else {
if (stop_at_next_non_matching)
break;
sec_start = new_start;
}
if (merge)
prev_sec_same_chars = true;
sec_end = module_base + sec->VirtualAddress +
get_image_section_size(sec, nt);
sec_end_nopad = module_base + sec->VirtualAddress +
get_image_section_unpadded_size(sec _IF_DEBUG(nt));
LOG(GLOBAL, LOG_VMAREAS, 2,
"is_in_executable_file_section (module "PFX", region "PFX"-"PFX"): "
"%.*s == "PFX"-"PFX"\n",
module_base, start_pc, end_pc, IMAGE_SIZEOF_SHORT_NAME,
sec->Name, sec_start, sec_end);
if (start_pc == NULL ||
(start_pc >= sec_start && start_pc <= sec_end)) {
if (sec_start_out != NULL)
*sec_start_out = sec_start; /* merged section start */
if (sec_end_out != NULL) {
if (map_size) {
*sec_end_out = module_base + sec->VirtualAddress +
get_image_section_map_size(sec, nt);
} else {
*sec_end_out = sec_end; /* merged section end */
}
}
if (sec_end_nopad_out != NULL)
*sec_end_nopad_out = sec_end_nopad; /* merged nopad section end */
if (sec_characteristics_out != NULL)
*sec_characteristics_out = sec->Characteristics;
if (sec_header_out != NULL)
*sec_header_out = *sec;
if (start_pc == NULL || end_pc <= sec_end) {
/* we found what we were looking for, stop looping as soon as we
* finish merging into the current region. */
result = true;
if (merge)
stop_at_next_non_matching = true;
else
break;
}
}
} else {
prev_sec_same_chars = false;
if (nth > -1 && i > 0) {
/* count segments */
app_pc new_start = module_base + sec->VirtualAddress;
if (sec_end != new_start || prev_chars != sec->Characteristics)
seg_num++;
sec_end = module_base + sec->VirtualAddress +
get_image_section_size(sec, nt);
sec_end_nopad = module_base + sec->VirtualAddress +
get_image_section_unpadded_size(sec _IF_DEBUG(nt));
}
}
prev_chars = sec->Characteristics;
}
return result;
}
bool
module_pc_section_lookup(app_pc module_base, app_pc pc, IMAGE_SECTION_HEADER *section_out)
{
ASSERT(is_readable_pe_base(module_base));
if (section_out != NULL)
memset(section_out, 0, sizeof(*section_out));
return is_in_executable_file_section(module_base, pc, pc+1,
NULL, NULL, NULL, NULL, section_out,
0 /* any section */, NULL, false, -1, false);
}
/* Returns true if [start_pc, end_pc) is within a single code section.
* Returns the bounds of the enclosing section in sec_start and sec_end.
* Note that unlike is_in_*_section routines, does not merge sections. */
bool
is_range_in_code_section(app_pc module_base, app_pc start_pc, app_pc end_pc,
app_pc *sec_start /* OPTIONAL OUT */,
app_pc *sec_end /* OPTIONAL OUT */)
{
return is_in_executable_file_section(module_base, start_pc, end_pc,
sec_start, sec_end, NULL, NULL, NULL,
IMAGE_SCN_CNT_CODE, NULL,
false /* don't merge */, -1, false);
}
/* Returns true if addr is in a code section and if so returns in sec_start and sec_end
* the bounds of the section containing addr (merged with adjacent code sections). */
bool
is_in_code_section(app_pc module_base, app_pc addr,
app_pc *sec_start /* OPTIONAL OUT */,
app_pc *sec_end /* OPTIONAL OUT */)
{
return is_in_executable_file_section(module_base, addr, addr+1,
sec_start, sec_end, NULL, NULL, NULL,
IMAGE_SCN_CNT_CODE, NULL,
true /* merge */, -1, false);
}
/* Same as above only for initialized data sections instead of code. */
bool
is_in_dot_data_section(app_pc module_base, app_pc addr,
app_pc *sec_start /* OPTIONAL OUT */,
app_pc *sec_end /* OPTIONAL OUT */)
{
return is_in_executable_file_section(module_base, addr, addr+1,
sec_start, sec_end, NULL, NULL, NULL,
IMAGE_SCN_CNT_INITIALIZED_DATA |
IMAGE_SCN_CNT_UNINITIALIZED_DATA,
NULL, true /* merge */, -1, false);
}
/* Same as above only for xdata sections (see below) instead of code. */
bool
is_in_xdata_section(app_pc module_base, app_pc addr,
app_pc *sec_start /* OPTIONAL OUT */,
app_pc *sec_end /* OPTIONAL OUT */)
{
/* .xdata is present in .NET2.0 .ni.dll files
* it is marked as +rwx initialized data
*/
uint sec_flags = 0;
if (is_in_executable_file_section(module_base, addr, addr+1,
sec_start, sec_end, NULL, &sec_flags, NULL,
IMAGE_SCN_CNT_INITIALIZED_DATA, ".xdata",
false /* don't merge */, -1, false)) {
bool xdata_prot_match = TESTALL(IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE |
IMAGE_SCN_MEM_EXECUTE, sec_flags);
ASSERT_CURIOSITY(xdata_prot_match && "unexpected xdata section characteristics");
return xdata_prot_match;
}
return false;
}
/* This is a more restrictive test than (get_module_base() != NULL) because it
* checks for the start of the PE and examines at least one section in it before
* concluding that addr belongs to that module. */
bool
is_in_any_section(app_pc module_base, app_pc addr,
app_pc *sec_start /* OPTIONAL OUT */,
app_pc *sec_end /* OPTIONAL OUT */)
{
return is_in_executable_file_section(module_base, addr, addr+1,
sec_start, sec_end, NULL, NULL, NULL,
0 /* any section */, NULL,
true /* merge */, -1, false);
}
bool
get_executable_segment(app_pc module_base,
app_pc *sec_start /* OPTIONAL OUT */,
app_pc *sec_end /* OPTIONAL OUT */,
app_pc *sec_end_nopad /* OPTIONAL OUT */)
{
return is_in_executable_file_section(module_base, NULL, NULL,
sec_start, sec_end, sec_end_nopad, NULL, NULL,
IMAGE_SCN_MEM_EXECUTE,
NULL, true /* merge */, -1, false);
}
/* allow only true MEM_IMAGE mappings */
bool
is_mapped_as_image(app_pc module_base)
{
MEMORY_BASIC_INFORMATION mbi;
if (query_virtual_memory(module_base, &mbi, sizeof(mbi)) == sizeof(mbi)
&& mbi.State == MEM_COMMIT /* header should always be committed */
&& mbi.Type == MEM_IMAGE) {
return true;
}
/* although mbi.Type may be undefined, most callers should get
* this far only if not MEM_FREE, so ok to ASSERT. Note all
* Type's are MEM_FREE, MEM_PRIVATE, MEM_MAPPED, and MEM_IMAGE */
ASSERT_CURIOSITY(mbi.Type == MEM_PRIVATE ||
mbi.Type == MEM_MAPPED);
return false;
}
/* Returns true if the module has an nth segment, false otherwise. */
bool
module_get_nth_segment(app_pc module_base, uint n,
app_pc *start/*OPTIONAL OUT*/, app_pc *end/*OPTIONAL OUT*/,
uint *chars/*OPTIONAL OUT*/)
{
if (!is_in_executable_file_section
(module_base, NULL, NULL, start, end, NULL, chars, NULL, 0/* any section */, NULL,
true /* merge to make segments */, n, true/*mapped size*/)) {
return false;
}
return true;
}
size_t
module_get_header_size(app_pc module_base)
{
IMAGE_NT_HEADERS *nt;
ASSERT(is_readable_pe_base(module_base));
nt = NT_HEADER(module_base);
return nt->OptionalHeader.SizeOfHeaders;
}
/* returns true if a matching section is found, false otherwise */
bool
get_named_section_bounds(app_pc module_base, const char *name,
app_pc *start/*OPTIONAL OUT*/, app_pc *end/*OPTIONAL OUT*/)
{
if (!is_in_executable_file_section(module_base, NULL, NULL, start, end, NULL, NULL,
NULL, 0 /* any section */, name, true /* merge */,
-1, false)) {
if (start != NULL)
*start = NULL;
if (end != NULL)
*end = NULL;
return false;
}
return true;
}
bool
get_IAT_section_bounds(app_pc module_base, app_pc *iat_start, app_pc *iat_end)
{
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *) module_base;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *) (((ptr_uint_t)dos) + dos->e_lfanew);
IMAGE_DATA_DIRECTORY *dir;
if (dos->e_magic != IMAGE_DOS_SIGNATURE ||
nt == NULL || nt->Signature != IMAGE_NT_SIGNATURE)
return false;
dir = &OPT_HDR(nt, DataDirectory)[IMAGE_DIRECTORY_ENTRY_IAT];
*iat_start = module_base + dir->VirtualAddress;
*iat_end = module_base + dir->VirtualAddress + dir->Size;
return true;
}
bool
is_IAT(app_pc start, app_pc end, bool page_align, app_pc *iat_start, app_pc *iat_end)
{
app_pc IAT_start, IAT_end;
app_pc base = get_module_base(start);
if (base == NULL)
return false;
if (!get_IAT_section_bounds(base, &IAT_start, &IAT_end))
return false;
if (iat_start != NULL)
*iat_start = IAT_start;
if (iat_end != NULL)
*iat_end = IAT_end;
if (page_align) {
IAT_start = (app_pc) ALIGN_BACKWARD(IAT_start, PAGE_SIZE);
IAT_end = (app_pc) ALIGN_FORWARD(IAT_end, PAGE_SIZE);
}
LOG(THREAD_GET, LOG_VMAREAS, 3,
"is_IAT("PFX","PFX") vs ("PFX","PFX") == %d\n", start, end,
IAT_start, IAT_end, IAT_start == start && IAT_end == end);
return (IAT_start == start && IAT_end == end);
}
bool
is_in_IAT(app_pc addr)
{
app_pc IAT_start, IAT_end;
app_pc base = get_module_base(addr);
if (base == NULL)
return false;
if (!get_IAT_section_bounds(base, &IAT_start, &IAT_end))
return false;
return (IAT_start <= addr && addr < IAT_end);
}
app_pc
get_module_entry(app_pc module_base)
{
/* N.B.: do not use imagehlp routines like ImageNtHeader here, since the
* dependency on that dll causes sqlsrvr to crash.
* It's not that hard to directly read the headers.
*/
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *) module_base;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *) (((ptr_uint_t)dos) + dos->e_lfanew);
ASSERT(is_readable_pe_base(module_base));
ASSERT(dos->e_magic == IMAGE_DOS_SIGNATURE);
ASSERT(nt != NULL && nt->Signature == IMAGE_NT_SIGNATURE);
/* note that the entry point for .NET executables is clobbered by
* mscoree.dll to point directly at either mscoree!_CorDllMain or
* mscoree!_CorExeMain (though the LDR_MODULE struct entry is still the
* original one), so don't assume that it's inside the PE module itself
* (see case 3714)
*/
return ((app_pc)dos) + nt->OptionalHeader.AddressOfEntryPoint;
}
app_pc
get_module_base(app_pc pc)
{
/* We get the base from the allocation region. We cannot simply back-align
* to 64K, the Windows allocation granularity on all platforms, since some
* modules have code sections beyond 64K from the start of the module.
*/
app_pc base = get_allocation_base(pc);
if (!is_readable_pe_base(base)) {
/* not readable, or not PE */
return NULL;
}
return base;
}
/* gets the preferred base of the module containing pc from PE header */
app_pc
get_module_preferred_base(app_pc pc)
{
IMAGE_DOS_HEADER *dos;
IMAGE_NT_HEADERS *nt;
app_pc module_base = get_allocation_base(pc);
if (!is_readable_pe_base(module_base))
return NULL;
dos = (IMAGE_DOS_HEADER *) module_base;
nt = (IMAGE_NT_HEADERS *) (((ptr_uint_t)dos) + dos->e_lfanew);
/* we return NULL on error above, make sure no one actually sets their
* preferred base address to NULL */
ASSERT_CURIOSITY(OPT_HDR(nt, ImageBase) != 0);
return (app_pc) OPT_HDR(nt, ImageBase);
}
/* we simply test if allocation bases of a region are the same */
bool
in_same_module(app_pc target, app_pc source)
{
app_pc target_base = get_allocation_base(target);
app_pc source_base = get_allocation_base(source);
LOG(THREAD_GET, LOG_VMAREAS, 2,
"in_same_module("PFX","PFX") => ("PFX","PFX") == %d\n", target, source,
target_base, source_base, (target_base == source_base));
/* all unallocated memory regions will get a base of 0 */
return (target_base != NULL) && (target_base == source_base);
}
/* Use get_module_short_name for arbitrary pcs -- only call this if
* you KNOW this is the base addr of a non-executable module, as it
* bypasses some safety checks in get_module_short_name to avoid 4
* system calls.
* Returns the short module name from the PE exports section, or NULL if invalid
*/
char*
get_dll_short_name(app_pc base_addr)
{
/* FIXME: We'll have a name pointer in a DLL that may get unloaded
by another thread, so it would be nice to synchronize this call
with UnmapViewOfSection so that we can get a safe copy of the name.
FIXME: How can make sure we can't fail on strncpy(buf, name, max_chars);
we can't test is_readable_without_exception(dll_name, max_chars)
because our max_chars may be too long and of course we can't use strlen(),
a TRY block would work.
For now we avoid copying altogether and callers are expected to
synchronize with DLL unloads, or otherwise to be ready to take the risk.
Nearly all callers should be looking up in the loaded_module_areas vector
and using the copy there, which is copied under TRY/EXCEPT, so the
racy window while update_module_list() copies from here is now safe.
*/
IMAGE_EXPORT_DIRECTORY *exports;
ASSERT(base_addr == get_allocation_base(base_addr) &&
is_readable_pe_base(base_addr));
exports = get_module_exports_directory(base_addr, NULL);
if (exports != NULL) {
char *dll_name = (char*) (base_addr + exports->Name /* RVA */);
/* sanity check whether really MEM_IMAGE, but too late */
if (!is_string_readable_without_exception(dll_name, NULL)) {
ASSERT_CURIOSITY(false && "Exports name not readable, partial map?" ||
EXEMPT_TEST("win32.partial_map.exe"));
dll_name = NULL;
}
LOG(THREAD_GET, LOG_SYMBOLS, 3,
"get_dll_short_name(base_addr="PFX") exports=%d dll_name=%s\n",
base_addr, exports, dll_name == NULL ? "<invalid>" : dll_name);
return dll_name;
}
return NULL;
}
/* Get all possible names for the module corresponding to pc. Part of fix for
* case 9842. We have to maintain all different module names, can't just use
* a precedence rule for deciding at all points.
* The ma parameter is optional: if set, ma->full_path is set.
*/
static void
get_all_module_short_names_uncached(dcontext_t *dcontext, app_pc pc, bool at_map,
module_names_t *names, module_area_t *ma,
version_info_t *info, /*OPTIONAL IN*/
const char *file_path HEAPACCT(which_heap_t which))
{
const char *name;
app_pc base;
char buf[MAXIMUM_PATH];
ASSERT(names != NULL);
if (names == NULL)
return;
memset(names, 0, sizeof(*names));
base = get_allocation_base(pc);
LOG(THREAD_GET, LOG_VMAREAS, 5,
"get_all_module_short_names_uncached: start "PFX" -> base "PFX"\n", pc, base);
if (!is_readable_pe_base(base)) {
LOG(THREAD_GET, LOG_VMAREAS, 5,
"get_all_module_short_names_uncached: not a module\n");
return;
}
#ifndef X64
if (module_is_64bit(base)) {
/* For 32-bit dr we ignore 64-bit dlls in a wow64 process. */
ASSERT_CURIOSITY(is_wow64_process(NT_CURRENT_PROCESS));
LOG(THREAD_GET, LOG_VMAREAS, 5,
"get_all_module_short_names_uncached: ignoring 64-bit module in "
"wow64 process\n");
return;
}
#endif
/* FIXME: we do have a race here where the module can be unloaded
* before we finish making a copy of its name
*/
if (dynamo_exited)
return; /* no heap for strdup */
/* Ensure we don't crash if a dll is unloaded racily underneath us */
TRY_EXCEPT_ALLOW_NO_DCONTEXT(dcontext, {
app_pc process_image;
/* Choice #1: PE exports name */
name = get_dll_short_name(base);
if (name != NULL)
names->module_name = dr_strdup(name HEAPACCT(which));
else
names->module_name = NULL;
/* Choice #2: executable qualified name
* This would be the last choice except historically it's been #2 so
* we'll stick with that.
* check if target is in process image -
* in which case we use our unqualified name for the executable
*/
process_image = get_own_peb()->ImageBaseAddress;
/* check if pc region base matches the image base */
/* FIXME: they should be aligned anyways, can remove this */
ASSERT(ALIGNED(process_image, PAGE_SIZE) && ALIGNED(base, PAGE_SIZE));
if (process_image == base) {
name = get_short_name(get_application_name());
if (name != NULL)
names->exe_name = dr_strdup(name HEAPACCT(which));
else
names->exe_name = NULL;
}
/* Choice #3: .rsrc original filename, already strduped */
names->rsrc_name = (char *)
get_module_original_filename(base, info HEAPACCT(which));
/* Choice #4: file name
* At init time it's safe enough to walk loader list. At run time
* we rely on being at_map and using -track_module_filenames which
* will result in a non-NULL file_path parameter.
*/
if (file_path != NULL) {
name = get_short_name(file_path);
if (ma != NULL)
ma->full_path = dr_strdup(file_path HEAPACCT(which));
} else if (!dynamo_initialized) {
const char *path = buf;
get_module_name(base, buf, BUFFER_SIZE_ELEMENTS(buf));
if (buf[0] == '\0' && is_in_dynamo_dll(base))
path = get_dynamorio_library_path();
IF_CLIENT_INTERFACE({
if (path[0] == '\0' && is_in_client_lib(base))
path = get_client_path_from_addr(base);
});
name = get_short_name(path);
/* Set the path too. We could avoid a strdup by sharing the
* same alloc w/ the short name, but simpler to separate.
*/
if (ma != NULL)
ma->full_path = dr_strdup(path HEAPACCT(which));
}
if (name != NULL)
names->file_name = dr_strdup(name HEAPACCT(which));
else
names->file_name = NULL;
DOLOG(3, LOG_VMAREAS, {
LOG(GLOBAL, LOG_INTERP|LOG_VMAREAS, 1,
"get_all_module_short_names_uncached "PFX":\n", base);
LOG(GLOBAL, LOG_INTERP|LOG_VMAREAS, 1, "\tPE name=%s\n",
names->module_name == NULL ? "<unavailable>" : names->module_name);
LOG(GLOBAL, LOG_INTERP|LOG_VMAREAS, 1, "\texe name=%s\n",
names->exe_name == NULL ? "<unavailable>" : names->exe_name);
LOG(GLOBAL, LOG_INTERP|LOG_VMAREAS, 1, "\t.rsrc original filename=%s\n",
names->rsrc_name == NULL ? "<unavailable>" : names->rsrc_name);
if (at_map && DYNAMO_OPTION(track_module_filenames) && dcontext != NULL)
LOG(GLOBAL, LOG_INTERP|LOG_VMAREAS, 1, "\tfilename=%s\n",
names->file_name == NULL ? "<unavailable>" : names->file_name);
});
}, {
/* Free all allocations in the event of an exception and return NULL
* for all names. */
free_module_names(names HEAPACCT(which));
memset(names, 0, sizeof(*names));
});
/* theoretically possible to fail, since section matching can be thwarted,
* or if we came in late
*/
ASSERT_CURIOSITY(names->module_name != NULL || names->exe_name != NULL ||
names->rsrc_name != NULL || names->file_name != NULL ||
!at_map ||
/* PR 229284: a partial map can cause this */
check_filter("win32.partial_map.exe",
get_short_name(get_application_name())));
}
/* Caller should use get_module_short_name() unless calling before or after
* we set up the loaded_module_areas vector.
*
* If map is true, this routine finds our official internal name for a
* module, which is done in this priority order:
* 1) PE exports name
* 2) If pc is in the main executable image we use our fully qualified name
* 3) .rsrc original file name
* 4) if at_map, file name; else unavailable
* FIXME: is PEB->SubSystemData = PathFileName.Buffer (see
* notes in aslr_generate_relocated_section()) available w/o a debugger,
* and should we check whether it equals our stored name?
*
* 1 and 2 need not be present, and 3 can be invalid if the app creates multiple
* sections before mapping any, so we can have a NULL name for a module.
* Also returns NULL if pc is not in a valid module.
* This name precedence is established by GET_MODULE_NAME().
*
* The name string is dr_strduped with HEAPACCT(which) and must be freed
* by the caller calling dr_strfree.
*/
const char *
get_module_short_name_uncached(dcontext_t *dcontext, app_pc pc, bool at_map
HEAPACCT(which_heap_t which))
{
module_names_t names = {0};
const char *res;
get_all_module_short_names_uncached(dcontext, pc, at_map, &names, NULL, NULL, NULL
HEAPACCT(which));
res = dr_strdup(GET_MODULE_NAME(&names) HEAPACCT(which));
free_module_names(&names HEAPACCT(which));
return res;
}
/* All internal uses of module names should be calling this routine,
* which not only looks up the cached name but uses the priority-order
* naming scheme that avoids modules without names, rather than
* explicitly get_dll_short_name() (PE name only) or the other
* individual name gathering routines.
* For safety this routine makes a copy of the name.
* For more-performant uses, use os_get_module_name(), which allows the caller
* to hold a lock and use the original string.
*/
const char *
get_module_short_name(app_pc pc HEAPACCT(which_heap_t which))
{
/* Our module list name is the short name */
return os_get_module_name_strdup(pc HEAPACCT(which));
}
/* If the PC resides in a module that has been relocated from
* its preferred base, get_module_preferred_base_delta() returns
* the delta of the preferred base and its actual base (used to
* normalize the Threat ID). If the PC does not reside in a
* module or it is invalid, the function returns 0 (since there
* is no need to normalize the Threat ID in those cases).
*/
ssize_t
get_module_preferred_base_delta(app_pc pc)
{
app_pc preferred_base_addr = get_module_preferred_base(pc);
app_pc current_base_addr = get_allocation_base(pc);
/* FIXME : optimization add out argument to get_module_preferred_base to
* return the allocation base */
if (preferred_base_addr == NULL || current_base_addr == NULL)
return 0;
return (preferred_base_addr - current_base_addr);
}
/* returns 0 if no loader module is found
* N.B.: walking loader data structures at random times is dangerous!
*/
LDR_MODULE *
get_ldr_module_by_pc(app_pc pc)
{
PEB *peb = get_own_peb();
PEB_LDR_DATA *ldr = peb->LoaderData;
LIST_ENTRY *e, *mark;
LDR_MODULE *mod;
uint traversed = 0; /* a simple infinite loop break out */
#ifdef DEBUG
RTL_CRITICAL_SECTION *lock;
thread_id_t owner;
#endif
if (ldr == NULL) {
ASSERT(dr_earliest_injected);
return NULL;
}
#ifdef DEBUG
lock = (RTL_CRITICAL_SECTION *) peb->LoaderLock;
owner = (thread_id_t) lock->OwningThread;
if (owner != 0 && owner != get_thread_id()) {
/* This will be a risky operation but we'll live with it.
In case we walk in a list in an inconsistent state
1) we may get trapped in an infinite loop when following a partially updated list
so we'll bail out in case of a deep loop
2) list entries and pointed data may be removed and even deallocated
we can't just check for is_readable_without_exception
since it won't help if we're in a race
FIXME: we should mark we started this routine,
and if we get an exception retry or give up gracefully
*/
LOG(GLOBAL, LOG_ALL, 3, "WARNING: get_ldr_module_by_pc w/o holding LoaderLock\n");
DOLOG_ONCE(2, LOG_ALL, {
SYSLOG_INTERNAL_WARNING("get_ldr_module_by_pc w/o holding LoaderLock");
});
}
#endif
/* Now, you'd think these would actually be in memory order, but they
* don't seem to be for me!
*/
mark = &ldr->InMemoryOrderModuleList;
for (e = mark->Flink; e != mark; e = e->Flink) {
app_pc start, end;
mod = (LDR_MODULE *) ((char *)e - offsetof(LDR_MODULE, InMemoryOrderModuleList));
start = (app_pc) mod->BaseAddress;
end = start + mod->SizeOfImage;
if (pc >= start && pc < end) {
return mod;
}
if (traversed++ > MAX_MODULE_LIST_INFINITE_LOOP_THRESHOLD) {
LOG(GLOBAL, LOG_ALL, 1,
"WARNING: get_ldr_module_by_pc too many modules, or an infinte loop due to a race\n");
SYSLOG_INTERNAL_WARNING_ONCE("get_ldr_module_by_pc too many modules");
/* TODO: In case we ever hit this we may want to retry the traversal once more */
return NULL;
}
}
return NULL;
}
/* N.B.: walking loader data structures at random times is dangerous!
* Do not call this for non-debug reasons if you can help it!
* See get_module_status for a safer approach to walking loader structs.
*/
void
get_module_name(app_pc pc, char *buf, int max_chars)
{
LDR_MODULE *mod = get_ldr_module_by_pc(pc);
/* FIXME i#812: at earliest inject point this doesn't work: hardcode ntdll.dll? */
if (mod != NULL) {
wchar_to_char(buf, max_chars, mod->FullDllName.Buffer, mod->FullDllName.Length);
return;
}
buf[0] = '\0';
}
static
IMAGE_BASE_RELOCATION *
get_module_base_reloc(app_pc module_base, size_t *base_reloc_size /* OPTIONAL OUT */)
{
IMAGE_NT_HEADERS *nt;
IMAGE_DATA_DIRECTORY *base_reloc_dir = NULL;
app_rva_t base_reloc_vaddr = 0;
size_t size = 0;
IMAGE_BASE_RELOCATION *base_reloc = NULL;
VERIFY_NT_HEADER(module_base);
/* callers should have done this in release builds */
ASSERT(is_readable_pe_base(module_base));
nt = NT_HEADER(module_base);
base_reloc_dir = OPT_HDR(nt, DataDirectory) +
IMAGE_DIRECTORY_ENTRY_BASERELOC;
if (base_reloc_size != NULL)
*base_reloc_size = 0;
/* Don't expect base_reloc_dir to be NULL, but to be safe */
if (base_reloc_dir == NULL) {
ASSERT_CURIOSITY(false &&
"DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] NULL");
/* base_reloc_size set to 0 */
return NULL;
}
/* sanity check */
ASSERT(is_readable_without_exception((app_pc)base_reloc_dir, 8));
base_reloc_vaddr = base_reloc_dir->VirtualAddress;
size = base_reloc_dir->Size;
/* /FIXED dlls have the vaddr as 0, but size may be garbage */
if (base_reloc_vaddr == 0) {
/* $ dumpbin /headers xpsp2res.dll
* 210E characteristics
* 0 [ 0] RVA [size] of Base Relocation Directory
* has only one section .rsrc
*/
return NULL; /* base_reloc_size set to 0 */
}
if (base_reloc_vaddr != 0 && size == 0) {
ASSERT_CURIOSITY(false && "expect non-zero base_reloc");
return NULL; /* base_reloc_size set to 0 */
}
LOG(GLOBAL, LOG_RCT, 2,
"reloc: get_module_base_reloc: module_base="PFX", "
"base_reloc_dir="PFX", base_reloc_vaddr="PFX", size="PFX")\n",
module_base, base_reloc_dir, base_reloc_vaddr, size);
base_reloc = (IMAGE_BASE_RELOCATION *) RVA_TO_VA(module_base, base_reloc_vaddr);
if (is_readable_without_exception((app_pc)base_reloc, size)) {
if (base_reloc_size != NULL)
*base_reloc_size = size;
return base_reloc;
} else {
ASSERT_CURIOSITY(false && "bad base relocation" ||
EXEMPT_TEST("win32.partial_map.exe"));/*expected for partial map*/
}
return NULL;
}
/* returns FileHeader.Characteristics */
/* should be used only under after is_readable_pe_base */
uint
get_module_characteristics(app_pc module_base)
{
IMAGE_NT_HEADERS *nt = NULL;
IMAGE_DATA_DIRECTORY *com_desc_dir = NULL;
VERIFY_NT_HEADER(module_base);
/* callers should have done this in release builds */
ASSERT(is_readable_pe_base(module_base));
nt = NT_HEADER(module_base);
/* note this is not the same as OptionalHeader.DllCharacteristics */
return nt->FileHeader.Characteristics;
}
/* Parse PE and return IMAGE_COR20_HEADER * if it has a valid COM header.
* Optional OUT: cor20_header_size.
* NOTE: returning a pointer into a dll that could be unloaded could be racy
* (though we don't expect a race, see case 1272 for _safe/_unsafe routines)
*/
IMAGE_COR20_HEADER *
get_module_cor20_header(app_pc module_base, size_t *cor20_header_size)
{
IMAGE_NT_HEADERS *nt = NULL;
IMAGE_DATA_DIRECTORY *com_desc_dir = NULL;
VERIFY_NT_HEADER(module_base);
/* callers should have done this in release builds */
ASSERT(is_readable_pe_base(module_base));
nt = NT_HEADER(module_base);
/* IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR < IMAGE_NUMBEROF_DIRECTORY_ENTRIES */
com_desc_dir = OPT_HDR(nt, DataDirectory) +
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR;
/* sanity check */
ASSERT(is_readable_without_exception((app_pc)com_desc_dir, 8));
LOG(GLOBAL, LOG_RCT, 3,
"get_module_cor20_header: module_base="PFX", com_desc_dir="PFX")\n",
module_base, com_desc_dir);
if (cor20_header_size != NULL)
*cor20_header_size = 0;
if (com_desc_dir != NULL) {
app_rva_t com_desc_vaddr = com_desc_dir->VirtualAddress;
size_t size = com_desc_dir->Size; /* only a dword but we plan for future */
LOG(GLOBAL, LOG_RCT, 3,
"get_module_cor20_header: module_base="PFX", "
"com_desc_dir="PFX", com_desc_vaddr="PFX", size="PFX")\n",
module_base, com_desc_dir, com_desc_vaddr, size);
if (com_desc_vaddr != 0 && size == 0 ||
com_desc_vaddr == 0 && size > 0) {
ASSERT_CURIOSITY(false && "bad cor20 header");
/* cor20_header_size set to 0 */
return NULL;
}
if (size > 0) {
IMAGE_COR20_HEADER *cor20_header = (IMAGE_COR20_HEADER *)
RVA_TO_VA(module_base, com_desc_dir->VirtualAddress);
if (is_readable_without_exception((app_pc)cor20_header,
sizeof(IMAGE_COR20_HEADER))) {
if (cor20_header_size != NULL) {
*cor20_header_size = size;
}
return cor20_header;
} else
ASSERT_CURIOSITY(false && "bad cor20 header");
}
}
else
ASSERT_CURIOSITY(false && "no cor20_header directory entry");
return NULL;
}
/* PE files, for exes and dlls, with managed code have IMAGE_COR20_HEADER
* defined in their PE. Return if PE has cor20 header or not.
*/
bool
module_has_cor20_header(app_pc module_base)
{
size_t cor20_header_size = 0;
IMAGE_COR20_HEADER *cor20_header =
get_module_cor20_header(module_base, &cor20_header_size);
return (cor20_header != NULL && cor20_header_size > 0);
}
static WORD
get_module_magic(app_pc module_base)
{
IMAGE_NT_HEADERS *nt = NULL;
if (!is_readable_pe_base(module_base))
return false;
VERIFY_NT_HEADER(module_base);
nt = NT_HEADER(module_base);
return nt->OptionalHeader.Magic;
}
bool
module_is_32bit(app_pc module_base)
{
return (get_module_magic(module_base) == IMAGE_NT_OPTIONAL_HDR32_MAGIC);
}
bool
module_is_64bit(app_pc module_base)
{
return (get_module_magic(module_base) == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
}
/* WARNING: this routine relies on observed behavior and data structures that
* may change in future versions of Windows
*
* Returns true if start:end matches a code/IAT section of a module that the loader
* would legitimately update, AND the module is currently being initialized
* by this thread (or a guess as to that effect for 2003).
* If conservative is true, makes fewer guesses and uses stricter guidelines,
* so may have false negatives but should have no false positives after
* the image entry point.
* Caller must distinguish IAT in .rdata from IAT in .text.
*/
bool
is_module_patch_region(dcontext_t *dcontext, app_pc start, app_pc end,
bool conservative)
{
PEB *peb = get_own_peb();
LDR_MODULE *mod;
RTL_CRITICAL_SECTION *lock = (RTL_CRITICAL_SECTION *) peb->LoaderLock;
app_pc IAT_start, IAT_end;
bool match_IAT = false;
app_pc base = get_module_base(start);
LOG(THREAD, LOG_VMAREAS, 2,
"is_module_patch_region: start "PFX" -> base "PFX"\n", start, base);
if (base == NULL) {
LOG(THREAD, LOG_VMAREAS, 2,
"is_module_patch_region: not readable or not PE => NO\n");
return false;
}
/* The only module changes we recognize are rebasing, where the entire
* code section should be written to, and rebinding, where only
* the IAT should be written to. We ignore relocation of other data.
* We allow for page rounding at end.
*/
if (is_IAT(start, (app_pc) ALIGN_FORWARD(end, PAGE_SIZE),
true /*page align*/, &IAT_start, &IAT_end)) {
LOG(THREAD, LOG_VMAREAS, 2,
"is_module_patch_region: matches IAT "PFX"-"PFX"\n", IAT_start, IAT_end);
match_IAT = true;
} else {
/* ASSUMPTION: if multiple code sections, they are always protected separately.
* We walk the code sections and see if our region is inside one of them.
*/
app_pc sec_start = NULL, sec_end = NULL;
if (!is_range_in_code_section(base, start, end, &sec_start, &sec_end)) {
LOG(THREAD, LOG_VMAREAS, 2,
"is_module_patch_region: not IAT or inside code section => NO\n");
return false;
}
LOG(THREAD, LOG_VMAREAS, 2,
"is_module_patch_region: target "PFX"-"PFX" => section "PFX"-"PFX"\n",
start, end, sec_start, sec_end);
/* FIXME - check what alignment the loader uses when section alignment is
* < than page size (check on all platforms) to tighten this up. According to
* Derek the IAT requests are very percise, but the loader may do exact
* (rebase restore) or page-aligned (rebind restore) on xpsp2 at least. */
if (ALIGN_BACKWARD(start, PAGE_SIZE) != ALIGN_BACKWARD(sec_start, PAGE_SIZE) ||
ALIGN_FORWARD(end, PAGE_SIZE) != ALIGN_FORWARD(sec_end, PAGE_SIZE)) {
LOG(THREAD, LOG_VMAREAS, 2,
"is_module_patch_region: not targeting whole code or IAT section => NO\n");
return false;
}
}
/* On 2K and XP, the LoaderLock is always held when loading a module,
* but on 2003 it is not held for loads prior to the image entry point!
* Even worse, we've seen apps that create a 2nd thread prior to the entry
* point, meaning we cannot safely walk the list.
*/
if ((thread_id_t) lock->OwningThread == get_thread_id()) {
/* Walk the list
* FIXME: just look at the last entry, since it's appended to the memory-order
* list?
*/
mod = get_ldr_module_by_pc(start);
if (mod != NULL) {
/* How do we know if module is initialized? LoadCount is 0 for a while, but
* on win2003 it becomes 1 and the loader is still mucking around. But when it
* does become 1, the flags have 0x1000 set. So we have this hack.
* ASSUMPTION: module is uninitialized if either LoadCount is 0 or
* flags have 0x1000 set.
* Note that LoadCount is -1 for statically linked dlls and the exe itself.
* We also see cases where a module's IAT is patched, and later is re-patched
* once the module's count and flags indicate it's initialized.
* We go ahead and allow that, since it's only data and not much of a security
* risk.
* FIXME: figure out what the loader is doing there -- I saw it on sqlservr
* on 2003 loading msdtcprx.dll and then a series of dependent dlls was loaded
* and patched and re-patched, perhaps due to forwarding?
*/
LOG(THREAD, LOG_VMAREAS, 2,
"is_module_patch_region: count=%d, flags=0x%x, %s\n",
mod->LoadCount, mod->Flags, match_IAT ? "IAT" : "not IAT");
if (mod->LoadCount == 0 || TEST(LDR_LOAD_IN_PROGRESS, mod->Flags) ||
/* case 10180: executable itself has unknown flag 0x00004000 set; we
* relax to consider it the loader if the lock is held and we are
* before the image entry, but only when we track the image entry */
(!reached_image_entry_yet() && !RUNNING_WITHOUT_CODE_CACHE()) ||
(!conservative && match_IAT))
return true;
else
return false;
}
} else {
if (get_os_version() >= WINDOWS_VERSION_2003 && !reached_image_entry_yet()) {
#ifdef HOT_PATCHING_INTERFACE
/* This one of the uses of reached_image_entry that may conflict
* with -hotp_only not setting it because interp is not done. Others
* are safe. If this is hit, then the image_entry hook will have
* to placed in callback_interception_init for -hotp_only. A TODO.
*/
if(DYNAMO_OPTION(hotp_only)) {
LOG(GLOBAL, LOG_HOT_PATCHING, 1, "Warning: On w2k3, for "
"hotp_only, image entry won't be detected because no "
"interp is done and hook is placed late");
}
#endif
/* On 2003, we cannot safely walk the module list (grabbing
* the LoaderLock is fraught with deadlock problems...)
* We haven't put in the effort in analyzing the
* LdrLockLoaderLock routine that decides not to grab it
* to find the flag the loader is using to decide when to
* start using the lock, but it coincides with the image
* entry point (or loader finishing initialization), so we
* use that.
* FIXME: this isn't as narrow as we'd like --
* we're letting anyone modify a .text section prior to
* image entry on 2003!
*/
return true;
}
}
return false;
}
#define IMAGE_REL_BASED_TYPE(x) ((x) >> 12)
#define IMAGE_REL_BASED_OFFSET_MASK 0x0FFF
#define IMAGE_REL_BASED_OFFSET(x) ((ushort)((x) & IMAGE_REL_BASED_OFFSET_MASK))
/* Processes a single relocation and returns the relocated address. If
* apply_reloc is false, the actual relocation isn't performed on the
* image only the relocated address is returned.
*
* Note: this routine handles 32-bit dlls for both 32-bit dr and for 64-bit dr
* (wow64 process), and 64-bit dlls for 64-bit dr.
*
* X86 relocation types can be: IMAGE_REL_BASED_HIGHLOW |
* IMAGE_REL_BASED_ABSOLUTE -- offsets pointing to 32-bit
* immediate (see case 6424). For X64 it is IMAGE_REL_BASED_DIR64
* or IMAGE_REL_BASED_ABSOLUTE.
*
* Returns relocated_addr for HIGHLOW & DIR64.
*/
static app_pc
process_one_relocation(const app_pc module_base, app_pc reloc_entry_p,
uint reloc_array_rva, ssize_t relocation_delta,
bool apply_reloc, bool *null_ref /* OUT */,
bool *unsup_reloc /* OUT */,
app_pc *relocatee_addr /* OUT */, bool is_module_32bit
_IF_DEBUG(size_t module_size))
{
ushort reloc_entry = *(ushort *)reloc_entry_p;
int reloc_type = IMAGE_REL_BASED_TYPE(reloc_entry);
ushort offset = IMAGE_REL_BASED_OFFSET(reloc_entry);
app_pc cur_addr, addr_to_reloc, relocated_addr = NULL;
DEBUG_DECLARE(char *rel_name = "unsupported";)
cur_addr = (app_pc) RVA_TO_VA(module_base, (reloc_array_rva + offset));
/* relocatee_addr is used to return the address of the value that is to be
* relocated. */
if (relocatee_addr != NULL)
*relocatee_addr = cur_addr;
/* curiosity: sometimes cur_addr is not within module */
ASSERT_CURIOSITY(module_base <= cur_addr &&
cur_addr < (module_base + module_size));
#ifdef X64
if (reloc_type == IMAGE_REL_BASED_DIR64) {
/* this relocation is only on 64-bits */
addr_to_reloc = (app_pc)(*(uint64 *)cur_addr);
if (addr_to_reloc == NULL) {
if (null_ref != NULL)
*null_ref = true;
ASSERT_CURIOSITY(false && "relocation entry for a null ref?");
}
relocated_addr = relocation_delta + addr_to_reloc;
if (apply_reloc) {
*(uint64 *)cur_addr = (uint64) relocated_addr;
}
DEBUG_DECLARE(rel_name = "DIR64");
} else
#endif
if (reloc_type == IMAGE_REL_BASED_HIGHLOW) {
/* This is a 32-bit relocation type and can found only in a 32-bit dll.
* If it is found in a 64-bit process then the process must be wow64.
*/
IF_X64(ASSERT(is_wow64_process(NT_CURRENT_PROCESS));)
IF_X64(ASSERT(is_module_32bit);)
if (!is_module_32bit) {
if (unsup_reloc != NULL)
*unsup_reloc = true;
return NULL;
}
/* Relocation delta can't be greater than 32-bits! It is a bug if so
* because 32-bit dlls only have 32-bit quantities to be relocated,
* i.e., only within a 2 GB space - so can't add a relocation delta
* that is greater than 31 bits (which is possible to generate while
* rebasing in a 64-bit address space).
*/
ASSERT(CHECK_TRUNCATE_TYPE_int(relocation_delta));
/* IMAGE_REL_BASED_HIGHLOW relocations are defined to operate
* on 32-bits, irrespective of 32-bit or 64-bit DR, so read
* exactly 32-bits.
*/
ASSERT(sizeof(uint) == 4); /* this relocation is only on 32-bits */
addr_to_reloc = (app_pc)(ptr_uint_t)(*(uint*)cur_addr);
if (addr_to_reloc == NULL) {
if (null_ref != NULL)
*null_ref = true;
ASSERT_CURIOSITY(false && "relocation entry for a null ref?");
}
relocated_addr = relocation_delta + addr_to_reloc;
/* Just like relocation delta, relocated addr can't be greater than
* 32-bits too.
*/
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint((ptr_uint_t)relocated_addr));)
if (apply_reloc) {
*(uint *)cur_addr = (uint)(ptr_uint_t) relocated_addr;
}
DEBUG_DECLARE(rel_name = "HIGHLOW");
} else if (reloc_type == IMAGE_REL_BASED_ABSOLUTE) {
/* This is just a padding type so ignore. */
DEBUG_DECLARE(rel_name = "ABS");
} else {
/* unsupported types */
/* LOW: *(ushort*) cur_addr += LOWORD(relocation_delta)
* HIGH: *(ushort*) cur_addr += HIWORD(relocation_delta)
*/
/* FIXME: case 8515: it is better to implement these
* in case some stupid compiler is generating them for random reasons
*/
ASSERT_CURIOSITY("Unsupported relocation encountered");
if (unsup_reloc != NULL)
*unsup_reloc = true;
}
LOG(GLOBAL, LOG_RCT, 6, "\t%8x %s\n", offset, rel_name);
return relocated_addr;
}
/****************************************************************************/
#ifdef RCT_IND_BRANCH
# ifdef X64
static void
add_SEH_address(dcontext_t *dcontext, app_pc addr, app_pc modbase, size_t modsize)
{
/* If except_handler isn't within the image, don't add to RCT table */
if (addr > modbase && addr < modbase + modsize) {
if (rct_add_valid_ind_branch_target(dcontext, addr)) {
STATS_INC(rct_ind_branch_valid_targets);
STATS_INC(rct_ind_seh64_new);
} else
STATS_INC(rct_ind_seh64_old);
} else {
ASSERT_CURIOSITY(false && "SEH address out of module");
}
}
/* This routine analyzes the .pdata section in a PE32+ module and adds
* exception handler addresses to rct table. Note, this isn't applicable for
* 32-bit dlls as SEH aren't specified like this in those dlls. PR 250395.
*/
static void
add_SEH_to_rct_table(dcontext_t *dcontext, app_pc module_base)
{
IMAGE_NT_HEADERS *nt = NT_HEADER(module_base);
IMAGE_DATA_DIRECTORY *except_dir;
PIMAGE_RUNTIME_FUNCTION_ENTRY func_entry, func_entry_end;
byte *pdata_start;
uint image_size;
ASSERT_OWN_MUTEX(true, &rct_module_lock);
ASSERT(module_base != NULL);
if (module_base == NULL)
return;
/* Ignore 32-bit dlls in a wow64 process. */
if (!module_is_64bit(module_base))
return;
nt = NT_HEADER(module_base);
/* Exception directories can be NULL if the compiler didn't put it in, but
* unusual for PE32+ dlls to not have an exception directory.
*/
except_dir = OPT_HDR(nt, DataDirectory) + IMAGE_DIRECTORY_ENTRY_EXCEPTION;
if (except_dir == NULL) {
ASSERT_CURIOSITY(false && "no exception data directory (no .pdata?)");
return;
}
image_size = nt->OptionalHeader.SizeOfImage;
/* Exception directory entry must lie within the image */
ASSERT((app_pc)except_dir > module_base &&
(app_pc)except_dir < module_base + image_size);
/* Exception directory, i.e., function table entries, must lie within the
* image.
*/
func_entry = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(module_base +
except_dir->VirtualAddress);
pdata_start = (byte *) func_entry;
func_entry_end = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(module_base +
except_dir->VirtualAddress +
except_dir->Size);
/* spec says func_entry must be dword-aligned */
ASSERT_CURIOSITY(ALIGNED(func_entry, sizeof(DWORD)));
ASSERT((app_pc)func_entry_end >= module_base &&
(app_pc)func_entry < module_base + image_size);
LOG(GLOBAL, LOG_RCT, 2, "parsing .pdata of pe32+ module "PFX"\n", module_base);
for (; func_entry < func_entry_end; func_entry++) {
unwind_info_t *info = (unwind_info_t*)
(module_base + func_entry->UnwindInfoAddress);
/* Spec says unwind info must be dword-aligned, but we also have
* special entries that point at other RUNTIME_FUNCTION slots in
* the .pdata array, but with a 1-byte offset. It seems to be a
* way to share unwind info for non-contiguous pieces of the same
* function without wasting space on a chained info structure
* (see PR 250395).
*/
if (info > (unwind_info_t *) pdata_start &&
info < (unwind_info_t *) func_entry_end) {
/* All the ones I've seen have been 1 byte in */
ASSERT_CURIOSITY(ALIGNED(((byte*)info)-1, sizeof(DWORD)));
/* We just skip this entry, as it's subsumed by the one it points at */
STATS_INC(rct_ind_seh64_plus1);
continue;
}
ASSERT_CURIOSITY(ALIGNED(info, sizeof(DWORD)));
/* If it is a chain entry, then walk the chain to get the exception
* handler.
*/
while (TEST(UNW_FLAG_CHAININFO, info->Flags)) {
/* Note that while one page of the specs, and the
* GetChainedFunctionEntry() macro, say that there is a pointer to a
* RUNTIME_FUNCTION, another page, and all the instances I've seen, have
* the RUNTIME_FUNCTION inlined. We handle both.
*/
byte *ptr;
uint rva;
PIMAGE_RUNTIME_FUNCTION_ENTRY chain_func;
/* if chained, can't have handler flags set */
ASSERT_CURIOSITY(!TESTANY(UNW_FLAG_EHANDLER|UNW_FLAG_UHANDLER, info->Flags));
ptr = module_base + UNWIND_INFO_PTR_RVA(info);
if (ptr > pdata_start && ptr < (byte *) func_entry_end)
chain_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY) ptr;
else /* inlined */
chain_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY) UNWIND_INFO_PTR_ADDR(info);
if (!safe_read(&chain_func->UnwindInfoAddress, sizeof(rva), &rva)) {
ASSERT_CURIOSITY(false && "unwind_info_t corrupted/misinterpreted");
continue;
}
info = (unwind_info_t *)(module_base + rva/*chain_func->UnwindInfoAddress*/);
/* spec says unwind info must be dword-aligned */
ASSERT_CURIOSITY(ALIGNED(info, sizeof(DWORD)));
}
/* If unwind info is either UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER,
* then it has an exception handler address.
*/
if (TESTANY(UNW_FLAG_EHANDLER|UNW_FLAG_UHANDLER, info->Flags)) {
app_pc handler = module_base + UNWIND_INFO_PTR_RVA(info);
/* PR 276527: also process the scope table addresses */
scope_table_t *scope;
uint i;
bool is_scope = true;
add_SEH_address(dcontext, handler, module_base, image_size);
LOG(GLOBAL, LOG_RCT, 4, "added RCT SEH64 handler "PFX
" (from "PFX")\n", handler, info);
/* Note that I'm seeing user32 and kernel32 each having a single
* master handler, which calls the ntdll master handler, which uses
* the scope table to dive down into details. Given the single
* master, why not provide an efficient way to have a single master
* handler, which would allow handling misaligned stack pointers?
*/
/* Like the chained info, the scope table is described as being
* out-of-line, but I'm seeing it inlined.
*/
scope = (scope_table_t *) UNWIND_INFO_DATA_ADDR(info);
/* Not all entries have this: e.g., calc.exe's _CxxFrameHandler doesn't
* use this setup; only the _C_specific_handler routines do.
* We use a heuristic where we assume there won't be over 4K entries;
* most other fields in these tables are > 0x1000. That breaks down
* when we hit other lang-specific structs, though, so we have
* further checks below.
*/
if (scope->Count == 0 || scope->Count >= 0x1000)
is_scope = false;
else {
/* Do one pass through to make sure it all looks right. FIXME: we
* need a stronger way to tell when there's a scope table and when
* not. It would be nice to check the scope entry's range, but it
* seems to not need to be a subset of func_entry's range: many
* func_entries point at the same unwind_info, which then re-expands
* via the scope table. We could check that [Begin, End) is in a
* code section.
*/
for (i = 0; i < scope->Count; i++) {
if (scope->ScopeRecord[i].EndAddress <=
scope->ScopeRecord[i].BeginAddress ||
/* Yes, you can have tiny dlls, but we'll adjust when we hit
* that: I'm seeing other lang-specific structs and I need
* heuristics to distinguish from the scope table we know */
scope->ScopeRecord[i].BeginAddress < PAGE_SIZE ||
scope->ScopeRecord[i].EndAddress > image_size ||
(scope->ScopeRecord[i].HandlerAddress >
EXCEPTION_EXECUTE_HANDLER &&
scope->ScopeRecord[i].HandlerAddress < PAGE_SIZE) ||
scope->ScopeRecord[i].HandlerAddress > image_size ||
scope->ScopeRecord[i].JumpTarget > image_size) {
LOG(GLOBAL, LOG_RCT, 4, "NOT a scope table entry %d info "PFX
"\n", i, info);
is_scope = false;
break;
}
}
}
if (is_scope) {
for (i = 0; i < scope->Count; i++) {
/* Add the filter address */
if (scope->ScopeRecord[i].HandlerAddress !=
EXCEPTION_EXECUTE_HANDLER &&
/* Often they're all the same */
(i == 0 || scope->ScopeRecord[i].HandlerAddress !=
scope->ScopeRecord[i-1].HandlerAddress)) {
add_SEH_address(dcontext, module_base +
scope->ScopeRecord[i].HandlerAddress,
module_base, image_size);
LOG(GLOBAL, LOG_RCT, 4, "added RCT SEH64 filter %d "PFX"\n",
i, module_base +
scope->ScopeRecord[i].HandlerAddress);
}
if (scope->ScopeRecord[i].JumpTarget != 0 &&
/* Often they're all the same */
(i == 0 || scope->ScopeRecord[i].JumpTarget !=
scope->ScopeRecord[i-1].JumpTarget)) {
/* Add the catch block entry address */
add_SEH_address(dcontext,
module_base + scope->ScopeRecord[i].JumpTarget,
module_base, image_size);
LOG(GLOBAL, LOG_RCT, 4, "added RCT SEH64 catch %d "PFX
"\n", i, module_base + scope->ScopeRecord[i].JumpTarget);
}
}
} else {