// 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. #include "sandbox/win/wow_helper/service64_resolver.h" #include "base/memory/scoped_ptr.h" #include "sandbox/win/wow_helper/target_code.h" namespace { #pragma pack(push, 1) const BYTE kMovEax = 0xB8; const BYTE kMovEdx = 0xBA; const USHORT kCallPtrEdx = 0x12FF; const BYTE kRet = 0xC2; const BYTE kNop = 0x90; const USHORT kJmpEdx = 0xE2FF; const USHORT kXorEcx = 0xC933; const ULONG kLeaEdx = 0x0424548D; const ULONG kCallFs1 = 0xC015FF64; const ULONG kCallFs2Ret = 0xC2000000; const BYTE kPopEdx = 0x5A; const BYTE kPushEdx = 0x52; const BYTE kPush32 = 0x68; const ULONG kMmovR10EcxMovEax = 0xB8D18B4C; const USHORT kSyscall = 0x050F; const BYTE kRetNp = 0xC3; const BYTE kPad = 0x66; const USHORT kNop16 = 0x9066; const BYTE kRelJmp = 0xE9; const ULONG kXorRaxMovEax = 0xB8C03148; const ULONG kSaveRcx = 0x10488948; const ULONG kMovRcxRaxJmp = 0xE9C88B48; // Service code for 64 bit systems. struct ServiceEntry { // this struct contains roughly the following code: // mov r10,rcx // mov eax,52h // syscall // ret // xchg ax,ax // xchg ax,ax ULONG mov_r10_ecx_mov_eax; // = 4C 8B D1 B8 ULONG service_id; USHORT syscall; // = 0F 05 BYTE ret; // = C3 BYTE pad; // = 66 USHORT xchg_ax_ax1; // = 66 90 USHORT xchg_ax_ax2; // = 66 90 }; struct Redirected { // this struct contains roughly the following code: // jmp relative_32 // xchg ax,ax // 3 byte nop Redirected() { jmp = kRelJmp; relative = 0; pad = kPad; xchg_ax_ax = kNop16; }; BYTE jmp; // = E9 ULONG relative; BYTE pad; // = 66 USHORT xchg_ax_ax; // = 66 90 }; struct InternalThunk { // this struct contains roughly the following code: // xor rax,rax // mov eax, 0x00080000 // Thunk storage. // mov [rax]PatchInfo.service, rcx // Save first argument. // mov rcx, rax // jmp relative_to_interceptor InternalThunk() { xor_rax_mov_eax = kXorRaxMovEax; patch_info = 0; save_rcx = kSaveRcx; mov_rcx_rax_jmp = kMovRcxRaxJmp; relative = 0; }; ULONG xor_rax_mov_eax; // = 48 31 C0 B8 ULONG patch_info; ULONG save_rcx; // = 48 89 48 10 ULONG mov_rcx_rax_jmp; // = 48 8b c8 e9 ULONG relative; }; struct ServiceFullThunk { sandbox::PatchInfo patch_info; ServiceEntry original; InternalThunk internal_thunk; }; #pragma pack(pop) // Simple utility function to write to a buffer on the child, if the memery has // write protection attributes. // Arguments: // child_process (in): process to write to. // address (out): memory position on the child to write to. // buffer (in): local buffer with the data to write . // length (in): number of bytes to write. // Returns true on success. bool WriteProtectedChildMemory(HANDLE child_process, void* address, const void* buffer, size_t length) { // first, remove the protections DWORD old_protection; if (!::VirtualProtectEx(child_process, address, length, PAGE_WRITECOPY, &old_protection)) return false; SIZE_T written; bool ok = ::WriteProcessMemory(child_process, address, buffer, length, &written) && (length == written); // always attempt to restore the original protection if (!::VirtualProtectEx(child_process, address, length, old_protection, &old_protection)) return false; return ok; } // Get pointers to the functions that we need from ntdll.dll. NTSTATUS ResolveNtdll(sandbox::PatchInfo* patch_info) { wchar_t* ntdll_name = L"ntdll.dll"; HMODULE ntdll = ::GetModuleHandle(ntdll_name); if (!ntdll) return STATUS_PROCEDURE_NOT_FOUND; void* signal = ::GetProcAddress(ntdll, "NtSignalAndWaitForSingleObject"); if (!signal) return STATUS_PROCEDURE_NOT_FOUND; patch_info->signal_and_wait = reinterpret_cast(signal); return STATUS_SUCCESS; } }; // namespace namespace sandbox { NTSTATUS ResolverThunk::Init(const void* target_module, const void* interceptor_module, const char* target_name, const char* interceptor_name, const void* interceptor_entry_point, void* thunk_storage, size_t storage_bytes) { if (NULL == thunk_storage || 0 == storage_bytes || NULL == target_module || NULL == target_name) return STATUS_INVALID_PARAMETER; if (storage_bytes < GetThunkSize()) return STATUS_BUFFER_TOO_SMALL; NTSTATUS ret = STATUS_SUCCESS; if (NULL == interceptor_entry_point) { ret = ResolveInterceptor(interceptor_module, interceptor_name, &interceptor_entry_point); if (!NT_SUCCESS(ret)) return ret; } ret = ResolveTarget(target_module, target_name, &target_); if (!NT_SUCCESS(ret)) return ret; interceptor_ = interceptor_entry_point; return ret; } NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, const char* interceptor_name, const void** address) { return STATUS_NOT_IMPLEMENTED; } NTSTATUS ResolverThunk::ResolveTarget(const void* module, const char* function_name, void** address) { return STATUS_NOT_IMPLEMENTED; } NTSTATUS Service64ResolverThunk::Setup(const void* target_module, const void* interceptor_module, const char* target_name, const char* interceptor_name, const void* interceptor_entry_point, void* thunk_storage, size_t storage_bytes, size_t* storage_used) { NTSTATUS ret = Init(target_module, interceptor_module, target_name, interceptor_name, interceptor_entry_point, thunk_storage, storage_bytes); if (!NT_SUCCESS(ret)) return ret; size_t thunk_bytes = GetThunkSize(); scoped_ptr thunk_buffer(new char[thunk_bytes]); ServiceFullThunk* thunk = reinterpret_cast( thunk_buffer.get()); if (!IsFunctionAService(&thunk->original)) return STATUS_UNSUCCESSFUL; ret = PerformPatch(thunk, thunk_storage); if (NULL != storage_used) *storage_used = thunk_bytes; return ret; } NTSTATUS Service64ResolverThunk::ResolveInterceptor( const void* interceptor_module, const char* interceptor_name, const void** address) { // After all, we are using a locally mapped version of the exe, so the // action is the same as for a target function. return ResolveTarget(interceptor_module, interceptor_name, const_cast(address)); } // In this case all the work is done from the parent, so resolve is // just a simple GetProcAddress. NTSTATUS Service64ResolverThunk::ResolveTarget(const void* module, const char* function_name, void** address) { if (NULL == module) return STATUS_UNSUCCESSFUL; *address = ::GetProcAddress(bit_cast(module), function_name); if (NULL == *address) return STATUS_UNSUCCESSFUL; return STATUS_SUCCESS; } size_t Service64ResolverThunk::GetThunkSize() const { return sizeof(ServiceFullThunk); } bool Service64ResolverThunk::IsFunctionAService(void* local_thunk) const { ServiceEntry function_code; SIZE_T read; if (!::ReadProcessMemory(process_, target_, &function_code, sizeof(function_code), &read)) return false; if (sizeof(function_code) != read) return false; if (kMmovR10EcxMovEax != function_code.mov_r10_ecx_mov_eax || kSyscall != function_code.syscall || kRetNp != function_code.ret) return false; // Save the verified code memcpy(local_thunk, &function_code, sizeof(function_code)); return true; } NTSTATUS Service64ResolverThunk::PerformPatch(void* local_thunk, void* remote_thunk) { ServiceFullThunk* full_local_thunk = reinterpret_cast( local_thunk); ServiceFullThunk* full_remote_thunk = reinterpret_cast( remote_thunk); // If the source or target are above 4GB we cannot do this relative jump. if (reinterpret_cast(full_remote_thunk) > static_cast(ULONG_MAX)) return STATUS_CONFLICTING_ADDRESSES; if (reinterpret_cast(target_) > static_cast(ULONG_MAX)) return STATUS_CONFLICTING_ADDRESSES; // Patch the original code. Redirected local_service; Redirected* remote_service = reinterpret_cast(target_); ULONG_PTR diff = reinterpret_cast(&full_remote_thunk->internal_thunk) - &remote_service->pad; local_service.relative = static_cast(diff); // Setup the PatchInfo structure. SIZE_T actual; if (!::ReadProcessMemory(process_, remote_thunk, local_thunk, sizeof(PatchInfo), &actual)) return STATUS_UNSUCCESSFUL; if (sizeof(PatchInfo) != actual) return STATUS_UNSUCCESSFUL; full_local_thunk->patch_info.orig_MapViewOfSection = reinterpret_cast< NtMapViewOfSectionFunction>(&full_remote_thunk->original); full_local_thunk->patch_info.patch_location = target_; NTSTATUS ret = ResolveNtdll(&full_local_thunk->patch_info); if (!NT_SUCCESS(ret)) return ret; // Setup the thunk. The jump out is performed from right after the end of the // thunk (full_remote_thunk + 1). InternalThunk my_thunk; ULONG_PTR patch_info = reinterpret_cast(remote_thunk); my_thunk.patch_info = static_cast(patch_info); diff = reinterpret_cast(interceptor_) - reinterpret_cast(full_remote_thunk + 1); my_thunk.relative = static_cast(diff); memcpy(&full_local_thunk->internal_thunk, &my_thunk, sizeof(my_thunk)); // copy the local thunk buffer to the child if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, sizeof(ServiceFullThunk), &actual)) return STATUS_UNSUCCESSFUL; if (sizeof(ServiceFullThunk) != actual) return STATUS_UNSUCCESSFUL; // and now change the function to intercept, on the child if (!::WriteProtectedChildMemory(process_, target_, &local_service, sizeof(local_service))) return STATUS_UNSUCCESSFUL; return STATUS_SUCCESS; } } // namespace sandbox