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