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