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