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 "chrome/chrome_elf/nt_registry/nt_registry.h" // utils
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
22 // Change the page protections to writable, copy the data,
23 // restore protections. Returns a winerror code.
PatchMem(void * target,void * new_bytes,size_t length)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
GetIATFunctionPtr(IMAGE_THUNK_DATA * iat_thunk)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.
IATFindHookFuncCallback(const base::win::PEImage & image,const char * module,DWORD ordinal,const char * import_name,DWORD hint,IMAGE_THUNK_DATA * iat,void * cookie)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 function.
109 if (import_name == nullptr ||
110 ::strnicmp(import_name, hook_func_info->function_name,
111 ::strlen(import_name)) != 0)
112 return true;
113
114 // At this point, the target function was found. Even if something fails now,
115 // don't do any further enumerating.
116 hook_func_info->finished_operation = true;
117
118 // This is it. Do the hook!
119 // 1) Save the old function pointer.
120 *(hook_func_info->old_function) = GetIATFunctionPtr(iat);
121
122 // 2) Save the IAT thunk.
123 *(hook_func_info->iat_thunk) = iat;
124
125 // 3) Sanity check the pointer sizes (architectures).
126 if (sizeof(iat->u1.Function) != sizeof(hook_func_info->new_function)) {
127 hook_func_info->return_code = ERROR_BAD_ENVIRONMENT;
128 #ifdef _DEBUG
129 assert(false);
130 #endif // _DEBUG
131 return false;
132 }
133
134 // 4) Sanity check that the new hook function is not actually the
135 // same as the existing function for this import!
136 if (*(hook_func_info->old_function) == hook_func_info->new_function) {
137 hook_func_info->return_code = ERROR_INVALID_FUNCTION;
138 #ifdef _DEBUG
139 assert(false);
140 #endif // _DEBUG
141 return false;
142 }
143
144 // 5) Patch the function pointer.
145 hook_func_info->return_code =
146 PatchMem(&(iat->u1.Function), &(hook_func_info->new_function),
147 sizeof(hook_func_info->new_function));
148
149 return false;
150 }
151
152 // Applies an import-address-table hook. Returns a system winerror.h code.
153 // Call RemoveIATHook() with |new_function|, |old_function| and |iat_thunk|
154 // to remove the hook.
ApplyIATHook(HMODULE module_handle,const char * imported_from_module,const char * function_name,void * new_function,void ** old_function,IMAGE_THUNK_DATA ** iat_thunk)155 DWORD ApplyIATHook(HMODULE module_handle,
156 const char* imported_from_module,
157 const char* function_name,
158 void* new_function,
159 void** old_function,
160 IMAGE_THUNK_DATA** iat_thunk) {
161 base::win::PEImage target_image(module_handle);
162 if (!target_image.VerifyMagic())
163 return ERROR_INVALID_PARAMETER;
164
165 IATHookFunctionInfo hook_info = {false,
166 imported_from_module,
167 function_name,
168 new_function,
169 old_function,
170 iat_thunk,
171 ERROR_PROC_NOT_FOUND};
172
173 // First go through the IAT. If we don't find the import we are looking
174 // for in IAT, search delay import table.
175 target_image.EnumAllImports(IATFindHookFuncCallback, &hook_info,
176 imported_from_module);
177 if (!hook_info.finished_operation) {
178 target_image.EnumAllDelayImports(IATFindHookFuncCallback, &hook_info,
179 imported_from_module);
180 }
181
182 return hook_info.return_code;
183 }
184
185 // Removes an import-address-table hook. Returns a system winerror.h code.
RemoveIATHook(void * intercept_function,void * original_function,IMAGE_THUNK_DATA * iat_thunk)186 DWORD RemoveIATHook(void* intercept_function,
187 void* original_function,
188 IMAGE_THUNK_DATA* iat_thunk) {
189 if (GetIATFunctionPtr(iat_thunk) != intercept_function)
190 // Someone else has messed with the same target. Cannot unpatch.
191 return ERROR_INVALID_FUNCTION;
192
193 return PatchMem(&(iat_thunk->u1.Function), &original_function,
194 sizeof(original_function));
195 }
196
197 } // namespace
198
199 namespace elf_hook {
200
201 //------------------------------------------------------------------------------
202 // System Service hooking support
203 //------------------------------------------------------------------------------
204
HookSystemService(bool relaxed)205 sandbox::ServiceResolverThunk* HookSystemService(bool relaxed) {
206 // Create a thunk via the appropriate ServiceResolver instance.
207 sandbox::ServiceResolverThunk* thunk = nullptr;
208
209 // No hooking on unsupported OS versions.
210 if (!::IsWindows7OrGreater())
211 return thunk;
212
213 // Pseudo-handle, no need to close.
214 HANDLE current_process = ::GetCurrentProcess();
215
216 #if defined(_WIN64)
217 // ServiceResolverThunk can handle all the formats in 64-bit (instead only
218 // handling one like it does in 32-bit versions).
219 thunk = new sandbox::ServiceResolverThunk(current_process, relaxed);
220 #else
221 if (nt::IsCurrentProcWow64()) {
222 if (::IsWindows10OrGreater())
223 thunk = new sandbox::Wow64W10ResolverThunk(current_process, relaxed);
224 else if (::IsWindows8OrGreater())
225 thunk = new sandbox::Wow64W8ResolverThunk(current_process, relaxed);
226 else
227 thunk = new sandbox::Wow64ResolverThunk(current_process, relaxed);
228 } else if (::IsWindows8OrGreater()) {
229 thunk = new sandbox::Win8ResolverThunk(current_process, relaxed);
230 } else {
231 thunk = new sandbox::ServiceResolverThunk(current_process, relaxed);
232 }
233 #endif
234
235 return thunk;
236 }
237
238 //------------------------------------------------------------------------------
239 // Import Address Table hooking support
240 //------------------------------------------------------------------------------
241
IATHook()242 IATHook::IATHook()
243 : intercept_function_(nullptr),
244 original_function_(nullptr),
245 iat_thunk_(nullptr) {}
246
~IATHook()247 IATHook::~IATHook() {
248 if (intercept_function_) {
249 if (Unhook() != NO_ERROR) {
250 #ifdef _DEBUG
251 assert(false);
252 #endif // _DEBUG
253 }
254 }
255 }
256
Hook(HMODULE module,const char * imported_from_module,const char * function_name,void * new_function)257 DWORD IATHook::Hook(HMODULE module,
258 const char* imported_from_module,
259 const char* function_name,
260 void* new_function) {
261 if ((module == 0 || module == INVALID_HANDLE_VALUE) ||
262 imported_from_module == nullptr || function_name == nullptr ||
263 new_function == nullptr)
264 return ERROR_INVALID_PARAMETER;
265
266 // Only hook once per object, to ensure unhook.
267 if (intercept_function_ || original_function_ || iat_thunk_)
268 return ERROR_SHARING_VIOLATION;
269
270 DWORD winerror = ApplyIATHook(module, imported_from_module, function_name,
271 new_function, &original_function_, &iat_thunk_);
272 if (winerror == NO_ERROR)
273 intercept_function_ = new_function;
274
275 return winerror;
276 }
277
Unhook()278 DWORD IATHook::Unhook() {
279 if (intercept_function_ == nullptr || original_function_ == nullptr ||
280 iat_thunk_ == nullptr)
281 return ERROR_INVALID_PARAMETER;
282
283 DWORD winerror =
284 RemoveIATHook(intercept_function_, original_function_, iat_thunk_);
285
286 intercept_function_ = nullptr;
287 original_function_ = nullptr;
288 iat_thunk_ = nullptr;
289
290 return winerror;
291 }
292
293 } // namespace elf_hook
294