| 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..e16330d9975e54f34242f9d0cacd8d4127edbb07
|
| --- /dev/null
|
| +++ b/chrome_elf/hook_util/hook_util.cc
|
| @@ -0,0 +1,334 @@
|
| +// 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;
|
| +#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) {
|
| +#ifdef _DEBUG
|
| + assert(false);
|
| +#endif // _DEBUG
|
| + // 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 (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) {
|
| + 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) {
|
| +#ifdef _DEBUG
|
| + assert(false);
|
| +#endif //_DEBUG
|
| + 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_);
|
| +#ifdef _DEBUG
|
| + if (winerror != NO_ERROR)
|
| + assert(false);
|
| +#endif //_DEBUG
|
| +
|
| + intercept_function_ = nullptr;
|
| + original_function_ = nullptr;
|
| + iat_thunk_ = nullptr;
|
| +
|
| + return winerror;
|
| +}
|
| +
|
| +} // namespace elf_hook
|
|
|