// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "HookProcessor.h" #include #include #include #include "logging.h" #include "RegistryUtilities.h" #include "StringUtilities.h" #define MAX_BUFFER_SIZE 32768 #define NAMED_PIPE_BUFFER_SIZE 1024 #define LOW_INTEGRITY_SDDL_SACL L"S:(ML;;NW;;;LW)" #define PIPE_CONNECTION_TIMEOUT_IN_MILLISECONDS 5000 #define PIPE_NAME_TEMPLATE L"\\\\.\\pipe\\IEDriverPipe%d" // Define a shared data segment. Variables in this segment can be // shared across processes that load this DLL. #pragma data_seg("SHARED") bool flag = false; int event_count = 0; int data_buffer_size = MAX_BUFFER_SIZE; char data_buffer[MAX_BUFFER_SIZE]; #pragma data_seg() #pragma comment(linker, "/section:SHARED,RWS") namespace webdriver { class CopyDataHolderWindow : public CWindowImpl { public: DECLARE_WND_CLASS(L"CopyDataHolderWindow") BEGIN_MSG_MAP(CopyDataHolderWindow) END_MSG_MAP() LRESULT CopyData(HWND destination_window_handle, int data_size, void* pointer_to_data) { if (data_size > data_buffer_size) { LOG(WARN) << "Destination data buffer not large enough"; } // Copy the contents of local memory into a temporary structure // for transport across the process boundary. std::vector buffer(data_size); memcpy_s(&buffer[0], data_size, pointer_to_data, data_size); // Send the data across using SendMessage with the WM_COPYDATA // message. N.B., the window procedure in the other process *must* // have a handler for the WM_COPYDATA message, which copies the // content from the message payload into a local buffer. The // HookProcessor::CopyDataToBuffer method provides a common // implementation to copy the data into the shared buffer location. COPYDATASTRUCT data; data.dwData = 1; data.cbData = static_cast(buffer.size()); data.lpData = &buffer[0]; LRESULT result = ::SendMessage(destination_window_handle, WM_COPYDATA, reinterpret_cast(this->m_hWnd), reinterpret_cast(&data)); return result; } }; HookProcessor::HookProcessor() { this->window_handle_ = NULL; this->hook_procedure_handle_ = NULL; this->pipe_handle_ = INVALID_HANDLE_VALUE; this->communication_type_ = OneWay; } HookProcessor::~HookProcessor() { this->Dispose(); } void HookProcessor::Initialize(const std::string& hook_procedure_name, const int hook_procedure_type) { LOG(TRACE) << "Entering HookProcessor::Initialize"; HookSettings hook_settings; hook_settings.hook_procedure_name = hook_procedure_name; hook_settings.hook_procedure_type = hook_procedure_type; hook_settings.window_handle = NULL; hook_settings.communication_type = OneWay; this->Initialize(hook_settings); } void HookProcessor::Initialize(const HookSettings& settings) { LOG(TRACE) << "Entering HookProcessor::Initialize"; this->window_handle_ = settings.window_handle; this->pipe_handle_ = INVALID_HANDLE_VALUE; this->communication_type_ = settings.communication_type; if (settings.communication_type == TwoWay) { this->CreateReturnPipe(); } bool is_hook_installed = this->InstallWindowsHook(settings.hook_procedure_name, settings.hook_procedure_type); } bool HookProcessor::CanSetWindowsHook(HWND window_handle) { int driver_bitness = 32; HANDLE driver_process_handle = ::GetCurrentProcess(); if (Is64BitProcess(driver_process_handle)) { driver_bitness = 64; } ::CloseHandle(driver_process_handle); DWORD process_id; DWORD thread_id = ::GetWindowThreadProcessId(window_handle, &process_id); HANDLE browser_process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, process_id); int browser_bitness = 32; if (Is64BitProcess(browser_process_handle)) { browser_bitness = 64; } if (driver_bitness != browser_bitness) { LOG(WARN) << "Unable to set Windows hook procedure. Driver is a " << driver_bitness << "-bit process, but browser is a " << browser_bitness << "-bit process."; } return driver_bitness == browser_bitness; } bool HookProcessor::Is64BitProcess(HANDLE process_handle) { if (!RegistryUtilities::Is64BitWindows()) { // A 64-bit process can never run on the 32-bit OS, // so the process must be 32-bit. return false; } // If the processor architecture is not x86, the process could // be 64-bit, or it could be 32-bit. We still need to determine // that. BOOL is_emulated; ::IsWow64Process(process_handle, &is_emulated); if (!is_emulated) { return true; } // The OS is 64-bit, but the process is running in the // Windows-on-Windows (Wow64) subsystem, so it must be a 64-bit // process. return false; } bool HookProcessor::InstallWindowsHook(const std::string& hook_proc_name, const int hook_proc_type) { LOG(TRACE) << "Entering HookProcessor::InstallWindowsHook"; HINSTANCE instance_handle = _AtlBaseModule.GetModuleInstance(); FARPROC hook_procedure_address = ::GetProcAddress(instance_handle, hook_proc_name.c_str()); if (hook_procedure_address == NULL) { LOGERR(WARN) << "Unable to get address of hook procedure named " << hook_proc_name; return false; } HOOKPROC hook_procedure = reinterpret_cast(hook_procedure_address); // Install the Windows hook. DWORD thread_id = 0; if (this->window_handle_ != NULL) { thread_id = ::GetWindowThreadProcessId(this->window_handle_, NULL); } this->hook_procedure_handle_ = ::SetWindowsHookEx(hook_proc_type, hook_procedure, instance_handle, thread_id); if (this->hook_procedure_handle_ == NULL) { LOGERR(WARN) << "Unable to set windows hook"; return false; } return true; } void HookProcessor::UninstallWindowsHook() { LOG(TRACE) << "Entering HookProcessor::UninstallWindowsHook"; if (this->hook_procedure_handle_ != NULL) { ::UnhookWindowsHookEx(this->hook_procedure_handle_); } } void HookProcessor::CreateReturnPipe() { LOG(TRACE) << "Entering HookProcessor::CreateReturnPipe"; std::wstring pipe_name = StringUtilities::Format(PIPE_NAME_TEMPLATE, ::GetCurrentProcessId()); PSECURITY_ATTRIBUTES security_attributes_pointer = NULL; // Set security descriptor so low-integrity processes can write to it. PSECURITY_DESCRIPTOR pointer_to_descriptor; BOOL descriptor_created = ::ConvertStringSecurityDescriptorToSecurityDescriptor( LOW_INTEGRITY_SDDL_SACL, SDDL_REVISION_1, &pointer_to_descriptor, NULL); if (!descriptor_created) { LOGERR(DEBUG) << "Could not create security descriptor. " << "Assuming OS does not support low-integrity processes."; } else { SECURITY_ATTRIBUTES security_attributes; security_attributes.lpSecurityDescriptor = pointer_to_descriptor; security_attributes.nLength = sizeof(security_attributes); security_attributes_pointer = &security_attributes; } this->pipe_handle_ = ::CreateNamedPipe(pipe_name.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, NAMED_PIPE_BUFFER_SIZE, PIPE_CONNECTION_TIMEOUT_IN_MILLISECONDS, security_attributes_pointer); // failed to create pipe? if (this->pipe_handle_ == INVALID_HANDLE_VALUE) { LOG(WARN) << "Failed to create named pipe. Communication back from browser will not work."; } else { LOG(DEBUG) << "Created named pipe " << LOGWSTRING(pipe_name); } } void HookProcessor::Dispose() { LOG(TRACE) << "Entering HookProcessor::Dispose"; ClearBuffer(); if (this->pipe_handle_ != INVALID_HANDLE_VALUE && this->pipe_handle_ != NULL) { ::CloseHandle(this->pipe_handle_); this->pipe_handle_ = INVALID_HANDLE_VALUE; } if (this->hook_procedure_handle_ != NULL) { this->UninstallWindowsHook(); this->hook_procedure_handle_ = NULL; } } bool HookProcessor::PushData(int data_size, void* pointer_to_data) { LOG(TRACE) << "Entering HookProcessor::PushData"; if (this->hook_procedure_handle_ == NULL) { LOG(WARN) << "No hook procedure has been set"; return false; } CopyDataHolderWindow holder; holder.Create(/*HWND*/ HWND_MESSAGE, /*_U_RECT rect*/ CWindow::rcDefault, /*LPCTSTR szWindowName*/ NULL, /*DWORD dwStyle*/ NULL, /*DWORD dwExStyle*/ NULL, /*_U_MENUorID MenuOrID*/ 0U, /*LPVOID lpCreateParam*/ NULL); LRESULT result = holder.CopyData(this->window_handle_, data_size, pointer_to_data); holder.DestroyWindow(); return true; } bool HookProcessor::PushData(const std::wstring& data) { std::wstring mutable_data = data; return this->PushData(static_cast(mutable_data.size() * sizeof(wchar_t)), &mutable_data[0]); } int HookProcessor::PullData(std::vector* data) { LOG(TRACE) << "Entering HookProcessor::PullData"; std::vector buffer(NAMED_PIPE_BUFFER_SIZE); // Wait for the client to connect; if it succeeds, // the function returns a nonzero value. If the function // returns zero, GetLastError returns ERROR_PIPE_CONNECTED. LOG(DEBUG) << "Waiting for connection from browser via named pipe"; bool is_connected = true; if (!::ConnectNamedPipe(this->pipe_handle_, NULL)) { DWORD error = ::GetLastError(); if (error != ERROR_PIPE_CONNECTED) { is_connected = false; } } if (is_connected) { LOG(DEBUG) << "Connection from browser established via named pipe"; unsigned long bytes_read = 0; BOOL is_read_successful = ::ReadFile(this->pipe_handle_, &buffer[0], NAMED_PIPE_BUFFER_SIZE, &bytes_read, NULL); while (!is_read_successful && ERROR_MORE_DATA == ::GetLastError()) { data->insert(data->end(), buffer.begin(), buffer.begin() + bytes_read); is_read_successful = ::ReadFile(this->pipe_handle_, &buffer[0], NAMED_PIPE_BUFFER_SIZE, &bytes_read, NULL); } if (is_read_successful) { data->insert(data->end(), buffer.begin(), buffer.begin() + bytes_read); } ::DisconnectNamedPipe(this->pipe_handle_); } else { LOG(WARN) << "No connection received from browser via named pipe"; } return static_cast(data->size()); } void HookProcessor::ResetFlag() { flag = false; } bool HookProcessor::GetFlagValue(void) { return flag; } void HookProcessor::SetFlagValue(bool flag_value) { flag = flag_value; } int HookProcessor::GetEventCount() { return event_count; } void HookProcessor::IncrementEventCount(int increment) { event_count += increment; } void HookProcessor::ResetEventCount() { event_count = 0; } int HookProcessor::GetDataBufferSize() { return data_buffer_size; } void HookProcessor::SetDataBufferSize(int size) { data_buffer_size = size; } void* HookProcessor::GetDataBufferAddress() { return &data_buffer; } void HookProcessor::CopyDataToBuffer(int source_data_size, void* source) { // clear the shared buffer before putting data into it ClearBuffer(); if (source_data_size < data_buffer_size) { data_buffer_size = source_data_size; } memcpy_s(data_buffer, data_buffer_size, source, data_buffer_size); } void HookProcessor::CopyDataFromBuffer(int destination_data_size, void* destination) { if (data_buffer_size >= destination_data_size) { destination_data_size = data_buffer_size; } memcpy_s(destination, destination_data_size, data_buffer, destination_data_size); // clear the shared buffer after taking data out of it. ClearBuffer(); } void HookProcessor::CopyWStringToBuffer(const std::wstring& data) { std::vector local_buffer(0); StringUtilities::ToBuffer(data, &local_buffer); int local_size = static_cast(local_buffer.size() * sizeof(wchar_t)); CopyDataToBuffer(local_size, &local_buffer[0]); } std::wstring HookProcessor::CopyWStringFromBuffer() { // Allocate a buffer of wchar_t the length of the data in the // shared memory buffer, plus one extra wide char, so that we // can null terminate. int local_buffer_size = GetDataBufferSize() + sizeof(wchar_t); std::vector local_buffer(local_buffer_size / sizeof(wchar_t)); // Copy the data from the shared memory buffer, and force // a terminating null char into the local vector, then // convert to wstring. CopyDataFromBuffer(local_buffer_size, &local_buffer[0]); local_buffer[local_buffer.size() - 1] = L'\0'; std::wstring data = &local_buffer[0]; return data; } void HookProcessor::ClearBuffer() { // Zero out the shared buffer data_buffer_size = MAX_BUFFER_SIZE; memset(data_buffer, 0, MAX_BUFFER_SIZE); } void HookProcessor::WriteBufferToPipe(const int process_id) { std::wstring pipe_name = StringUtilities::Format(PIPE_NAME_TEMPLATE, process_id); HANDLE pipe_handle = INVALID_HANDLE_VALUE; // Create the named pipe handle. Retry up until a timeout to give // the driver end of the pipe time to start listening for a connection. BOOL is_pipe_available = ::WaitNamedPipe(pipe_name.c_str(), PIPE_CONNECTION_TIMEOUT_IN_MILLISECONDS); if (is_pipe_available) { pipe_handle = ::CreateFile(pipe_name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); } // if everything ok set mode to message mode if (INVALID_HANDLE_VALUE != pipe_handle) { DWORD pipe_mode = PIPE_READMODE_MESSAGE; // if this fails bail out if (::SetNamedPipeHandleState(pipe_handle, &pipe_mode, NULL, NULL)) { unsigned long bytes_written = 0; BOOL is_write_successful = ::WriteFile(pipe_handle, GetDataBufferAddress(), GetDataBufferSize(), &bytes_written, NULL); ::FlushFileBuffers(pipe_handle); ClearBuffer(); } ::CloseHandle(pipe_handle); } } } // namespace webdriver