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 #ifdef _DEBUG | |
219 assert(false); | |
220 #endif // _DEBUG | |
221 // Someone else has messed with the same target. Cannot unpatch. | |
222 return ERROR_INVALID_FUNCTION; | |
223 } | |
224 | |
225 return PatchMem(&(iat_thunk->u1.Function), &original_function, | |
226 sizeof(original_function)); | |
227 } | |
228 | |
229 } // namespace | |
230 | |
231 namespace elf_hook { | |
232 | |
233 //------------------------------------------------------------------------------ | |
234 // System Service hooking support | |
235 //------------------------------------------------------------------------------ | |
236 | |
237 sandbox::ServiceResolverThunk* HookSystemService(bool relaxed) { | |
238 // Create a thunk via the appropriate ServiceResolver instance. | |
239 sandbox::ServiceResolverThunk* thunk = nullptr; | |
240 | |
241 // No hooking on unsupported OS versions. | |
242 if (!::IsWindows7OrGreater()) | |
243 return thunk; | |
244 | |
245 // Pseudo-handle, no need to close. | |
246 HANDLE current_process = ::GetCurrentProcess(); | |
247 | |
248 #if defined(_WIN64) | |
249 // ServiceResolverThunk can handle all the formats in 64-bit (instead only | |
250 // handling one like it does in 32-bit versions). | |
251 thunk = new sandbox::ServiceResolverThunk(current_process, relaxed); | |
252 #else | |
253 if (GetWOW64StatusForCurrentProcess() == WOW64_ENABLED) { | |
254 if (::IsWindows10OrGreater()) | |
255 thunk = new sandbox::Wow64W10ResolverThunk(current_process, relaxed); | |
256 else if (::IsWindows8OrGreater()) | |
257 thunk = new sandbox::Wow64W8ResolverThunk(current_process, relaxed); | |
258 else | |
259 thunk = new sandbox::Wow64ResolverThunk(current_process, relaxed); | |
260 } else if (::IsWindows8OrGreater()) { | |
261 thunk = new sandbox::Win8ResolverThunk(current_process, relaxed); | |
262 } else { | |
263 thunk = new sandbox::ServiceResolverThunk(current_process, relaxed); | |
264 } | |
265 #endif | |
266 | |
267 return thunk; | |
268 } | |
269 | |
270 //------------------------------------------------------------------------------ | |
271 // Import Address Table hooking support | |
272 //------------------------------------------------------------------------------ | |
273 | |
274 IATHook::IATHook() | |
275 : intercept_function_(nullptr), | |
276 original_function_(nullptr), | |
277 iat_thunk_(nullptr) {} | |
278 | |
279 IATHook::~IATHook() { | |
280 if (intercept_function_ != nullptr) { | |
281 if (Unhook() != NO_ERROR) { | |
282 #ifdef _DEBUG | |
283 assert(false); | |
284 #endif // _DEBUG | |
285 } | |
286 } | |
287 } | |
288 | |
289 DWORD IATHook::Hook(HMODULE module, | |
290 const char* imported_from_module, | |
291 const char* function_name, | |
292 void* new_function) { | |
293 if ((module == 0 || module == INVALID_HANDLE_VALUE) || | |
294 imported_from_module == nullptr || function_name == nullptr || | |
295 new_function == nullptr) | |
296 return ERROR_INVALID_PARAMETER; | |
297 | |
298 // Only hook once per object, to ensure unhook. | |
299 if (intercept_function_ != nullptr || original_function_ != nullptr || | |
300 iat_thunk_ != nullptr) { | |
301 #ifdef _DEBUG | |
302 assert(false); | |
303 #endif //_DEBUG | |
304 return ERROR_SHARING_VIOLATION; | |
305 } | |
306 | |
307 DWORD winerror = ApplyIATHook(module, imported_from_module, function_name, | |
308 new_function, &original_function_, &iat_thunk_); | |
309 if (winerror == NO_ERROR) | |
310 intercept_function_ = new_function; | |
311 | |
312 return winerror; | |
313 } | |
314 | |
315 DWORD IATHook::Unhook() { | |
316 if (intercept_function_ == nullptr || original_function_ == nullptr || | |
317 iat_thunk_ == nullptr) | |
318 return ERROR_INVALID_PARAMETER; | |
319 | |
320 DWORD winerror = | |
321 RemoveIATHook(intercept_function_, original_function_, iat_thunk_); | |
322 #ifdef _DEBUG | |
323 if (winerror != NO_ERROR) | |
324 assert(false); | |
325 #endif //_DEBUG | |
326 | |
327 intercept_function_ = nullptr; | |
328 original_function_ = nullptr; | |
329 iat_thunk_ = nullptr; | |
330 | |
331 return winerror; | |
332 } | |
333 | |
334 } // namespace elf_hook | |
OLD | NEW |