michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "sandbox/win/src/service_resolver.h" michael@0: michael@0: #include "base/memory/scoped_ptr.h" michael@0: #include "sandbox/win/src/win_utils.h" michael@0: michael@0: namespace { michael@0: #pragma pack(push, 1) michael@0: michael@0: const BYTE kMovEax = 0xB8; michael@0: const BYTE kMovEdx = 0xBA; michael@0: const USHORT kMovEdxEsp = 0xD48B; michael@0: const USHORT kCallPtrEdx = 0x12FF; michael@0: const USHORT kCallEdx = 0xD2FF; michael@0: const BYTE kCallEip = 0xE8; michael@0: const BYTE kRet = 0xC2; michael@0: const BYTE kRet2 = 0xC3; michael@0: const BYTE kNop = 0x90; michael@0: const USHORT kJmpEdx = 0xE2FF; michael@0: const USHORT kXorEcx = 0xC933; michael@0: const ULONG kLeaEdx = 0x0424548D; michael@0: const ULONG kCallFs1 = 0xC015FF64; michael@0: const USHORT kCallFs2 = 0; michael@0: const BYTE kCallFs3 = 0; michael@0: const BYTE kAddEsp1 = 0x83; michael@0: const USHORT kAddEsp2 = 0x4C4; michael@0: const BYTE kJmp32 = 0xE9; michael@0: const USHORT kSysenter = 0x340F; michael@0: michael@0: const int kMaxService = 1000; michael@0: michael@0: // Service code for 32 bit systems. michael@0: // NOTE: on win2003 "call dword ptr [edx]" is "call edx". michael@0: struct ServiceEntry { michael@0: // This struct contains roughly the following code: michael@0: // 00 mov eax,25h michael@0: // 05 mov edx,offset SharedUserData!SystemCallStub (7ffe0300) michael@0: // 0a call dword ptr [edx] michael@0: // 0c ret 2Ch michael@0: // 0f nop michael@0: BYTE mov_eax; // = B8 michael@0: ULONG service_id; michael@0: BYTE mov_edx; // = BA michael@0: ULONG stub; michael@0: USHORT call_ptr_edx; // = FF 12 michael@0: BYTE ret; // = C2 michael@0: USHORT num_params; michael@0: BYTE nop; michael@0: }; michael@0: michael@0: // Service code for 32 bit Windows 8. michael@0: struct ServiceEntryW8 { michael@0: // This struct contains the following code: michael@0: // 00 b825000000 mov eax,25h michael@0: // 05 e803000000 call eip+3 michael@0: // 0a c22c00 ret 2Ch michael@0: // 0d 8bd4 mov edx,esp michael@0: // 0f 0f34 sysenter michael@0: // 11 c3 ret michael@0: // 12 8bff mov edi,edi michael@0: BYTE mov_eax; // = B8 michael@0: ULONG service_id; michael@0: BYTE call_eip; // = E8 michael@0: ULONG call_offset; michael@0: BYTE ret_p; // = C2 michael@0: USHORT num_params; michael@0: USHORT mov_edx_esp; // = BD D4 michael@0: USHORT sysenter; // = 0F 34 michael@0: BYTE ret; // = C3 michael@0: USHORT nop; michael@0: }; michael@0: michael@0: // Service code for a 32 bit process running on a 64 bit os. michael@0: struct Wow64Entry { michael@0: // This struct may contain one of two versions of code: michael@0: // 1. For XP, Vista and 2K3: michael@0: // 00 b825000000 mov eax, 25h michael@0: // 05 33c9 xor ecx, ecx michael@0: // 07 8d542404 lea edx, [esp + 4] michael@0: // 0b 64ff15c0000000 call dword ptr fs:[0C0h] michael@0: // 12 c22c00 ret 2Ch michael@0: // michael@0: // 2. For Windows 7: michael@0: // 00 b825000000 mov eax, 25h michael@0: // 05 33c9 xor ecx, ecx michael@0: // 07 8d542404 lea edx, [esp + 4] michael@0: // 0b 64ff15c0000000 call dword ptr fs:[0C0h] michael@0: // 12 83c404 add esp, 4 michael@0: // 15 c22c00 ret 2Ch michael@0: // michael@0: // So we base the structure on the bigger one: michael@0: BYTE mov_eax; // = B8 michael@0: ULONG service_id; michael@0: USHORT xor_ecx; // = 33 C9 michael@0: ULONG lea_edx; // = 8D 54 24 04 michael@0: ULONG call_fs1; // = 64 FF 15 C0 michael@0: USHORT call_fs2; // = 00 00 michael@0: BYTE call_fs3; // = 00 michael@0: BYTE add_esp1; // = 83 or ret michael@0: USHORT add_esp2; // = C4 04 or num_params michael@0: BYTE ret; // = C2 michael@0: USHORT num_params; michael@0: }; michael@0: michael@0: // Service code for a 32 bit process running on 64 bit Windows 8. michael@0: struct Wow64EntryW8 { michael@0: // 00 b825000000 mov eax, 25h michael@0: // 05 64ff15c0000000 call dword ptr fs:[0C0h] michael@0: // 0b c22c00 ret 2Ch michael@0: // 0f 90 nop michael@0: BYTE mov_eax; // = B8 michael@0: ULONG service_id; michael@0: ULONG call_fs1; // = 64 FF 15 C0 michael@0: USHORT call_fs2; // = 00 00 michael@0: BYTE call_fs3; // = 00 michael@0: BYTE ret; // = C2 michael@0: USHORT num_params; michael@0: BYTE nop; michael@0: }; michael@0: michael@0: // Make sure that relaxed patching works as expected. michael@0: const size_t kMinServiceSize = offsetof(ServiceEntry, ret); michael@0: COMPILE_ASSERT(sizeof(ServiceEntryW8) >= kMinServiceSize, wrong_service_len); michael@0: COMPILE_ASSERT(sizeof(Wow64Entry) >= kMinServiceSize, wrong_service_len); michael@0: COMPILE_ASSERT(sizeof(Wow64EntryW8) >= kMinServiceSize, wrong_service_len); michael@0: michael@0: struct ServiceFullThunk { michael@0: union { michael@0: ServiceEntry original; michael@0: ServiceEntryW8 original_w8; michael@0: Wow64Entry wow_64; michael@0: Wow64EntryW8 wow_64_w8; michael@0: }; michael@0: int internal_thunk; // Dummy member to the beginning of the internal thunk. michael@0: }; michael@0: michael@0: #pragma pack(pop) michael@0: michael@0: }; // namespace michael@0: michael@0: namespace sandbox { michael@0: michael@0: NTSTATUS ServiceResolverThunk::Setup(const void* target_module, michael@0: const void* interceptor_module, michael@0: const char* target_name, michael@0: const char* interceptor_name, michael@0: const void* interceptor_entry_point, michael@0: void* thunk_storage, michael@0: size_t storage_bytes, michael@0: size_t* storage_used) { michael@0: NTSTATUS ret = Init(target_module, interceptor_module, target_name, michael@0: interceptor_name, interceptor_entry_point, michael@0: thunk_storage, storage_bytes); michael@0: if (!NT_SUCCESS(ret)) michael@0: return ret; michael@0: michael@0: relative_jump_ = 0; michael@0: size_t thunk_bytes = GetThunkSize(); michael@0: scoped_ptr thunk_buffer(new char[thunk_bytes]); michael@0: ServiceFullThunk* thunk = reinterpret_cast( michael@0: thunk_buffer.get()); michael@0: michael@0: if (!IsFunctionAService(&thunk->original) && michael@0: (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) michael@0: return STATUS_UNSUCCESSFUL; michael@0: michael@0: ret = PerformPatch(thunk, thunk_storage); michael@0: michael@0: if (NULL != storage_used) michael@0: *storage_used = thunk_bytes; michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: size_t ServiceResolverThunk::GetThunkSize() const { michael@0: return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); michael@0: } michael@0: michael@0: bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { michael@0: ServiceEntry function_code; michael@0: SIZE_T read; michael@0: if (!::ReadProcessMemory(process_, target_, &function_code, michael@0: sizeof(function_code), &read)) michael@0: return false; michael@0: michael@0: if (sizeof(function_code) != read) michael@0: return false; michael@0: michael@0: if (kMovEax != function_code.mov_eax || michael@0: kMovEdx != function_code.mov_edx || michael@0: (kCallPtrEdx != function_code.call_ptr_edx && michael@0: kCallEdx != function_code.call_ptr_edx) || michael@0: kRet != function_code.ret) michael@0: return false; michael@0: michael@0: // Find the system call pointer if we don't already have it. michael@0: if (kCallEdx != function_code.call_ptr_edx) { michael@0: DWORD ki_system_call; michael@0: if (!::ReadProcessMemory(process_, michael@0: bit_cast(function_code.stub), michael@0: &ki_system_call, sizeof(ki_system_call), &read)) michael@0: return false; michael@0: michael@0: if (sizeof(ki_system_call) != read) michael@0: return false; michael@0: michael@0: HMODULE module_1, module_2; michael@0: // last check, call_stub should point to a KiXXSystemCall function on ntdll michael@0: if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | michael@0: GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, michael@0: bit_cast(ki_system_call), &module_1)) michael@0: return false; michael@0: michael@0: if (NULL != ntdll_base_) { michael@0: // This path is only taken when running the unit tests. We want to be michael@0: // able to patch a buffer in memory, so target_ is not inside ntdll. michael@0: module_2 = ntdll_base_; michael@0: } else { michael@0: if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | michael@0: GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, michael@0: reinterpret_cast(target_), michael@0: &module_2)) michael@0: return false; michael@0: } michael@0: michael@0: if (module_1 != module_2) michael@0: return false; michael@0: } michael@0: michael@0: // Save the verified code michael@0: memcpy(local_thunk, &function_code, sizeof(function_code)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, michael@0: void* remote_thunk) { michael@0: ServiceEntry intercepted_code; michael@0: size_t bytes_to_write = sizeof(intercepted_code); michael@0: ServiceFullThunk *full_local_thunk = reinterpret_cast( michael@0: local_thunk); michael@0: ServiceFullThunk *full_remote_thunk = reinterpret_cast( michael@0: remote_thunk); michael@0: michael@0: // patch the original code michael@0: memcpy(&intercepted_code, &full_local_thunk->original, michael@0: sizeof(intercepted_code)); michael@0: intercepted_code.mov_eax = kMovEax; michael@0: intercepted_code.service_id = full_local_thunk->original.service_id; michael@0: intercepted_code.mov_edx = kMovEdx; michael@0: intercepted_code.stub = bit_cast(&full_remote_thunk->internal_thunk); michael@0: intercepted_code.call_ptr_edx = kJmpEdx; michael@0: bytes_to_write = kMinServiceSize; michael@0: michael@0: if (relative_jump_) { michael@0: intercepted_code.mov_eax = kJmp32; michael@0: intercepted_code.service_id = relative_jump_; michael@0: bytes_to_write = offsetof(ServiceEntry, mov_edx); michael@0: } michael@0: michael@0: // setup the thunk michael@0: SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(), michael@0: remote_thunk, interceptor_); michael@0: michael@0: size_t thunk_size = GetThunkSize(); michael@0: michael@0: // copy the local thunk buffer to the child michael@0: SIZE_T written; michael@0: if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, michael@0: thunk_size, &written)) michael@0: return STATUS_UNSUCCESSFUL; michael@0: michael@0: if (thunk_size != written) michael@0: return STATUS_UNSUCCESSFUL; michael@0: michael@0: // and now change the function to intercept, on the child michael@0: if (NULL != ntdll_base_) { michael@0: // running a unit test michael@0: if (!::WriteProcessMemory(process_, target_, &intercepted_code, michael@0: bytes_to_write, &written)) michael@0: return STATUS_UNSUCCESSFUL; michael@0: } else { michael@0: if (!WriteProtectedChildMemory(process_, target_, &intercepted_code, michael@0: bytes_to_write)) michael@0: return STATUS_UNSUCCESSFUL; michael@0: } michael@0: michael@0: return STATUS_SUCCESS; michael@0: } michael@0: michael@0: bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk, michael@0: void* remote_thunk) { michael@0: ServiceEntry function_code; michael@0: SIZE_T read; michael@0: if (!::ReadProcessMemory(process_, target_, &function_code, michael@0: sizeof(function_code), &read)) michael@0: return false; michael@0: michael@0: if (sizeof(function_code) != read) michael@0: return false; michael@0: michael@0: if (kJmp32 == function_code.mov_eax) { michael@0: // Plain old entry point patch. The relative jump address follows it. michael@0: ULONG relative = function_code.service_id; michael@0: michael@0: // First, fix our copy of their patch. michael@0: relative += bit_cast(target_) - bit_cast(remote_thunk); michael@0: michael@0: function_code.service_id = relative; michael@0: michael@0: // And now, remember how to re-patch it. michael@0: ServiceFullThunk *full_thunk = michael@0: reinterpret_cast(remote_thunk); michael@0: michael@0: const ULONG kJmp32Size = 5; michael@0: michael@0: relative_jump_ = bit_cast(&full_thunk->internal_thunk) - michael@0: bit_cast(target_) - kJmp32Size; michael@0: } michael@0: michael@0: // Save the verified code michael@0: memcpy(local_thunk, &function_code, sizeof(function_code)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { michael@0: Wow64Entry function_code; michael@0: SIZE_T read; michael@0: if (!::ReadProcessMemory(process_, target_, &function_code, michael@0: sizeof(function_code), &read)) michael@0: return false; michael@0: michael@0: if (sizeof(function_code) != read) michael@0: return false; michael@0: michael@0: if (kMovEax != function_code.mov_eax || kXorEcx != function_code.xor_ecx || michael@0: kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 || michael@0: kCallFs2 != function_code.call_fs2 || kCallFs3 != function_code.call_fs3) michael@0: return false; michael@0: michael@0: if ((kAddEsp1 == function_code.add_esp1 && michael@0: kAddEsp2 == function_code.add_esp2 && michael@0: kRet == function_code.ret) || kRet == function_code.add_esp1) { michael@0: // Save the verified code michael@0: memcpy(local_thunk, &function_code, sizeof(function_code)); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool Wow64W8ResolverThunk::IsFunctionAService(void* local_thunk) const { michael@0: Wow64EntryW8 function_code; michael@0: SIZE_T read; michael@0: if (!::ReadProcessMemory(process_, target_, &function_code, michael@0: sizeof(function_code), &read)) michael@0: return false; michael@0: michael@0: if (sizeof(function_code) != read) michael@0: return false; michael@0: michael@0: if (kMovEax != function_code.mov_eax || kCallFs1 != function_code.call_fs1 || michael@0: kCallFs2 != function_code.call_fs2 || michael@0: kCallFs3 != function_code.call_fs3 || kRet != function_code.ret) { michael@0: return false; michael@0: } michael@0: michael@0: // Save the verified code michael@0: memcpy(local_thunk, &function_code, sizeof(function_code)); michael@0: return true; michael@0: } michael@0: michael@0: bool Win2kResolverThunk::IsFunctionAService(void* local_thunk) const { michael@0: ServiceEntry function_code; michael@0: SIZE_T read; michael@0: if (!::ReadProcessMemory(process_, target_, &function_code, michael@0: sizeof(function_code), &read)) michael@0: return false; michael@0: michael@0: if (sizeof(function_code) != read) michael@0: return false; michael@0: michael@0: if (kMovEax != function_code.mov_eax || michael@0: function_code.service_id > kMaxService) michael@0: return false; michael@0: michael@0: // Save the verified code michael@0: memcpy(local_thunk, &function_code, sizeof(function_code)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool Win8ResolverThunk::IsFunctionAService(void* local_thunk) const { michael@0: ServiceEntryW8 function_code; michael@0: SIZE_T read; michael@0: if (!::ReadProcessMemory(process_, target_, &function_code, michael@0: sizeof(function_code), &read)) michael@0: return false; michael@0: michael@0: if (sizeof(function_code) != read) michael@0: return false; michael@0: michael@0: if (kMovEax != function_code.mov_eax || kCallEip != function_code.call_eip || michael@0: function_code.call_offset != 3 || kRet != function_code.ret_p || michael@0: kMovEdxEsp != function_code.mov_edx_esp || michael@0: kSysenter != function_code.sysenter || kRet2 != function_code.ret) { michael@0: return false; michael@0: } michael@0: michael@0: // Save the verified code michael@0: memcpy(local_thunk, &function_code, sizeof(function_code)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: } // namespace sandbox