// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Defines InterceptionManager, the class in charge of setting up interceptions // for the sandboxed process. For more details see // http://dev.chromium.org/developers/design-documents/sandbox . #ifndef SANDBOX_SRC_INTERCEPTION_H_ #define SANDBOX_SRC_INTERCEPTION_H_ #include <list> #include <string> #include "base/basictypes.h" #include "base/gtest_prod_util.h" #include "base/strings/string16.h" #include "sandbox/win/src/sandbox_types.h" namespace sandbox { class TargetProcess; enum InterceptorId; // Internal structures used for communication between the broker and the target. struct DllPatchInfo; struct DllInterceptionData; // The InterceptionManager executes on the parent application, and it is in // charge of setting up the desired interceptions, and placing the Interception // Agent into the child application. // // The exposed API consists of two methods: AddToPatchedFunctions to set up a // particular interception, and InitializeInterceptions to actually go ahead and // perform all interceptions and transfer data to the child application. // // The typical usage is something like this: // // InterceptionManager interception_manager(child); // if (!interception_manager.AddToPatchedFunctions( // L"ntdll.dll", "NtCreateFile", // sandbox::INTERCEPTION_SERVICE_CALL, &MyNtCreateFile, MY_ID_1)) // return false; // // if (!interception_manager.AddToPatchedFunctions( // L"kernel32.dll", "CreateDirectoryW", // sandbox::INTERCEPTION_EAT, L"MyCreateDirectoryW@12", MY_ID_2)) // return false; // // if (!interception_manager.InitializeInterceptions()) { // DWORD error = ::GetLastError(); // return false; // } // // Any required syncronization must be performed outside this class. Also, it is // not possible to perform further interceptions after InitializeInterceptions // is called. // class InterceptionManager { // The unit test will access private members. // Allow tests to be marked DISABLED_. Note that FLAKY_ and FAILS_ prefixes // do not work with sandbox tests. FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout1); FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout2); public: // An interception manager performs interceptions on a given child process. // If we are allowed to intercept functions that have been patched by somebody // else, relaxed should be set to true. // Note: We increase the child's reference count internally. InterceptionManager(TargetProcess* child_process, bool relaxed); ~InterceptionManager(); // Patches function_name inside dll_name to point to replacement_code_address. // function_name has to be an exported symbol of dll_name. // Returns true on success. // // The new function should match the prototype and calling convention of the // function to intercept except for one extra argument (the first one) that // contains a pointer to the original function, to simplify the development // of interceptors (for IA32). In x64, there is no extra argument to the // interceptor, so the provided InterceptorId is used to keep a table of // intercepted functions so that the interceptor can index that table to get // the pointer that would have been the first argument (g_originals[id]). // // For example, to intercept NtClose, the following code could be used: // // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); // NTSTATUS WINAPI MyNtCose(IN NtCloseFunction OriginalClose, // IN HANDLE Handle) { // // do something // // call the original function // return OriginalClose(Handle); // } // // And in x64: // // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); // NTSTATUS WINAPI MyNtCose64(IN HANDLE Handle) { // // do something // // call the original function // NtCloseFunction OriginalClose = g_originals[NT_CLOSE_ID]; // return OriginalClose(Handle); // } bool AddToPatchedFunctions(const wchar_t* dll_name, const char* function_name, InterceptionType interception_type, const void* replacement_code_address, InterceptorId id); // Patches function_name inside dll_name to point to // replacement_function_name. bool AddToPatchedFunctions(const wchar_t* dll_name, const char* function_name, InterceptionType interception_type, const char* replacement_function_name, InterceptorId id); // The interception agent will unload the dll with dll_name. bool AddToUnloadModules(const wchar_t* dll_name); // Initializes all interceptions on the client. // Returns true on success. // // The child process must be created suspended, and cannot be resumed until // after this method returns. In addition, no action should be performed on // the child that may cause it to resume momentarily, such as injecting // threads or APCs. // // This function must be called only once, after all interceptions have been // set up using AddToPatchedFunctions. bool InitializeInterceptions(); private: // Used to store the interception information until the actual set-up. struct InterceptionData { InterceptionType type; // Interception type. InterceptorId id; // Interceptor id. base::string16 dll; // Name of dll to intercept. std::string function; // Name of function to intercept. std::string interceptor; // Name of interceptor function. const void* interceptor_address; // Interceptor's entry point. }; // Calculates the size of the required configuration buffer. size_t GetBufferSize() const; // Rounds up the size of a given buffer, considering alignment (padding). // value is the current size of the buffer, and alignment is specified in // bytes. static inline size_t RoundUpToMultiple(size_t value, size_t alignment) { return ((value + alignment -1) / alignment) * alignment; } // Sets up a given buffer with all the information that has to be transfered // to the child. // Returns true on success. // // The buffer size should be at least the value returned by GetBufferSize bool SetupConfigBuffer(void* buffer, size_t buffer_bytes); // Fills up the part of the transfer buffer that corresponds to information // about one dll to patch. // data is the first recorded interception for this dll. // Returns true on success. // // On successful return, buffer will be advanced from it's current position // to the point where the next block of configuration data should be written // (the actual interception info), and the current size of the buffer will // decrease to account the space used by this method. bool SetupDllInfo(const InterceptionData& data, void** buffer, size_t* buffer_bytes) const; // Fills up the part of the transfer buffer that corresponds to a single // function to patch. // dll_info points to the dll being updated with the interception stored on // data. The buffer pointer and remaining size are updated by this call. // Returns true on success. bool SetupInterceptionInfo(const InterceptionData& data, void** buffer, size_t* buffer_bytes, DllPatchInfo* dll_info) const; // Returns true if this interception is to be performed by the child // as opposed to from the parent. bool IsInterceptionPerformedByChild(const InterceptionData& data) const; // Allocates a buffer on the child's address space (returned on // remote_buffer), and fills it with the contents of a local buffer. // Returns true on success. bool CopyDataToChild(const void* local_buffer, size_t buffer_bytes, void** remote_buffer) const; // Performs the cold patch (from the parent) of ntdll. // Returns true on success. // // This method will insert additional interceptions to launch the interceptor // agent on the child process, if there are additional interceptions to do. bool PatchNtdll(bool hot_patch_needed); // Peforms the actual interceptions on ntdll. // thunks is the memory to store all the thunks for this dll (on the child), // and dll_data is a local buffer to hold global dll interception info. // Returns true on success. bool PatchClientFunctions(DllInterceptionData* thunks, size_t thunk_bytes, DllInterceptionData* dll_data); // The process to intercept. TargetProcess* child_; // Holds all interception info until the call to initialize (perform the // actual patch). std::list<InterceptionData> interceptions_; // Keep track of patches added by name. bool names_used_; // true if we are allowed to patch already-patched functions. bool relaxed_; DISALLOW_COPY_AND_ASSIGN(InterceptionManager); }; // This macro simply calls interception_manager.AddToPatchedFunctions with // the given service to intercept (INTERCEPTION_SERVICE_CALL), and assumes that // the interceptor is called "TargetXXX", where XXX is the name of the service. // Note that num_params is the number of bytes to pop out of the stack for // the exported interceptor, following the calling convention of a service call // (WINAPI = with the "C" underscore). #if SANDBOX_EXPORTS #if defined(_WIN64) #define MAKE_SERVICE_NAME(service, params) "Target" # service "64" #else #define MAKE_SERVICE_NAME(service, params) "_Target" # service "@" # params #endif #define ADD_NT_INTERCEPTION(service, id, num_params) \ AddToPatchedFunctions(kNtdllName, #service, \ sandbox::INTERCEPTION_SERVICE_CALL, \ MAKE_SERVICE_NAME(service, num_params), id) #define INTERCEPT_NT(manager, service, id, num_params) \ ((&Target##service) ? \ manager->ADD_NT_INTERCEPTION(service, id, num_params) : false) // When intercepting the EAT it is important that the patched version of the // function not call any functions imported from system libraries unless // |TargetServices::InitCalled()| returns true, because it is only then that // we are guaranteed that our IAT has been initialized. #define INTERCEPT_EAT(manager, dll, function, id, num_params) \ ((&Target##function) ? \ manager->AddToPatchedFunctions(dll, #function, sandbox::INTERCEPTION_EAT, \ MAKE_SERVICE_NAME(function, num_params), \ id) : \ false) #else // SANDBOX_EXPORTS #if defined(_WIN64) #define MAKE_SERVICE_NAME(service) &Target##service##64 #else #define MAKE_SERVICE_NAME(service) &Target##service #endif #define ADD_NT_INTERCEPTION(service, id, num_params) \ AddToPatchedFunctions(kNtdllName, #service, \ sandbox::INTERCEPTION_SERVICE_CALL, \ MAKE_SERVICE_NAME(service), id) #define INTERCEPT_NT(manager, service, id, num_params) \ manager->ADD_NT_INTERCEPTION(service, id, num_params) // When intercepting the EAT it is important that the patched version of the // function not call any functions imported from system libraries unless // |TargetServices::InitCalled()| returns true, because it is only then that // we are guaranteed that our IAT has been initialized. #define INTERCEPT_EAT(manager, dll, function, id, num_params) \ manager->AddToPatchedFunctions(dll, #function, sandbox::INTERCEPTION_EAT, \ MAKE_SERVICE_NAME(function), id) #endif // SANDBOX_EXPORTS } // namespace sandbox #endif // SANDBOX_SRC_INTERCEPTION_H_