| /* ********************************************************** |
| * Copyright (c) 2011-2014 Google, Inc. All rights reserved. |
| * Copyright (c) 2006-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) 2006-2007 Determina Corp. */ |
| |
| /* |
| * syscall.c - win32-specific system call handling routines |
| */ |
| |
| #include "../globals.h" |
| #include "../fragment.h" |
| #include "ntdll.h" |
| #include "os_private.h" |
| #include "aslr.h" |
| #include "instrument.h" |
| #include "../synch.h" |
| |
| /* this points to one of the os-version-specific system call # arrays below */ |
| int *syscalls = NULL; |
| /* this points to one of the os-version-specific wow64 argument conversion arrays */ |
| int *wow64_index = NULL; |
| |
| /* Ref case 5217 - for Sygate compatibility we indirect int 2e system |
| * calls through the int_syscall_address (which after syscalls_init() |
| * will point to an int 2e, ret 0 in ntdll.dll. This is, for all intents |
| * and purposes, a function pointer that will be set only once early |
| * during app init, so we keep it here with the options to leverage their |
| * protection. */ |
| app_pc int_syscall_address = NULL; |
| /* Ref case 5441 - for Sygate compatibility we fake our return address from |
| * sysenter system calls (they sometimes verify) to this address which will |
| * (by default) point to a ret 0 in ntdll.dll. This is, for all intents and |
| * purposes, a function pointer that will be set only once early during app |
| * init, so we keep it here with the options to leverage their protection. */ |
| app_pc sysenter_ret_address = NULL; |
| /* i#537: sysenter returns to KiFastSystemCallRet from kernel */ |
| app_pc KiFastSystemCallRet_address = NULL; |
| |
| /* Snapshots are relatively heavyweight, so we do not take them on every memory |
| * system call. On the other hand, if we only did them when we dumped |
| * stats, we'd miss large memory allocations that were freed prior |
| * to the next stats dump (which can be far between if not much new code |
| * is being executed). Thus, we do them whenever we print stats and on |
| * every memory operation larger than this threshold: |
| */ |
| #define SNAPSHOT_THRESHOLD (16*PAGE_SIZE) |
| |
| /*******************************************************/ |
| |
| #ifdef CLIENT_INTERFACE |
| /* i#1230: we support a limited number of extra interceptions. |
| * We add extra slots to all of the arrays. |
| */ |
| # define CLIENT_EXTRA_TRAMPOLINE 12 |
| # define TRAMPOLINE_MAX (SYS_MAX + CLIENT_EXTRA_TRAMPOLINE) |
| /* no lock needed since only supported during dr_init */ |
| static uint syscall_extra_idx; |
| #else |
| # define TRAMPOLINE_MAX SYS_MAX |
| #endif |
| |
| const char * SYS_CONST syscall_names[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| "Nt"#name, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_81_x64_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w81x64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_81_wow64_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w81w64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_81_x86_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w81x86, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_8_x64_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w8x64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_8_wow64_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w8w64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_8_x86_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w8x86, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_7_x64_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w7x64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_7_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w7x86, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_vista_sp1_x64_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| vista1_x64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_vista_sp1_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| vista1, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_vista_sp0_x64_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| vista0_x64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_vista_sp0_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| vista0, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_2003_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w2k3, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_XP_x64_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| xp64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| /* This is the index for XP through Win7. */ |
| SYS_CONST int windows_XP_wow64_index[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| wow64, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_XP_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| xp, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_2000_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| w2k, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| SYS_CONST int windows_NT_sp4_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| ntsp4, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| /* for SP3 (and maybe SP2 or SP1 -- haven't checked those) */ |
| SYS_CONST int windows_NT_sp3_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| ntsp3, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| /* for SP0 (and maybe SP2 or SP1 -- haven't checked those) */ |
| SYS_CONST int windows_NT_sp0_syscalls[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| ntsp0, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| |
| /* for x64 this is the # of args */ |
| SYS_CONST uint syscall_argsz[TRAMPOLINE_MAX] = { |
| #ifdef X64 |
| # define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| nargs, |
| #else |
| # define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| arg32, |
| #endif |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| |
| /* FIXME: currently whether a syscall needs action or not can't be |
| * dynamically changed since this flag is used early on by |
| * intercept_native_syscall() */ |
| static SYS_CONST int syscall_requires_action[TRAMPOLINE_MAX] = { |
| #define SYSCALL(name, act, nargs, arg32, ntsp0, ntsp3, ntsp4, w2k, xp, wow64, xp64,\ |
| w2k3, vista0, vista0_x64, vista1, vista1_x64, w7x86, w7x64, \ |
| w8x86, w8w64, w8x64, w81x86, w81w64, w81x64) \ |
| act, |
| #include "syscallx.h" |
| #undef SYSCALL |
| }; |
| |
| /* used to intercept syscalls while native */ |
| static byte *syscall_trampoline_pc[TRAMPOLINE_MAX]; |
| static app_pc syscall_trampoline_skip_pc[TRAMPOLINE_MAX]; |
| static app_pc syscall_trampoline_hook_pc[TRAMPOLINE_MAX]; |
| static app_pc syscall_trampoline_copy_pc[TRAMPOLINE_MAX]; |
| |
| #ifdef GBOP |
| /* GBOP stack adjustment - currently either always 0 or always 4 for |
| * vsyscall calls, but may need to be a more general array in case |
| * HOOKED_TRAMPOLINE_HOOK_DEEPER allows different offsets |
| * FIXME: case 7127 this can be compressed further, if really only a bitmask |
| * see intercept_syscall_wrapper |
| */ |
| static byte syscall_trampoline_gbop_fpo_offset[TRAMPOLINE_MAX]; |
| #endif /* GBOP */ |
| |
| /****************************************************************************/ |
| |
| /* System call interception: put any special handling here |
| * Arguments come from the pusha right before the call |
| * Win32 syscall: int 0x2e, number is in eax, address of start of params |
| * on user stack is in edx |
| * |
| * WinXP uses sysenter instruction and does a call to it since sysenter |
| * doesn't store return info -- instead sysexit (called from kernel) grabs |
| * continuation pc from edx. So the callee, same one used by all syscalls, |
| * puts esp in edx so that kernel just has to dereference it. |
| * Actually, on closer examination, it looks like the kernel sends control |
| * directly to 0x7ffe0304, which does a ret to get back to the ret after |
| * the call %edx -- since the 0x7ffe0304 ret executes natively we can't tell |
| * the difference, but we should be aware of it! If this is true, why bother |
| * filling in edx for sysenter? Seems like the kernel must be hardcoding it |
| * with 0x7ffe0304. |
| * FIXME: think about whether want to |
| * insert a trampoline (and risk clobbering entry point after the ret) |
| * instead of the current method of clobbering the return address |
| * |
| * Here are some win2000 examples (from ntdll.dll): |
| NtSetContextThread: |
| 77F97BFA: B8 BA 00 00 00 mov eax,0BAh |
| 77F97BFF: 8D 54 24 04 lea edx,[esp+4] |
| 77F97C03: CD 2E int 2Eh |
| 77F97C05: C2 08 00 ret 8 |
| this is the only one that does not immediately have a ret, though it |
| does ret after a jump, some poorly chosen "optimization": |
| NtContinue: |
| 77F82872: B8 1C 00 00 00 mov eax,1Ch |
| 77F82877: 8D 54 24 04 lea edx,[esp+4] |
| 77F8287B: CD 2E int 2Eh |
| 77F8287D: E9 82 74 01 00 jmp 77F99D04 |
| 77F99D04: C2 08 00 ret 8 |
| * |
| * WinXP example: |
| NtOpenKey: |
| 0x77f7eb23 b8 77 00 00 00 mov $0x00000077 -> %eax |
| 0x77f7eb28 ba 00 03 fe 7f mov $0x7ffe0300 -> %edx |
| 0x77f7eb2d ff d2 call %edx |
| 0x7ffe0300 8b d4 mov %esp -> %edx |
| 0x7ffe0302 0f 34 sysenter |
| 0x7ffe0304 c3 ret %esp (%esp) -> %esp |
| 0x77f7eb2f c2 0c 00 ret $0x000c %esp (%esp) -> %esp |
| */ |
| |
| |
| /* the win32ksys calls are all above 0x1000, only Zw/Nt* are below */ |
| #define MAX_NTOSKRNL_SYSCALL_NUM 0x1000 |
| |
| bool |
| ignorable_system_call(int num, instr_t *gateway, dcontext_t *dcontext_live) |
| { |
| /* FIXME: this should really be a complete list of ignorable calls, |
| * just ntoskrnl ones that we understand, to avoid surprises |
| * with added calls? |
| */ |
| /* FIXME: switch to a bit vector? |
| * we may want an inverted bit vector instead (inw2k p.123 - lower 12 bits) |
| * there are 285 syscalls on xp - let's say we support 320 |
| * instead of the 40 ints (160 bytes) and a loop we're using now, |
| * we can grab 40 bytes for 320 syscalls and do the bit extraction |
| * precomputing from this table will be easy |
| */ |
| /* FIXME : it looks like most file IO/creation syscalls are alertable |
| * ref bug 2520, should be added to non-ignorable */ |
| /* FIXME : we just return false for all system calls, to be safe we should |
| * really be checking for known ignoreable system calls rather then the reverse, |
| * see syscallx.h for old enumeration. */ |
| return false; |
| } |
| |
| bool |
| optimizable_system_call(int num) |
| { |
| if (INTERNAL_OPTION(shared_eq_ignore)) |
| return ignorable_system_call(num, NULL, NULL); |
| else { |
| |
| int i; |
| |
| /* FIXME: switch to a bit vector, just as for the syscalls array? */ |
| for (i = 0; i < SYS_MAX; i++) { |
| if (num == syscalls[i]) |
| return !syscall_requires_action[i]; |
| } |
| |
| /* If the syscall isn't in the array, DR doesn't care about it. */ |
| return true; |
| } |
| } |
| |
| /* The trampoline handler called for ntdll syscall wrappers that we |
| * care about, so that we can act on them while native_exec-ing |
| */ |
| after_intercept_action_t |
| syscall_while_native(app_state_at_intercept_t *state) |
| { |
| int sysnum = (int) (ptr_int_t) state->callee_arg; |
| /* FIXME : if dr calls through ntdll functions that are hooked by a third |
| * party (say Sygate's sysfer.dll) then they could perform syscalls that |
| * would get us here. Most of the time we'll be ok, but if the current |
| * thread is under_dyn_hack or native_exec we might try to process the |
| * system call or takeover, neither of which is safe. Currently we avoid |
| * calling through nt wrappers that sysfer.dll hooks (doing system call |
| * internally instead). This also applies if we call our own hooks, which |
| * we avoid in a similar manner. |
| */ |
| /* Returning AFTER_INTERCEPT_LET_GO will perform the syscall natively, |
| * while AFTER_INTERCEPT_LET_GO_ALT_DYN will skip it. Modify the register |
| * arguments to change the returned state, note that the stack will have |
| * to be popped once (modify reg_esp) to match up the returns. |
| */ |
| dcontext_t *dcontext = get_thread_private_dcontext(); |
| IF_X64(ASSERT_TRUNCATE(int, int, (ptr_int_t)state->callee_arg)); |
| /* N.B.: if any intercepted syscalls are used by DR from ntdll, rather |
| * than custom wrappers, then a recursion-avoidance check here would |
| * be required to avoid infinite loop on error here! |
| */ |
| STATS_INC(num_syscall_trampolines); |
| if (dcontext == NULL) { |
| /* unknown thread */ |
| return AFTER_INTERCEPT_LET_GO; /* do syscall natively */ |
| } else if (IS_UNDER_DYN_HACK(dcontext->thread_record->under_dynamo_control) || |
| dcontext->thread_record->retakeover) { |
| /* this trampoline is our ticket to taking control again prior |
| * to the image entry point |
| * we often hit this on NtAllocateVirtualMemory from HeapCreate for |
| * the next dll init after the cb ret where we lost control |
| */ |
| STATS_INC(num_syscall_trampolines_retakeover); |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "syscall_while_native: retakeover in %s after native cb return lost control\n", |
| syscall_names[sysnum]); |
| retakeover_after_native(dcontext->thread_record, INTERCEPT_SYSCALL); |
| dcontext->thread_record->retakeover = false; |
| return AFTER_INTERCEPT_TAKE_OVER; /* syscall under DR */ |
| } else if (!dcontext->thread_record->under_dynamo_control |
| /* xref PR 230836 */ |
| IF_CLIENT_INTERFACE(&& !IS_CLIENT_THREAD(dcontext)) |
| /* i#1318: may get here from privlib at exit, at least until we |
| * redirect *everything*. From privlib we need to keep |
| * the syscall native as DR locks may be held. |
| */ |
| IF_CLIENT_INTERFACE(&& dcontext->whereami == WHERE_APP)) { |
| /* assumption is that any known native thread is one we control in general, |
| * just not right now while in a native_exec_list dll */ |
| STATS_INC(num_syscall_trampolines_native); |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "NATIVE system call %s\n", syscall_names[sysnum]); |
| DOLOG(IF_DGCDIAG_ELSE(1, 2), LOG_SYSCALLS, { |
| dump_callstack(*((byte **)state->mc.xsp) /*retaddr*/, |
| (app_pc) state->mc.xbp, |
| THREAD, DUMP_NOT_XML); |
| }); |
| |
| #ifdef GBOP |
| /* case 7127 - validate GBOP on syscalls that are already hooked for |
| * hotp_only on native_exec |
| */ |
| if (DYNAMO_OPTION(gbop) != GBOP_DISABLED) { |
| /* FIXME: case 7127: should enforce here GBOP_WHEN_NATIVE_EXEC if we |
| * want to apply for -hotp_only but not for native_exec. |
| * Today we always validate. |
| */ |
| |
| /* FIXME: case 7127: for -exclude_gbop_list need to check a flag |
| * whether this ntdll!Nt* hook has been excluded |
| */ |
| /* state->xsp is the wishful thinking after syscall |
| * address, instead of the original one - |
| * intercept_syscall_wrapper() keeps the relevant |
| * FPO information: 4 on XP SP2+, or 0 earlier |
| */ |
| gbop_validate_and_act(state, |
| /* adjust ESP */ |
| syscall_trampoline_gbop_fpo_offset[sysnum], |
| syscall_trampoline_hook_pc[sysnum]); |
| /* if the routine at all returns it passed the GBOP checks */ |
| |
| /* FIXME: case 7127: may want alternative handling |
| * and for system calls returning an error of some kind |
| * like STATUS_INVALID_ADDRESS or STATUS_BUFFER_OVERFLOW |
| * may be a somewhat useful attack handling alternative |
| */ |
| |
| /* FIXME: case 7127 for completeness should be able to add |
| * this check to the regular DR syscalls where we'll be at |
| * the PC calling sysenter, not necessarily at the start |
| * of a function. Though other than uniform testing it |
| * won't serve much else. There we'll have to match the |
| * correct FPO offset at the syscall as well. |
| */ |
| } |
| #endif /* GBOP */ |
| /* Notes on handling syscalls for native threads: |
| * |
| * FIXME: make sure each syscall handler can handle this thread being native, |
| * as well as target being native. E.g., will a native thread terminating |
| * itself hit any assertion about not coming back under DR control first? |
| * Another example, will GetCxt fail trying to translate a native thread's |
| * context? |
| * FIXME: what about asynch event while in syscall? none of ones we |
| * intercept are alertable? |
| * FIXME: exception during pre-syscall sequence can cause us to miss |
| * the go-native trigger! |
| * |
| * Be careful with cache consistency events -- we assume in general that |
| * code executed natively is never mixed with code executed under DR, in |
| * both execution and manipulation, and we try to have _all_ DGC-using |
| * dlls listed in the native_exec_list. We do handle write faults from |
| * cache consistency in native threads, so we'll have correct behavior, |
| * but we don't want a performance hit from in-cache DGC slowing down |
| * from-native DGC b/c they share memory and it keeps bouncing from RO to |
| * RW -- that's a big reason we're going native in the first place! For |
| * handling app memory-changing syscalls, we don't mark new code as |
| * read-only until executed from, so in the common case we should not |
| * incur any cost from cache consistency while native. |
| */ |
| /* Invoke normal DR syscall-handling by calling dispatch() with a |
| * linkstub_t marked just like those for fragments ending in syscalls. |
| * (We cannot return to the trampoline tail for asynch_take_over() since |
| * it will clobber out next_tag and last_exit and will execute the jmp |
| * back to the syscall under DR, requiring a more intrusive way of going |
| * native afterward.) Normal handling may skip the syscall or do |
| * whatever, but we expect it to not change control flow (we don't |
| * intercept those while threads are native) and to come out of the |
| * cache and continue on with the next_tag that we set here, which is a |
| * special stopping point routine of ours that causes DR to go native @ |
| * the pc we store in dcontext->native_exec_postsyscall. |
| */ |
| dcontext->next_tag = BACK_TO_NATIVE_AFTER_SYSCALL; |
| /* start_pc is the take-over pc that will jmp to the syscall instr, while |
| * we need the post-syscall pc, which we stored when generating the trampoline |
| */ |
| ASSERT(syscall_trampoline_skip_pc[sysnum] != NULL); |
| dcontext->native_exec_postsyscall = syscall_trampoline_skip_pc[sysnum]; |
| ASSERT(dcontext->whereami == WHERE_APP); |
| dcontext->whereami = WHERE_TRAMPOLINE; |
| set_last_exit(dcontext, (linkstub_t *) get_native_exec_syscall_linkstub()); |
| /* assumption: no special cleanup from tail of trampoline needed */ |
| transfer_to_dispatch(dcontext, &state->mc, false/*!full_DR_state*/); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* This routine tries to handle syscalls from DR, but will fail in some |
| * cases (if the current thread has certain under_dynamo_control values) -- |
| * so we use our own custom wrapper rather than go through ntdll when we |
| * expect going through wrapper to reach here (FIXME should do this for |
| * all system calls). */ |
| /* i#924: this happens at exit during os_loader_exit(), and at thread init |
| * when priv libs call routines we haven't yet redirected. Best to disable |
| * the syslog for clients (we still have the log warning). |
| */ |
| #ifndef CLIENT_INTERFACE |
| DODEBUG({ |
| /* Unfortunately we use various ntdll routines (most notably Ldr*) |
| * that may be hooked (hook code could do anything including making |
| * system calls). Also some the of the ntdll Rtl routines we |
| * import may be similarly ill behaved (though we don't believe any |
| * of the currently used ones are problematic). Also calling |
| * through Sygate hooks may reach here. |
| */ |
| SYSLOG_INTERNAL_WARNING_ONCE("syscall_while_native: using %s - maybe hooked?", |
| syscall_names[sysnum]); |
| }); |
| #endif |
| STATS_INC(num_syscall_trampolines_DR); |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "WARNING: syscall_while_native: syscall from DR %s\n", |
| syscall_names[sysnum]); |
| return AFTER_INTERCEPT_LET_GO; /* do syscall natively */ |
| } |
| |
| |
| static inline bool |
| intercept_syscall_for_thin_client(int SYSnum) |
| { |
| if (SYSnum == SYS_CreateThread || |
| SYSnum == SYS_CreateProcess || |
| SYSnum == SYS_CreateProcessEx || |
| SYSnum == SYS_CreateUserProcess || |
| SYSnum == SYS_TerminateThread || /* Case 9079. */ |
| SYSnum == SYS_ResumeThread || /* i#1198: for env var propagation */ |
| /* case 8866: for -early_inject we must intercept NtMapViewOfSection */ |
| (DYNAMO_OPTION(early_inject) && SYSnum == SYS_MapViewOfSection)) { |
| return true; |
| } |
| return false; |
| } |
| |
| static inline bool |
| intercept_native_syscall(int SYSnum) |
| { |
| ASSERT(SYSnum < TRAMPOLINE_MAX); |
| #ifdef CLIENT_INTERFACE |
| if ((uint)SYSnum >= SYS_MAX + syscall_extra_idx) |
| return false; |
| #endif |
| /* Don't hook all syscalls for thin_client. */ |
| if (DYNAMO_OPTION(thin_client) && !intercept_syscall_for_thin_client(SYSnum)) |
| return false; |
| |
| if (!syscall_requires_action[SYSnum] || syscalls[SYSnum] == SYSCALL_NOT_PRESENT) |
| return false; |
| /* ignore control transfer system calls: |
| * 1) NtCallbackReturn (assume the corresponding cb was native as well, |
| * else we have big problems! we could detect |
| * by stacking up info on native cbs, if nobody ever |
| * did an int 2b natively...not worth it for now) |
| * 2) NtContinue |
| * 3) NtCreateThread |
| * Ref case 5295 - Sygate hooks this nt wrapper differently then the |
| * others (@ 2nd instruction). We only need to hook CreateThread |
| * system call for follow children from native exec threads anyways, so |
| * is easiest to just skip this one and live without that ability. |
| * 4) NtWriteVirtualMemory: |
| * Case 9156/9103: we don't hook it to avoid removing |
| * our own GBOP hook, until we actually implement acting on it (case 8321) |
| * |
| * We do NOT ignore SetContextThread or suspension/resumption, since |
| * the target could be in DR! |
| */ |
| if (SYSnum == SYS_CallbackReturn || |
| SYSnum == SYS_Continue || |
| (!DYNAMO_OPTION(native_exec_hook_create_thread) && |
| SYSnum == SYS_CreateThread) || |
| SYSnum == SYS_WriteVirtualMemory) |
| return false; |
| return true; |
| } |
| |
| void |
| init_syscall_trampolines(void) |
| { |
| int i; |
| HMODULE h = (HMODULE)get_ntdll_base(); |
| ASSERT(DYNAMO_OPTION(native_exec_syscalls)); |
| for (i = 0; i < TRAMPOLINE_MAX; i++) { |
| if (intercept_native_syscall(i)) { |
| byte *fpo_adjustment = NULL; |
| #ifdef GBOP |
| fpo_adjustment = &syscall_trampoline_gbop_fpo_offset[i]; |
| #endif |
| |
| syscall_trampoline_hook_pc[i] = (app_pc)get_proc_address(h, syscall_names[i]); |
| syscall_trampoline_pc[i] = |
| /* FIXME: would like to use static references to entry points -- yet, |
| * set of those we care about varies dynamically by platform, and |
| * we cannot include a pointer to a 2003-only Nt* entry point and |
| * avoid a loader link error on 2000, right? |
| * For now just using get_proc_address! |
| */ |
| intercept_syscall_wrapper(&syscall_trampoline_hook_pc[i], |
| syscall_while_native, |
| (void *) (ptr_int_t) i /* callee arg */, |
| AFTER_INTERCEPT_DYNAMIC_DECISION, |
| /* must store the skip_pc for the new dispatch() |
| * to know where to go after handling from DR -- |
| * this is simpler than having trampoline |
| * pass it in as an arg to syscall_while_native |
| * or trying to decode it. |
| */ |
| &syscall_trampoline_skip_pc[i], |
| /* Returns a pointer to a copy of the original |
| * first 5 bytes for removing the trampoline |
| * later. Excepting hook chaining situations |
| * this could just simply be the same as the |
| * returned syscall_trampoline_pc. */ |
| &syscall_trampoline_copy_pc[i], |
| fpo_adjustment, syscall_names[i]); |
| } |
| } |
| } |
| |
| void |
| exit_syscall_trampolines(void) |
| { |
| int i; |
| ASSERT(DYNAMO_OPTION(native_exec_syscalls)); |
| for (i = 0; i < TRAMPOLINE_MAX; i++) { |
| if (intercept_native_syscall(i)) { |
| if (syscall_trampoline_pc[i] != NULL) { |
| ASSERT(syscall_trampoline_copy_pc[i] != NULL && |
| syscall_trampoline_hook_pc[i] != NULL); |
| remove_trampoline(syscall_trampoline_copy_pc[i], |
| syscall_trampoline_hook_pc[i]); |
| } else { |
| ASSERT(DYNAMO_OPTION(native_exec_hook_conflict) == |
| HOOKED_TRAMPOLINE_NO_HOOK); |
| } |
| } |
| DEBUG_DECLARE(else ASSERT(syscall_trampoline_pc[i] == NULL)); |
| } |
| } |
| |
| #ifdef DEBUG |
| void |
| check_syscall_array_sizes() |
| { |
| ASSERT(sizeof(windows_81_x64_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_81_wow64_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_81_x86_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_8_x64_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_8_wow64_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_8_x86_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_7_x64_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_7_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_vista_sp1_x64_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_vista_sp1_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_vista_sp0_x64_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_vista_sp0_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_2003_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_XP_x64_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_XP_wow64_index) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_2003_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_XP_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_NT_sp4_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_NT_sp3_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_NT_sp0_syscalls) == sizeof(windows_2000_syscalls)); |
| ASSERT(sizeof(windows_2000_syscalls)/sizeof(windows_2000_syscalls[0]) == |
| sizeof(syscall_requires_action)/sizeof(syscall_requires_action[0])); |
| ASSERT(sizeof(windows_2000_syscalls)/sizeof(windows_2000_syscalls[0]) == |
| sizeof(syscall_names)/sizeof(syscall_names[0])); |
| } |
| |
| /* verify that syscall numbers match our static lists in an attempt to catch |
| * changes to syscall interface across Windows patches and service packs |
| */ |
| void |
| check_syscall_numbers(dcontext_t *dcontext) |
| { |
| int i; |
| int sysnum; |
| byte *addr; |
| module_handle_t h = get_ntdll_base(); |
| ASSERT(h != NULL && h != INVALID_HANDLE_VALUE); |
| LOG(GLOBAL, LOG_SYSCALLS, 4, "check_syscall_numbers: ntdll @ "PFX"\n", h); |
| for (i = 0; i < SYS_MAX; i++) { |
| if (syscalls[i] == SYSCALL_NOT_PRESENT) |
| continue; |
| addr = (byte *)get_proc_address(h, syscall_names[i]); |
| ASSERT(addr != NULL); |
| LOG(GLOBAL, LOG_SYSCALLS, 4, |
| "\tsyscall 0x%x %s: addr "PFX"\n", i, syscall_names[i], addr); |
| sysnum = decode_syscall_num(dcontext, addr); |
| /* because of Sygate hooks can't assert sysnum is valid here */ |
| if (sysnum >= 0 && sysnum != syscalls[i]) { |
| SYSLOG_INTERNAL_ERROR("syscall %s is really 0x%x not 0x%x\n", |
| syscall_names[i], sysnum, syscalls[i]); |
| syscalls[i] = sysnum; |
| /* of course is much too late to fix if we already used via |
| * NT_SYSCALL */ |
| } |
| } |
| } |
| #endif |
| |
| /* adjust region to page boundaries, since Windows lets you pass |
| * non-aligned values, unlike Linux |
| * e.g. a two byte cross-page request will result in a two page region |
| */ |
| static inline void |
| align_page_boundary(dcontext_t *dcontext, |
| app_pc *base /* IN OUT */, size_t *size/* IN OUT */) |
| { |
| if (!ALIGNED(*base, PAGE_SIZE) || !ALIGNED(*size, PAGE_SIZE)) { |
| /* need to cover all pages overlapping the region [base, base + size) */ |
| *size = ALIGN_FORWARD(*base+*size, PAGE_SIZE) - PAGE_START(*base); |
| *base = (app_pc) PAGE_START(*base); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\talign_page_boundary => base="PFX" size="PIFX"\n", *base, *size); |
| } |
| } |
| |
| /* verifies whether target process is being created, presumably as a |
| * child of the current process |
| */ |
| bool |
| is_newly_created_process(HANDLE process_handle) |
| { |
| uint remote_ldr_data; |
| /* We check based on - trait 3) PEB.Ldr |
| * The Ldr entry is created by the running process itself later */ |
| |
| /* ATTIC - rejected traits |
| * trait 1) it doesn't have any threads created |
| * Seems overly expensive to have no easy alternative to |
| * NtQuerySystemInformation to tell there are no threads created |
| * in the process, should use to verify new process since that |
| * should be the rare case |
| * FIXME: could at least store the last created pid and a flag indicating if |
| * its thread has been created and use that as an auxiliary check |
| * |
| * May be easier to check the PEB |
| * trait 2) PEB.ProcessParameters |
| * The process parameters are available only after they have been created, |
| * (in fact a good trait that a process without them has just been created, |
| * yet they are created at the time the first thread's stack is needed. |
| * |
| * NOTE - in Vista traits 1 and 2 are no longer valid for this |
| * purpose. NtCreateUserProcess creates the first thread and sets up |
| * the process parameters in addition to creating the process. However this |
| * is only used for aslr_stack so doesn't really matter that much. Trait 3 |
| * (the one we use) should still work anyways (and cover anyone using the legacy |
| * native interface NtCreateProcess to create the process). |
| */ |
| |
| DODEBUG({ |
| /* dead end approach, this code can be removed*/ |
| |
| /* invalid trait 4: shouldn't have many handles open |
| * Attempted using NtQueryInformationProcess |
| * ProcessHandleCount which is usually 1 on XP at the time |
| * a new process is created, if it holds on all platforms |
| * |
| * Note unfortunately this cannot be counted on, since handles may |
| * be inherited - and processes created by cygwin do inherit a lot |
| * of handles. |
| */ |
| ulong remote_process_handle_count; |
| NTSTATUS res = get_process_handle_count(process_handle, |
| &remote_process_handle_count); |
| if (NT_SUCCESS(res)) { |
| LOG(GLOBAL, LOG_ALL, 2, |
| "is_newly_created_process: process "PIDFMT" has %d handles -> %s\n", |
| process_id_from_handle(process_handle), |
| remote_process_handle_count, |
| remote_process_handle_count == 1 ? "NEW" : "maybe new"); |
| } |
| }); |
| |
| remote_ldr_data = get_remote_process_ldr_status(process_handle); |
| if (remote_ldr_data >= 0) { |
| LOG(GLOBAL, LOG_ALL, 1, |
| "is_newly_created_process: process "PIDFMT" PEB->Ldr = %s\n", |
| process_id_from_handle(process_handle), |
| remote_ldr_data != 0 ? "initialized" : "NULL -> new process"); |
| |
| return (remote_ldr_data == 0); /* new process */ |
| } else { |
| /* xref case 9800 - can happen if the app handle lacks the rights we |
| * need (in which case isn't a new process since the handle used then has |
| * full rights). Get handle rights in local since won't be available in an |
| * ldmp. */ |
| DEBUG_DECLARE(ACCESS_MASK rights = nt_get_handle_access_rights(process_handle);) |
| ASSERT_CURIOSITY(get_os_version() >= WINDOWS_VERSION_VISTA && |
| "xref case 9800, is_newly_created_process failure"); |
| } |
| |
| return false; |
| } |
| |
| /* Rather than split up get_syscall_method() we have routines like these |
| * to query variations |
| */ |
| bool |
| syscall_uses_wow64_index() |
| { |
| ASSERT(get_syscall_method() == SYSCALL_METHOD_WOW64); |
| return (get_os_version() < WINDOWS_VERSION_8); |
| } |
| |
| bool |
| syscall_uses_edx_param_base() |
| { |
| return (get_syscall_method() != SYSCALL_METHOD_WOW64 || |
| get_os_version() < WINDOWS_VERSION_8); |
| } |
| |
| /* FIXME : For int/syscall we can just subtract 2 from the post syscall pc but for |
| * sysenter we do the post-syscall ret native and therefore we've lost the |
| * address of the actual syscall, but we are only going to use this for |
| * certain ntdll system calls so is almost certainly the ntdll sysenter. As |
| * a hack for now we just use the address of the first system call we saw |
| * (which should be ntdll's), this is good enough for detach and prob. good |
| * enough for app GetThreadContext (we could just use 0x7ffe0302 but it moved |
| * on xp sp2) */ |
| #define SYSCALL_PC(dc) \ |
| ((get_syscall_method() == SYSCALL_METHOD_INT || \ |
| get_syscall_method() == SYSCALL_METHOD_SYSCALL) ? \ |
| (ASSERT(SYSCALL_LENGTH == INT_LENGTH), \ |
| POST_SYSCALL_PC(dc) - INT_LENGTH) : \ |
| (get_syscall_method() == SYSCALL_METHOD_WOW64 ? \ |
| (POST_SYSCALL_PC(dc) - CTI_FAR_ABS_LENGTH) : \ |
| get_app_sysenter_addr())) |
| |
| /* since always coming from dispatch now, only need to set mcontext */ |
| #define SET_RETURN_VAL(dc, val) \ |
| get_mcontext(dc)->xax = (reg_t) (val) |
| |
| /*************************************************************************** |
| * PRE SYSTEM CALL |
| * |
| * FIXME: should we pass mcontext to these routines to avoid |
| * the get_mcontext() call and derefs? |
| * => now we're forcing the inline of get_mcontext() so should be fine |
| */ |
| |
| static reg_t * |
| pre_system_call_param_base(priv_mcontext_t *mc) |
| { |
| #ifdef X64 |
| reg_t *param_base = (reg_t *) mc->xsp; |
| #else |
| /* On Win8, wow64 syscalls do not point edx at the params and |
| * instead simply use esp. |
| */ |
| reg_t *param_base = (reg_t *) |
| (syscall_uses_edx_param_base() ? mc->xdx : mc->xsp); |
| #endif |
| param_base += (SYSCALL_PARAM_OFFSET() / sizeof(reg_t)); |
| return param_base; |
| } |
| |
| /* NtCreateProcess, NtCreateProcessEx */ |
| static void |
| presys_CreateProcess(dcontext_t *dcontext, reg_t *param_base, bool ex) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE *process_handle = (HANDLE *) sys_param(dcontext, param_base, 0); |
| uint access_mask = (uint) sys_param(dcontext, param_base, 1); |
| uint attributes = (uint) sys_param(dcontext, param_base, 2); |
| uint inherit_from_process = (uint) sys_param(dcontext, param_base, 3); |
| BOOLEAN inherit_handles_only = (BOOLEAN) sys_param(dcontext, param_base, 4); |
| HANDLE section_handle = (HANDLE) sys_param(dcontext, param_base, 5); |
| HANDLE debug_handle = (HANDLE) sys_param(dcontext, param_base, 6); |
| HANDLE exception_handle = (HANDLE) sys_param(dcontext, param_base, 7); |
| |
| if (ex) { |
| /* according to metasploit, others type as HANDLE unknown etc. */ |
| uint job_member_level = (uint) sys_param(dcontext, param_base, 8); |
| } |
| |
| /* Case 9173: guard against pid reuse. Better in post after success |
| * check but not a big deal. |
| * We don't do this on CreateThread b/c is_newly_created_process() is still |
| * true after the first thread (one fix is to store the last created pid and |
| * a flag indicating if its thread has been created and use that as an auxiliary |
| * check in is_newly_created_process()) |
| */ |
| dcontext->aslr_context.last_child_padded = 0; |
| |
| DOLOG(1, LOG_SYSCALLS, { |
| app_pc base = (app_pc) get_section_address(section_handle); |
| /* we will inject in post_syscall or when the first thread is about |
| * to be created */ |
| LOG(THREAD, LOG_SYSCALLS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtCreateProcess section @"PFX"\n", base); |
| DOLOG(1, LOG_SYSCALLS, { |
| char buf[MAXIMUM_PATH]; |
| get_module_name(base, buf, sizeof(buf)); |
| if (buf[0] != '\0') |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "\tNtCreateProcess for module %s\n", buf); |
| }); |
| }); |
| } |
| |
| #ifdef DEBUG |
| /* NtCreateUserProcess */ |
| static void |
| presys_CreateUserProcess(dcontext_t *dcontext, reg_t *param_base) |
| { |
| /* New in Vista, here's what I got reverse engineering |
| * NtCreateUserProcess (11 args, using windows types) |
| * |
| * NtCreateUserProcess ( |
| * OUT PHANDLE ProcessHandle, |
| * OUT PHANDLE ThreadHandle, |
| * IN ACCESS_MASK ProcDesiredAccess, |
| * IN ACCESS_MASK ThreadDesiredAccess, |
| * IN POBJECT_ATTRIBUTES ProcObjectAttributes, |
| * IN POBJECT_ATTRIBUTES ThreadObjectAttributes, |
| * IN uint? unknown, [ observed 0x4 ] |
| * IN BOOL CreateSuspended, [ refers to the thread not the process ] |
| * IN PRTL_USER_PROCESS_PARAMETERS Params, |
| * INOUT proc_stuff proc, |
| * INOUT create_proc_thread_info_t *thread [ see ntdll.h ]) |
| * CreateProcess hardcodes 0x2000000 (== MAXIMUM_ALLOWED) for both |
| * ACCESS_MASK arguments. I've only observed NULL (== default) for the |
| * OBJECT_ATTRIBUTES arguments so they are a bit of a guess, but they |
| * need to be here somewhere and based on error codes I know they are |
| * ptr arguments so seems quite likely esp. given the arg layout. |
| * |
| * where proc_stuff { \\ speculative - the 64bit differences are odd and imply |
| * \\ more then just size changes |
| * size_t struct_size, [observed 0x48 (0x58 for 64bit)] \\ prob. sizeof(proc_stuff) |
| * ptr_uint_t unknown_p2, \\ OUT |
| * ptr_uint_t unknown_p3, \\ IN/OUT |
| * OUT HANDLE file_handle, [exe file handle] |
| * OUT HANDLE section_handle, [exe section handle] |
| * uint32 unknown_p6, \\ OUT |
| * uint32 unknown_p7, \\ OUT |
| * uint32 unknown_p8, \\ OUT |
| * uint32 unknown_p9, \\ OUT |
| * #ifndef X64 |
| * uint32 unknown_p10, \\ OUT |
| * #endif |
| * OUT PEB *new_proc_peb, |
| * uint32 unknown_p12_p17[6], \\ OUT |
| * #ifndef X64 |
| * uint32 unknown_p18, \\ OUT |
| * #endif |
| * } |
| */ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| ACCESS_MASK proc_access_mask = (uint) sys_param(dcontext, param_base, 2); |
| ACCESS_MASK thread_access_mask = (uint) sys_param(dcontext, param_base, 3); |
| /* might be BOOLEAN instead? though separate param should zero out rest */ |
| BOOL create_suspended = (BOOL) sys_param(dcontext, param_base, 7); |
| create_proc_thread_info_t *thread_stuff = (void *) sys_param(dcontext, param_base, 10); |
| ASSERT(get_os_version() >= WINDOWS_VERSION_VISTA); |
| |
| /* might need these in post, note CreateProcess appears to hardcode them */ |
| ASSERT_CURIOSITY(proc_access_mask == MAXIMUM_ALLOWED); |
| ASSERT_CURIOSITY(thread_access_mask == MAXIMUM_ALLOWED); |
| ASSERT_CURIOSITY(create_suspended); |
| /* FIXME - NYI - if any of the above curiosities don't hold we should |
| * change them here and then fixup as needed in post. */ |
| |
| /* Potentially dangerous deref of app ptr, but is only for debug logging */ |
| ASSERT(thread_stuff != NULL && thread_stuff->nt_path_to_exe.buffer != NULL); |
| LOG(THREAD, LOG_SYSCALLS, 1, "syscall: NtCreateUserProcess presys %.*S\n", |
| MIN(MAXIMUM_PATH, thread_stuff->nt_path_to_exe.buffer_size), |
| (wchar_t *)thread_stuff->nt_path_to_exe.buffer); |
| |
| /* The thread can be resumed inside the kernel so ideally we would |
| * insert the DR env vars into the pp param here (i#349). |
| * However, no matter what I do, the syscall returns STATUS_INVALID_PARAMETER. |
| * I made a complete copy of pp and updated the unicode pointers so it's |
| * all contiguous, but still the error. Perhaps it must be on the app heap? |
| * In any case, kernel32!CreateProcess is hardcoding that the thread be |
| * suspended (presumably to do its csrss and other inits safely) so we rely |
| * on seeing NtResumeThread. |
| */ |
| } |
| #endif |
| |
| /* NtCreateThread */ |
| static void |
| presys_CreateThread(dcontext_t *dcontext, reg_t *param_base) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE *thread_handle= (HANDLE *) sys_param(dcontext, param_base, 0); |
| uint access_mask = (uint) sys_param(dcontext, param_base, 1); |
| uint attributes = (uint) sys_param(dcontext, param_base, 2); |
| HANDLE process_handle= (HANDLE) sys_param(dcontext, param_base, 3); |
| uint *client_id = (uint*) sys_param(dcontext, param_base, 4); |
| CONTEXT *cxt = (CONTEXT *) sys_param(dcontext, param_base, 5); |
| USER_STACK *stack = (USER_STACK *) sys_param(dcontext, param_base, 6); |
| BOOLEAN suspended = (BOOLEAN) sys_param(dcontext, param_base, 7); |
| DEBUG_DECLARE(process_id_t pid = process_id_from_handle(process_handle);) |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtCreateThread pid="PFX" suspended=%d\n", |
| pid, suspended); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 2, |
| "\tstack: "PFX" "PFX" "PFX" "PFX" "PFX"\n", |
| stack->FixedStackBase, stack->FixedStackLimit, |
| stack->ExpandableStackBase, stack->ExpandableStackLimit, |
| stack->ExpandableStackBottom); |
| /* According to Nebbett, in eax is the win32 start address |
| * (stored in ThreadQuerySetWin32StartAddress slot, though that |
| * is reused by the os, so might not be the same later) and eax is used |
| * by the thread start kernel32 thunk. It also appears from the thunk |
| * that the argument to the thread start function is in ebx */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 2, |
| "\tesp="PFX", xip="PFX"\n\tstart address "PFX" with arg "PFX"\n", |
| cxt->CXT_XSP, cxt->CXT_XIP, cxt->CXT_XAX, cxt->CXT_XBX); |
| DOLOG(2, LOG_SYSCALLS|LOG_THREADS, { |
| char buf[MAXIMUM_PATH]; |
| print_symbolic_address((app_pc)cxt->CXT_XAX, buf, sizeof(buf), false); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 2, |
| "\tsymbol info for start address : %s\n", buf); |
| }); |
| ASSERT(cxt != NULL); |
| /* if not early injecting, we will unsafely modify cxt (for late follow |
| * children) FIXME |
| * if not injecting at all we won't change cxt. |
| */ |
| maybe_inject_into_process(dcontext, process_handle, cxt); |
| |
| if (is_phandle_me(process_handle)) |
| pre_second_thread(); |
| } |
| |
| /* NtCreateThreadEx */ |
| static void |
| presys_CreateThreadEx(dcontext_t *dcontext, reg_t *param_base) |
| { |
| /* New in Vista, here's what I got reverse engineering NtCreateThreadEx |
| * (11 args, using windows types) |
| * |
| * NtCreateThreadEx ( |
| * OUT PHANDLE ThreadHandle, |
| * IN ACCESS_MASK DesiredAccess, |
| * IN POBJECT_ATTRIBUTES ObjectAttributes, |
| * IN HANDLE ProcessHandle, |
| * IN LPTHREAD_START_ROUTINE Win32StartAddress, |
| * IN LPVOID StartParameter, |
| * IN BOOL CreateSuspended, |
| * IN uint unknown, [ CreateThread hardcodes to 0 ] |
| * IN SIZE_T StackCommitSize, |
| * IN SIZE_T StackReserveSize, |
| * INOUT create_thread_info_t *thread_info [ see ntdll.h ]) |
| */ |
| DEBUG_DECLARE(priv_mcontext_t *mc = get_mcontext(dcontext);) |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 3); |
| DEBUG_DECLARE(byte *start_addr = (byte *) sys_param(dcontext, param_base, 4);) |
| DEBUG_DECLARE(void *start_parameter = (void *) sys_param(dcontext, param_base, 5);) |
| DEBUG_DECLARE(bool create_suspended = (bool) sys_param(dcontext, param_base, 6);) |
| DEBUG_DECLARE(process_id_t pid = process_id_from_handle(process_handle);) |
| ASSERT(get_os_version() >= WINDOWS_VERSION_VISTA); |
| |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 2, |
| "syscall: NtCreateThread pid="PFX" suspended=%d\n" |
| "\tstart_addr="PFX" arg="PFX"\n", |
| pid, create_suspended, start_addr, start_parameter); |
| DOLOG(2, LOG_SYSCALLS|LOG_THREADS, { |
| char buf[MAXIMUM_PATH]; |
| print_symbolic_address(start_addr, buf, sizeof(buf), false); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 2, |
| "\tsymbol info for start address : %s\n", buf); |
| }); |
| |
| if (is_phandle_me(process_handle)) |
| pre_second_thread(); |
| } |
| |
| /* NtCreateWorkerFactory */ |
| static void |
| presys_CreateWorkerFactory(dcontext_t *dcontext, reg_t *param_base) |
| { |
| /* New in Vista. 10 args: |
| * NtCreateWorkerFactory( |
| * __out PHANDLE FactoryHandle, |
| * __in ACCESS_MASK DesiredAccess, |
| * __in_opt POBJECT_ATTRIBUTES ObjectAttributes, |
| * __in HANDLE CompletionPortHandle, |
| * __in HANDLE ProcessHandle, |
| * __in PVOID StartRoutine, |
| * __in_opt PVOID StartParameter, |
| * __in_opt ULONG MaxThreadCount, |
| * __in_opt SIZE_T StackReserve, |
| * __in_opt SIZE_T StackCommit) |
| */ |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 4); |
| ASSERT(get_os_version() >= WINDOWS_VERSION_VISTA); |
| |
| if (is_phandle_me(process_handle)) |
| pre_second_thread(); |
| } |
| |
| /*************************************************************************** |
| * ENV VAR PROPAGATION |
| */ |
| |
| /* There is some overlap w/ handle_execve() in unix/os.c but not |
| * quite enough to easily share this. |
| */ |
| static const char * const env_to_propagate[] = { |
| DYNAMORIO_VAR_RUNUNDER, |
| DYNAMORIO_VAR_OPTIONS, |
| DYNAMORIO_VAR_AUTOINJECT, |
| DYNAMORIO_VAR_LOGDIR, |
| DYNAMORIO_VAR_CONFIGDIR, |
| }; |
| static const wchar_t * const wenv_to_propagate[] = { |
| L_DYNAMORIO_VAR_RUNUNDER, |
| L_DYNAMORIO_VAR_OPTIONS, |
| L_DYNAMORIO_VAR_AUTOINJECT, |
| L_DYNAMORIO_VAR_LOGDIR, |
| L_DYNAMORIO_VAR_CONFIGDIR, |
| }; |
| #define NUM_ENV_TO_PROPAGATE (sizeof(env_to_propagate)/sizeof(env_to_propagate[0])) |
| |
| /* read env var from remote process: |
| * - return true on read successfully or until end of reading |
| * - skip DR env vars |
| */ |
| static wchar_t * |
| get_process_env_var(HANDLE phandle, wchar_t *env_ptr, wchar_t *buf, size_t toread) |
| { |
| int i; |
| size_t got; |
| bool keep_env; |
| while (true) { |
| keep_env = true; |
| ASSERT(toread <= (size_t)PAGE_SIZE); |
| /* if an env var is too long we're ok: DR vars will fit, and if longer we'll |
| * handle rest next call. |
| */ |
| if (!nt_read_virtual_memory(phandle, env_ptr, buf, toread, &got)) { |
| /* may have crossed page boundary and the next page is inaccessible */ |
| byte *start = (byte *) env_ptr; |
| if (PAGE_START(start) != PAGE_START(start + toread)) { |
| ASSERT((size_t)((byte *)ALIGN_FORWARD(start, PAGE_SIZE)-start) <= toread); |
| toread = (byte *) ALIGN_FORWARD(start, PAGE_SIZE) - start; |
| if (!nt_read_virtual_memory(phandle, env_ptr, buf, toread, &got)) |
| return NULL; |
| } else |
| return NULL; |
| continue; |
| } |
| buf[got/sizeof(buf[0]) - 1] = '\0'; |
| if (buf[0] == '\0') |
| return env_ptr; |
| for (i = 0; i < NUM_ENV_TO_PROPAGATE; i++) { |
| /* if conflict between env and cfg, we use cfg */ |
| if (wcsncmp(wenv_to_propagate[i], buf, wcslen(wenv_to_propagate[i])) == 0) { |
| keep_env = false; |
| } |
| } |
| if (keep_env) |
| return env_ptr; |
| env_ptr += wcslen(buf) + 1; |
| } |
| return false; |
| } |
| |
| /* called at presys-ResumeThread to append DR env vars in the target process PEB */ |
| static bool |
| add_dr_env_vars(dcontext_t *dcontext, HANDLE phandle, wchar_t **env_ptr) |
| { |
| wchar_t *env, *cur; |
| size_t tot_sz = 0, app_sz, sz; |
| size_t got; |
| wchar_t *new_env = NULL; |
| wchar_t buf[MAX_OPTIONS_STRING]; |
| bool need_var[NUM_ENV_TO_PROPAGATE]; |
| size_t sz_var[NUM_ENV_TO_PROPAGATE]; |
| NTSTATUS res; |
| uint old_prot = PAGE_NOACCESS; |
| int i, num_propagate = 0; |
| |
| for (i = 0; i < NUM_ENV_TO_PROPAGATE; i++) { |
| if (get_config_val(env_to_propagate[i]) == NULL) |
| need_var[i] = false; |
| else { |
| need_var[i] = true; |
| num_propagate++; |
| } |
| } |
| if (num_propagate == 0) { |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "%s: no DR env vars to propagate\n", __FUNCTION__); |
| return true; /* nothing to do */ |
| } |
| |
| ASSERT(env_ptr != NULL); |
| if (!nt_read_virtual_memory(phandle, env_ptr, &env, sizeof(env), NULL)) |
| goto add_dr_env_failure; |
| if (env != NULL) { |
| /* compute size of current env block, and check for existing DR vars */ |
| cur = env; |
| while (true) { |
| /* for simplicity we do a syscall for each var */ |
| cur = get_process_env_var(phandle, cur, buf, sizeof(buf)); |
| if (cur == NULL) |
| return false; |
| if (buf[0] == '\0') |
| break; |
| tot_sz += wcslen(buf) + 1; |
| cur += wcslen(buf) + 1; |
| } |
| tot_sz++; /* final 0 marking end */ |
| /* from here on out, all *sz vars are total bytes, not wchar_t elements */ |
| tot_sz *= sizeof(*env); |
| } |
| app_sz = tot_sz; |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "%s: orig app env vars at "PFX"-"PFX"\n", |
| __FUNCTION__, env, env + app_sz/sizeof(*env)); |
| |
| /* calculate size needed for adding DR env vars. |
| * for each var, we truncate if too big for buf. |
| */ |
| for (i = 0; i < NUM_ENV_TO_PROPAGATE; i++) { |
| if (need_var[i]) { |
| sz_var[i] = wcslen(wenv_to_propagate[i]) + |
| strlen(get_config_val(env_to_propagate[i])) + 2/*=+0*/; |
| if (sz_var[i] > BUFFER_SIZE_ELEMENTS(buf)) { |
| SYSLOG_INTERNAL(SYSLOG_WARNING, "truncating DR env var for child"); |
| sz_var[i] = BUFFER_SIZE_ELEMENTS(buf); |
| } |
| sz_var[i] *= sizeof(*env); |
| tot_sz += sz_var[i]; |
| } |
| } |
| /* allocate a new env block and copy over the old */ |
| res = nt_remote_allocate_virtual_memory(phandle, &new_env, tot_sz, |
| PAGE_READWRITE, MEM_COMMIT); |
| if (!NT_SUCCESS(res)) { |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "%s: failed to allocate new env "PIFX"\n", __FUNCTION__, res); |
| goto add_dr_env_failure; |
| } |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "%s: new app env vars allocated at "PFX"-"PFX"\n", |
| __FUNCTION__, new_env, new_env + tot_sz/sizeof(*env)); |
| cur = env; |
| sz = 0; |
| while (true) { |
| /* for simplicity we do a syscall for each var */ |
| size_t towrite = 0; |
| cur = get_process_env_var(phandle, cur, buf, sizeof(buf)); |
| if (cur == NULL) |
| goto add_dr_env_failure; |
| if (buf[0] == '\0') |
| break; |
| towrite = (wcslen(buf) + 1); |
| res = nt_raw_write_virtual_memory(phandle, new_env + sz/sizeof(*env), |
| buf, towrite * sizeof(*env), &got); |
| if (!NT_SUCCESS(res)) { |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "%s copy: got status "PFX", wrote "PIFX" vs requested "PIFX"\n", |
| __FUNCTION__, res, got, towrite); |
| goto add_dr_env_failure; |
| } |
| sz += towrite * sizeof(*env); |
| cur += towrite; |
| } |
| ASSERT(sz == app_sz - sizeof(*env) /* before final 0 */ ); |
| |
| /* add DR env vars at the end. |
| * XXX: is alphabetical sorting relied upon? adding to end is working. |
| */ |
| for (i = 0; i < NUM_ENV_TO_PROPAGATE; i++) { |
| if (need_var[i]) { |
| _snwprintf(buf, BUFFER_SIZE_ELEMENTS(buf), L"%s=%S", |
| wenv_to_propagate[i], get_config_val(env_to_propagate[i])); |
| NULL_TERMINATE_BUFFER(buf); |
| if (!nt_write_virtual_memory(phandle, new_env + sz/sizeof(*env), |
| buf, sz_var[i], &got)) |
| goto add_dr_env_failure; |
| sz += sz_var[i]; |
| } |
| } |
| ASSERT(sz == tot_sz - sizeof(*env) /* before final 0 */ ); |
| /* write final 0 */ |
| buf[0] = 0; |
| if (!nt_write_virtual_memory(phandle, new_env + sz/sizeof(*env), buf, |
| sizeof(*env), &got)) |
| goto add_dr_env_failure; |
| |
| /* install new env */ |
| if (!nt_remote_protect_virtual_memory(phandle, (byte*)PAGE_START(env_ptr), PAGE_SIZE, |
| PAGE_READWRITE, &old_prot)) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "%s: failed to mark "PFX" writable\n", __FUNCTION__, env_ptr); |
| goto add_dr_env_failure; |
| } |
| if (!nt_write_virtual_memory(phandle, env_ptr, &new_env, sizeof(new_env), &got)) |
| goto add_dr_env_failure; |
| if (!nt_remote_protect_virtual_memory(phandle, (byte*)PAGE_START(env_ptr), PAGE_SIZE, |
| old_prot, &old_prot)) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "%s: failed to restore "PFX" to "PIFX"\n", __FUNCTION__, env_ptr, old_prot); |
| /* not a fatal error */ |
| } |
| /* XXX: free the original? on Vista+ it's part of the pp alloc and |
| * is on the app heap so we can't. we could query and see if it's |
| * a separate alloc. for now we just leave it be. |
| */ |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "%s: installed new env "PFX" at "PFX"\n", __FUNCTION__, new_env, env_ptr); |
| return true; |
| |
| add_dr_env_failure: |
| if (new_env != NULL) { |
| if (!NT_SUCCESS(nt_remote_free_virtual_memory(phandle, new_env))) { |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "%s: unable to free new env "PFX"\n", __FUNCTION__, new_env); |
| } |
| if (old_prot != PAGE_NOACCESS) { |
| if (!nt_remote_protect_virtual_memory(phandle, (byte*)PAGE_START(env_ptr), |
| PAGE_SIZE, old_prot, &old_prot)) { |
| LOG(THREAD, LOG_SYSCALLS, 1, "%s: failed to restore "PFX" to "PIFX"\n", |
| __FUNCTION__, env_ptr, old_prot); |
| } |
| } |
| } |
| return false; |
| } |
| |
| /* If unable to find info, return false (i.e., assume it might be the |
| * first thread). Retrieves context from thread handle. |
| */ |
| static bool |
| not_first_thread_in_new_process(HANDLE process_handle, HANDLE thread_handle) |
| { |
| char buf[MAX_CONTEXT_SIZE]; |
| CONTEXT *cxt = nt_initialize_context(buf, CONTEXT_DR_STATE); |
| if (NT_SUCCESS(nt_get_context(thread_handle, cxt))) |
| return !is_first_thread_in_new_process(process_handle, cxt); |
| return false; |
| } |
| |
| /* NtResumeThread */ |
| static void |
| presys_ResumeThread(dcontext_t *dcontext, reg_t *param_base) |
| { |
| HANDLE thread_handle= (HANDLE) sys_param(dcontext, param_base, 0); |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| process_id_t pid = process_id_from_thread_handle(thread_handle); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtResumeThread pid=%d tid=%d\n", pid, tid); |
| if (DYNAMO_OPTION(follow_children) && pid != POINTER_MAX && !is_pid_me(pid)) { |
| /* For -follow_children we propagate env vars (current |
| * DYNAMORIO_RUNUNDER, DYNAMORIO_OPTIONS, DYNAMORIO_AUTOINJECT, and |
| * DYNAMORIO_LOGDIR) to the child to support a simple run-all-children |
| * model without requiring setting up config files for children. |
| * |
| * It's possible the app is explicitly resuming a thread in another |
| * process and this has nothing to do with a new process: but our env |
| * var insertion should be innocuous in that case. |
| * |
| * For pre-Vista, the initial thread is always suspended, and is either |
| * resumed inside kernel32!CreateProcessW or by the app, so we should |
| * always see a resume. For Vista+ NtCreateUserProcess has suspend as a |
| * param and ideally we should replace the env pre-NtCreateUserProcess, |
| * but we have yet to get that to work, so for now we rely on |
| * Vista+ process creation going through the kernel32 routines, |
| * which do hardcode the thread as being suspended. |
| */ |
| PEB *peb; |
| HANDLE process_handle = process_handle_from_id(pid); |
| RTL_USER_PROCESS_PARAMETERS *pp = NULL; |
| if (process_handle == INVALID_HANDLE_VALUE) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "WARNING: error acquiring process handle for pid="PIFX"\n", pid); |
| return; |
| } |
| if (!should_inject_into_process(dcontext, process_handle, NULL, NULL)) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "Not injecting so not setting DR env vars in pid="PIFX"\n", pid); |
| return; |
| } |
| if (not_first_thread_in_new_process(process_handle, thread_handle)) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "Not first thread so not setting DR env vars in pid="PIFX"\n", pid); |
| return; |
| } |
| peb = get_peb(process_handle); |
| if (peb == NULL) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "WARNING: error acquiring PEB for pid="PIFX"\n", pid); |
| close_handle(process_handle); |
| return; |
| } |
| if (!nt_read_virtual_memory(process_handle, &peb->ProcessParameters, &pp, |
| sizeof(pp), NULL) || pp == NULL) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "WARNING: error acquiring ProcessParameters for pid="PIFX"\n", pid); |
| close_handle(process_handle); |
| return; |
| } |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "inserting DR env vars to pid="PIFX" &pp->Environment="PFX"\n", |
| pid, &pp->Environment); |
| if (!add_dr_env_vars(dcontext, process_handle, (wchar_t**)&pp->Environment)) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "WARNING: unable to add DR env vars for child pid="PIFX"\n", pid); |
| close_handle(process_handle); |
| return; |
| } |
| close_handle(process_handle); |
| } |
| } |
| |
| /* NtTerminateProcess */ |
| static bool /* returns whether to execute syscall */ |
| presys_TerminateProcess(dcontext_t *dcontext, reg_t *param_base) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| NTSTATUS exit_status = (NTSTATUS) sys_param(dcontext, param_base, 1); |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "syscall: NtTerminateProcess handle="PFX" pid=%d exit=%d\n", |
| process_handle, |
| process_id_from_handle((process_handle == 0) ? NT_CURRENT_PROCESS : process_handle), |
| exit_status); |
| if (process_handle == 0) { |
| NTSTATUS return_val; |
| thread_record_t **threads; |
| int num_threads; |
| priv_mcontext_t mcontext; |
| DEBUG_DECLARE(bool ok;) |
| /* this thread won't be terminated! */ |
| LOG(THREAD, LOG_SYSCALLS, 2, "terminating all other threads, not this one\n"); |
| copy_mcontext(mc, &mcontext); |
| mc->pc = SYSCALL_PC(dcontext); |
| |
| #ifdef CLIENT_INTERFACE |
| /* make sure client nudges are finished */ |
| wait_for_outstanding_nudges(); |
| #endif |
| |
| /* FIXME : issues with cleaning up here what if syscall fails */ |
| DEBUG_DECLARE(ok =) |
| synch_with_all_threads(THREAD_SYNCH_SUSPENDED_AND_CLEANED, |
| &threads, &num_threads, |
| /* Case 6821: while we're ok to be detached, we're |
| * not ok to be reset since we won't have the |
| * last_exit flag set for coming back here (plus |
| * our kstats get off since we didn't yet enter |
| * the cache) |
| */ |
| THREAD_SYNCH_VALID_MCONTEXT_NO_XFER, |
| /* if we fail to suspend a thread (e.g., privilege |
| * problems) ignore it. FIXME: retry instead? */ |
| THREAD_SYNCH_SUSPEND_FAILURE_IGNORE); |
| ASSERT(ok); |
| ASSERT(threads == NULL && num_threads == 0); /* We asked for CLEANED */ |
| copy_mcontext(&mcontext, mc); |
| |
| /* we hold the initexit lock at this point, but we cannot release |
| * it, b/c a new thread waiting on it could start initializing and |
| * then we'd issue the syscall and kill it while it's holding our |
| * lock, causing a deadlock when the subsequent process-terminating |
| * syscall comes in! (==case 4243) So, we hold the lock to issue |
| * the syscall, safest to do syscall right here rather than going |
| * back to handle_system_call() |
| */ |
| return_val = nt_terminate_process_for_app(process_handle, exit_status); |
| SET_RETURN_VAL(dcontext, return_val); |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "\tNtTerminateProcess("PFX", "PFX") => "PIFX" on behalf of app\n", |
| process_handle, exit_status, return_val); |
| |
| end_synch_with_all_threads(threads, num_threads, false/*no resume*/); |
| |
| return false; /* do not execute syscall -- we already did it */ |
| } else if (is_phandle_me((process_handle == 0) ? NT_CURRENT_PROCESS : process_handle)) { |
| /* case 10338: we don't synchall here for faster shutdown, but we have |
| * to try and not crash any other threads. FIXME: if it's rare to get here |
| * w/ > 1 thread perhaps we should do the synchall. |
| */ |
| LOG(THREAD, LOG_SYSCALLS, 2, "\tterminating process w/ %d running thread(s)\n", |
| get_num_threads()); |
| KSTOP(pre_syscall); |
| KSTOP(num_exits_dir_syscall); |
| if (is_thread_currently_native(dcontext->thread_record)) { |
| /* Avoid hooks on syscalls made while cleaning up: such as |
| * private libraries making system lib calls |
| */ |
| dynamo_thread_under_dynamo(dcontext); |
| } |
| /* FIXME: what if syscall returns w/ STATUS_PROCESS_IS_TERMINATING? */ |
| os_terminate_wow64_write_args(true/*process*/, process_handle, exit_status); |
| cleanup_and_terminate(dcontext, syscalls[SYS_TerminateProcess], |
| IF_X64_ELSE(mc->xcx, mc->xdx), |
| mc->xdx, true /* entire process */, 0, 0); |
| } |
| return true; |
| } |
| |
| /* NtTerminateThread */ |
| static void |
| presys_TerminateThread(dcontext_t *dcontext, reg_t *param_base) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| /* NtTerminateThread(IN HANDLE ThreadHandle OPTIONAL, IN NTSTATUS ExitStatus) */ |
| HANDLE thread_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| NTSTATUS exit_status = (NTSTATUS) sys_param(dcontext, param_base, 1); |
| /* need to determine which thread is being terminated |
| * it's harder than you'd think -- we can get its handle but |
| * the handle may have been duplicated, no way to test |
| * equivalence, we have to get the thread id |
| */ |
| thread_id_t tid; |
| thread_record_t *tr = thread_lookup(get_thread_id()); |
| ASSERT(tr != NULL); |
| if (thread_handle == 0) |
| thread_handle = NT_CURRENT_THREAD; |
| tid = thread_id_from_handle(thread_handle); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, "syscall: NtTerminateThread tid=%d\n", tid); |
| |
| if (tid == 0xFFFFFFFF) { |
| /* probably invalid handle, do nothing for now */ |
| /* FIXME: case 2573 about adding ASSERT_CURIOSITY replacing the ASSERT we had */ |
| } else if (tid != tr->id) { |
| priv_mcontext_t mcontext; |
| DEBUG_DECLARE(thread_synch_result_t synch_res;) |
| |
| copy_mcontext(mc, &mcontext); |
| mc->pc = SYSCALL_PC(dcontext); |
| |
| /* Fixme : issues with cleaning up here, what if syscall fails */ |
| DEBUG_DECLARE(synch_res =) |
| synch_with_thread(tid, true, false, THREAD_SYNCH_VALID_MCONTEXT, |
| THREAD_SYNCH_SUSPENDED_AND_CLEANED, |
| /* if we fail to suspend a thread (e.g., privilege |
| * problems) ignore it. FIXME: retry instead? */ |
| THREAD_SYNCH_SUSPEND_FAILURE_IGNORE); |
| ASSERT(synch_res == THREAD_SYNCH_RESULT_SUCCESS || |
| /* App could be calling on already exited thread (xref 8125) |
| * or thread could have exited while we were synching. |
| * FIXME - check is racy since for dr purposes the thread is |
| * considered exited just before it is signaled, but is ok |
| * for an assert. */ |
| is_thread_exited(thread_handle) == THREAD_EXITED || |
| !is_pid_me(process_id_from_thread_handle(thread_handle))); |
| copy_mcontext(&mcontext, mc); |
| } else { |
| /* case 9347 - racy early thread, yet primary is not yet 'known' */ |
| /* we should evaluate dr_late_injected_primary_thread before |
| * get_num_threads() |
| */ |
| bool secondary = dr_injected_secondary_thread && |
| !dr_late_injected_primary_thread; |
| |
| bool exitproc = !secondary && (is_last_app_thread() && !dynamo_exited); |
| /* this should really be check_sole_thread() */ |
| /* FIXME: case 9461 - we may not control all threads, |
| * the syscall may fail and may not be allowed to kill last thread |
| */ |
| |
| if (secondary) { |
| SYSLOG_INTERNAL_WARNING("secondary thread terminating, primary not ready\n"); |
| ASSERT(!exitproc); |
| ASSERT(!check_sole_thread()); |
| } |
| ASSERT(!exitproc || check_sole_thread()); |
| |
| KSTOP(pre_syscall); |
| KSTOP(num_exits_dir_syscall); |
| os_terminate_wow64_write_args(false/*thread*/, thread_handle, exit_status); |
| cleanup_and_terminate(dcontext, syscalls[SYS_TerminateThread], |
| IF_X64_ELSE(mc->xcx, mc->xdx), |
| mc->xdx, exitproc, 0, 0); |
| } |
| } |
| |
| /* NtSetContextThread */ |
| static bool |
| presys_SetContextThread(dcontext_t *dcontext, reg_t *param_base) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE thread_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| CONTEXT *cxt = (CONTEXT *) sys_param(dcontext, param_base, 1); |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| bool intercept = true; |
| bool execute_syscall = true; |
| /* FIXME : we are going to read and write to cxt, which may be unsafe */ |
| ASSERT(tid != 0xFFFFFFFF); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtSetContextThread handle="PFX" tid=%d cxt->Xip="PFX"\n", |
| thread_handle, tid, cxt->CXT_XIP); |
| mutex_lock(&thread_initexit_lock); /* need lock to lookup thread */ |
| if (intercept_asynch_for_thread(tid, false/*no unknown threads*/)) { |
| priv_mcontext_t mcontext; |
| thread_record_t *tr = thread_lookup(tid); |
| CONTEXT *my_cxt; |
| NTSTATUS res; |
| const thread_synch_state_t desired_state = THREAD_SYNCH_VALID_MCONTEXT; |
| DEBUG_DECLARE(thread_synch_result_t synch_res;) |
| ASSERT(tr != NULL); |
| SELF_PROTECT_LOCAL(tr->dcontext, WRITABLE); |
| /* now ensure target thread is at a safe point when it gets reset */ |
| copy_mcontext(mc, &mcontext); |
| mc->pc = SYSCALL_PC(dcontext); |
| |
| DEBUG_DECLARE(synch_res =) |
| synch_with_thread(tid, true, true, desired_state, |
| THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT, |
| /* if we fail to suspend a thread (e.g., privilege |
| * problems) ignore it. FIXME: retry instead? */ |
| THREAD_SYNCH_SUSPEND_FAILURE_IGNORE); |
| ASSERT(synch_res == THREAD_SYNCH_RESULT_SUCCESS); |
| copy_mcontext(&mcontext, mc); |
| if (!TESTALL(CONTEXT_CONTROL/*2 bits so ALL*/, cxt->ContextFlags)) { |
| /* app didn't request pc so we'd better get it now. |
| * FIXME: this isn't transparent as we have to clobber |
| * fields in the app cxt: should restore in post-syscall. |
| */ |
| char buf[MAX_CONTEXT_SIZE]; |
| CONTEXT *alt_cxt = nt_initialize_context(buf, CONTEXT_DR_STATE); |
| STATS_INC(num_app_setcontext_no_control); |
| if (thread_get_context(tr, alt_cxt) && |
| translate_context(tr, alt_cxt, true/*set memory*/)) { |
| LOG(THREAD, LOG_SYSCALLS, 2, "no CONTROL flag on original cxt:\n"); |
| DOLOG(3, LOG_SYSCALLS, { dump_context_info(cxt, THREAD, true); }); |
| cxt->ContextFlags |= CONTEXT_CONTROL; |
| cxt->CXT_XIP = alt_cxt->CXT_XIP; |
| cxt->CXT_XFLAGS = alt_cxt->CXT_XFLAGS; |
| cxt->CXT_XSP = alt_cxt->CXT_XSP; |
| cxt->CXT_XBP = alt_cxt->CXT_XBP; |
| IF_X64(ASSERT_NOT_IMPLEMENTED(false)); /* Rbp not part of CONTROL */ |
| cxt->SegCs = alt_cxt->SegCs; |
| cxt->SegSs = alt_cxt->SegSs; |
| LOG(THREAD, LOG_SYSCALLS, 3, "changed cxt:\n"); |
| DOLOG(3, LOG_SYSCALLS, { dump_context_info(cxt, THREAD, true); }); |
| /* don't care about other regs -- if app didn't |
| * specify CONTEXT_INTEGER that's fine |
| */ |
| } else { |
| /* just don't intercept: could crash us in middle of mangled |
| * sequence once we start translating there and treating them |
| * as safe spots, but for now will be ok. |
| */ |
| intercept = false; |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| if (intercept) { |
| /* modify the being-set cxt so that we retain control */ |
| intercept_nt_setcontext(tr->dcontext, cxt); |
| LOG(THREAD, LOG_SYSCALLS, 3, "final cxt passed to syscall:\n"); |
| DOLOG(3, LOG_SYSCALLS, { dump_context_info(cxt, THREAD, true); }); |
| } |
| /* nt_continue_dynamo_start path assumes target is !couldbelinking |
| * all synch_with_thread synch points should be, we check here |
| */ |
| ASSERT(!is_couldbelinking(tr->dcontext)); |
| if (TEST(THREAD_SET_CONTEXT, nt_get_handle_access_rights(thread_handle))) { |
| /* Case 10101: a thread waiting at check_wait_at_safe_spot can't |
| * be directly setcontext-ed so we explicitly do the context |
| * set request here and skip the system call. |
| * A waiting thread does NtContinue and so bypasses permission issues, |
| * so we explicitly check for setcontext permission. |
| * We have to make a copy since the app could de-allocate or modify |
| * cxt before a waiting thread examines it. |
| */ |
| DEBUG_DECLARE(bool ok;) |
| #ifdef X64 |
| /* PR 263338: we need to align to 16 on x64. Heap is 8-byte aligned. */ |
| byte *cxt_alloc; |
| #endif |
| my_cxt = global_heap_alloc(CONTEXT_HEAP_SIZE(*my_cxt) HEAPACCT(ACCT_OTHER)); |
| #ifdef X64 |
| cxt_alloc = (byte *) cxt; |
| if (!ALIGNED(cxt, 16)) { |
| ASSERT(ALIGNED(cxt, 8)); |
| cxt = (CONTEXT *) ( ((app_pc)cxt)+8 ); |
| } |
| ASSERT(ALIGNED(cxt, 16)); |
| #endif |
| *my_cxt = *cxt; |
| /* my_cxt is freed by set_synched_thread_context() or target thread */ |
| DEBUG_DECLARE(ok = ) |
| set_synched_thread_context(tr, NULL, (void *) my_cxt, |
| CONTEXT_HEAP_SIZE(*my_cxt), desired_state |
| _IF_X64(cxt_alloc) _IF_WINDOWS(&res)); |
| /* We just tested permissions, but could be bad handle, etc. |
| * FIXME: if so and thread was waiting we have transparency violation |
| */ |
| ASSERT_CURIOSITY(ok); |
| SET_RETURN_VAL(tr->dcontext, res); |
| /* must wake up thread so it can go to nt_continue_dynamo_start */ |
| nt_thread_resume(tr->handle, NULL); |
| execute_syscall = false; |
| } else { |
| /* we expect the system call to fail */ |
| DODEBUG({ tr->dcontext->expect_last_syscall_to_fail = true; }); |
| } |
| SELF_PROTECT_LOCAL(tr->dcontext, READONLY); |
| } |
| mutex_unlock(&thread_initexit_lock); |
| return execute_syscall; |
| } |
| |
| /* Assumes mc is app state prior to system call. |
| * Returns true iff system call is a callback return that does transfer control |
| * (xref case 10579). |
| */ |
| bool |
| is_cb_return_syscall(dcontext_t *dcontext) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| if (mc->xax == (reg_t) syscalls[SYS_CallbackReturn]) { |
| reg_t *param_base = pre_system_call_param_base(mc); |
| if ((NTSTATUS)sys_param(dcontext, param_base, 2) != STATUS_CALLBACK_POP_STACK) |
| return true; |
| } |
| return false; |
| } |
| |
| /* NtCallbackReturn */ |
| static void |
| presys_CallbackReturn(dcontext_t *dcontext, reg_t *param_base) |
| { |
| /* args are: |
| * IN PVOID Result OPTIONAL, IN ULONG ResultLength, IN NTSTATUS Status |
| * same args go to int 2b (my theory anyway), where they are passed in |
| * eax, ecx, and edx. if KiUserCallbackDispatcher returns, it leaves |
| * eax w/ result value of callback, and zeros out ecx and edx, then int 2b. |
| * people doing the int 2b in user32 set ecx and edx to what they want, then |
| * call a routine that simply pulls first arg into eax and then does int 2b. |
| */ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| NTSTATUS status = (NTSTATUS) sys_param(dcontext, param_base, 2); |
| if (status == STATUS_CALLBACK_POP_STACK) { |
| /* case 10579: this status code instructs the kernel to only |
| * pop the stack and not transfer control there */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtCallbackReturn STATUS_CALLBACK_POP_STACK\n"); |
| } else { |
| /* NtCallbackReturn returns from callback via a syscall, and it |
| * requires us to restore the prev dcontext immediately prior |
| * to the syscall (want to use current dcontext in prior instructions |
| * in shared_syscall). |
| * N.B.: this means that the return from the call to pre_system_call |
| * uses a different dcontext than the setup for the call! |
| * the popa and popf will be ok -- old dstack is still in esp, isn't |
| * restored, isn't deleted by swapping to new dcontext. |
| * The problem is the restore of the app's esp -- so we fix that by |
| * having the clean call to pre_system_call store and restore app's esp |
| * from a special nonswapped dcontext slot. |
| */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtCallbackReturn\n"); |
| callback_start_return(mc); |
| } |
| } |
| |
| static void |
| check_for_stack_free(dcontext_t *dcontext, byte *base, size_t size) |
| { |
| /* Ref case 5518 - on some versions of windows the thread stack is freed |
| * in process. So we watch here for the free to keep from removing again |
| * at thread exit. */ |
| os_thread_data_t *ostd = (os_thread_data_t *) dcontext->os_field; |
| ASSERT(dcontext == get_thread_private_dcontext()); |
| if (base == ostd->stack_base) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "Thread's os stack is being freed\n"); |
| ASSERT(base + size == ostd->stack_top); |
| /* only seen the in process free on 2k and NT */ |
| ASSERT_CURIOSITY(get_os_version() <= WINDOWS_VERSION_2000); |
| /* When we've seen it happen (in kernel32!ExitThread), ExitThread uses |
| * a chunk of the TEB as the stack while freeing and calling |
| * NtTerminate. */ |
| ASSERT_CURIOSITY((byte *)get_mcontext(dcontext)->xsp >= |
| (byte *)get_own_teb() && |
| (byte *)get_mcontext(dcontext)->xsp < |
| ((byte *)get_own_teb()) + PAGE_SIZE); |
| /* FIXME - Instead of saying the teb stack is no longer valid, we could |
| * instead change the bounds to be the TEB region. Other users could |
| * then always we assert we have something valid set. Is slightly |
| * greater dependence on observed behavior though. */ |
| ostd->teb_stack_no_longer_valid = true; |
| ostd->stack_base = NULL; |
| ostd->stack_top = NULL; |
| } |
| } |
| |
| /* NtAllocateVirtualMemory */ |
| static bool |
| presys_AllocateVirtualMemory(dcontext_t *dcontext, reg_t *param_base, int sysnum) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| void **pbase = (void **) sys_param(dcontext, param_base, 1); |
| /* XXX i#899: NtWow64AllocateVirtualMemory64 has an extra arg after ZeroBits but |
| * it's ignored in wow64!whNtWow64AllocateVirtualMemory64. We should keep an eye |
| * out: maybe a future service pack or win9 will use it. |
| */ |
| int arg_shift = (sysnum == syscalls[SYS_Wow64AllocateVirtualMemory64] ? 1 : 0); |
| size_t *psize = (size_t *) sys_param(dcontext, param_base, 3 + arg_shift); |
| uint type = (uint) sys_param(dcontext, param_base, 4 + arg_shift); |
| uint prot = (uint) sys_param(dcontext, param_base, 5 + arg_shift); |
| app_pc base; |
| if (is_phandle_me(process_handle) && TEST(MEM_COMMIT, type) && |
| /* Any overlap when asking for MEM_RESERVE (even when combined w/ MEM_COMMIT) |
| * will fail anyway, so we only have to worry about overlap on plain MEM_COMMIT |
| */ |
| !TEST(MEM_RESERVE, type)) { |
| /* i#1175: NtAllocateVirtualMemory can modify prot on existing pages */ |
| size_t size; |
| if (safe_read(pbase, sizeof(base), &base) && |
| safe_read(psize, sizeof(size), &size) && |
| base != NULL && |
| !app_memory_pre_alloc(dcontext, base, size, osprot_to_memprot(prot), false)) { |
| SET_RETURN_VAL(dcontext, STATUS_CONFLICTING_ADDRESSES); |
| return false; /* do not execute system call */ |
| } |
| } |
| #ifdef PROGRAM_SHEPHERDING |
| if (is_phandle_me(process_handle) && TEST(MEM_COMMIT, type) && |
| TESTALL(PAGE_EXECUTE_READWRITE, prot)) { |
| /* executable_if_alloc policy says we only add a region to the future |
| * list if it is committed rwx with no prior reservation. |
| * - if a base is passed and MEM_RESERVE is not set, there must be a prior |
| * reservation |
| * - if a base is passed and MEM_RESERVE is set, do a query to see if |
| * reservation existed before |
| * - if no base is passed, there was no reservation |
| */ |
| /* unfortunately no way to avoid syscall to check readability |
| * (unless have try...except) |
| */ |
| if (safe_read(pbase, sizeof(base), &base)) { |
| dcontext->alloc_no_reserve = |
| (base == NULL || |
| (TEST(MEM_RESERVE, type) && !get_memory_info(base, NULL, NULL, NULL))); |
| /* FIXME: can one MEM_RESERVE an address previously |
| * MEM_RESERVEd - at least on XP that's not allowed */ |
| } |
| } else if (TEST(ASLR_STACK, DYNAMO_OPTION(aslr)) && |
| !is_phandle_me(process_handle) && |
| TEST(MEM_RESERVE, type) |
| && is_newly_created_process(process_handle)) { |
| /* pre-processing of remote NtAllocateVirtualMemory reservation */ |
| /* Case 9173: ignore allocations with a requested base. These may come |
| * after we've inserted our pad (is_newly_created_process() isn't |
| * perfect), but may also come before, and we do not want to cause |
| * interop issues. We could instead try to adjust our pad to not cause |
| * their alloc to fail, but may end up eliminating any security |
| * advantage anyway. |
| */ |
| if (safe_read(pbase, sizeof(base), &base)) { |
| if (base == NULL) { |
| /* FIXME: make the above check stronger */ |
| ASSERT_CURIOSITY(prot == PAGE_READWRITE); |
| /* this is just a reservation, so can be anything */ |
| |
| /* currently not following child flags, so maybe is almost always */ |
| /* NOTE - on vista we should only ever get here if someone is using |
| * the legacy NtCreateProcess native api (vs NtCreateUserProcess) or |
| * the app is injecting memory into a new process before it's started |
| * initializing itself. */ |
| ASSERT_CURIOSITY(get_os_version() < WINDOWS_VERSION_VISTA); |
| aslr_maybe_pad_stack(dcontext, process_handle); |
| } else { |
| DODEBUG({ |
| if (process_id_from_handle(process_handle) != |
| dcontext->aslr_context.last_child_padded) { |
| SYSLOG_INTERNAL_WARNING_ONCE("aslr stack: allowing alloc prior " |
| "to pad"); |
| } |
| }); |
| } |
| } |
| } |
| #endif /* PROGRAM_SHEPHERDING */ |
| return true; |
| } |
| |
| /* NtFreeVirtualMemory */ |
| static void |
| presys_FreeVirtualMemory(dcontext_t *dcontext, reg_t *param_base) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| void **pbase = (void **) sys_param(dcontext, param_base, 1); |
| size_t *psize = (size_t *) sys_param(dcontext, param_base, 2); |
| uint type = (uint) sys_param(dcontext, param_base, 3); |
| app_pc base; |
| size_t size; |
| |
| /* check for common argument problems, apps tend to screw this call |
| * up a lot (who cares about a memory leak, esp. at process exit) */ |
| /* ref case 3536, 545, 4046 */ |
| if (!safe_read(pbase, sizeof(base), &base) || base == NULL || |
| !safe_read(psize, sizeof(size), &size) || |
| !(type == MEM_RELEASE || type == MEM_DECOMMIT)) { |
| /* we expect the system call to fail */ |
| DODEBUG(dcontext->expect_last_syscall_to_fail = true;); |
| return; |
| } |
| |
| if (!is_phandle_me(process_handle)) { |
| IPC_ALERT("ERROR: FreeVirtualMemory %s "PFX" "PIFX" on another process", |
| type == MEM_DECOMMIT ? "MEM_DECOMMIT" : "MEM_RELEASE", |
| base, size); |
| return; |
| } |
| |
| if ((type == MEM_DECOMMIT && size == 0) || (type == MEM_RELEASE)) { |
| app_pc real_base; |
| /* whole region being freed, we must look up size, ignore psize |
| * msdn and Nebbet claim that you need *psize == 0 for MEM_RELEASE |
| * but that doesn't seem to be true on all platforms */ |
| |
| /* 2K+: if base is anywhere on the first page of region this succeeds, |
| * and doesn't otherwise. |
| * NT: base must be the actual base. |
| */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtFreeVirtualMemory type=%s region base="PFX" size="PIFX"\n", |
| type == MEM_DECOMMIT ? "MEM_DECOMMIT" : "MEM_RELEASE", |
| base, size); |
| |
| size = get_allocation_size(base, &real_base); |
| ASSERT(ALIGNED(real_base, PAGE_SIZE)); |
| /* if region has been already been freed */ |
| if (((app_pc) ALIGN_BACKWARD(base, PAGE_SIZE) != real_base) || |
| (get_os_version() == WINDOWS_VERSION_NT && base != real_base)) { |
| /* we expect the system call to fail |
| * with (NTSTATUS) 0xc000009f - |
| * "Virtual memory cannot be freed as base address is not |
| * the base of the region and a region size of zero was |
| * specified" |
| */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtFreeVirtualMemory base="PFX", size="PIFX" invalid base\n", |
| base, size); |
| DODEBUG(dcontext->expect_last_syscall_to_fail = true;); |
| return; |
| } |
| /* make sure we use correct region base address, */ |
| /* otherwise we'll free an extra page */ |
| base = real_base; |
| ASSERT(real_base != NULL && "already freed"); |
| } |
| |
| DODEBUG({ |
| /* FIXME: this shouldn't be DODEBUG since we need to handle syscall failure */ |
| if (type == MEM_DECOMMIT && size != 0) { |
| size_t real_size = get_allocation_size(base, NULL); |
| if ((app_pc)ALIGN_BACKWARD(base, PAGE_SIZE) + real_size < base + size) { |
| /* we expect the system call to fail with |
| * (NTSTATUS) 0xc000001a - "Virtual memory cannot be freed." |
| */ |
| DODEBUG(dcontext->expect_last_syscall_to_fail = true;); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtFreeVirtualMemory base="PFX", size="PIFX |
| " too large should fail \n", base, size); |
| return; |
| } |
| } |
| }); |
| |
| |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtFreeVirtualMemory base="PFX" size="PIFX"\n", |
| base, size); |
| DOLOG(1, LOG_SYSCALLS|LOG_VMAREAS, { |
| char buf[MAXIMUM_PATH]; |
| get_module_name(base, buf, sizeof(buf)); |
| if (buf[0] != '\0') { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\tNtFreeVirtualMemory called on module %s\n", buf); |
| ASSERT_CURIOSITY(false && "NtFreeVirtualMemory called on module"); |
| /* should switch to PE name and then can do this at loglevel 0 */ |
| } |
| }); |
| DOLOG(1, LOG_MEMSTATS, { |
| /* snapshots are heavyweight, so do rarely */ |
| if (size > SNAPSHOT_THRESHOLD) |
| mem_stats_snapshot(); |
| }); |
| |
| align_page_boundary(dcontext, &base, &size); |
| ASSERT_BUG_NUM(4511, ALIGNED(base, PAGE_SIZE) && ALIGNED(size, PAGE_SIZE)); |
| /* ref case 5518 - we need to keep track if the thread stack is freed */ |
| if (type == MEM_RELEASE) { |
| check_for_stack_free(dcontext, base, size); |
| } |
| if (type == MEM_RELEASE && |
| TEST(ASLR_HEAP_FILL, DYNAMO_OPTION(aslr))) { |
| /* We free our allocation before the application |
| * reservation is released. Not a critical failure if the |
| * application free fails but we have freed our pad. Also |
| * avoids fragmentation if a racy allocation. |
| */ |
| aslr_pre_process_free_virtual_memory(dcontext, base, size); |
| /* note we handle the untracked stack free in |
| * os_thread_stack_exit() */ |
| } |
| |
| app_memory_deallocation(dcontext, base, size, |
| false /* don't own thread_initexit_lock */, |
| false /* not image */); |
| } |
| |
| /* NtProtectVirtualMemory */ |
| static bool /* returns whether to execute syscall */ |
| presys_ProtectVirtualMemory(dcontext_t *dcontext, reg_t *param_base) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| void **pbase = (void **) sys_param(dcontext, param_base, 1); |
| size_t *psize = (size_t *) sys_param(dcontext, param_base, 2); |
| uint prot = (uint) sys_param(dcontext, param_base, 3); |
| uint *oldprot = (uint *) sys_param(dcontext, param_base, 4); |
| app_pc base; |
| size_t size; |
| uint old_memprot = MEMPROT_NONE; /* for SUBSET_APP_MEM_PROT_CHANGE |
| * or PRETEND_APP_MEM_PROT_CHANGE */ |
| uint subset_memprot = MEMPROT_NONE; /* for SUBSET_APP_MEM_PROT_CHANGE */ |
| |
| if (!safe_read(pbase, sizeof(base), &base) || |
| !safe_read(psize, sizeof(size), &size)) { |
| /* we expect the system call to fail */ |
| DODEBUG(dcontext->expect_last_syscall_to_fail = true;); |
| return true; |
| } |
| |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtProtectVirtualMemory process="PFX" base="PFX" size=" |
| PIFX" prot=%s 0x%x\n", process_handle, base, size, prot_string(prot), prot); |
| if (is_phandle_me(process_handle)) { |
| uint res; |
| /* go to page boundaries, since windows lets you pass non-aligned |
| * values, unlike Linux |
| */ |
| /* FIXME: use align_page_boundary(dcontext, &base, &size) instead */ |
| if (!ALIGNED(base, PAGE_SIZE) || !ALIGNED(base+size, PAGE_SIZE)) { |
| /* need to cover all pages between base and base + size */ |
| size = ALIGN_FORWARD(base+size, PAGE_SIZE) - PAGE_START(base); |
| base = (app_pc) PAGE_START(base); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\tpage boundaries => base="PFX" size="PIFX"\n", base, size); |
| } |
| DOLOG(1, LOG_SYSCALLS|LOG_VMAREAS, { |
| char module_name[MAX_MODNAME_INTERNAL]; |
| if (os_get_module_name_buf(base, module_name, |
| BUFFER_SIZE_ELEMENTS(module_name)) > 0) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\tNtProtectVirtualMemory called on module %s\n", module_name); |
| } |
| }); |
| #ifdef DGC_DIAGNOSTICS |
| DOLOG(1, LOG_VMAREAS, { |
| dump_callstack(POST_SYSCALL_PC(dcontext), |
| (app_pc) mc->xbp, THREAD, |
| DUMP_NOT_XML); |
| }); |
| #endif |
| res = app_memory_protection_change(dcontext, base, size, |
| osprot_to_memprot(prot), |
| &subset_memprot, |
| &old_memprot); |
| if (res != DO_APP_MEM_PROT_CHANGE) { |
| /* from experimentation it seems to return |
| * STATUS_CONFLICTING_ADDRESSES |
| * rather than STATUS_NOT_COMMITTED for invalid memory |
| */ |
| if (res == FAIL_APP_MEM_PROT_CHANGE) { |
| SET_RETURN_VAL(dcontext, STATUS_CONFLICTING_ADDRESSES); |
| } else if (res == PRETEND_APP_MEM_PROT_CHANGE || |
| res == SUBSET_APP_MEM_PROT_CHANGE) { |
| /* |
| * FIXME: is alternative of letting it go through and undoing in |
| * post-handler simpler and safer (here we have to emulate kernel |
| * behavior), if we remove +w flag to avoid other-thread issues? |
| */ |
| uint pretend_oldprot; |
| uint old_osprot = PAGE_NOACCESS; |
| SET_RETURN_VAL(dcontext, STATUS_SUCCESS); |
| |
| if (res == SUBSET_APP_MEM_PROT_CHANGE) { |
| uint subset_osprot = |
| osprot_replace_memprot(prot, subset_memprot); |
| |
| /* we explicitly make our system call. Although in this |
| * case we could change the application arguments as |
| * well, in general it is not nice to the application |
| * to change IN arguments. |
| */ |
| bool ok = |
| nt_remote_protect_virtual_memory(process_handle, |
| base, size, |
| subset_osprot, &old_osprot); |
| /* using app's handle in case it has different rights that current thread */ |
| ASSERT_CURIOSITY(process_handle == NT_CURRENT_PROCESS); |
| ASSERT_CURIOSITY(ok); |
| /* we'll keep going anyways as if it would have worked */ |
| } else { |
| ASSERT_NOT_TESTED(); |
| ASSERT(res == PRETEND_APP_MEM_PROT_CHANGE); |
| /* pretend it worked but don't execute system call */ |
| old_osprot = get_current_protection(base); |
| } |
| |
| /* Today we base on the current actual flags |
| * (old_osprot), and preserve WRITECOPY and other |
| * unlikely original flags. |
| * |
| * We should be using our value for what the correct |
| * view of the application memory should be. case |
| * 10437 we should be able to transparently carry the |
| * original protection flags across multiple calls to |
| * NtProtectVirtualMemory. |
| */ |
| pretend_oldprot = osprot_replace_memprot(old_osprot, |
| old_memprot); |
| |
| /* have to set OUT vars properly */ |
| /* size and base were already aligned up above */ |
| ASSERT(ALIGNED(size, PAGE_SIZE)); |
| ASSERT(ALIGNED(base, PAGE_SIZE)); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "skipping NtProtectVirtualMemory, returning base="PFX", size=" |
| PIFX", oldprot=%s 0x%x\n", |
| base, size, prot_string(pretend_oldprot), pretend_oldprot); |
| |
| /* FIXME: we really should be _probing_ these writes |
| * to make sure not targeting DR addresses when |
| * PROTECT_FROM_APP |
| */ |
| safe_write(oldprot, sizeof(pretend_oldprot), &pretend_oldprot); |
| safe_write(pbase, sizeof(base), &base); |
| safe_write(psize, sizeof(size), &size); |
| } else { |
| ASSERT_NOT_REACHED(); |
| } |
| |
| return false; /* do not execute system call */ |
| } else { |
| /* FIXME i#143: we still need to tweak the returned oldprot (in |
| * post-syscall) for writable areas we've made read-only |
| */ |
| /* FIXME: ASSERT here that have not modified size unless using, e.g. fix_unsafe_hooker */ |
| } |
| } else { |
| /* FIXME: should we try to alert any dynamo running the other process? |
| */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "WARNING: ProtectVirtualMemory called on process "PFX" %d\n", |
| process_handle, process_id_from_handle(process_handle)); |
| /* this actually happens (e.g., in calc.exe's winhlp popups) |
| * so don't die here with IPC_ALERT |
| */ |
| } |
| return true; |
| } |
| |
| /* NtMapViewOfSection */ |
| static void |
| presys_MapViewOfSection(dcontext_t *dcontext, reg_t *param_base) |
| { |
| DODEBUG({ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE section_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| /* trying to make sure we're tracking properly all section |
| * handles |
| * |
| * Unfortunately SHELL32!SHChangeRegistration_Create seems |
| * to be using sections to communicate with explorer.exe |
| * and sends a message via sending a duplicate section |
| * handle, and likely receives a message back in a |
| * similarly duplicated handle from the other process. |
| * Hard to match that particular call so cannot keep a |
| * CURIOSITY here. |
| * |
| * Note we also wouldn't like some global handle being used by |
| * different threads as well, or any other unusually nested |
| * use of NtCreateSection/NtOpenSection before NtMapViewOfSection. |
| * |
| * For non-image sections accessed via OpenSection rather than CreateSection, |
| * we do NOT have the file name here, but we can get it once we have a mapping |
| * via MemorySectionName: plus we don't care about non-images. But, we don't |
| * have a test for image here, so we leave this LOG note. |
| */ |
| const char *file = section_to_file_lookup(section_handle); |
| if (file == NULL && |
| section_handle != dcontext->aslr_context.randomized_section_handle) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, |
| 2, "syscall: NtMapViewOfSection unusual section mapping\n"); |
| } |
| if (file != NULL) |
| dr_strfree(file HEAPACCT(ACCT_VMAREAS)); |
| }); |
| |
| /* no pre-processing needed except for ASLR */ |
| if (TESTANY(ASLR_DLL|ASLR_MAPPED, DYNAMO_OPTION(aslr))) { |
| aslr_pre_process_mapview(dcontext); |
| } |
| } |
| |
| /* NtUnmapViewOfSection{,Ex} */ |
| static void |
| presys_UnmapViewOfSection(dcontext_t *dcontext, reg_t *param_base, int sysnum) |
| { |
| /* This is what actually removes a dll from memory */ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| app_pc base = (app_pc) sys_param(dcontext, param_base, 1); |
| app_pc real_base; |
| size_t size = get_allocation_size(base, &real_base); |
| MEMORY_BASIC_INFORMATION mbi; |
| if (sysnum == syscalls[SYS_UnmapViewOfSectionEx]) { |
| ptr_int_t arg3 = (ptr_int_t) sys_param(dcontext, param_base, 2); |
| /* FIXME i#899: new Win8 syscall w/ 3rd arg that's 0 by default. |
| * We want to know when we see non-zero so we have some code to study. |
| */ |
| ASSERT_CURIOSITY(arg3 == 0 && "i#899: unknown new param"); |
| } |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtUnmapViewOfSection "PFX" size="PIFX"\n", base, size); |
| |
| if (!is_phandle_me(process_handle)) { |
| IPC_ALERT("ERROR: UnmapViewOfSection on another process"); |
| return; |
| } |
| |
| /* check for args we expect to fail, ref case 545, 3697, on east coast |
| * xp server shell32 dllmain process attach calls kernel32 |
| * CreateActCtxW which ends up calling this with an unaligned pointer |
| * into private memory (which is suspicously just a few bytes under |
| * the base address of a recently freed mapped region) */ |
| /* Don't worry about the query_virtual_memory cost, we are already |
| * doing a ton of them for the get_allocation_size and process_mmap |
| * calls */ |
| if (query_virtual_memory(base, &mbi, sizeof(mbi)) != sizeof(mbi) || |
| (mbi.Type != MEM_IMAGE && mbi.Type != MEM_MAPPED)) { |
| DODEBUG(dcontext->expect_last_syscall_to_fail = true;); |
| return; |
| } |
| /* people don't always call with the actual base address (see east |
| * coast xp server (sp1) whose uxtheme.dll CThemeSignature:: |
| * CalculateHash always calls this with base+0x130, is hardcoded in |
| * the assembly). OS doesn't seem to care as the syscall still |
| * succeeds. */ |
| if (base != mbi.AllocationBase) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtUnmapViewOfSection real base is "PFX"\n", |
| mbi.AllocationBase); |
| base = mbi.AllocationBase; |
| } |
| |
| DOLOG(1, LOG_MEMSTATS, { |
| /* snapshots are heavyweight, so do rarely */ |
| if (size > SNAPSHOT_THRESHOLD) |
| mem_stats_snapshot(); |
| }); |
| RSTATS_INC(num_app_munmaps); |
| |
| /* we have to mark before any policy processing gets started */ |
| |
| /* FIXME: we could also allow MEM_MAPPED areas here, since .B |
| * policies may in fact allow such to be executable areas, but |
| * since we can keep track of only one, focusing on MEM_IMAGE only |
| */ |
| if (DYNAMO_OPTION(unloaded_target_exception) && |
| mbi.Type == MEM_IMAGE) { |
| mark_unload_start(base, size); |
| } |
| |
| if (TESTANY(ASLR_DLL|ASLR_MAPPED, DYNAMO_OPTION(aslr))) { |
| aslr_pre_process_unmapview(dcontext, base, size); |
| } |
| process_mmap(dcontext, base, size, false/*unmap*/, NULL); |
| } |
| |
| /* NtFlushInstructionCache */ |
| static void |
| presys_FlushInstructionCache(dcontext_t *dcontext, reg_t *param_base) |
| { |
| /* This syscall is from the days when Windows ran on multiple |
| * architectures, but many apps still use it |
| */ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| app_pc base = (app_pc) sys_param(dcontext, param_base, 1); |
| size_t size = (size_t) sys_param(dcontext, param_base, 2); |
| #ifdef PROGRAM_SHEPHERDING |
| uint prot; |
| #endif |
| /* base can be NULL, in which case size is meaningless |
| * loader calls w/ NULL & 0 on rebasing -- means entire icache? |
| */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtFlushInstructionCache "PFX" size="PIFX"\n", base, size); |
| if (base == NULL) |
| return; |
| if (is_phandle_me(process_handle)) { |
| #ifdef DGC_DIAGNOSTICS |
| DOLOG(1, LOG_VMAREAS, { |
| dump_callstack(POST_SYSCALL_PC(dcontext), |
| (app_pc) mc->xbp, THREAD, |
| DUMP_NOT_XML); |
| }); |
| #endif |
| #ifdef PROGRAM_SHEPHERDING |
| prot = osprot_to_memprot(get_current_protection(base)); |
| app_memory_flush(dcontext, base, size, prot); |
| #endif |
| } else { |
| /* FIXME: should we try to alert any dynamo running the other process? |
| * no reason to ASSERT here, not critical like alloc/dealloc in other process |
| */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "WARNING: NtFlushInstructionCache on another process\n"); |
| } |
| } |
| |
| /* NtCreateSection */ |
| static void |
| presys_CreateSection(dcontext_t *dcontext, reg_t *param_base) |
| { |
| /* a section is an object that can be mmapped */ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE *section_handle = (HANDLE*) sys_param(dcontext, param_base, 0); |
| uint access_mask = (uint) sys_param(dcontext, param_base, 1); |
| POBJECT_ATTRIBUTES obj = (POBJECT_ATTRIBUTES) sys_param(dcontext, param_base, 2); |
| void *size = (void *) sys_param(dcontext, param_base, 3); |
| uint protect = (uint) sys_param(dcontext, param_base, 4); |
| uint attributes = (uint) sys_param(dcontext, param_base, 5); |
| HANDLE file_handle = (HANDLE) sys_param(dcontext, param_base, 6); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtCreateSection protect 0x%x, attributes 0x%x, file "PIFX"\n", |
| protect, attributes, file_handle); |
| |
| DODEBUG({ |
| if (obj != NULL && obj->ObjectName != NULL) { |
| DEBUG_DECLARE(char buf[MAXIMUM_PATH];) |
| /* convert name from unicode to ansi */ |
| wchar_t *name = obj->ObjectName->Buffer; |
| _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%S", name); |
| NULL_TERMINATE_BUFFER(buf); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtCreateSection %s\n", buf); |
| } else { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtCreateSection\n"); |
| } |
| }); |
| } |
| |
| /* NtClose */ |
| static void |
| presys_Close(dcontext_t *dcontext, reg_t *param_base) |
| { |
| if (DYNAMO_OPTION(track_module_filenames)) { |
| HANDLE handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| if (section_to_file_remove(handle)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtClose of section handle "PFX"\n", handle); |
| } |
| } |
| } |
| |
| #ifdef DEBUG |
| /* NtOpenFile */ |
| static void |
| presys_OpenFile(dcontext_t *dcontext, reg_t *param_base) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE *file_handle = (HANDLE*) sys_param(dcontext, param_base, 0); |
| uint access_mask = (uint) sys_param(dcontext, param_base, 1); |
| POBJECT_ATTRIBUTES obj = (POBJECT_ATTRIBUTES) sys_param(dcontext, param_base, 2); |
| void *status = (void *) sys_param(dcontext, param_base, 3); |
| uint share = (uint) sys_param(dcontext, param_base, 4); |
| uint options = (uint) sys_param(dcontext, param_base, 5); |
| if (obj != NULL) { |
| /* convert name from unicode to ansi */ |
| char buf[MAXIMUM_PATH]; |
| wchar_t *name = obj->ObjectName->Buffer; |
| /* not always null-terminated */ |
| _snprintf(buf, MIN(obj->ObjectName->Length/sizeof(obj->ObjectName->Buffer[0]), |
| BUFFER_SIZE_ELEMENTS(buf)), |
| "%S", name); |
| NULL_TERMINATE_BUFFER(buf); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtOpenFile %s\n", buf); |
| } else { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtOpenFile\n"); |
| } |
| } |
| #endif |
| |
| int |
| os_normalized_sysnum(int num_raw, instr_t *gateway, dcontext_t *dcontext_live) |
| { |
| return num_raw; |
| } |
| |
| /* WARNING: flush_fragments_and_remove_region assumes that pre and post system |
| * call handlers do not examine or modify fcache or its fragments in any |
| * way except for calling flush_fragments_and_remove_region! |
| */ |
| bool |
| pre_system_call(dcontext_t *dcontext) |
| { |
| bool execute_syscall = true; |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| int sysnum = (int) mc->xax; |
| reg_t *param_base = pre_system_call_param_base(mc); |
| where_am_i_t old_whereami = dcontext->whereami; |
| dcontext->whereami = WHERE_SYSCALL_HANDLER; |
| IF_X64(ASSERT_TRUNCATE(sysnum, int, mc->xax)); |
| DODEBUG(dcontext->expect_last_syscall_to_fail = false;); |
| |
| KSTART(pre_syscall); |
| RSTATS_INC(pre_syscall); |
| DOSTATS({ |
| if (ignorable_system_call(sysnum, NULL, dcontext)) |
| STATS_INC(pre_syscall_ignorable); |
| }); |
| LOG(THREAD, LOG_SYSCALLS, 2, "system call: sysnum = "PFX", param_base = "PFX"\n", |
| sysnum, param_base); |
| |
| #ifdef DEBUG |
| DOLOG(2, LOG_SYSCALLS, { |
| dump_mcontext(mc, THREAD, false/*not xml*/); |
| }); |
| /* we can't pass other than a numeric literal anymore */ |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 0: "PFX"\n", sys_param(dcontext, param_base, 0)); |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 1: "PFX"\n", sys_param(dcontext, param_base, 1)); |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 2: "PFX"\n", sys_param(dcontext, param_base, 2)); |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 3: "PFX"\n", sys_param(dcontext, param_base, 3)); |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 4: "PFX"\n", sys_param(dcontext, param_base, 4)); |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 5: "PFX"\n", sys_param(dcontext, param_base, 5)); |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 6: "PFX"\n", sys_param(dcontext, param_base, 6)); |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 7: "PFX"\n", sys_param(dcontext, param_base, 7)); |
| LOG(THREAD, LOG_SYSCALLS, 3, "\tparam 8: "PFX"\n", sys_param(dcontext, param_base, 8)); |
| DOLOG(3, LOG_SYSCALLS, { |
| /* ebp isn't in mcontext right now, so pass ebp */ |
| dump_callstack(POST_SYSCALL_PC(dcontext), (app_pc) mc->xbp, THREAD, |
| DUMP_NOT_XML); |
| }); |
| #endif |
| |
| /* save key register values for post_system_call (they get clobbered |
| * in syscall itself) |
| * FIXME: our new stateless asynch handling means that these values |
| * are wrong when we finally return to an interrupted syscall, so post-processing |
| * looks at the wrong system call! |
| * Fortunately it always looks at NtContinue, and we haven't yet implemented |
| * NtContinue failure |
| * We need fields analogous to asynch_target: asynch_sys_num and |
| * asynch_param_base. Unlike callbacks only one outstanding return-to point |
| * can exist. Let's do this when we go and make our syscall failure handling |
| * more robust. (This is case 1501) |
| */ |
| dcontext->sys_num = sysnum; |
| dcontext->sys_param_base = param_base; |
| #ifdef X64 |
| /* save params that are in registers */ |
| dcontext->sys_param0 = sys_param(dcontext, param_base, 0); |
| dcontext->sys_param1 = sys_param(dcontext, param_base, 1); |
| dcontext->sys_param2 = sys_param(dcontext, param_base, 2); |
| dcontext->sys_param3 = sys_param(dcontext, param_base, 3); |
| #endif |
| |
| if (sysnum == syscalls[SYS_Continue]) { |
| CONTEXT *cxt = (CONTEXT *) sys_param(dcontext, param_base, 0); |
| /* FIXME : we are going to read and write to cxt, which may be unsafe */ |
| int flag = (int) sys_param(dcontext, param_base, 1); |
| LOG(THREAD, LOG_SYSCALLS|LOG_ASYNCH, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtContinue cxt->Xip="PFX" flag="PFX"\n", |
| cxt->CXT_XIP, flag); |
| intercept_nt_continue(cxt, flag); |
| } |
| else if (sysnum == syscalls[SYS_CallbackReturn]) { |
| presys_CallbackReturn(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_SetContextThread]) { |
| execute_syscall = presys_SetContextThread(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_CreateProcess]) { |
| presys_CreateProcess(dcontext, param_base, false/*!Ex*/); |
| } |
| else if (sysnum == syscalls[SYS_CreateProcessEx]) { |
| presys_CreateProcess(dcontext, param_base, true/*Ex*/); |
| } |
| #ifdef DEBUG |
| else if (sysnum == syscalls[SYS_CreateUserProcess]) { |
| presys_CreateUserProcess(dcontext, param_base); |
| } |
| #endif |
| else if (sysnum == syscalls[SYS_CreateThread]) { |
| presys_CreateThread(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_CreateThreadEx]) { |
| presys_CreateThreadEx(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_CreateWorkerFactory]) { |
| presys_CreateWorkerFactory(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_SuspendThread]) { |
| HANDLE thread_handle= (HANDLE) sys_param(dcontext, param_base, 0); |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtSuspendThread tid=%d\n", tid); |
| if (SELF_PROTECT_ON_CXT_SWITCH) { |
| /* This thread must make it back out of the cache for post-syscall |
| * processing, regardless of what locks target thread holds at |
| * suspension point, so we have to turn off our cxt switch hooks |
| * (see case 4942) |
| */ |
| dcontext->ignore_enterexit = true; |
| } |
| } |
| else if (sysnum == syscalls[SYS_ResumeThread]) { |
| presys_ResumeThread(dcontext, param_base); |
| } |
| #ifdef DEBUG |
| else if (sysnum == syscalls[SYS_AlertResumeThread]) { |
| HANDLE thread_handle= (HANDLE) sys_param(dcontext, param_base, 0); |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtAlertResumeThread tid=%d\n", tid); |
| } |
| #endif |
| else if (sysnum == syscalls[SYS_TerminateProcess]) { |
| execute_syscall = presys_TerminateProcess(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_TerminateThread]) { |
| presys_TerminateThread(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_AllocateVirtualMemory] || |
| /* i#899: new win8 syscall w/ similar params to NtAllocateVirtualMemory */ |
| sysnum == syscalls[SYS_Wow64AllocateVirtualMemory64]) { |
| execute_syscall = presys_AllocateVirtualMemory(dcontext, param_base, sysnum); |
| } |
| else if (sysnum == syscalls[SYS_FreeVirtualMemory]) { |
| KSTART(pre_syscall_free); |
| presys_FreeVirtualMemory(dcontext, param_base); |
| KSTOP(pre_syscall_free); |
| } |
| else if (sysnum == syscalls[SYS_ProtectVirtualMemory]) { |
| KSTART(pre_syscall_protect); |
| execute_syscall = presys_ProtectVirtualMemory(dcontext, param_base); |
| KSTOP(pre_syscall_protect); |
| } |
| else if (sysnum == syscalls[SYS_WriteVirtualMemory]) { |
| /* FIXME NYI: case 8321: need to watch for cache consistency |
| * FIXME case 9103: note that we don't hook this for native_exec yet |
| */ |
| } |
| else if (sysnum == syscalls[SYS_MapViewOfSection]) { |
| presys_MapViewOfSection(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_UnmapViewOfSection] || |
| sysnum == syscalls[SYS_UnmapViewOfSectionEx]) { |
| KSTART(pre_syscall_unmap); |
| presys_UnmapViewOfSection(dcontext, param_base, sysnum); |
| KSTOP(pre_syscall_unmap); |
| } |
| else if (sysnum == syscalls[SYS_FlushInstructionCache]) { |
| presys_FlushInstructionCache(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_CreateSection]) { |
| presys_CreateSection(dcontext, param_base); |
| } |
| else if (sysnum == syscalls[SYS_Close]) { |
| presys_Close(dcontext, param_base); |
| } |
| #ifdef DEBUG |
| /* FIXME: move this stuff to an strace-like client, not needed |
| * for core DynamoRIO (at least not that we know of) |
| */ |
| else if (sysnum == syscalls[SYS_OpenFile]) { |
| presys_OpenFile(dcontext, param_base); |
| } |
| #endif |
| /* Address Windowing Extensions (win2k only): |
| * swap pieces of memory in and out of virtual address space |
| * => we must intercept when virtual addresses could point to something new |
| */ |
| else if (sysnum == syscalls[SYS_FreeUserPhysicalPages]) { |
| HANDLE process_handle = (HANDLE) sys_param(dcontext, param_base, 0); |
| uint *num_pages = (uint *) sys_param(dcontext, param_base, 1); |
| uint *page_frame_nums = (uint *) sys_param(dcontext, param_base, 2); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtFreeUserPhysicalPages %d pages\n", num_pages); |
| /* FIXME: need to know base if currently mapped, must |
| * record every mapping to do so |
| */ |
| SYSLOG_INTERNAL_WARNING_ONCE(PRODUCT_NAME" is using un-supported " |
| "Address Windowing Extensions"); |
| } |
| else if (sysnum == syscalls[SYS_MapUserPhysicalPages]) { |
| app_pc base = (app_pc) sys_param(dcontext, param_base, 0); |
| uint *pnum_pages = (uint *) sys_param(dcontext, param_base, 1); |
| uint *page_frame_nums = (uint *) sys_param(dcontext, param_base, 2); |
| uint num_pages; |
| if (safe_read(pnum_pages, sizeof(num_pages), &num_pages)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtMapUserPhysicalPages "PFX" pages=%d\n", |
| base, num_pages); |
| base = proc_get_containing_page(base); |
| app_memory_deallocation(dcontext, base, num_pages*PAGE_SIZE, |
| false /* don't own thread_initexit_lock */, |
| false /* not image */); |
| } else { |
| DODEBUG(dcontext->expect_last_syscall_to_fail = true;); |
| goto exit_pre_system_call; |
| } |
| } |
| else if (sysnum == syscalls[SYS_SetInformationVirtualMemory]) { |
| /* FIXME i#899: new Win8 syscall NYI. |
| * We want to know when we see it so we have some code to study. |
| */ |
| ASSERT_NOT_IMPLEMENTED(false); |
| } |
| |
| exit_pre_system_call: |
| dcontext->whereami = old_whereami; |
| KSTOP(pre_syscall); |
| return execute_syscall; |
| } |
| |
| /*************************************************************************** |
| * POST SYSTEM CALL |
| */ |
| |
| /* NtCreateUserProcess */ |
| static void |
| postsys_CreateUserProcess(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| /* See notes in presys_CreateUserProcess for information on signature |
| * of NtCreateUserProcess. */ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE *proc_handle_ptr = (HANDLE *) postsys_param(dcontext, param_base, 0); |
| HANDLE *thread_handle_ptr = (HANDLE *) postsys_param(dcontext, param_base, 1); |
| BOOL create_suspended = (BOOL) postsys_param(dcontext, param_base, 7); |
| HANDLE proc_handle, thread_handle; |
| /* FIXME should have type for this */ |
| DEBUG_DECLARE(create_proc_thread_info_t *thread_stuff = |
| (create_proc_thread_info_t *) postsys_param(dcontext, param_base, 10);) |
| ASSERT(get_os_version() >= WINDOWS_VERSION_VISTA); |
| |
| LOG(THREAD, LOG_SYSCALLS, 1, "syscall: NtCreateUserProcess => "PIFX"\n", mc->xax); |
| DOLOG(1, LOG_SYSCALLS, { |
| if (success) { |
| PCLIENT_ID client_id; |
| ASSERT(thread_stuff != NULL && thread_stuff->client_id.buffer != NULL); |
| /* Potentially dangerous deref of app ptr, |
| * but is only for debug logging */ |
| client_id = (PCLIENT_ID)thread_stuff->client_id.buffer; |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "syscall: NtCreateUserProcess created process "PIFX" " |
| "with main thread "PIFX"\n", |
| client_id->UniqueProcess, client_id->UniqueThread); |
| } |
| }); |
| |
| /* Even though syscall succeeded we use safe_read to be sure */ |
| if (success && |
| safe_read(proc_handle_ptr, sizeof(proc_handle), &proc_handle) && |
| safe_read(thread_handle_ptr, sizeof(thread_handle), &thread_handle)) { |
| ACCESS_MASK rights = nt_get_handle_access_rights(proc_handle); |
| |
| if (TESTALL(PROCESS_VM_OPERATION|PROCESS_VM_READ| |
| PROCESS_VM_WRITE|PROCESS_QUERY_INFORMATION, rights)) { |
| if (create_suspended) { |
| char buf[MAX_CONTEXT_SIZE]; |
| CONTEXT *context; |
| CONTEXT *cxt = NULL; |
| int res; |
| /* Since this syscall is vista+ only, whether a wow64 process |
| * has no bearing (xref i#381) |
| */ |
| ASSERT(get_os_version() >= WINDOWS_VERSION_VISTA); |
| if (!DYNAMO_OPTION(early_inject)) { |
| /* If no early injection we have to do thread injection, and |
| * on Vista+ we don't see the |
| * NtCreateThread so we do it here. PR 215423. |
| */ |
| context = nt_initialize_context(buf, CONTEXT_DR_STATE); |
| res = nt_get_context(thread_handle, context); |
| if (NT_SUCCESS(res)) |
| cxt = context; |
| else { |
| /* FIXME i#49: cross-arch injection can end up here w/ |
| * STATUS_INVALID_PARAMETER. Need to use proper platform's |
| * CONTEXT for target. |
| */ |
| DODEBUG({ |
| if (is_wow64_process(NT_CURRENT_PROCESS) && |
| !is_wow64_process(proc_handle)) { |
| SYSLOG_INTERNAL_WARNING_ONCE |
| ("Injecting from 32-bit into 64-bit process is not " |
| "yet supported."); |
| } |
| }); |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "syscall: NtCreateUserProcess: WARNING: failed to get cxt of " |
| "thread ("PIFX") so can't follow children on WOW64.\n", res); |
| } |
| } |
| if ((cxt != NULL || DYNAMO_OPTION(early_inject)) && |
| maybe_inject_into_process(dcontext, proc_handle, cxt) && |
| cxt != NULL) { |
| /* injection routine is assuming doesn't have to install cxt */ |
| res = nt_set_context(thread_handle, cxt); |
| if (!NT_SUCCESS(res)) { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "syscall: NtCreateUserProcess: WARNING: failed to set cxt of " |
| "thread ("PIFX") so can't follow children on WOW64.\n", res); |
| } |
| } |
| } else { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "syscall: NtCreateUserProcess first thread not suspended " |
| "can't safely follow children.\n"); |
| ASSERT_NOT_IMPLEMENTED(create_suspended); |
| /* FIXME - NYI - should change in pre and resume the thread |
| * after we inject. */ |
| } |
| } else { |
| LOG(THREAD, LOG_SYSCALLS, 1, |
| "syscall: NtCreateUserProcess unable to get sufficient rights" |
| " to follow children\n"); |
| /* This happens for Vista protected processes (drm). xref 8485 */ |
| /* FIXME - could check against executable file name from |
| * thread_stuff to see if this was a process we're configured to |
| * protect. */ |
| SYSLOG_INTERNAL_WARNING("Insufficient permissions to examine " |
| "child process\n"); |
| } |
| /* Case 9173: guard against pid reuse */ |
| dcontext->aslr_context.last_child_padded = 0; |
| } |
| } |
| |
| /* NtGetContextThread */ |
| static void |
| postsys_GetContextThread(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE thread_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| CONTEXT *cxt = (CONTEXT *) postsys_param(dcontext, param_base, 1); |
| thread_record_t *trec; |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| char buf[MAX_CONTEXT_SIZE]; |
| CONTEXT *alt_cxt; |
| CONTEXT *xlate_cxt; |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "syscall: NtGetContextThread handle="PFX" (tid=%d) flags=0x%x" |
| " cxt->Xip="PFX" => "PIFX"\n", |
| thread_handle, tid, cxt->ContextFlags, cxt->CXT_XIP, mc->xax); |
| if (!success) |
| return; |
| |
| /* FIXME : we are going to read/write the context argument which is |
| * potentially unsafe, since success it must have been readable when |
| * at the os call, but there could always be multi-thread races */ |
| |
| /* so trec remains valid, we are !could_be_linking */ |
| mutex_lock(&thread_initexit_lock); |
| trec = thread_lookup(tid); |
| if (trec == NULL) { |
| /* this can occur if the target thread hasn't been scheduled yet |
| * and therefore we haven't initialized it yet, (scheduled for |
| * fixing), OR if the thread is in another process (FIXME : IPC) |
| * for either case we do nothing for now |
| */ |
| DODEBUG({ |
| process_id_t pid = process_id_from_thread_handle(thread_handle); |
| if (!is_pid_me(pid)) { |
| IPC_ALERT("Warning: NtGetContextThread called on thread " |
| "tid="PFX" in different process, pid="PFX, |
| tid, pid); |
| } else { |
| SYSLOG_INTERNAL_WARNING_ONCE("Warning: NtGetContextThread " |
| "called on unknown thread " |
| PFX, tid); |
| } |
| }); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 2, |
| "NtGetContextThread on unknown thread "TIDFMT"\n", tid); |
| } else { |
| /* FIXME : the following routine (and the routines it calls |
| * namely recreate_app_state) require that trec thread be |
| * suspended at a consistent spot, but we could have that the |
| * trec thread is not suspended (get_thread_context doesn't |
| * require it!), should we check the suspend count? |
| */ |
| bool translate = true; |
| xlate_cxt = cxt; |
| if (!TESTALL(CONTEXT_DR_STATE, cxt->ContextFlags)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 2, |
| "NtGetContextThread: app didn't ask for enough, querying ourselves\n"); |
| STATS_INC(num_app_getcontext_no_control); |
| /* we need esp and eip, plus all regs + xmm, to translate the machine state. |
| * no further permissions are needed to acquire them so we |
| * get our own context w/ them. |
| */ |
| alt_cxt = nt_initialize_context(buf, CONTEXT_DR_STATE); |
| /* if asking for own context, thread_get_context() will point at |
| * dynamorio_syscall_* and we'll fail to translate so we special-case |
| */ |
| if (tid == get_thread_id()) { |
| /* only fields that DR might change are propagated to cxt below, |
| * so set set_cur_seg to false. |
| */ |
| mcontext_to_context(alt_cxt, mc, false /* !set_cur_seg */); |
| alt_cxt->CXT_XIP = (ptr_uint_t) dcontext->asynch_target; |
| translate = false; |
| } else if (!thread_get_context(trec, alt_cxt)) { |
| ASSERT_NOT_REACHED(); |
| /* FIXME: just don't translate -- right now won't hurt us since |
| * we don't translate other than the pc anyway. |
| */ |
| return; |
| } |
| xlate_cxt = alt_cxt; |
| } |
| |
| SELF_PROTECT_LOCAL(trec->dcontext, WRITABLE); |
| /* PR 214962: since we are not relocating the target thread, we do NOT |
| * want to restore memory. This is no less transparent, because |
| * this thread could read the target thread's memory at any time anyway. |
| */ |
| if (translate && |
| !translate_context(trec, xlate_cxt, false/*leave memory alone*/)) { |
| /* FIXME: can get here native if GetThreadContext on |
| * an un-suspended thread, but then API says result is |
| * undefined so just pass anything reasonable |
| * PLUS, need to handle unknown (unscheduled yet) thread -- |
| * passing native should be fine |
| */ |
| SYSLOG_INTERNAL_WARNING("NtGetContextThread called for thread not in translatable spot"); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "ERROR: NtGetContextThread called for thread not in translatable spot\n"); |
| } else if (xlate_cxt != cxt) { |
| /* copy the fields we may have changed that app requested */ |
| ASSERT(!TESTALL(CONTEXT_DR_STATE, cxt->ContextFlags)); |
| if (TESTALL(CONTEXT_CONTROL/*2 bits so ALL*/, cxt->ContextFlags)) { |
| cxt->CXT_XIP = xlate_cxt->CXT_XIP; |
| cxt->CXT_XFLAGS = xlate_cxt->CXT_XFLAGS; |
| cxt->CXT_XSP = xlate_cxt->CXT_XSP; |
| #ifndef X64 |
| cxt->CXT_XBP = xlate_cxt->CXT_XBP; |
| #endif |
| } |
| if (TESTALL(CONTEXT_INTEGER/*2 bits so ALL*/, cxt->ContextFlags)) { |
| cxt->CXT_XAX = xlate_cxt->CXT_XAX; |
| cxt->CXT_XBX = xlate_cxt->CXT_XBX; |
| cxt->CXT_XCX = xlate_cxt->CXT_XCX; |
| cxt->CXT_XDX = xlate_cxt->CXT_XDX; |
| cxt->CXT_XSI = xlate_cxt->CXT_XSI; |
| cxt->CXT_XDI = xlate_cxt->CXT_XDI; |
| #ifdef X64 |
| cxt->CXT_XBP = xlate_cxt->CXT_XBP; |
| cxt->R8 = xlate_cxt->R8; |
| cxt->R9 = xlate_cxt->R9; |
| cxt->R10 = xlate_cxt->R10; |
| cxt->R11 = xlate_cxt->R11; |
| cxt->R12 = xlate_cxt->R12; |
| cxt->R13 = xlate_cxt->R13; |
| cxt->R14 = xlate_cxt->R14; |
| cxt->R15 = xlate_cxt->R15; |
| #endif |
| } |
| if (TESTALL(CONTEXT_XMM_FLAG, cxt->ContextFlags) && |
| preserve_xmm_caller_saved()) { |
| /* PR 264138 */ |
| memcpy(CXT_XMM(cxt, 0), CXT_XMM(xlate_cxt, 0), XMM_SAVED_SIZE); |
| } |
| if (TESTALL(CONTEXT_YMM_FLAG, cxt->ContextFlags) && |
| preserve_xmm_caller_saved()) { |
| byte *ymmh_area = context_ymmh_saved_area(cxt); |
| ASSERT(ymmh_area != NULL); |
| memcpy(ymmh_area, context_ymmh_saved_area(xlate_cxt), YMMH_SAVED_SIZE); |
| } |
| } |
| SELF_PROTECT_LOCAL(trec->dcontext, READONLY); |
| } |
| mutex_unlock(&thread_initexit_lock); |
| } |
| |
| /* NtSuspendThread */ |
| static void |
| postsys_SuspendThread(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE thread_handle= (HANDLE) postsys_param(dcontext, param_base, 0); |
| /* ignoring 2nd argument (OUT PULONG PreviousSuspendCount OPTIONAL) */ |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| process_id_t pid; |
| |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "syscall: NtSuspendThread tid=%d => "PIFX"\n", tid, mc->xax); |
| if (SELF_PROTECT_ON_CXT_SWITCH) { |
| /* No matter what, restore ignore to default value */ |
| dcontext->ignore_enterexit = false; |
| } |
| /* if we suspended ourselves then skip synchronization, |
| * already resumed, FIXME : what if someone else resumes the thread |
| * while we are trying to synch with it */ |
| if (!success || tid == get_thread_id()) |
| return; |
| |
| pid = process_id_from_thread_handle(thread_handle); |
| if (!is_pid_me(pid)) { |
| /* (FIXME : IPC) */ |
| IPC_ALERT("Warning: SuspendThread called on thread in " |
| "different process, pid="PFX, pid); |
| return; |
| } |
| |
| /* as optimization check if at good spot already before resuming for |
| * synch, use trylocks in case suspended thread is holding any locks */ |
| if (mutex_trylock(&thread_initexit_lock)) { |
| if (!mutex_testlock(&all_threads_lock)) { |
| char buf[MAX_CONTEXT_SIZE]; |
| CONTEXT *cxt = nt_initialize_context(buf, CONTEXT_DR_STATE); |
| thread_record_t *tr; |
| /* know thread isn't holding any of the locks we will need */ |
| LOG(THREAD, LOG_SYNCH, 2, |
| "SuspendThread got necessary locks to test if thread "TIDFMT" suspended at good spot without resuming\n", |
| tid); |
| tr = thread_lookup(tid); |
| if (tr == NULL) { |
| /* Could be unknown thread, a thread just starting up or |
| * a thread that is in the process of exiting. |
| * synch_with_thread will take care of the last case at |
| * least so we fall through to that. */ |
| } else if (thread_get_context(tr, cxt)) { |
| priv_mcontext_t mc; |
| context_to_mcontext(&mc, cxt); |
| SELF_PROTECT_LOCAL(tr->dcontext, WRITABLE); |
| if (at_safe_spot(tr, &mc, |
| THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT)) { |
| /* suspended at good spot, skip synch */ |
| mutex_unlock(&thread_initexit_lock); |
| LOG(THREAD, LOG_SYNCH, 2, |
| "SuspendThread suspended thread "TIDFMT" at good place\n", |
| tid); |
| SELF_PROTECT_LOCAL(tr->dcontext, READONLY); |
| return; |
| } |
| SELF_PROTECT_LOCAL(tr->dcontext, READONLY); |
| } |
| } else { |
| LOG(THREAD, LOG_SYNCH, 2, |
| "SuspendThread couldn't get all_threads_lock to test if thread "TIDFMT" at good spot without resuming\n", |
| tid); |
| } |
| mutex_unlock(&thread_initexit_lock); |
| } else { |
| LOG(THREAD, LOG_SYNCH, 2, |
| "SuspendThread couldn't get thread_initexit_lock to test if thread "TIDFMT" at good spot without resuming\n", |
| tid); |
| } |
| LOG(THREAD, LOG_SYNCH, 2, |
| "SuspendThread resuming suspended thread "TIDFMT" for synch routine\n", |
| tid); |
| |
| /* resume for synch */ |
| nt_thread_resume(thread_handle, NULL); |
| |
| /* do synch */ |
| { |
| priv_mcontext_t mcontext; |
| thread_synch_result_t synch_res; |
| copy_mcontext(mc, &mcontext); |
| mc->pc = POST_SYSCALL_PC(dcontext); |
| |
| /* we hold the initexit lock for case 9489, see comment below in failure |
| * to synch path for details why */ |
| if (DYNAMO_OPTION(suspend_on_synch_failure_for_app_suspend)) |
| mutex_lock(&thread_initexit_lock); |
| synch_res = |
| synch_with_thread(tid, true /* block */, |
| /* initexit lock status */ |
| DYNAMO_OPTION(suspend_on_synch_failure_for_app_suspend), |
| THREAD_SYNCH_VALID_MCONTEXT, |
| THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT, |
| /* if we fail to suspend a thread (e.g., privilege |
| * problems) ignore it. FIXME: retry instead? */ |
| THREAD_SYNCH_SUSPEND_FAILURE_IGNORE); |
| if (synch_res != THREAD_SYNCH_RESULT_SUCCESS) { |
| /* xref case 9488 - we failed to synch, could be we exceeded our loop count |
| * for some reason, we lack GetContext permission (or the apps handle has |
| * suspend and ours doesn't somehow), or could be an unknown thread. FIXME - |
| * we suspend the thread so the app doesn't get screwed up (it expects a |
| * suspended thread) at the risk of possibly deadlocking DR if it holds |
| * one of our locks etc. */ |
| /* If the thread is unknown everything might be ok, could be a thread that's |
| * almost exited (should be fine though app might get slightly screwy result |
| * if it calls get context, e.g. an eip in our dll) or a new thread that |
| * hasn't yet initialized (see case 9489, should also be fine since we hold |
| * the initexit lock so the thread can't have gone anywhere since the |
| * synch_with_thread checks). NOTE - SetEvent appears to do the sensible |
| * thing when an auto-reset event that has a suspended thread waiting on it |
| * is signaled (the new thread could be waiting on the initexit lock), i.e. |
| * leave the event signaled for someone else to grab. */ |
| /* Full ASSERT if thread is known (always bad to fail then), curiosity |
| * instead if thread is unknown (since expected to be ok). */ |
| ASSERT(thread_lookup(tid) == NULL); /* i.e. thread not known */ |
| /* The suspend.c unit test can hit this regularly on (via suspend new thread) |
| * though we expect it to be unusual in normal applications. Same thing with |
| * detach_test.exe and threadinjection.exe. */ |
| ASSERT_CURIOSITY_ONCE((thread_lookup(tid) != NULL || /* thread known */ |
| EXEMPT_TEST("win32.suspend.exe;runall.detach_test.exe;" |
| "win32.threadinjection.exe")) && |
| "app suspending unknown thread"); |
| if (DYNAMO_OPTION(suspend_on_synch_failure_for_app_suspend)) { |
| /* thread may already be exited in which case this will fail */ |
| DEBUG_DECLARE(bool res = )nt_thread_suspend(thread_handle, NULL); |
| ASSERT(res || is_thread_exited(thread_handle) == THREAD_EXITED); |
| } |
| } |
| if (DYNAMO_OPTION(suspend_on_synch_failure_for_app_suspend)) |
| mutex_unlock(&thread_initexit_lock); |
| |
| /* FIXME - if the thread exited we should prob. change the return value to |
| * the app to a failure value. Only an assert_curiosity for now to see if any |
| * apps suspend threads while the threads are exiting and if so what they expect |
| * to happen. */ |
| ASSERT_CURIOSITY(is_thread_exited(thread_handle) == THREAD_NOT_EXITED); |
| |
| copy_mcontext(&mcontext, mc); |
| } |
| } |
| |
| #ifdef CLIENT_INTERFACE |
| /* NtQueryInformationThread */ |
| static void |
| postsys_QueryInformationThread(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| THREADINFOCLASS class = (THREADINFOCLASS) postsys_param(dcontext, param_base, 1); |
| if (success && class == ThreadAmILastThread) { |
| HANDLE thread_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| process_id_t pid = process_id_from_thread_handle(thread_handle); |
| if (pid != POINTER_MAX && is_pid_me(pid) && |
| get_num_client_threads() > 0 && is_last_app_thread()) { |
| PVOID info = (PVOID) postsys_param(dcontext, param_base, 2); |
| ULONG info_sz = (ULONG) postsys_param(dcontext, param_base, 3); |
| BOOL pretend_val = TRUE; |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtQueryInformationThread ThreadAmILastThread fooling\n"); |
| ASSERT_CURIOSITY(info_sz == sizeof(BOOL)); |
| if (info_sz == sizeof(BOOL)) |
| safe_write(info, info_sz, &pretend_val); |
| } |
| } |
| } |
| #endif |
| |
| /* NtAllocateVirtualMemory */ |
| static void |
| postsys_AllocateVirtualMemory(dcontext_t *dcontext, reg_t *param_base, bool success, |
| int sysnum) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE process_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| /* XXX i#899: for NtWow64AllocateVirtualMemory64, the base and |
| * size may be 64-bit values? But, when allocating in wow64 |
| * child, the address should be in low 2GB, as only ntdll64 is up |
| * high. If the extra arg were before ZeroBits, it could be a pointer |
| * to the high bits of the base addr, like NtWow64ReadVirtualMemory64(), |
| * but that doesn't seem to be the case. |
| */ |
| void **pbase = (void **) postsys_param(dcontext, param_base, 1); |
| uint zerobits = (uint) postsys_param(dcontext, param_base, 2); |
| /* XXX i#899: NtWow64AllocateVirtualMemory64 has an extra arg after ZeroBits but |
| * it's ignored in wow64!whNtWow64AllocateVirtualMemory64. We should keep an eye |
| * out: maybe a future service pack or win9 will use it. |
| */ |
| int arg_shift = (sysnum == syscalls[SYS_Wow64AllocateVirtualMemory64] ? 1 : 0); |
| size_t *psize = (size_t *) postsys_param(dcontext, param_base, 3 + arg_shift); |
| uint type = (uint) postsys_param(dcontext, param_base, 4 + arg_shift); |
| uint prot = (uint) postsys_param(dcontext, param_base, 5 + arg_shift); |
| app_pc base; |
| size_t size; |
| if (!success) { |
| /* FIXME i#148: should try to recover from any prot change -- though today we |
| * don't even do so on NtProtectVirtualMemory failing. |
| */ |
| return; |
| } |
| if (!safe_read(pbase, sizeof(base), &base) || !safe_read(psize, sizeof(size), &size)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtAllocateVirtualMemory: failed to read params "PFX" "PFX"\n", |
| pbase, psize); |
| return; |
| } |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, prot_is_executable(prot) ? 1U : 2U, |
| "syscall: NtAllocateVirtualMemory%s%s%s@"PFX" sz="PIFX" prot=%s 0x%x => 0x%x\n", |
| is_phandle_me(process_handle) ? "" : " IPC", |
| TEST(MEM_RESERVE, type) ? " reserve" : "", |
| TEST(MEM_COMMIT, type) ? " commit " : " ", |
| base, size, prot_string(prot), prot, mc->xax); |
| DOLOG(1, LOG_MEMSTATS, { |
| /* snapshots are heavyweight, so do rarely */ |
| if (size > SNAPSHOT_THRESHOLD && is_phandle_me(process_handle)) |
| mem_stats_snapshot(); |
| }); |
| |
| if (TEST(ASLR_HEAP_FILL, DYNAMO_OPTION(aslr)) && |
| is_phandle_me(process_handle)) { |
| /* We allocate our padding after the application region is |
| * successfully reserved. FIXME: assuming that one cannot |
| * pass MEM_RESERVE|MEM_COMMIT on an already reserved |
| * region. Yet note one can MEM_COMMIT a region that has |
| * been committed already. Note that it is OK to pass |
| * MEM_COMMIT with original base set to NULL, and then the |
| * allocation will act as MEM_RESERVE|MEM_COMMIT! One |
| * can't pass MEM_COMMIT that with non-zero base on a |
| * region that hasn't been reserved before. We want to |
| * make sure we pad only an amount corresponding to the |
| * new reservations. (Currently we only pad immediately |
| * after an allocation but that may change.) |
| */ |
| |
| /* FIXME: case 6287 we should TEST(MEM_RESERVE, type) if |
| * allocation has just been reserved, or if pre_syscall |
| * base was NULL for a MEM_COMMIT. Currently a pad is |
| * reserved only in case immediate region has not been |
| * reserved, so we're ok to attempt to pad even |
| * a MEM_COMMIT with an existing reservation |
| */ |
| aslr_post_process_allocate_virtual_memory(dcontext, base, size); |
| } |
| |
| if (!TEST(MEM_COMMIT, type)) { |
| /* MEM_RESERVE only: protection bits are meaningless, we do nothing |
| * MEM_RESET: we do not need to flush on a reset, since whatever is there |
| * cannot be changed without writing to it! |
| * the subsequent commit to the already committed region will work fine. |
| */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, "not committing, so ignorable\n"); |
| return; |
| } |
| if (is_phandle_me(process_handle)) { |
| #ifdef DGC_DIAGNOSTICS |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, TEST(MEM_COMMIT, type) ? 1U : 2U, |
| "syscall: NtAllocateVirtualMemory%s%s@"PFX" sz="PIFX" prot=%s 0x%x => 0x%x\n", |
| TEST(MEM_RESERVE, type) ? " reserve" : "", |
| TEST(MEM_COMMIT, type) ? " commit " : " ", |
| base, size, prot_string(prot), prot, mc->xax); |
| DOLOG(1, LOG_VMAREAS, { |
| dump_callstack(POST_SYSCALL_PC(dcontext), |
| (app_pc) mc->xbp, THREAD, DUMP_NOT_XML); |
| }); |
| #endif |
| app_memory_allocation(dcontext, base, size, osprot_to_memprot(prot), |
| false/*not image*/ |
| _IF_DEBUG("NtAllocateVirtualMemory")); |
| #ifdef DGC_DIAGNOSTICS |
| DOLOG(3, LOG_VMAREAS, { |
| /* make all heap RO in attempt to view generation of DGC */ |
| if (!is_address_on_stack(dcontext, base) && prot_is_writable(prot)) { |
| /* new thread stack: reserve big region, commit 2 pages, then mark |
| * 1 page as PAGE_GUARD. strangely thread gets resumed sometimes |
| * before see PAGE_GUARD prot, so instead of tracking that we have |
| * a hack to guess if this is a thread stack: |
| */ |
| IF_X64(ASSERT_NOT_IMPLEMENTED(false)); |
| if (size == 0x2000 && ((ptr_uint_t)base & 0xf0000000) == 0x00000000 && |
| prot == PAGE_READWRITE) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "Guessing "PFX"-"PFX" is thread stack\n", |
| base, base+size); |
| } else { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "Making "PFX"-"PFX" 0x%x unwritable\n", |
| base, base+size, prot); |
| make_unwritable(base, size); |
| } |
| } |
| }); |
| #endif |
| } else { |
| /* FIXME: should we try to alert any dynamo running the other process? |
| */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "WARNING: NtAllocateVirtualMemory for process "PFX" %d\n", |
| process_handle, process_id_from_handle(process_handle)); |
| DODEBUG({ |
| if (prot_is_executable(prot)) { |
| IPC_ALERT("NtAllocateVirtualMemory for process "PFX" %d prot=%s", |
| process_handle, process_id_from_handle(process_handle), |
| prot_string(prot)); |
| } |
| }); |
| |
| /* This actually happens in calc's help defn popup! |
| * FIXME: we need IPC! Plus need to queue up msgs to child dynamo, |
| * for calc it did NtCreateProcess, NtAllocateVirtualMemory, then |
| * the NtCreateThread that triggers our fork injection! |
| * don't die with IPC_ALERT |
| */ |
| } |
| } |
| |
| /* NtQueryVirtualMemory */ |
| static void |
| postsys_QueryVirtualMemory(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| /* we intercept this for transparency wrt the executable regions |
| * that we mark as read-only |
| */ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE process_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| app_pc base = (app_pc) postsys_param(dcontext, param_base, 1); |
| uint class = (uint) postsys_param(dcontext, param_base, 2); |
| MEMORY_BASIC_INFORMATION *mbi = |
| (MEMORY_BASIC_INFORMATION *) postsys_param(dcontext, param_base, 3); |
| size_t infolen = (size_t) postsys_param(dcontext, param_base, 4); |
| size_t *returnlen = (size_t *) postsys_param(dcontext, param_base, 5); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtQueryVirtualMemory base="PFX" => "PIFX"\n", base, mc->xax); |
| if (!success) |
| return; |
| /* FIXME : since success we assume that all argument dereferences are |
| * safe though there could always be multi-thread races */ |
| if (is_phandle_me(process_handle)) { |
| if (class == MemoryBasicInformation) { |
| /* see if asking about an executable area we made read-only */ |
| if (is_pretend_or_executable_writable(base)) { |
| /* pretend area is writable */ |
| uint flags = |
| mbi->Protect & ~PAGE_PROTECTION_QUALIFIERS; |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "WARNING: Query to now-readonly executable area, pretending writable\n"); |
| if (flags == PAGE_READONLY) { |
| mbi->Protect &= ~PAGE_READONLY; |
| mbi->Protect |= PAGE_READWRITE; |
| } else if (flags == PAGE_EXECUTE_READ) { |
| mbi->Protect &= ~PAGE_EXECUTE_READ; |
| mbi->Protect |= PAGE_EXECUTE_READWRITE; |
| } else { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "ERROR: Query to now-readonly executable area w/ bad flags %s\n", |
| prot_string(mbi->Protect)); |
| SYSLOG_INTERNAL_INFO("ERROR: Query to now-readonly executable area w/ bad flags"); |
| } |
| } else if (is_dynamo_address(base)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "WARNING: QueryVM to DR memory "PFX"\n", base); |
| if (base == dynamo_dll_start && mbi != NULL && |
| DYNAMO_OPTION(hide_from_query) != 0) { |
| /* pretend area is un-allocated */ |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "WARNING: QueryVM to DR DLL "PFX", pretending not a dll\n", base); |
| if (TEST(HIDE_FROM_QUERY_TYPE_PROTECT, |
| DYNAMO_OPTION(hide_from_query))) { |
| mbi->Type = MEM_PRIVATE; /* not image! */ |
| mbi->Protect = PAGE_NOACCESS; |
| } |
| /* now do an off-by-1 to fool any calls to GetModuleFileName |
| * (it doesn't turn into a syscall) |
| * FIXME: app could still use a snapshot to get list |
| * of modules, but that is covered by -hide |
| */ |
| if (TEST(HIDE_FROM_QUERY_BASE_SIZE, |
| DYNAMO_OPTION(hide_from_query))) { |
| mbi->AllocationBase = ((app_pc)mbi->AllocationBase) |
| + PAGE_SIZE; |
| mbi->BaseAddress = ((app_pc)mbi->BaseAddress) |
| + PAGE_SIZE; |
| /* skip over the other regions in our dll -- ok to |
| * be PAGE_SIZE off, better to be beyond than |
| * return too small and have caller incrementing |
| * only and ignoring bases! |
| */ |
| mbi->RegionSize = (dynamo_dll_end - dynamo_dll_start); |
| } |
| /* note that returning STATUS_INVALID_ADDRESS is too |
| * extreme of a solution, so this is off by default */ |
| if (TEST(HIDE_FROM_QUERY_RETURN_INVALID, |
| DYNAMO_OPTION(hide_from_query))) { |
| /* FIXME: SET_RETURN_VAL bug 5068 had return val as 0 |
| * Need to re-test this with this actual return val |
| */ |
| SET_RETURN_VAL(dcontext, STATUS_INVALID_ADDRESS); |
| } |
| } |
| } |
| } else if (class == MemorySectionName) { |
| /* This does work on image sections on later Windows */ |
| if (is_dynamo_address(base)) { |
| /* Apps should be fine with this failing. This is the failure |
| * status for an address that does not contain a mapped file. |
| */ |
| SET_RETURN_VAL(dcontext, STATUS_INVALID_ADDRESS); |
| } |
| } |
| } else { |
| IPC_ALERT("Warning: QueryVirtualMemory on another process"); |
| } |
| } |
| |
| static void |
| postsys_create_or_open_section(dcontext_t *dcontext, |
| HANDLE *unsafe_section_handle, HANDLE file_handle, |
| bool non_image) |
| { |
| HANDLE section_handle = INVALID_HANDLE_VALUE; |
| if (DYNAMO_OPTION(track_module_filenames) && |
| safe_read(unsafe_section_handle, sizeof(section_handle), §ion_handle)) { |
| /* Case 1272: keep file name around to use for module identification */ |
| FILE_NAME_INFORMATION name_info; /* note: large struct */ |
| wchar_t buf[MAXIMUM_PATH]; |
| wchar_t *fname = name_info.FileName; |
| /* For i#138 we want the full path so we ignore the short name |
| * returned by get_file_short_name |
| */ |
| if (file_handle != INVALID_HANDLE_VALUE && |
| get_file_short_name(file_handle, &name_info) != NULL) { |
| bool have_name = false; |
| if (convert_NT_to_Dos_path(buf, name_info.FileName, |
| BUFFER_SIZE_ELEMENTS(buf))) { |
| fname = buf; |
| have_name = true; |
| } else if (get_os_version() <= WINDOWS_VERSION_2000 && !non_image) { |
| /* It's normal for NtQueryInformationFile to return a relative path. |
| * For non-images, or for XP+ for all sections, we can get the |
| * absolute path at map time: but for images (or if we don't know |
| * whether image, e.g. for OpenSection) on NT/2K we map in the file |
| * as a non-image to find the name. Kind of expensive, but it's only |
| * for legacy platforms, and option-controlled. |
| */ |
| size_t size = 0; |
| byte *pc = os_map_file(file_handle, &size, 0, NULL, MEMPROT_READ, |
| 0/*not cow or image*/); |
| if (pc == NULL) { |
| /* We don't know what perms the file was opened with. Sometimes |
| * we can only map +x so try that. |
| */ |
| pc = os_map_file(file_handle, &size, 0, NULL, MEMPROT_EXEC, |
| 0/*not cow or image*/); |
| } |
| if (pc != NULL) { |
| NTSTATUS res = get_mapped_file_name(pc, buf, BUFFER_SIZE_BYTES(buf)); |
| if (NT_SUCCESS(res)) { |
| have_name = convert_NT_to_Dos_path |
| (name_info.FileName, buf, |
| BUFFER_SIZE_ELEMENTS(name_info.FileName)); |
| } |
| os_unmap_file(pc, size); |
| } |
| } |
| if (!have_name) { |
| /* i#1180: we get non-drive absolute DOS paths here which naturally |
| * convert_NT_to_Dos_path can't handle (e.g., |
| * "\Windows\Globalization\Sorting\SortDefault.nls"). |
| * We expect to get an NT path at map time on XP+, so we only |
| * warn for 2K- images. |
| */ |
| DODEBUG({ |
| if (get_os_version() <= WINDOWS_VERSION_2000 && !non_image) { |
| STATS_INC(map_unknown_Dos_name); |
| SYSLOG_INTERNAL_WARNING_ONCE("unknown mapfile Dos name"); |
| } |
| }); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\t%s: pre-map, unable to convert NT to Dos path for \"%S\"\n", |
| __FUNCTION__, fname); |
| } |
| section_to_file_add_wide(section_handle, fname); |
| } else { |
| /* We assume that we'll have the file_handle for image sections: |
| * either we'll see a CreateSection w/ a file, or we'll see OpenSection |
| * on a KnownDlls path w/ RootDirectory set. So this is likely a |
| * non-image section, whose backing file we'll query at map time. |
| */ |
| DODEBUG(name_info.FileName[0] = '\0';); |
| } |
| #ifdef DEBUG |
| dcontext->aslr_context.last_app_section_handle = section_handle; |
| #endif |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\tNt{Create,Open}Section: sec handle "PIFX", file "PFX" => \"%S\"\n", |
| section_handle, file_handle, fname); |
| } |
| } |
| |
| /* NtCreateSection */ |
| static void |
| postsys_CreateSection(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| /* a section is an object that can be mmapped */ |
| HANDLE *unsafe_section_handle = (HANDLE*) postsys_param(dcontext, param_base, 0); |
| uint access_mask = (uint) postsys_param(dcontext, param_base, 1); |
| POBJECT_ATTRIBUTES obj = (POBJECT_ATTRIBUTES) postsys_param(dcontext, param_base, 2); |
| void *size = (void *) postsys_param(dcontext, param_base, 3); |
| uint protect = (uint) postsys_param(dcontext, param_base, 4); |
| uint attributes = (uint) postsys_param(dcontext, param_base, 5); |
| HANDLE file_handle = (HANDLE) postsys_param(dcontext, param_base, 6); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtCreateSection protect 0x%x, attributes 0x%x\n", |
| protect, attributes); |
| if (!success) |
| return; |
| |
| postsys_create_or_open_section(dcontext, unsafe_section_handle, file_handle, |
| !TEST(SEC_IMAGE, attributes)); |
| |
| if (TEST(ASLR_DLL, DYNAMO_OPTION(aslr))) { |
| if (TEST(SEC_IMAGE, attributes)) { |
| if (aslr_post_process_create_or_open_section(dcontext, |
| true, /* create */ |
| file_handle, |
| unsafe_section_handle)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: ASLR: NtCreateSection replaced with new section "PIFX"\n", |
| *unsafe_section_handle); |
| } else { |
| /* leaving as is */ |
| } |
| } else { |
| /* ignoring SEC_COMMIT mappings - since SEC_COMMIT is |
| * default it doesn't need to be set */ |
| } |
| } |
| } |
| |
| /* NtOpenSection */ |
| static void |
| postsys_OpenSection(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| /* a section is an object that can be mmapped, here opened by object name */ |
| HANDLE *unsafe_section_handle = (HANDLE*) postsys_param(dcontext, param_base, 0); |
| uint access_mask = (uint) postsys_param(dcontext, param_base, 1); |
| POBJECT_ATTRIBUTES obj_attr = (POBJECT_ATTRIBUTES) |
| postsys_param(dcontext, param_base, 2); |
| HANDLE new_file_handle = INVALID_HANDLE_VALUE; |
| if (!success) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtOpenSection, failed, access 0x%x\n", |
| access_mask); |
| return; |
| } |
| |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtOpenSection opened sh "PIFX", access_mask 0x%x, obj_attr "PFX"\n", |
| *unsafe_section_handle, access_mask, obj_attr); |
| |
| /* If we only wanted short names for -track_module_filenames, |
| * could we use obj_attr->ObjectName->Buffer and not |
| * call aslr_recreate_known_dll_file() at all? |
| */ |
| if ((DYNAMO_OPTION(track_module_filenames) || |
| (TEST(ASLR_DLL, DYNAMO_OPTION(aslr)) && |
| TEST(ASLR_SHARED_CONTENTS, DYNAMO_OPTION(aslr_cache)))) && |
| obj_attr != NULL) { |
| /* need to identify KnownDlls here */ |
| /* FIXME: NtOpenSection doesn't give us section attributes, |
| * and we can't even query them - the only reasonable solution is to |
| * match the directory handle |
| * |
| * FIXME: case 9032 about possibly duplicating the handle if |
| * that is any faster than any other syscalls we're making here |
| */ |
| /* FIXME: we could restrict the check to potential DLLs |
| * based on access_mask, although most users use |
| * SECTION_ALL_ACCESS */ |
| HANDLE root_directory = NULL; |
| bool ok = safe_read(&obj_attr->RootDirectory, |
| sizeof(root_directory), &root_directory); |
| if (ok && root_directory != NULL && |
| aslr_is_handle_KnownDlls(root_directory)) { |
| |
| if (aslr_recreate_known_dll_file(obj_attr, &new_file_handle)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtOpenSection: recreated file handle "PIFX"\n", |
| new_file_handle); |
| } else { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtOpenSection: unable to recreate file handle\n"); |
| } |
| |
| if (TEST(ASLR_DLL, DYNAMO_OPTION(aslr)) && |
| TEST(ASLR_SHARED_CONTENTS, DYNAMO_OPTION(aslr_cache))) { |
| if (aslr_post_process_create_or_open_section(dcontext, |
| false, /* open */ |
| /* recreated file */ |
| new_file_handle, |
| unsafe_section_handle)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: ASLR: NtOpenSection replaced with new section "PIFX"\n", |
| *unsafe_section_handle); |
| } else { |
| /* leaving as is */ |
| } |
| } |
| /* If we're not replacing the section (i.e., not doing ASLR_DLL), |
| * we need new_file_handle for postsys_create_or_open_section so we do |
| * not close here |
| */ |
| } else { |
| /* nothing */ |
| } |
| } |
| if (DYNAMO_OPTION(track_module_filenames)) { |
| postsys_create_or_open_section(dcontext, unsafe_section_handle, |
| new_file_handle, false/*don't know*/); |
| } |
| if (new_file_handle != INVALID_HANDLE_VALUE) |
| close_handle(new_file_handle); |
| } |
| |
| /* NtMapViewOfSection */ |
| static void |
| postsys_MapViewOfSection(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| /* This is what actually allocates a dll into memory */ |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| HANDLE section_handle; /* 0 */ |
| HANDLE process_handle; /* 1 */ |
| void **pbase_unsafe; /* 2 */ |
| uint zerobits; /* 3 */ |
| size_t commit_size; /* 4 */ |
| LARGE_INTEGER *section_offs; /* 5 */ |
| size_t *view_size; /* 6 */ |
| uint inherit_disposition; /* 7 */ |
| uint type; /* 8 */ |
| uint prot; /* 9 */ |
| |
| size_t size; /* safe dereferenced values */ |
| app_pc base; |
| |
| /* only process if we acted on this call in aslr_pre_process_mapview */ |
| if (dcontext->aslr_context.sys_aslr_clobbered) { |
| aslr_post_process_mapview(dcontext); |
| /* preceding call sets mcontext.xax so re-evaluate */ |
| success = NT_SUCCESS(mc->xax); |
| /* reevaluate all system call OUT arguments, since they |
| * may have changed in aslr_post_process_mapview()! |
| */ |
| |
| /* FIXME: registers may not necessarily match state of |
| * mangled system call, but we assume only state->mc.xax matters |
| */ |
| } |
| |
| if (!success) { |
| prot = (uint) postsys_param(dcontext, param_base, 9); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: failed NtMapViewOfSection prot=%s => "PFX"\n", |
| prot_string(prot), mc->xax); |
| |
| return; |
| } |
| |
| section_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| process_handle = (HANDLE) postsys_param(dcontext, param_base, 1); |
| pbase_unsafe = (void *) postsys_param(dcontext, param_base, 2); |
| zerobits = (uint) postsys_param(dcontext, param_base, 3); |
| commit_size = (size_t) postsys_param(dcontext, param_base, 4); |
| section_offs = (LARGE_INTEGER *) postsys_param(dcontext, param_base, 5); |
| view_size = (size_t *) postsys_param(dcontext, param_base, 6); |
| inherit_disposition = (uint) postsys_param(dcontext, param_base, 7); |
| type = (uint) postsys_param(dcontext, param_base, 8); |
| prot = (uint) postsys_param(dcontext, param_base, 9); |
| |
| /* we assume that since syscall succeeded these dereferences are safe |
| * FIXME : could always be multi-thread races though */ |
| size = *view_size; /* ignore commit_size? */ |
| base = *((app_pc *)pbase_unsafe); |
| |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "syscall: NtMapViewOfSection "PFX" size="PIFX" prot=%s => "PFX"\n", |
| base, size, prot_string(prot), mc->xax); |
| |
| if (is_phandle_me(process_handle)) { |
| /* Check if we are looking for LdrpLoadImportModule address */ |
| if (dcontext == early_inject_load_helper_dcontext) { |
| check_for_ldrpLoadImportModule(base, (uint *) mc->xbp); |
| } |
| DOLOG(1, LOG_MEMSTATS, { |
| /* snapshots are heavyweight, so do rarely */ |
| if (size > SNAPSHOT_THRESHOLD) |
| mem_stats_snapshot(); |
| }); |
| #ifdef DGC_DIAGNOSTICS |
| DOLOG(1, LOG_VMAREAS, { |
| dump_callstack(POST_SYSCALL_PC(dcontext), |
| (app_pc) mc->xbp, THREAD, |
| DUMP_NOT_XML); |
| }); |
| #endif |
| RSTATS_INC(num_app_mmaps); |
| if (!DYNAMO_OPTION(thin_client)) { |
| const char *file = NULL; |
| DEBUG_DECLARE(const char *reason = "";) |
| if (DYNAMO_OPTION(track_module_filenames)) { |
| bool unknown = true; |
| /* get_mapped_file_name always gives an absolute path, so it's |
| * preferable to using our section_to_file table. but, |
| * get_mapped_file_name only works on image sections on XP+. |
| * we go ahead and use it on all sections here, even though |
| * we don't use the names of non-image sections, to avoid |
| * warnings below (where we don't know whether image or not). |
| */ |
| wchar_t buf[MAXIMUM_PATH]; |
| /* FIXME: should we heap alloc to avoid these huge buffers */ |
| wchar_t buf2[MAXIMUM_PATH]; |
| NTSTATUS res = get_mapped_file_name(base, buf, BUFFER_SIZE_BYTES(buf)); |
| if (NT_SUCCESS(res)) { |
| if (convert_NT_to_Dos_path(buf2, buf, BUFFER_SIZE_ELEMENTS(buf2))) { |
| file = dr_wstrdup(buf2 HEAPACCT(ACCT_VMAREAS)); |
| } else { |
| file = dr_wstrdup(buf HEAPACCT(ACCT_VMAREAS)); |
| STATS_INC(map_unknown_Dos_name); |
| SYSLOG_INTERNAL_WARNING_ONCE("unknown mapfile Dos name"); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\t%s: WARNING: unable to convert NT to Dos path for \"%S\"\n", |
| __FUNCTION__, buf2); |
| } |
| /* may as well update the table: if already there this is a nop */ |
| section_to_file_add(section_handle, file); |
| unknown = false; |
| } else if (res == STATUS_FILE_INVALID) { |
| /* An anonymous section backed by the pagefile. Should we |
| * verify that its CreateSection was passed NULL for a file? |
| * You can see some of these just starting up calc. They |
| * have names like |
| * "\BaseNamedObjects\CiceroSharedMemDefaultS-1-5-21-1262752155-650278929-1212509807-100" |
| */ |
| unknown = false; |
| DODEBUG(reason = " (pagefile-backed)";); |
| } else { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\tget_mapped_file_name failed error="PFX"\n", res); |
| } |
| if (file == NULL) { |
| file = section_to_file_lookup(section_handle); |
| if (file != NULL) |
| unknown = false; |
| } |
| if (unknown) { |
| /* Since we have a process-wide handle map and we watch |
| * close and duplicate, we should only mess up when handles |
| * are passed via IPC. |
| */ |
| STATS_INC(map_section_mismatch); |
| SYSLOG_INTERNAL_WARNING_ONCE("unknown mapped section "PIFX, |
| section_handle); |
| } |
| } |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "\tNtMapViewOfSection: sec handle "PIFX" == file \"%s\"%s\n", |
| section_handle, file == NULL ? "<null>" : file, reason); |
| process_mmap(dcontext, base, size, true/*map*/, file); |
| if (file != NULL) |
| dr_strfree(file HEAPACCT(ACCT_VMAREAS)); |
| } |
| } else { |
| IPC_ALERT("WARNING: MapViewOfSection on another process"); |
| } |
| } |
| |
| |
| /* NtUnmapViewOfSection */ |
| static void |
| postsys_UnmapViewOfSection(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| /* This is what actually removes a dll from memory */ |
| HANDLE process_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| #ifdef DEBUG |
| if (dcontext->expect_last_syscall_to_fail) { |
| ASSERT(!success); |
| } else { |
| /* FIXME : try to recover if the syscall fails, could re-walk this |
| * region but that gets us in trouble with the stateful policies */ |
| ASSERT_CURIOSITY(success || !is_phandle_me(process_handle)); |
| } |
| #endif |
| /* note that if expected this to fail we wouldn't have really registered, |
| * but we don't keep track in release builds |
| */ |
| if (DYNAMO_OPTION(unloaded_target_exception) && |
| is_phandle_me(process_handle)) { |
| app_pc base = (app_pc) postsys_param(dcontext, param_base, 1); |
| /* We always mark end of unmap no matter what the original |
| * section really was. FIXME: Note we can't get the real_base |
| * of the allocation, unless we keep it in dcontext from |
| * presys_UnmapViewOfSection, but we don't really need it in |
| * release build. We don't care about success or !success |
| * either. Note that this means that if a MEM_MAPPED UnMap |
| * ends before an overlapping MEM_IMAGE UnMap, we will mark |
| * end too early. |
| */ |
| mark_unload_end(base); |
| } |
| } |
| |
| /* NtDuplicateObject */ |
| static void |
| postsys_DuplicateObject(dcontext_t *dcontext, reg_t *param_base, bool success) |
| { |
| if (DYNAMO_OPTION(track_module_filenames) && success) { |
| HANDLE src_process = (HANDLE) postsys_param(dcontext, param_base, 0); |
| HANDLE tgt_process = (HANDLE) postsys_param(dcontext, param_base, 2); |
| if (is_phandle_me(src_process) && is_phandle_me(tgt_process)) { |
| HANDLE src = (HANDLE) postsys_param(dcontext, param_base, 1); |
| HANDLE *dst = (HANDLE*) postsys_param(dcontext, param_base, 3); |
| const char *file = section_to_file_lookup(src); |
| if (file != NULL) { |
| HANDLE dup; |
| if (safe_read(dst, sizeof(dup), &dup)) { |
| /* Should already be converted to Dos path */ |
| section_to_file_add(dup, file); |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 2, |
| "syscall: NtDuplicateObject section handle "PFX" => "PFX"\n", |
| src, dup); |
| } else /* shouldn't happen: syscall succeeded; must be race */ |
| ASSERT_NOT_REACHED(); |
| dr_strfree(file HEAPACCT(ACCT_VMAREAS)); |
| } |
| } else { |
| IPC_ALERT("WARNING: handle via IPC may mess up section-to-handle mapping"); |
| } |
| } |
| } |
| |
| #ifdef CLIENT_INTERFACE |
| /* i#537: sysenter returns to KiFastSystemCallRet from kernel, and returns to DR |
| * from there. We restore the correct app return target and re-execute |
| * KiFastSystemCallRet to make sure client see the code at KiFastSystemCallRet. |
| */ |
| static void |
| restore_for_KiFastSystemCallRet(dcontext_t *dcontext) |
| { |
| reg_t adjust_esp; |
| ASSERT(get_syscall_method() == SYSCALL_METHOD_SYSENTER && |
| KiFastSystemCallRet_address != NULL); |
| #ifdef CLIENT_INTERFACE |
| /* We don't want to do this adjustment until after the final syscall |
| * in any invoke-another sequence (i#1210) |
| */ |
| if (instrument_invoke_another_syscall(dcontext)) |
| return; |
| #endif |
| /* If this thread is native, don't disrupt the return-to-native */ |
| if (!dcontext->thread_record->under_dynamo_control) |
| return; |
| adjust_esp = get_mcontext(dcontext)->xsp - XSP_SZ; |
| *(app_pc *)adjust_esp = dcontext->asynch_target; |
| get_mcontext(dcontext)->xsp = adjust_esp; |
| dcontext->asynch_target = KiFastSystemCallRet_address; |
| } |
| #endif |
| |
| /* NOTE : no locks can be grabbed on the path to SuspendThread handling code */ |
| void post_system_call(dcontext_t *dcontext) |
| { |
| /* registers have been clobbered, so grab key values that were |
| * saved in pre_system_call |
| */ |
| int sysnum = dcontext->sys_num; |
| reg_t *param_base = dcontext->sys_param_base; |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| bool success = NT_SUCCESS(mc->xax); |
| where_am_i_t old_whereami = dcontext->whereami; |
| KSTART(post_syscall); |
| dcontext->whereami = WHERE_SYSCALL_HANDLER; |
| DODEBUG({ dcontext->post_syscall = true; }); |
| |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "post syscall: sysnum="PFX", params @"PFX", result="PFX"\n", |
| sysnum, param_base, mc->xax); |
| |
| if (sysnum == syscalls[SYS_GetContextThread]) { |
| postsys_GetContextThread(dcontext, param_base, success); |
| } |
| else if (sysnum == syscalls[SYS_SuspendThread]) { |
| postsys_SuspendThread(dcontext, param_base, success); |
| } |
| else if (sysnum == syscalls[SYS_SetContextThread]) { |
| HANDLE thread_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| ASSERT(tid != 0xFFFFFFFF); |
| /* FIXME : we modified the passed in context, we should restore it |
| * to app state (same for SYS_Continue though is more difficult there) |
| */ |
| mutex_lock(&thread_initexit_lock); /* need lock to lookup thread */ |
| if (intercept_asynch_for_thread(tid, false/*no unknown threads*/)) { |
| /* Case 10101: we shouldn't get here since we now skip the system call, |
| * unless it should fail for permission issues */ |
| ASSERT(dcontext->expect_last_syscall_to_fail); |
| /* must wake up thread so it can go to nt_continue_dynamo_start */ |
| nt_thread_resume(thread_handle, NULL); |
| } |
| mutex_unlock(&thread_initexit_lock); /* need lock to lookup thread */ |
| } |
| #ifdef CLIENT_INTERFACE |
| else if (sysnum == syscalls[SYS_QueryInformationThread]) { |
| postsys_QueryInformationThread(dcontext, param_base, success); |
| } |
| #endif |
| else if (sysnum == syscalls[SYS_AllocateVirtualMemory] || |
| /* i#899: new win8 syscall w/ similar params to NtAllocateVirtualMemory */ |
| sysnum == syscalls[SYS_Wow64AllocateVirtualMemory64]) { |
| KSTART(post_syscall_alloc); |
| postsys_AllocateVirtualMemory(dcontext, param_base, success, sysnum); |
| KSTOP(post_syscall_alloc); |
| } |
| else if (sysnum == syscalls[SYS_QueryVirtualMemory]) { |
| postsys_QueryVirtualMemory(dcontext, param_base, success); |
| } |
| else if (sysnum == syscalls[SYS_CreateSection]) { |
| postsys_CreateSection(dcontext, param_base, success); |
| } |
| else if (sysnum == syscalls[SYS_OpenSection]) { |
| postsys_OpenSection(dcontext, param_base, success); |
| } |
| else if (sysnum == syscalls[SYS_MapViewOfSection]) { |
| KSTART(post_syscall_map); |
| postsys_MapViewOfSection(dcontext, param_base, success); |
| KSTOP(post_syscall_map); |
| } |
| else if (sysnum == syscalls[SYS_CreateProcess]) { |
| HANDLE *process_handle = (HANDLE *) postsys_param(dcontext, param_base, 0); |
| uint access_mask = (uint) postsys_param(dcontext, param_base, 1); |
| uint attributes = (uint) postsys_param(dcontext, param_base, 2); |
| uint inherit_from= (uint) postsys_param(dcontext, param_base, 3); |
| BOOLEAN inherit = (BOOLEAN) postsys_param(dcontext, param_base, 4); |
| HANDLE section_handle = (HANDLE) postsys_param(dcontext, param_base, 5); |
| HANDLE debug_handle = (HANDLE) postsys_param(dcontext, param_base, 6); |
| HANDLE exception_handle = (HANDLE) postsys_param(dcontext, param_base, 7); |
| HANDLE proc_handle; |
| |
| DOLOG(1, LOG_SYSCALLS, { |
| app_pc base = (app_pc) get_section_address(section_handle); |
| |
| LOG(THREAD, LOG_SYSCALLS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall post: NtCreateProcess section @"PFX"\n", base); |
| }); |
| if (safe_read(process_handle, sizeof(proc_handle), &proc_handle)) |
| maybe_inject_into_process(dcontext, proc_handle, NULL); |
| } |
| else if (sysnum == syscalls[SYS_CreateProcessEx]) { |
| HANDLE *process_handle = (HANDLE *) postsys_param(dcontext, param_base, 0); |
| uint access_mask = (uint) postsys_param(dcontext, param_base, 1); |
| uint attributes = (uint) postsys_param(dcontext, param_base, 2); |
| uint inherit_from= (uint) postsys_param(dcontext, param_base, 3); |
| BOOLEAN inherit = (BOOLEAN) postsys_param(dcontext, param_base, 4); |
| HANDLE section_handle = (HANDLE) postsys_param(dcontext, param_base, 5); |
| HANDLE debug_handle = (HANDLE) postsys_param(dcontext, param_base, 6); |
| HANDLE exception_handle = (HANDLE) postsys_param(dcontext, param_base, 7); |
| HANDLE proc_handle; |
| |
| /* according to metasploit, others type as HANDLE unknown etc. */ |
| uint job_member_level = (uint) postsys_param(dcontext, param_base, 8); |
| |
| DOLOG(1, LOG_SYSCALLS, { |
| app_pc base = (app_pc) get_section_address(section_handle); |
| |
| LOG(THREAD, LOG_SYSCALLS, IF_DGCDIAG_ELSE(1, 2), |
| "syscall: NtCreateProcessEx section @"PFX"\n", base); |
| }); |
| if (safe_read(process_handle, sizeof(proc_handle), &proc_handle)) |
| maybe_inject_into_process(dcontext, proc_handle, NULL); |
| } |
| else if (sysnum == syscalls[SYS_CreateUserProcess]) { |
| postsys_CreateUserProcess(dcontext, param_base, success); |
| } |
| else if (sysnum == syscalls[SYS_UnmapViewOfSection]) { |
| postsys_UnmapViewOfSection(dcontext, param_base, success); |
| } |
| else if (sysnum == syscalls[SYS_DuplicateObject]) { |
| postsys_DuplicateObject(dcontext, param_base, success); |
| #ifdef DEBUG |
| /* Check to see if any system calls for which we did non-reversible |
| * processing in pre_system_call() failed. FIXME : handle failure |
| * cases as needed */ |
| /* FIXME : because of our stateless apc handling we can't check |
| * SYS_Continue for success (all syscalls interrupted by an APC will |
| * look like a continue at post) |
| */ |
| } else if (sysnum == syscalls[SYS_CallbackReturn]) { |
| /* should never get here, also ref case 4121, except for |
| * STATUS_CALLBACK_POP_STACK (case 10579) */ |
| ASSERT_CURIOSITY((NTSTATUS)postsys_param(dcontext, param_base, 2) == |
| STATUS_CALLBACK_POP_STACK); |
| /* FIXME: should provide a routine to swap the dcontexts back so we can |
| * handle any future cases like case 10579 */ |
| } else if (sysnum == syscalls[SYS_TerminateProcess]) { |
| HANDLE process_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| NTSTATUS exit_status = (NTSTATUS) postsys_param(dcontext, param_base, 1); |
| /* FIXME : no way to recover if syscall fails and handle is 0 or us */ |
| /* Don't allow success && handle == us since we should never get here |
| * in that case */ |
| ASSERT((process_handle == 0 && success) || |
| !is_phandle_me(process_handle)); |
| } else if (sysnum == syscalls[SYS_TerminateThread]) { |
| HANDLE thread_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| ASSERT(thread_handle != 0); /* 0 => current thread */ |
| if (thread_handle != 0) { |
| thread_id_t tid = thread_id_from_handle(thread_handle); |
| process_id_t pid = process_id_from_thread_handle(thread_handle); |
| ASSERT(tid != get_thread_id()); /* not current thread */ |
| /* FIXME : if is thread in this process and syscall fails then |
| * no way to recover since we already cleaned up the thread */ |
| /* Don't allow success && handle == us since we should never get |
| * here in that case */ |
| ASSERT(success || |
| tid == 0xFFFFFFFF /* prob. bad/incorrect type handle */ || |
| is_thread_exited(thread_handle) == THREAD_EXITED || |
| !is_pid_me(pid)); |
| if (success && !is_pid_me(pid)) { |
| IPC_ALERT("Warning: NtTerminateThread on thread tid="PFX" " |
| "in other process pid="PFX, tid, pid); |
| } |
| } |
| } else if (sysnum == syscalls[SYS_CreateThread]) { |
| HANDLE process_handle= (HANDLE) postsys_param(dcontext, param_base, 3); |
| CONTEXT *cxt = (CONTEXT *) postsys_param(dcontext, param_base, 5); |
| /* FIXME : we are going to read cxt, this is potentially unsafe */ |
| if (is_first_thread_in_new_process(process_handle, cxt)) { |
| /* we might have tried to inject into the process with this |
| * new thread, assert curiosity to see if this ever fails */ |
| ASSERT_CURIOSITY(success); |
| } |
| } else if (sysnum == syscalls[SYS_FreeVirtualMemory]) { |
| HANDLE process_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| void **pbase = (void **) postsys_param(dcontext, param_base, 1); |
| size_t *psize = (size_t *) postsys_param(dcontext, param_base, 2); |
| uint type = (uint) postsys_param(dcontext, param_base, 3); |
| if (dcontext->expect_last_syscall_to_fail) { |
| ASSERT(!success); |
| } else { |
| /* FIXME i#148: try to recover if the syscall fails, could re-walk this |
| * region but that gets us in trouble with the stateful policies */ |
| ASSERT_CURIOSITY_ONCE(success || !is_phandle_me(process_handle));; |
| } |
| } else if (sysnum == syscalls[SYS_ProtectVirtualMemory]) { |
| HANDLE process_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| if (dcontext->expect_last_syscall_to_fail) { |
| ASSERT(!success); |
| } else { |
| /* FIXME : try to recover if the syscall fails, could re-walk this |
| * region but that gets us in trouble with the stateful policies */ |
| ASSERT_CURIOSITY(success || !is_phandle_me(process_handle)); |
| } |
| } else if (sysnum == syscalls[SYS_FlushInstructionCache]) { |
| HANDLE process_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| /* Even if this system call fails, doesn't affect our correctness, but |
| * lets see if this ever fails, slight false negative risk if it does */ |
| ASSERT_CURIOSITY(success || !is_phandle_me(process_handle)); |
| } else if (sysnum == syscalls[SYS_MapUserPhysicalPages]) { |
| HANDLE process_handle = (HANDLE) postsys_param(dcontext, param_base, 0); |
| /* Even if this system call fails, doesn't affect our correctness, but |
| * lets see if this ever fails, slight false negative risk if it does */ |
| if (dcontext->expect_last_syscall_to_fail) { |
| ASSERT(!success); |
| } else { |
| ASSERT_CURIOSITY(success || !is_phandle_me(process_handle)); |
| } |
| #endif /* DEBUG */ |
| } |
| |
| |
| #ifdef CLIENT_INTERFACE |
| /* The instrument_post_syscall should be called after DR finishes all |
| * its operations. Xref to i#1. |
| */ |
| /* i#202: ignore native syscalls in early_inject_init() */ |
| if (dynamo_initialized) |
| instrument_post_syscall(dcontext, sysnum); |
| |
| /* i#537 restore app stack for KiFastSystemCallRet |
| * this could be in handle_post_system_call@dispatch.c, but seems better |
| * here since it is windows specific. |
| */ |
| if (get_syscall_method() == SYSCALL_METHOD_SYSENTER && |
| KiFastSystemCallRet_address != NULL) |
| restore_for_KiFastSystemCallRet(dcontext); |
| #endif |
| |
| /* stats lock grabbing ok here, any synch with suspended threads taken |
| * care of already */ |
| RSTATS_INC(post_syscall); |
| DOSTATS({ |
| if (ignorable_system_call(sysnum, NULL, dcontext)) |
| STATS_INC(post_syscall_ignorable); |
| }); |
| dcontext->whereami = old_whereami; |
| DODEBUG({ dcontext->post_syscall = false; }); |
| KSTOP(post_syscall); |
| } |
| |
| /*************************************************************************** |
| * SYSTEM CALL API |
| */ |
| |
| #ifdef CLIENT_INTERFACE |
| DR_API |
| reg_t |
| dr_syscall_get_param(void *drcontext, int param_num) |
| { |
| dcontext_t *dcontext = (dcontext_t *) drcontext; |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| /* if we supported this from post-syscall we would need to |
| * get dcontext->sys_param_base() and call postsys_param() -- but |
| * then it would be confusing vs client checking its set param |
| */ |
| reg_t *param_base = pre_system_call_param_base(mc); |
| CLIENT_ASSERT(dcontext->client_data->in_pre_syscall, |
| "dr_syscall_get_param() can only be called from pre-syscall event"); |
| return sys_param(dcontext, param_base, param_num); |
| } |
| |
| DR_API |
| void |
| dr_syscall_set_param(void *drcontext, int param_num, reg_t new_value) |
| { |
| dcontext_t *dcontext = (dcontext_t *) drcontext; |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| reg_t *param_base; |
| CLIENT_ASSERT(dcontext->client_data->in_pre_syscall || |
| dcontext->client_data->in_post_syscall, |
| "dr_syscall_set_param() can only be called from a syscall event"); |
| if (dcontext->client_data->in_pre_syscall) |
| param_base = pre_system_call_param_base(mc); |
| else |
| param_base = dcontext->sys_param_base; |
| *sys_param_addr(dcontext, param_base, param_num) = new_value; |
| } |
| |
| DR_API |
| reg_t |
| dr_syscall_get_result(void *drcontext) |
| { |
| dcontext_t *dcontext = (dcontext_t *) drcontext; |
| CLIENT_ASSERT(dcontext->client_data->in_post_syscall, |
| "dr_syscall_get_result() can only be called from post-syscall event"); |
| return get_mcontext(dcontext)->xax; |
| } |
| |
| DR_API |
| bool |
| dr_syscall_get_result_ex(void *drcontext, dr_syscall_result_info_t *info INOUT) |
| { |
| dcontext_t *dcontext = (dcontext_t *) drcontext; |
| CLIENT_ASSERT(dcontext->client_data->in_post_syscall, |
| "only call dr_syscall_get_result_ex() from post-syscall event"); |
| CLIENT_ASSERT(info != NULL, "invalid parameter"); |
| CLIENT_ASSERT(info->size == sizeof(*info), "invalid dr_syscall_result_info_t size"); |
| if (info->size != sizeof(*info)) |
| return false; |
| info->value = dr_syscall_get_result(drcontext); |
| /* We document not to rely on this for non-ntoskrnl syscalls */ |
| info->succeeded = NT_SUCCESS(info->value); |
| if (info->use_high) |
| info->high = 0; |
| if (info->use_errno) |
| info->errno_value = (uint) info->value; |
| return true; |
| } |
| |
| DR_API |
| void |
| dr_syscall_set_result(void *drcontext, reg_t value) |
| { |
| dcontext_t *dcontext = (dcontext_t *) drcontext; |
| CLIENT_ASSERT(dcontext->client_data->in_pre_syscall || |
| dcontext->client_data->in_post_syscall, |
| "dr_syscall_set_result() can only be called from a syscall event"); |
| SET_RETURN_VAL(dcontext, value); |
| } |
| |
| DR_API |
| bool |
| dr_syscall_set_result_ex(void *drcontext, dr_syscall_result_info_t *info) |
| { |
| dcontext_t *dcontext = (dcontext_t *) drcontext; |
| CLIENT_ASSERT(dcontext->client_data->in_pre_syscall || |
| dcontext->client_data->in_post_syscall, |
| "only call dr_syscall_set_result_ex() from a syscall event"); |
| CLIENT_ASSERT(info != NULL, "invalid parameter"); |
| CLIENT_ASSERT(info->size == sizeof(*info), "invalid dr_syscall_result_info_t size"); |
| if (info->size != sizeof(*info)) |
| return false; |
| if (info->use_high) |
| return false; /* not supported */ |
| /* we ignore info->succeeded */ |
| if (info->use_errno) |
| SET_RETURN_VAL(dcontext, info->errno_value); |
| else |
| SET_RETURN_VAL(dcontext, info->value); |
| return true; |
| } |
| |
| DR_API |
| void |
| dr_syscall_set_sysnum(void *drcontext, int new_num) |
| { |
| dcontext_t *dcontext = (dcontext_t *) drcontext; |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| CLIENT_ASSERT(dcontext->client_data->in_pre_syscall || |
| dcontext->client_data->in_post_syscall, |
| "dr_syscall_set_sysnum() can only be called from a syscall event"); |
| mc->xax = new_num; |
| } |
| |
| DR_API |
| void |
| dr_syscall_invoke_another(void *drcontext) |
| { |
| dcontext_t *dcontext = (dcontext_t *) drcontext; |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| CLIENT_ASSERT(dcontext->client_data->in_post_syscall, |
| "dr_syscall_invoke_another() can only be called from post-syscall event"); |
| LOG(THREAD, LOG_SYSCALLS, 2, "invoking additional syscall on client request\n"); |
| /* Dispatch checks this flag immediately upon return from handle_post_system_call() |
| * and if set it invokes handle_system_call(). |
| */ |
| dcontext->client_data->invoke_another_syscall = true; |
| if (get_syscall_method() == SYSCALL_METHOD_SYSENTER) { |
| /* Since we're not regaining control immediately after sysenter, |
| * need to push regain-control retaddr on stack, and then copy esp to edx. |
| */ |
| mc->xsp -= XSP_SZ; |
| /* put the post-call-to-vsyscall address, currently in asynch_target, back |
| * on stack, and set asynch_target back to post-sysenter pc (will be put |
| * into next_tag back in handle_post_system_call()) |
| */ |
| *((app_pc *)mc->xsp) = dcontext->asynch_target; |
| dcontext->asynch_target = vsyscall_syscall_end_pc; |
| mc->xdx = mc->xsp; |
| } |
| else if (get_syscall_method() == SYSCALL_METHOD_WOW64) { |
| if (get_os_version() == WINDOWS_VERSION_7) { |
| /* emulate win7's add 4,esp after the call* in the syscall wrapper */ |
| mc->xsp += XSP_SZ; |
| } |
| if (syscall_uses_edx_param_base()) { |
| /* perform: lea edx,[esp+0x4] */ |
| mc->xdx = mc->xsp + XSP_SZ; |
| } |
| } |
| else if (get_syscall_method() == SYSCALL_METHOD_INT) { |
| if (syscall_uses_edx_param_base()) { |
| /* perform: lea edx,[esp+0x4] */ |
| mc->xdx = mc->xsp + XSP_SZ; |
| } |
| } |
| # ifdef X64 |
| else if (get_syscall_method() == SYSCALL_METHOD_SYSCALL) { |
| /* we could instead have sys_param_addr() use r10, like we do on linux */ |
| mc->r10 = mc->xcx; |
| } |
| # endif |
| } |
| |
| DR_API |
| bool |
| dr_syscall_intercept_natively(const char *name, int sysnum, int num_args, |
| int wow64_idx) |
| { |
| uint i, idx; |
| if (syscall_extra_idx >= CLIENT_EXTRA_TRAMPOLINE) |
| return false; |
| if (dynamo_initialized) |
| return false; |
| /* see whether we already intercept it */ |
| for (i = 0; i < SYS_MAX + syscall_extra_idx; i++) { |
| if (intercept_native_syscall(i) && strcmp(syscall_names[i], name) == 0) |
| return true; |
| } |
| if (get_proc_address(get_ntdll_base(), name) == NULL) |
| return false; |
| /* no lock needed since only supported during dr_init */ |
| idx = SYS_MAX + syscall_extra_idx; |
| syscall_names[idx] = name; |
| syscalls[idx] = sysnum; |
| syscall_argsz[idx] = num_args * 4; |
| if (wow64_index != NULL) |
| wow64_index[idx] = wow64_idx; |
| syscall_requires_action[idx] = true; |
| syscall_extra_idx++; |
| /* some syscalls we just don't support intercepting */ |
| if (!intercept_native_syscall(idx)) { |
| LOG(GLOBAL, LOG_SYSCALLS, 2, "%s: %s is not interceptable!\n", |
| __FUNCTION__, name); |
| syscall_extra_idx--; |
| return false; |
| } |
| LOG(GLOBAL, LOG_SYSCALLS, 2, "%s: intercepting %s as index %d\n", |
| __FUNCTION__, name, idx); |
| return true; |
| } |
| #endif /* CLIENT_INTERFACE */ |