| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "hook_util.h" |
| |
| #include <versionhelpers.h> // windows.h must be before |
| |
| #include "base/win/pe_image.h" |
| #include "chrome_elf/nt_registry/nt_registry.h" // utils |
| #include "sandbox/win/src/interception_internal.h" |
| #include "sandbox/win/src/internal_types.h" |
| #include "sandbox/win/src/sandbox_utils.h" |
| #include "sandbox/win/src/service_resolver.h" |
| |
| namespace { |
| |
| //------------------------------------------------------------------------------ |
| // Common hooking utility functions - LOCAL |
| //------------------------------------------------------------------------------ |
| |
| // Change the page protections to writable, copy the data, |
| // restore protections. Returns a winerror code. |
| DWORD PatchMem(void* target, void* new_bytes, size_t length) { |
| if (target == nullptr || new_bytes == nullptr || length == 0) |
| return ERROR_INVALID_PARAMETER; |
| |
| // Preserve executable state. |
| MEMORY_BASIC_INFORMATION memory_info = {}; |
| if (!::VirtualQuery(target, &memory_info, sizeof(memory_info))) { |
| return GetLastError(); |
| } |
| |
| DWORD is_executable = (PAGE_EXECUTE | PAGE_EXECUTE_READ | |
| PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) & |
| memory_info.Protect; |
| |
| // Make target writeable. |
| DWORD old_page_protection = 0; |
| if (!::VirtualProtect(target, length, |
| is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, |
| &old_page_protection)) { |
| return GetLastError(); |
| } |
| |
| // Write the data. |
| ::memcpy(target, new_bytes, length); |
| |
| // Restore old page protection. |
| if (!::VirtualProtect(target, length, old_page_protection, |
| &old_page_protection)) { |
| // Yes, this could fail. However, memory was already patched. |
| #ifdef _DEBUG |
| assert(false); |
| #endif // _DEBUG |
| } |
| |
| return NO_ERROR; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Import Address Table hooking support - LOCAL |
| //------------------------------------------------------------------------------ |
| |
| void* GetIATFunctionPtr(IMAGE_THUNK_DATA* iat_thunk) { |
| if (iat_thunk == nullptr) |
| return nullptr; |
| |
| // Works around the 64 bit portability warning: |
| // The Function member inside IMAGE_THUNK_DATA is really a pointer |
| // to the IAT function. IMAGE_THUNK_DATA correctly maps to IMAGE_THUNK_DATA32 |
| // or IMAGE_THUNK_DATA64 for correct pointer size. |
| union FunctionThunk { |
| IMAGE_THUNK_DATA thunk; |
| void* pointer; |
| } iat_function; |
| |
| iat_function.thunk = *iat_thunk; |
| return iat_function.pointer; |
| } |
| |
| // Used to pass target function information during pe_image enumeration. |
| struct IATHookFunctionInfo { |
| bool finished_operation; |
| const char* imported_from_module; |
| const char* function_name; |
| void* new_function; |
| void** old_function; |
| IMAGE_THUNK_DATA** iat_thunk; |
| DWORD return_code; |
| }; |
| |
| // Callback function for pe_image enumeration. This function is called from |
| // within PEImage::EnumOneImportChunk(). |
| // NOTE: Returning true means continue enumerating. False means stop. |
| bool IATFindHookFuncCallback(const base::win::PEImage& image, |
| const char* module, |
| DWORD ordinal, |
| const char* import_name, |
| DWORD hint, |
| IMAGE_THUNK_DATA* iat, |
| void* cookie) { |
| IATHookFunctionInfo* hook_func_info = |
| reinterpret_cast<IATHookFunctionInfo*>(cookie); |
| if (hook_func_info == nullptr) |
| return false; |
| |
| // Check for the right module. |
| if (module == nullptr || |
| ::strnicmp(module, hook_func_info->imported_from_module, |
| ::strlen(module)) != 0) |
| return true; |
| |
| // Check for the right function. |
| if (import_name == nullptr || |
| ::strnicmp(import_name, hook_func_info->function_name, |
| ::strlen(import_name)) != 0) |
| return true; |
| |
| // At this point, the target function was found. Even if something fails now, |
| // don't do any further enumerating. |
| hook_func_info->finished_operation = true; |
| |
| // This is it. Do the hook! |
| // 1) Save the old function pointer. |
| *(hook_func_info->old_function) = GetIATFunctionPtr(iat); |
| |
| // 2) Save the IAT thunk. |
| *(hook_func_info->iat_thunk) = iat; |
| |
| // 3) Sanity check the pointer sizes (architectures). |
| if (sizeof(iat->u1.Function) != sizeof(hook_func_info->new_function)) { |
| hook_func_info->return_code = ERROR_BAD_ENVIRONMENT; |
| #ifdef _DEBUG |
| assert(false); |
| #endif // _DEBUG |
| return false; |
| } |
| |
| // 4) Sanity check that the new hook function is not actually the |
| // same as the existing function for this import! |
| if (*(hook_func_info->old_function) == hook_func_info->new_function) { |
| hook_func_info->return_code = ERROR_INVALID_FUNCTION; |
| #ifdef _DEBUG |
| assert(false); |
| #endif // _DEBUG |
| return false; |
| } |
| |
| // 5) Patch the function pointer. |
| hook_func_info->return_code = |
| PatchMem(&(iat->u1.Function), &(hook_func_info->new_function), |
| sizeof(hook_func_info->new_function)); |
| |
| return false; |
| } |
| |
| // Applies an import-address-table hook. Returns a system winerror.h code. |
| // Call RemoveIATHook() with |new_function|, |old_function| and |iat_thunk| |
| // to remove the hook. |
| DWORD ApplyIATHook(HMODULE module_handle, |
| const char* imported_from_module, |
| const char* function_name, |
| void* new_function, |
| void** old_function, |
| IMAGE_THUNK_DATA** iat_thunk) { |
| base::win::PEImage target_image(module_handle); |
| if (!target_image.VerifyMagic()) |
| return ERROR_INVALID_PARAMETER; |
| |
| IATHookFunctionInfo hook_info = {false, |
| imported_from_module, |
| function_name, |
| new_function, |
| old_function, |
| iat_thunk, |
| ERROR_PROC_NOT_FOUND}; |
| |
| // First go through the IAT. If we don't find the import we are looking |
| // for in IAT, search delay import table. |
| target_image.EnumAllImports(IATFindHookFuncCallback, &hook_info); |
| if (!hook_info.finished_operation) { |
| target_image.EnumAllDelayImports(IATFindHookFuncCallback, &hook_info); |
| } |
| |
| return hook_info.return_code; |
| } |
| |
| // Removes an import-address-table hook. Returns a system winerror.h code. |
| DWORD RemoveIATHook(void* intercept_function, |
| void* original_function, |
| IMAGE_THUNK_DATA* iat_thunk) { |
| if (GetIATFunctionPtr(iat_thunk) != intercept_function) |
| // Someone else has messed with the same target. Cannot unpatch. |
| return ERROR_INVALID_FUNCTION; |
| |
| return PatchMem(&(iat_thunk->u1.Function), &original_function, |
| sizeof(original_function)); |
| } |
| |
| } // namespace |
| |
| namespace elf_hook { |
| |
| //------------------------------------------------------------------------------ |
| // System Service hooking support |
| //------------------------------------------------------------------------------ |
| |
| sandbox::ServiceResolverThunk* HookSystemService(bool relaxed) { |
| // Create a thunk via the appropriate ServiceResolver instance. |
| sandbox::ServiceResolverThunk* thunk = nullptr; |
| |
| // No hooking on unsupported OS versions. |
| if (!::IsWindows7OrGreater()) |
| return thunk; |
| |
| // Pseudo-handle, no need to close. |
| HANDLE current_process = ::GetCurrentProcess(); |
| |
| #if defined(_WIN64) |
| // ServiceResolverThunk can handle all the formats in 64-bit (instead only |
| // handling one like it does in 32-bit versions). |
| thunk = new sandbox::ServiceResolverThunk(current_process, relaxed); |
| #else |
| if (nt::IsCurrentProcWow64()) { |
| if (::IsWindows10OrGreater()) |
| thunk = new sandbox::Wow64W10ResolverThunk(current_process, relaxed); |
| else if (::IsWindows8OrGreater()) |
| thunk = new sandbox::Wow64W8ResolverThunk(current_process, relaxed); |
| else |
| thunk = new sandbox::Wow64ResolverThunk(current_process, relaxed); |
| } else if (::IsWindows8OrGreater()) { |
| thunk = new sandbox::Win8ResolverThunk(current_process, relaxed); |
| } else { |
| thunk = new sandbox::ServiceResolverThunk(current_process, relaxed); |
| } |
| #endif |
| |
| return thunk; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Import Address Table hooking support |
| //------------------------------------------------------------------------------ |
| |
| IATHook::IATHook() |
| : intercept_function_(nullptr), |
| original_function_(nullptr), |
| iat_thunk_(nullptr) {} |
| |
| IATHook::~IATHook() { |
| if (intercept_function_ != nullptr) { |
| if (Unhook() != NO_ERROR) { |
| #ifdef _DEBUG |
| assert(false); |
| #endif // _DEBUG |
| } |
| } |
| } |
| |
| DWORD IATHook::Hook(HMODULE module, |
| const char* imported_from_module, |
| const char* function_name, |
| void* new_function) { |
| if ((module == 0 || module == INVALID_HANDLE_VALUE) || |
| imported_from_module == nullptr || function_name == nullptr || |
| new_function == nullptr) |
| return ERROR_INVALID_PARAMETER; |
| |
| // Only hook once per object, to ensure unhook. |
| if (intercept_function_ != nullptr || original_function_ != nullptr || |
| iat_thunk_ != nullptr) |
| return ERROR_SHARING_VIOLATION; |
| |
| DWORD winerror = ApplyIATHook(module, imported_from_module, function_name, |
| new_function, &original_function_, &iat_thunk_); |
| if (winerror == NO_ERROR) |
| intercept_function_ = new_function; |
| |
| return winerror; |
| } |
| |
| DWORD IATHook::Unhook() { |
| if (intercept_function_ == nullptr || original_function_ == nullptr || |
| iat_thunk_ == nullptr) |
| return ERROR_INVALID_PARAMETER; |
| |
| DWORD winerror = |
| RemoveIATHook(intercept_function_, original_function_, iat_thunk_); |
| |
| intercept_function_ = nullptr; |
| original_function_ = nullptr; |
| iat_thunk_ = nullptr; |
| |
| return winerror; |
| } |
| |
| } // namespace elf_hook |