• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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 // For information about interceptions as a whole see
6 // http://dev.chromium.org/developers/design-documents/sandbox .
7 
8 #include <set>
9 
10 #include "sandbox/win/src/interception.h"
11 
12 #include "base/logging.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/strings/string16.h"
15 #include "base/win/pe_image.h"
16 #include "base/win/windows_version.h"
17 #include "sandbox/win/src/interception_internal.h"
18 #include "sandbox/win/src/interceptors.h"
19 #include "sandbox/win/src/sandbox.h"
20 #include "sandbox/win/src/service_resolver.h"
21 #include "sandbox/win/src/target_interceptions.h"
22 #include "sandbox/win/src/target_process.h"
23 #include "sandbox/win/src/wow64.h"
24 
25 namespace {
26 
27 const char kMapViewOfSectionName[] = "NtMapViewOfSection";
28 const char kUnmapViewOfSectionName[] = "NtUnmapViewOfSection";
29 
30 // Standard allocation granularity and page size for Windows.
31 const size_t kAllocGranularity = 65536;
32 const size_t kPageSize = 4096;
33 
34 // Find a random offset within 64k and aligned to ceil(log2(size)).
GetGranularAlignedRandomOffset(size_t size)35 size_t GetGranularAlignedRandomOffset(size_t size) {
36   CHECK_LE(size, kAllocGranularity);
37   unsigned int offset;
38 
39   do {
40     rand_s(&offset);
41     offset &= (kAllocGranularity - 1);
42   } while (offset > (kAllocGranularity - size));
43 
44   // Find an alignment between 64 and the page size (4096).
45   size_t align_size = kPageSize;
46   for (size_t new_size = align_size / 2;  new_size >= size; new_size /= 2) {
47     align_size = new_size;
48   }
49   return offset & ~(align_size - 1);
50 }
51 
52 }  // namespace
53 
54 namespace sandbox {
55 
56 SANDBOX_INTERCEPT SharedMemory* g_interceptions;
57 
58 // Table of the unpatched functions that we intercept. Mapped from the parent.
59 SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL };
60 
61 // Magic constant that identifies that this function is not to be patched.
62 const char kUnloadDLLDummyFunction[] = "@";
63 
InterceptionManager(TargetProcess * child_process,bool relaxed)64 InterceptionManager::InterceptionManager(TargetProcess* child_process,
65                                          bool relaxed)
66     : child_(child_process), names_used_(false), relaxed_(relaxed) {
67   child_->AddRef();
68 }
~InterceptionManager()69 InterceptionManager::~InterceptionManager() {
70   child_->Release();
71 }
72 
AddToPatchedFunctions(const wchar_t * dll_name,const char * function_name,InterceptionType interception_type,const void * replacement_code_address,InterceptorId id)73 bool InterceptionManager::AddToPatchedFunctions(
74     const wchar_t* dll_name, const char* function_name,
75     InterceptionType interception_type, const void* replacement_code_address,
76     InterceptorId id) {
77   InterceptionData function;
78   function.type = interception_type;
79   function.id = id;
80   function.dll = dll_name;
81   function.function = function_name;
82   function.interceptor_address = replacement_code_address;
83 
84   interceptions_.push_back(function);
85   return true;
86 }
87 
AddToPatchedFunctions(const wchar_t * dll_name,const char * function_name,InterceptionType interception_type,const char * replacement_function_name,InterceptorId id)88 bool InterceptionManager::AddToPatchedFunctions(
89     const wchar_t* dll_name, const char* function_name,
90     InterceptionType interception_type, const char* replacement_function_name,
91     InterceptorId id) {
92   InterceptionData function;
93   function.type = interception_type;
94   function.id = id;
95   function.dll = dll_name;
96   function.function = function_name;
97   function.interceptor = replacement_function_name;
98   function.interceptor_address = NULL;
99 
100   interceptions_.push_back(function);
101   names_used_ = true;
102   return true;
103 }
104 
AddToUnloadModules(const wchar_t * dll_name)105 bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) {
106   InterceptionData module_to_unload;
107   module_to_unload.type = INTERCEPTION_UNLOAD_MODULE;
108   module_to_unload.dll = dll_name;
109   // The next two are dummy values that make the structures regular, instead
110   // of having special cases. They should not be used.
111   module_to_unload.function = kUnloadDLLDummyFunction;
112   module_to_unload.interceptor_address = reinterpret_cast<void*>(1);
113 
114   interceptions_.push_back(module_to_unload);
115   return true;
116 }
117 
InitializeInterceptions()118 bool InterceptionManager::InitializeInterceptions() {
119   if (interceptions_.empty())
120     return true;  // Nothing to do here
121 
122   size_t buffer_bytes = GetBufferSize();
123   scoped_ptr<char[]> local_buffer(new char[buffer_bytes]);
124 
125   if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes))
126     return false;
127 
128   void* remote_buffer;
129   if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer))
130     return false;
131 
132   bool hot_patch_needed = (0 != buffer_bytes);
133   if (!PatchNtdll(hot_patch_needed))
134     return false;
135 
136   g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer);
137   ResultCode rc = child_->TransferVariable("g_interceptions",
138                                            &g_interceptions,
139                                            sizeof(g_interceptions));
140   return (SBOX_ALL_OK == rc);
141 }
142 
GetBufferSize() const143 size_t InterceptionManager::GetBufferSize() const {
144   std::set<base::string16> dlls;
145   size_t buffer_bytes = 0;
146 
147   std::list<InterceptionData>::const_iterator it = interceptions_.begin();
148   for (; it != interceptions_.end(); ++it) {
149     // skip interceptions that are performed from the parent
150     if (!IsInterceptionPerformedByChild(*it))
151       continue;
152 
153     if (!dlls.count(it->dll)) {
154       // NULL terminate the dll name on the structure
155       size_t dll_name_bytes = (it->dll.size() + 1) * sizeof(wchar_t);
156 
157       // include the dll related size
158       buffer_bytes += RoundUpToMultiple(offsetof(DllPatchInfo, dll_name) +
159                                             dll_name_bytes, sizeof(size_t));
160       dlls.insert(it->dll);
161     }
162 
163     // we have to NULL terminate the strings on the structure
164     size_t strings_chars = it->function.size() + it->interceptor.size() + 2;
165 
166     // a new FunctionInfo is required per function
167     size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars;
168     record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t));
169     buffer_bytes += record_bytes;
170   }
171 
172   if (0 != buffer_bytes)
173     // add the part of SharedMemory that we have not counted yet
174     buffer_bytes += offsetof(SharedMemory, dll_list);
175 
176   return buffer_bytes;
177 }
178 
179 // Basically, walk the list of interceptions moving them to the config buffer,
180 // but keeping together all interceptions that belong to the same dll.
181 // The config buffer is a local buffer, not the one allocated on the child.
SetupConfigBuffer(void * buffer,size_t buffer_bytes)182 bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) {
183   if (0 == buffer_bytes)
184     return true;
185 
186   DCHECK(buffer_bytes > sizeof(SharedMemory));
187 
188   SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer);
189   DllPatchInfo* dll_info = shared_memory->dll_list;
190   int num_dlls = 0;
191 
192   shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL;
193 
194   buffer_bytes -= offsetof(SharedMemory, dll_list);
195   buffer = dll_info;
196 
197   std::list<InterceptionData>::iterator it = interceptions_.begin();
198   for (; it != interceptions_.end();) {
199     // skip interceptions that are performed from the parent
200     if (!IsInterceptionPerformedByChild(*it)) {
201       ++it;
202       continue;
203     }
204 
205     const base::string16 dll = it->dll;
206     if (!SetupDllInfo(*it, &buffer, &buffer_bytes))
207       return false;
208 
209     // walk the interceptions from this point, saving the ones that are
210     // performed on this dll, and removing the entry from the list.
211     // advance the iterator before removing the element from the list
212     std::list<InterceptionData>::iterator rest = it;
213     for (; rest != interceptions_.end();) {
214       if (rest->dll == dll) {
215         if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info))
216           return false;
217         if (it == rest)
218           ++it;
219         rest = interceptions_.erase(rest);
220       } else {
221         ++rest;
222       }
223     }
224     dll_info = reinterpret_cast<DllPatchInfo*>(buffer);
225     ++num_dlls;
226   }
227 
228   shared_memory->num_intercepted_dlls = num_dlls;
229   return true;
230 }
231 
232 // Fills up just the part that depends on the dll, not the info that depends on
233 // the actual interception.
SetupDllInfo(const InterceptionData & data,void ** buffer,size_t * buffer_bytes) const234 bool InterceptionManager::SetupDllInfo(const InterceptionData& data,
235                                        void** buffer,
236                                        size_t* buffer_bytes) const {
237   DCHECK(buffer_bytes);
238   DCHECK(buffer);
239   DCHECK(*buffer);
240 
241   DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer);
242 
243   // the strings have to be zero terminated
244   size_t required = offsetof(DllPatchInfo, dll_name) +
245                     (data.dll.size() + 1) * sizeof(wchar_t);
246   required = RoundUpToMultiple(required, sizeof(size_t));
247   if (*buffer_bytes < required)
248     return false;
249 
250   *buffer_bytes -= required;
251   *buffer = reinterpret_cast<char*>(*buffer) + required;
252 
253   // set up the dll info to be what we know about it at this time
254   dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE);
255   dll_info->record_bytes = required;
256   dll_info->offset_to_functions = required;
257   dll_info->num_functions = 0;
258   data.dll._Copy_s(dll_info->dll_name, data.dll.size(), data.dll.size());
259   dll_info->dll_name[data.dll.size()] = L'\0';
260 
261   return true;
262 }
263 
SetupInterceptionInfo(const InterceptionData & data,void ** buffer,size_t * buffer_bytes,DllPatchInfo * dll_info) const264 bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data,
265                                                 void** buffer,
266                                                 size_t* buffer_bytes,
267                                                 DllPatchInfo* dll_info) const {
268   DCHECK(buffer_bytes);
269   DCHECK(buffer);
270   DCHECK(*buffer);
271 
272   if ((dll_info->unload_module) &&
273       (data.function != kUnloadDLLDummyFunction)) {
274     // Can't specify a dll for both patch and unload.
275     NOTREACHED();
276   }
277 
278   FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer);
279 
280   size_t name_bytes = data.function.size();
281   size_t interceptor_bytes = data.interceptor.size();
282 
283   // the strings at the end of the structure are zero terminated
284   size_t required = offsetof(FunctionInfo, function) +
285                     name_bytes + interceptor_bytes + 2;
286   required = RoundUpToMultiple(required, sizeof(size_t));
287   if (*buffer_bytes < required)
288     return false;
289 
290   // update the caller's values
291   *buffer_bytes -= required;
292   *buffer = reinterpret_cast<char*>(*buffer) + required;
293 
294   function->record_bytes = required;
295   function->type = data.type;
296   function->id = data.id;
297   function->interceptor_address = data.interceptor_address;
298   char* names = function->function;
299 
300   data.function._Copy_s(names, name_bytes, name_bytes);
301   names += name_bytes;
302   *names++ = '\0';
303 
304   // interceptor follows the function_name
305   data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes);
306   names += interceptor_bytes;
307   *names++ = '\0';
308 
309   // update the dll table
310   dll_info->num_functions++;
311   dll_info->record_bytes += required;
312 
313   return true;
314 }
315 
CopyDataToChild(const void * local_buffer,size_t buffer_bytes,void ** remote_buffer) const316 bool InterceptionManager::CopyDataToChild(const void* local_buffer,
317                                           size_t buffer_bytes,
318                                           void** remote_buffer) const {
319   DCHECK(NULL != remote_buffer);
320   if (0 == buffer_bytes) {
321     *remote_buffer = NULL;
322     return true;
323   }
324 
325   HANDLE child = child_->Process();
326 
327   // Allocate memory on the target process without specifying the address
328   void* remote_data = ::VirtualAllocEx(child, NULL, buffer_bytes,
329                                        MEM_COMMIT, PAGE_READWRITE);
330   if (NULL == remote_data)
331     return false;
332 
333   SIZE_T bytes_written;
334   BOOL success = ::WriteProcessMemory(child, remote_data, local_buffer,
335                                       buffer_bytes, &bytes_written);
336   if (FALSE == success || bytes_written != buffer_bytes) {
337     ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE);
338     return false;
339   }
340 
341   *remote_buffer = remote_data;
342 
343   return true;
344 }
345 
346 // Only return true if the child should be able to perform this interception.
IsInterceptionPerformedByChild(const InterceptionData & data) const347 bool InterceptionManager::IsInterceptionPerformedByChild(
348     const InterceptionData& data) const {
349   if (INTERCEPTION_INVALID == data.type)
350     return false;
351 
352   if (INTERCEPTION_SERVICE_CALL == data.type)
353     return false;
354 
355   if (data.type >= INTERCEPTION_LAST)
356     return false;
357 
358   base::string16 ntdll(kNtdllName);
359   if (ntdll == data.dll)
360     return false;  // ntdll has to be intercepted from the parent
361 
362   return true;
363 }
364 
PatchNtdll(bool hot_patch_needed)365 bool InterceptionManager::PatchNtdll(bool hot_patch_needed) {
366   // Maybe there is nothing to do
367   if (!hot_patch_needed && interceptions_.empty())
368     return true;
369 
370   if (hot_patch_needed) {
371 #if SANDBOX_EXPORTS
372     // Make sure the functions are not excluded by the linker.
373 #if defined(_WIN64)
374     #pragma comment(linker, "/include:TargetNtMapViewOfSection64")
375     #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64")
376 #else
377     #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44")
378     #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12")
379 #endif
380 #endif
381     ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44);
382     ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12);
383   }
384 
385   // Reserve a full 64k memory range in the child process.
386   HANDLE child = child_->Process();
387   BYTE* thunk_base = reinterpret_cast<BYTE*>(
388                          ::VirtualAllocEx(child, NULL, kAllocGranularity,
389                                           MEM_RESERVE, PAGE_NOACCESS));
390 
391   // Find an aligned, random location within the reserved range.
392   size_t thunk_bytes = interceptions_.size() * sizeof(ThunkData) +
393                        sizeof(DllInterceptionData);
394   size_t thunk_offset = GetGranularAlignedRandomOffset(thunk_bytes);
395 
396   // Split the base and offset along page boundaries.
397   thunk_base += thunk_offset & ~(kPageSize - 1);
398   thunk_offset &= kPageSize - 1;
399 
400   // Make an aligned, padded allocation, and move the pointer to our chunk.
401   size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & ~(kPageSize - 1);
402   thunk_base = reinterpret_cast<BYTE*>(
403                    ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded,
404                                     MEM_COMMIT, PAGE_EXECUTE_READWRITE));
405   CHECK(thunk_base);  // If this fails we'd crash anyway on an invalid access.
406   DllInterceptionData* thunks = reinterpret_cast<DllInterceptionData*>(
407                                     thunk_base + thunk_offset);
408 
409   DllInterceptionData dll_data;
410   dll_data.data_bytes = thunk_bytes;
411   dll_data.num_thunks = 0;
412   dll_data.used_bytes = offsetof(DllInterceptionData, thunks);
413 
414   // Reset all helpers for a new child.
415   memset(g_originals, 0, sizeof(g_originals));
416 
417   // this should write all the individual thunks to the child's memory
418   if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data))
419     return false;
420 
421   // and now write the first part of the table to the child's memory
422   SIZE_T written;
423   bool ok = FALSE != ::WriteProcessMemory(child, thunks, &dll_data,
424                                           offsetof(DllInterceptionData, thunks),
425                                           &written);
426 
427   if (!ok || (offsetof(DllInterceptionData, thunks) != written))
428     return false;
429 
430   // Attempt to protect all the thunks, but ignore failure
431   DWORD old_protection;
432   ::VirtualProtectEx(child, thunks, thunk_bytes,
433                      PAGE_EXECUTE_READ, &old_protection);
434 
435   ResultCode ret = child_->TransferVariable("g_originals", g_originals,
436                                             sizeof(g_originals));
437   return (SBOX_ALL_OK == ret);
438 }
439 
PatchClientFunctions(DllInterceptionData * thunks,size_t thunk_bytes,DllInterceptionData * dll_data)440 bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks,
441                                                size_t thunk_bytes,
442                                                DllInterceptionData* dll_data) {
443   DCHECK(NULL != thunks);
444   DCHECK(NULL != dll_data);
445 
446   HMODULE ntdll_base = ::GetModuleHandle(kNtdllName);
447   if (!ntdll_base)
448     return false;
449 
450   base::win::PEImage ntdll_image(ntdll_base);
451 
452   // Bypass purify's interception.
453   wchar_t* loader_get = reinterpret_cast<wchar_t*>(
454                             ntdll_image.GetProcAddress("LdrGetDllHandle"));
455   if (loader_get) {
456     if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
457                                GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
458                            loader_get, &ntdll_base))
459       return false;
460   }
461 
462   if (base::win::GetVersion() <= base::win::VERSION_VISTA) {
463     Wow64 WowHelper(child_, ntdll_base);
464     if (!WowHelper.WaitForNtdll())
465       return false;
466   }
467 
468   char* interceptor_base = NULL;
469 
470 #if SANDBOX_EXPORTS
471   interceptor_base = reinterpret_cast<char*>(child_->MainModule());
472   HMODULE local_interceptor = ::LoadLibrary(child_->Name());
473 #endif
474 
475   ServiceResolverThunk* thunk;
476 #if defined(_WIN64)
477   thunk = new ServiceResolverThunk(child_->Process(), relaxed_);
478 #else
479   base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
480   if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) {
481     if (os_info->version() >= base::win::VERSION_WIN8)
482       thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_);
483     else
484       thunk = new Wow64ResolverThunk(child_->Process(), relaxed_);
485   } else if (os_info->version() >= base::win::VERSION_WIN8) {
486     thunk = new Win8ResolverThunk(child_->Process(), relaxed_);
487   } else {
488     thunk = new ServiceResolverThunk(child_->Process(), relaxed_);
489   }
490 #endif
491 
492   std::list<InterceptionData>::iterator it = interceptions_.begin();
493   for (; it != interceptions_.end(); ++it) {
494     const base::string16 ntdll(kNtdllName);
495     if (it->dll != ntdll)
496       break;
497 
498     if (INTERCEPTION_SERVICE_CALL != it->type)
499       break;
500 
501 #if SANDBOX_EXPORTS
502     // We may be trying to patch by function name.
503     if (NULL == it->interceptor_address) {
504       const char* address;
505       NTSTATUS ret = thunk->ResolveInterceptor(local_interceptor,
506                                                it->interceptor.c_str(),
507                                                reinterpret_cast<const void**>(
508                                                &address));
509       if (!NT_SUCCESS(ret))
510         break;
511 
512       // Translate the local address to an address on the child.
513       it->interceptor_address = interceptor_base + (address -
514                                     reinterpret_cast<char*>(local_interceptor));
515     }
516 #endif
517     NTSTATUS ret = thunk->Setup(ntdll_base,
518                                 interceptor_base,
519                                 it->function.c_str(),
520                                 it->interceptor.c_str(),
521                                 it->interceptor_address,
522                                 &thunks->thunks[dll_data->num_thunks],
523                                 thunk_bytes - dll_data->used_bytes,
524                                 NULL);
525     if (!NT_SUCCESS(ret))
526       break;
527 
528     DCHECK(!g_originals[it->id]);
529     g_originals[it->id] = &thunks->thunks[dll_data->num_thunks];
530 
531     dll_data->num_thunks++;
532     dll_data->used_bytes += sizeof(ThunkData);
533   }
534 
535   delete(thunk);
536 
537 #if SANDBOX_EXPORTS
538   if (NULL != local_interceptor)
539     ::FreeLibrary(local_interceptor);
540 #endif
541 
542   if (it != interceptions_.end())
543     return false;
544 
545   return true;
546 }
547 
548 }  // namespace sandbox
549