pennymac | 5446d89 | 2016-08-27 10:45:12 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "hook_util.h" |
| 6 | |
| 7 | #include <versionhelpers.h> // windows.h must be before |
| 8 | |
| 9 | #include "base/win/pe_image.h" |
pennymac | 5379f17 | 2016-10-04 20:43:58 | [diff] [blame] | 10 | #include "chrome_elf/nt_registry/nt_registry.h" // utils |
pennymac | 5446d89 | 2016-08-27 10:45:12 | [diff] [blame] | 11 | #include "sandbox/win/src/interception_internal.h" |
| 12 | #include "sandbox/win/src/internal_types.h" |
| 13 | #include "sandbox/win/src/sandbox_utils.h" |
| 14 | #include "sandbox/win/src/service_resolver.h" |
| 15 | |
| 16 | namespace { |
| 17 | |
| 18 | //------------------------------------------------------------------------------ |
| 19 | // Common hooking utility functions - LOCAL |
| 20 | //------------------------------------------------------------------------------ |
| 21 | |
pennymac | 5446d89 | 2016-08-27 10:45:12 | [diff] [blame] | 22 | // Change the page protections to writable, copy the data, |
| 23 | // restore protections. Returns a winerror code. |
| 24 | DWORD PatchMem(void* target, void* new_bytes, size_t length) { |
| 25 | if (target == nullptr || new_bytes == nullptr || length == 0) |
| 26 | return ERROR_INVALID_PARAMETER; |
| 27 | |
| 28 | // Preserve executable state. |
| 29 | MEMORY_BASIC_INFORMATION memory_info = {}; |
| 30 | if (!::VirtualQuery(target, &memory_info, sizeof(memory_info))) { |
| 31 | return GetLastError(); |
| 32 | } |
| 33 | |
| 34 | DWORD is_executable = (PAGE_EXECUTE | PAGE_EXECUTE_READ | |
| 35 | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) & |
| 36 | memory_info.Protect; |
| 37 | |
| 38 | // Make target writeable. |
| 39 | DWORD old_page_protection = 0; |
| 40 | if (!::VirtualProtect(target, length, |
| 41 | is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, |
| 42 | &old_page_protection)) { |
| 43 | return GetLastError(); |
| 44 | } |
| 45 | |
| 46 | // Write the data. |
| 47 | ::memcpy(target, new_bytes, length); |
| 48 | |
| 49 | // Restore old page protection. |
| 50 | if (!::VirtualProtect(target, length, old_page_protection, |
| 51 | &old_page_protection)) { |
| 52 | // Yes, this could fail. However, memory was already patched. |
| 53 | #ifdef _DEBUG |
| 54 | assert(false); |
| 55 | #endif // _DEBUG |
| 56 | } |
| 57 | |
| 58 | return NO_ERROR; |
| 59 | } |
| 60 | |
| 61 | //------------------------------------------------------------------------------ |
| 62 | // Import Address Table hooking support - LOCAL |
| 63 | //------------------------------------------------------------------------------ |
| 64 | |
| 65 | void* GetIATFunctionPtr(IMAGE_THUNK_DATA* iat_thunk) { |
| 66 | if (iat_thunk == nullptr) |
| 67 | return nullptr; |
| 68 | |
| 69 | // Works around the 64 bit portability warning: |
| 70 | // The Function member inside IMAGE_THUNK_DATA is really a pointer |
| 71 | // to the IAT function. IMAGE_THUNK_DATA correctly maps to IMAGE_THUNK_DATA32 |
| 72 | // or IMAGE_THUNK_DATA64 for correct pointer size. |
| 73 | union FunctionThunk { |
| 74 | IMAGE_THUNK_DATA thunk; |
| 75 | void* pointer; |
| 76 | } iat_function; |
| 77 | |
| 78 | iat_function.thunk = *iat_thunk; |
| 79 | return iat_function.pointer; |
| 80 | } |
| 81 | |
| 82 | // Used to pass target function information during pe_image enumeration. |
| 83 | struct IATHookFunctionInfo { |
| 84 | bool finished_operation; |
| 85 | const char* imported_from_module; |
| 86 | const char* function_name; |
| 87 | void* new_function; |
| 88 | void** old_function; |
| 89 | IMAGE_THUNK_DATA** iat_thunk; |
| 90 | DWORD return_code; |
| 91 | }; |
| 92 | |
| 93 | // Callback function for pe_image enumeration. This function is called from |
| 94 | // within PEImage::EnumOneImportChunk(). |
| 95 | // NOTE: Returning true means continue enumerating. False means stop. |
| 96 | bool IATFindHookFuncCallback(const base::win::PEImage& image, |
| 97 | const char* module, |
| 98 | DWORD ordinal, |
| 99 | const char* import_name, |
| 100 | DWORD hint, |
| 101 | IMAGE_THUNK_DATA* iat, |
| 102 | void* cookie) { |
| 103 | IATHookFunctionInfo* hook_func_info = |
| 104 | reinterpret_cast<IATHookFunctionInfo*>(cookie); |
| 105 | if (hook_func_info == nullptr) |
| 106 | return false; |
| 107 | |
| 108 | // Check for the right module. |
| 109 | if (module == nullptr || |
| 110 | ::strnicmp(module, hook_func_info->imported_from_module, |
| 111 | ::strlen(module)) != 0) |
| 112 | return true; |
| 113 | |
| 114 | // Check for the right function. |
| 115 | if (import_name == nullptr || |
| 116 | ::strnicmp(import_name, hook_func_info->function_name, |
| 117 | ::strlen(import_name)) != 0) |
| 118 | return true; |
| 119 | |
| 120 | // At this point, the target function was found. Even if something fails now, |
| 121 | // don't do any further enumerating. |
| 122 | hook_func_info->finished_operation = true; |
| 123 | |
| 124 | // This is it. Do the hook! |
| 125 | // 1) Save the old function pointer. |
| 126 | *(hook_func_info->old_function) = GetIATFunctionPtr(iat); |
| 127 | |
| 128 | // 2) Save the IAT thunk. |
| 129 | *(hook_func_info->iat_thunk) = iat; |
| 130 | |
| 131 | // 3) Sanity check the pointer sizes (architectures). |
| 132 | if (sizeof(iat->u1.Function) != sizeof(hook_func_info->new_function)) { |
| 133 | hook_func_info->return_code = ERROR_BAD_ENVIRONMENT; |
| 134 | #ifdef _DEBUG |
| 135 | assert(false); |
| 136 | #endif // _DEBUG |
| 137 | return false; |
| 138 | } |
| 139 | |
| 140 | // 4) Sanity check that the new hook function is not actually the |
| 141 | // same as the existing function for this import! |
| 142 | if (*(hook_func_info->old_function) == hook_func_info->new_function) { |
| 143 | hook_func_info->return_code = ERROR_INVALID_FUNCTION; |
| 144 | #ifdef _DEBUG |
| 145 | assert(false); |
| 146 | #endif // _DEBUG |
| 147 | return false; |
| 148 | } |
| 149 | |
| 150 | // 5) Patch the function pointer. |
| 151 | hook_func_info->return_code = |
| 152 | PatchMem(&(iat->u1.Function), &(hook_func_info->new_function), |
| 153 | sizeof(hook_func_info->new_function)); |
| 154 | |
| 155 | return false; |
| 156 | } |
| 157 | |
| 158 | // Applies an import-address-table hook. Returns a system winerror.h code. |
| 159 | // Call RemoveIATHook() with |new_function|, |old_function| and |iat_thunk| |
| 160 | // to remove the hook. |
| 161 | DWORD ApplyIATHook(HMODULE module_handle, |
| 162 | const char* imported_from_module, |
| 163 | const char* function_name, |
| 164 | void* new_function, |
| 165 | void** old_function, |
| 166 | IMAGE_THUNK_DATA** iat_thunk) { |
| 167 | base::win::PEImage target_image(module_handle); |
| 168 | if (!target_image.VerifyMagic()) |
| 169 | return ERROR_INVALID_PARAMETER; |
| 170 | |
| 171 | IATHookFunctionInfo hook_info = {false, |
| 172 | imported_from_module, |
| 173 | function_name, |
| 174 | new_function, |
| 175 | old_function, |
| 176 | iat_thunk, |
| 177 | ERROR_PROC_NOT_FOUND}; |
| 178 | |
| 179 | // First go through the IAT. If we don't find the import we are looking |
| 180 | // for in IAT, search delay import table. |
| 181 | target_image.EnumAllImports(IATFindHookFuncCallback, &hook_info); |
| 182 | if (!hook_info.finished_operation) { |
| 183 | target_image.EnumAllDelayImports(IATFindHookFuncCallback, &hook_info); |
| 184 | } |
| 185 | |
| 186 | return hook_info.return_code; |
| 187 | } |
| 188 | |
| 189 | // Removes an import-address-table hook. Returns a system winerror.h code. |
| 190 | DWORD RemoveIATHook(void* intercept_function, |
| 191 | void* original_function, |
| 192 | IMAGE_THUNK_DATA* iat_thunk) { |
| 193 | if (GetIATFunctionPtr(iat_thunk) != intercept_function) |
| 194 | // Someone else has messed with the same target. Cannot unpatch. |
| 195 | return ERROR_INVALID_FUNCTION; |
| 196 | |
| 197 | return PatchMem(&(iat_thunk->u1.Function), &original_function, |
| 198 | sizeof(original_function)); |
| 199 | } |
| 200 | |
| 201 | } // namespace |
| 202 | |
| 203 | namespace elf_hook { |
| 204 | |
| 205 | //------------------------------------------------------------------------------ |
| 206 | // System Service hooking support |
| 207 | //------------------------------------------------------------------------------ |
| 208 | |
| 209 | sandbox::ServiceResolverThunk* HookSystemService(bool relaxed) { |
| 210 | // Create a thunk via the appropriate ServiceResolver instance. |
| 211 | sandbox::ServiceResolverThunk* thunk = nullptr; |
| 212 | |
| 213 | // No hooking on unsupported OS versions. |
| 214 | if (!::IsWindows7OrGreater()) |
| 215 | return thunk; |
| 216 | |
| 217 | // Pseudo-handle, no need to close. |
| 218 | HANDLE current_process = ::GetCurrentProcess(); |
| 219 | |
| 220 | #if defined(_WIN64) |
| 221 | // ServiceResolverThunk can handle all the formats in 64-bit (instead only |
| 222 | // handling one like it does in 32-bit versions). |
| 223 | thunk = new sandbox::ServiceResolverThunk(current_process, relaxed); |
| 224 | #else |
pennymac | 5379f17 | 2016-10-04 20:43:58 | [diff] [blame] | 225 | if (nt::IsCurrentProcWow64()) { |
pennymac | 5446d89 | 2016-08-27 10:45:12 | [diff] [blame] | 226 | if (::IsWindows10OrGreater()) |
| 227 | thunk = new sandbox::Wow64W10ResolverThunk(current_process, relaxed); |
| 228 | else if (::IsWindows8OrGreater()) |
| 229 | thunk = new sandbox::Wow64W8ResolverThunk(current_process, relaxed); |
| 230 | else |
| 231 | thunk = new sandbox::Wow64ResolverThunk(current_process, relaxed); |
| 232 | } else if (::IsWindows8OrGreater()) { |
| 233 | thunk = new sandbox::Win8ResolverThunk(current_process, relaxed); |
| 234 | } else { |
| 235 | thunk = new sandbox::ServiceResolverThunk(current_process, relaxed); |
| 236 | } |
| 237 | #endif |
| 238 | |
| 239 | return thunk; |
| 240 | } |
| 241 | |
| 242 | //------------------------------------------------------------------------------ |
| 243 | // Import Address Table hooking support |
| 244 | //------------------------------------------------------------------------------ |
| 245 | |
| 246 | IATHook::IATHook() |
| 247 | : intercept_function_(nullptr), |
| 248 | original_function_(nullptr), |
| 249 | iat_thunk_(nullptr) {} |
| 250 | |
| 251 | IATHook::~IATHook() { |
| 252 | if (intercept_function_ != nullptr) { |
| 253 | if (Unhook() != NO_ERROR) { |
| 254 | #ifdef _DEBUG |
| 255 | assert(false); |
| 256 | #endif // _DEBUG |
| 257 | } |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | DWORD IATHook::Hook(HMODULE module, |
| 262 | const char* imported_from_module, |
| 263 | const char* function_name, |
| 264 | void* new_function) { |
| 265 | if ((module == 0 || module == INVALID_HANDLE_VALUE) || |
| 266 | imported_from_module == nullptr || function_name == nullptr || |
| 267 | new_function == nullptr) |
| 268 | return ERROR_INVALID_PARAMETER; |
| 269 | |
| 270 | // Only hook once per object, to ensure unhook. |
| 271 | if (intercept_function_ != nullptr || original_function_ != nullptr || |
| 272 | iat_thunk_ != nullptr) |
| 273 | return ERROR_SHARING_VIOLATION; |
| 274 | |
| 275 | DWORD winerror = ApplyIATHook(module, imported_from_module, function_name, |
| 276 | new_function, &original_function_, &iat_thunk_); |
| 277 | if (winerror == NO_ERROR) |
| 278 | intercept_function_ = new_function; |
| 279 | |
| 280 | return winerror; |
| 281 | } |
| 282 | |
| 283 | DWORD IATHook::Unhook() { |
| 284 | if (intercept_function_ == nullptr || original_function_ == nullptr || |
| 285 | iat_thunk_ == nullptr) |
| 286 | return ERROR_INVALID_PARAMETER; |
| 287 | |
| 288 | DWORD winerror = |
| 289 | RemoveIATHook(intercept_function_, original_function_, iat_thunk_); |
| 290 | |
| 291 | intercept_function_ = nullptr; |
| 292 | original_function_ = nullptr; |
| 293 | iat_thunk_ = nullptr; |
| 294 | |
| 295 | return winerror; |
| 296 | } |
| 297 | |
| 298 | } // namespace elf_hook |