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 return false; | |
159 } | |
160 | |
161 // 4) Patch the function pointer. | |
162 hook_func_info->return_code = | |
163 PatchMem(&(iat->u1.Function), &(hook_func_info->new_function), | |
164 sizeof(hook_func_info->new_function)); | |
165 | |
166 return false; | |
167 } | |
168 | |
169 // Applies an import-address-table hook. Returns a system winerror.h code. | |
170 // Call RemoveIATHook() with |new_function|, |old_function| and |iat_thunk| | |
171 // to remove the hook. | |
172 DWORD ApplyIATHook(HMODULE module_handle, | |
173 const char* imported_from_module, | |
174 const char* function_name, | |
175 void* new_function, | |
176 void** old_function, | |
177 IMAGE_THUNK_DATA** iat_thunk) { | |
178 base::win::PEImage target_image(module_handle); | |
179 if (!target_image.VerifyMagic()) | |
180 return ERROR_INVALID_PARAMETER; | |
181 | |
182 IATHookFunctionInfo hook_info = {false, | |
183 imported_from_module, | |
184 function_name, | |
185 new_function, | |
186 old_function, | |
187 iat_thunk, | |
188 ERROR_GEN_FAILURE}; | |
189 | |
190 // First go through the IAT. If we don't find the import we are looking | |
191 // for in IAT, search delay import table. | |
192 target_image.EnumAllImports(IATFindHookFuncCallback, &hook_info); | |
193 if (!hook_info.finished_operation) { | |
194 target_image.EnumAllDelayImports(IATFindHookFuncCallback, &hook_info); | |
195 } | |
196 | |
197 return hook_info.return_code; | |
198 } | |
199 | |
200 // Removes an import-address-table hook. Returns a system winerror.h code. | |
201 DWORD RemoveIATHook(void* intercept_function, | |
202 void* original_function, | |
203 IMAGE_THUNK_DATA* iat_thunk) { | |
204 if (GetIATFunctionPtr(iat_thunk) != intercept_function) { | |
205 // Someone else has messed with the same target. Cannot unpatch. | |
206 #ifdef _DEBUG | |
207 assert(false); | |
208 #endif // _DEBUG | |
209 return ERROR_INVALID_FUNCTION; | |
210 } | |
211 | |
212 return PatchMem(&(iat_thunk->u1.Function), &original_function, | |
213 sizeof(original_function)); | |
214 } | |
215 | |
216 } // namespace | |
217 | |
218 namespace elf_hook { | |
219 | |
220 //------------------------------------------------------------------------------ | |
221 // System Service hooking support | |
222 //------------------------------------------------------------------------------ | |
223 | |
224 sandbox::ServiceResolverThunk* HookSystemService(bool relaxed) { | |
225 // Create a thunk via the appropriate ServiceResolver instance. | |
226 sandbox::ServiceResolverThunk* thunk = nullptr; | |
227 | |
228 // No hooking on unsupported OS versions. | |
229 if (!::IsWindows7OrGreater()) | |
230 return thunk; | |
231 | |
232 // Pseudo-handle, no need to close. | |
233 HANDLE current_process = ::GetCurrentProcess(); | |
234 | |
235 #if defined(_WIN64) | |
236 // ServiceResolverThunk can handle all the formats in 64-bit (instead only | |
237 // handling one like it does in 32-bit versions). | |
238 thunk = new sandbox::ServiceResolverThunk(current_process, relaxed); | |
239 #else | |
240 if (GetWOW64StatusForCurrentProcess() == WOW64_ENABLED) { | |
241 if (::IsWindows10OrGreater()) | |
242 thunk = new sandbox::Wow64W10ResolverThunk(current_process, relaxed); | |
243 else if (::IsWindows8OrGreater()) | |
244 thunk = new sandbox::Wow64W8ResolverThunk(current_process, relaxed); | |
245 else | |
246 thunk = new sandbox::Wow64ResolverThunk(current_process, relaxed); | |
247 } else if (::IsWindows8OrGreater()) { | |
248 thunk = new sandbox::Win8ResolverThunk(current_process, relaxed); | |
249 } else { | |
250 thunk = new sandbox::ServiceResolverThunk(current_process, relaxed); | |
251 } | |
252 #endif | |
253 | |
254 return thunk; | |
255 } | |
256 | |
257 //------------------------------------------------------------------------------ | |
258 // Import Address Table hooking support | |
259 //------------------------------------------------------------------------------ | |
260 | |
261 IATHook::IATHook() | |
262 : intercept_function_(nullptr), | |
263 original_function_(nullptr), | |
264 iat_thunk_(nullptr) {} | |
265 | |
266 IATHook::~IATHook() { | |
267 if (intercept_function_ != nullptr) { | |
268 if (Unhook() != NO_ERROR) { | |
269 #ifdef _DEBUG | |
270 assert(false); | |
271 #endif // _DEBUG | |
272 } | |
273 } | |
274 } | |
275 | |
276 DWORD IATHook::Hook(HMODULE module, | |
277 const char* imported_from_module, | |
278 const char* function_name, | |
279 void* new_function) { | |
robertshield
2016/08/03 04:52:45
It looks like this method shouldn't be called twic
penny
2016/08/04 18:55:35
Done.
| |
280 if ((module == 0 || module == INVALID_HANDLE_VALUE) || | |
281 imported_from_module == nullptr || function_name == nullptr || | |
282 new_function == nullptr) | |
283 return ERROR_INVALID_PARAMETER; | |
284 | |
285 DWORD winerror = ApplyIATHook(module, imported_from_module, function_name, | |
286 new_function, &original_function_, &iat_thunk_); | |
287 if (winerror == NO_ERROR) { | |
288 intercept_function_ = new_function; | |
289 #ifdef _DEBUG | |
290 if (original_function_ == new_function) | |
robertshield
2016/08/03 04:52:45
If this is an error condition, please consider add
penny
2016/08/04 18:55:35
I've added a check in IATFindHookFuncCallback() (w
| |
291 assert(false); | |
292 #endif //_DEBUG | |
293 } | |
294 | |
295 return winerror; | |
296 } | |
297 | |
298 DWORD IATHook::Unhook() { | |
299 DWORD winerror = | |
300 RemoveIATHook(intercept_function_, original_function_, iat_thunk_); | |
301 #ifdef _DEBUG | |
302 if (winerror != NO_ERROR) | |
303 assert(false); | |
304 #endif //_DEBUG | |
305 | |
306 intercept_function_ = nullptr; | |
307 original_function_ = nullptr; | |
308 iat_thunk_ = nullptr; | |
309 | |
310 return winerror; | |
311 } | |
312 | |
313 } // namespace elf_hook | |
OLD | NEW |