blob: 3933fdb96385eb4f4ab7a9370a7e95c2be3e8107 [file] [log] [blame]
pennymac5446d892016-08-27 10:45:121// 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"
pennymac5379f172016-10-04 20:43:5810#include "chrome_elf/nt_registry/nt_registry.h" // utils
pennymac5446d892016-08-27 10:45:1211#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
16namespace {
17
18//------------------------------------------------------------------------------
19// Common hooking utility functions - LOCAL
20//------------------------------------------------------------------------------
21
pennymac5446d892016-08-27 10:45:1222// Change the page protections to writable, copy the data,
23// restore protections. Returns a winerror code.
24DWORD 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
65void* 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.
83struct 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.
96bool 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.
161DWORD 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.
190DWORD 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
203namespace elf_hook {
204
205//------------------------------------------------------------------------------
206// System Service hooking support
207//------------------------------------------------------------------------------
208
209sandbox::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
pennymac5379f172016-10-04 20:43:58225 if (nt::IsCurrentProcWow64()) {
pennymac5446d892016-08-27 10:45:12226 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
246IATHook::IATHook()
247 : intercept_function_(nullptr),
248 original_function_(nullptr),
249 iat_thunk_(nullptr) {}
250
251IATHook::~IATHook() {
252 if (intercept_function_ != nullptr) {
253 if (Unhook() != NO_ERROR) {
254#ifdef _DEBUG
255 assert(false);
256#endif // _DEBUG
257 }
258 }
259}
260
261DWORD 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
283DWORD 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