Chromium Code Reviews| Index: chrome_elf/hook_util/hook_util.cc |
| diff --git a/chrome_elf/hook_util/hook_util.cc b/chrome_elf/hook_util/hook_util.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b95cf4eff7cdd8a198679f2813626a1546e0f65f |
| --- /dev/null |
| +++ b/chrome_elf/hook_util/hook_util.cc |
| @@ -0,0 +1,313 @@ |
| +// 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 "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 |
| +//------------------------------------------------------------------------------ |
| + |
| +#if !defined(_WIN64) |
| +// Whether a process is running under WOW64 (the wrapper that allows 32-bit |
| +// processes to run on 64-bit versions of Windows). This will return |
| +// WOW64_DISABLED for both "32-bit Chrome on 32-bit Windows" and "64-bit |
| +// Chrome on 64-bit Windows". WOW64_UNKNOWN means "an error occurred", e.g. |
| +// the process does not have sufficient access rights to determine this. |
| +enum WOW64Status { |
| + WOW64_DISABLED, |
| + WOW64_ENABLED, |
| + WOW64_UNKNOWN, |
| +}; |
| + |
| +WOW64Status GetWOW64StatusForCurrentProcess() { |
| + typedef BOOL(WINAPI * IsWow64ProcessFunc)(HANDLE, PBOOL); |
| + IsWow64ProcessFunc is_wow64_process = reinterpret_cast<IsWow64ProcessFunc>( |
| + GetProcAddress(GetModuleHandle(L"kernel32.dll"), "IsWow64Process")); |
| + if (!is_wow64_process) |
| + return WOW64_DISABLED; |
| + BOOL is_wow64 = FALSE; |
| + if (!is_wow64_process(GetCurrentProcess(), &is_wow64)) |
| + return WOW64_UNKNOWN; |
| + return is_wow64 ? WOW64_ENABLED : WOW64_DISABLED; |
| +} |
| +#endif // !defined(_WIN64) |
| + |
| +// 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; |
| + return false; |
| + } |
| + |
| + // 4) 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_GEN_FAILURE}; |
| + |
| + // 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. |
| +#ifdef _DEBUG |
| + assert(false); |
| +#endif // _DEBUG |
| + 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 (GetWOW64StatusForCurrentProcess() == WOW64_ENABLED) { |
| + 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) { |
|
robertshield
2016/08/03 04:52:45
It looks like this method shouldn't be called twic
penny
2016/08/04 18:55:35
Done.
|
| + if ((module == 0 || module == INVALID_HANDLE_VALUE) || |
| + imported_from_module == nullptr || function_name == nullptr || |
| + new_function == nullptr) |
| + return ERROR_INVALID_PARAMETER; |
| + |
| + DWORD winerror = ApplyIATHook(module, imported_from_module, function_name, |
| + new_function, &original_function_, &iat_thunk_); |
| + if (winerror == NO_ERROR) { |
| + intercept_function_ = new_function; |
| +#ifdef _DEBUG |
| + if (original_function_ == new_function) |
|
robertshield
2016/08/03 04:52:45
If this is an error condition, please consider add
penny
2016/08/04 18:55:35
I've added a check in IATFindHookFuncCallback() (w
|
| + assert(false); |
| +#endif //_DEBUG |
| + } |
| + |
| + return winerror; |
| +} |
| + |
| +DWORD IATHook::Unhook() { |
| + DWORD winerror = |
| + RemoveIATHook(intercept_function_, original_function_, iat_thunk_); |
| +#ifdef _DEBUG |
| + if (winerror != NO_ERROR) |
| + assert(false); |
| +#endif //_DEBUG |
| + |
| + intercept_function_ = nullptr; |
| + original_function_ = nullptr; |
| + iat_thunk_ = nullptr; |
| + |
| + return winerror; |
| +} |
| + |
| +} // namespace elf_hook |