| /* ********************************************************** |
| * 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 { |
|