diff options
author | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-11 22:20:31 +0000 |
---|---|---|
committer | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-11 22:20:31 +0000 |
commit | 0483cf1851e39d333d5fdab34506e1ef1006fc8a (patch) | |
tree | 2408fa7306b79dfd241b6a8deca616c21197ef2c /sandbox/src | |
parent | 83f30e9abf82b6e1defa5c6fcc3ed2bc6b2aa067 (diff) | |
download | chromium_src-0483cf1851e39d333d5fdab34506e1ef1006fc8a.zip chromium_src-0483cf1851e39d333d5fdab34506e1ef1006fc8a.tar.gz chromium_src-0483cf1851e39d333d5fdab34506e1ef1006fc8a.tar.bz2 |
Sandbox: Some cleanup after the previous changes.
No real code change.
BUG=27218
TEST=current tests.
Review URL: http://codereview.chromium.org/597050
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38837 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox/src')
-rw-r--r-- | sandbox/src/resolver.cc | 79 | ||||
-rw-r--r-- | sandbox/src/resolver_32.cc | 88 | ||||
-rw-r--r-- | sandbox/src/resolver_64.cc | 51 | ||||
-rw-r--r-- | sandbox/src/service_resolver.cc | 367 | ||||
-rw-r--r-- | sandbox/src/service_resolver_32.cc | 341 | ||||
-rw-r--r-- | sandbox/src/service_resolver_64.cc | 62 | ||||
-rw-r--r-- | sandbox/src/win_utils.cc | 20 | ||||
-rw-r--r-- | sandbox/src/win_utils.h | 6 |
8 files changed, 461 insertions, 553 deletions
diff --git a/sandbox/src/resolver.cc b/sandbox/src/resolver.cc index c2a8136..7ed02e4 100644 --- a/sandbox/src/resolver.cc +++ b/sandbox/src/resolver.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2006-2010 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. @@ -7,54 +7,6 @@ #include "base/pe_image.h" #include "sandbox/src/sandbox_nt_util.h" -namespace { - -#pragma pack(push, 1) -struct InternalThunk { - // This struct contains roughly the following code: - // sub esp, 8 // Create working space - // push edx // Save register - // mov edx, [esp + 0xc] // Get return adddress - // mov [esp + 8], edx // Store return address - // mov dword ptr [esp + 0xc], 0x7c401200 // Store extra argument - // mov dword ptr [esp + 4], 0x40010203 // Store address to jump to - // pop edx // Restore register - // ret // Jump to interceptor - // - // This code only modifies esp and eip so it must work with to normal calling - // convention. It is assembled as: - // - // 00 83ec08 sub esp,8 - // 03 52 push edx - // 04 8b54240c mov edx,dword ptr [esp + 0Ch] - // 08 89542408 mov dword ptr [esp + 8], edx - // 0c c744240c0012407c mov dword ptr [esp + 0Ch], 7C401200h - // 14 c744240403020140 mov dword ptr [esp + 4], 40010203h - // 1c 5a pop edx - // 1d c3 ret - InternalThunk() { - opcodes_1 = 0x5208ec83; - opcodes_2 = 0x0c24548b; - opcodes_3 = 0x08245489; - opcodes_4 = 0x0c2444c7; - opcodes_5 = 0x042444c7; - opcodes_6 = 0xc35a; - extra_argument = 0; - interceptor_function = 0; - }; - ULONG opcodes_1; // = 0x5208ec83 - ULONG opcodes_2; // = 0x0c24548b - ULONG opcodes_3; // = 0x08245489 - ULONG opcodes_4; // = 0x0c2444c7 - ULONG extra_argument; - ULONG opcodes_5; // = 0x042444c7 - ULONG interceptor_function; - USHORT opcodes_6; // = 0xc35a -}; -#pragma pack(pop) - -}; // namespace - namespace sandbox { NTSTATUS ResolverThunk::Init(const void* target_module, @@ -88,28 +40,6 @@ NTSTATUS ResolverThunk::Init(const void* target_module, return ret; } -size_t ResolverThunk::GetInternalThunkSize() const { - return sizeof(InternalThunk); -} - -bool ResolverThunk::SetInternalThunk(void* storage, size_t storage_bytes, - const void* original_function, - const void* interceptor) { - if (storage_bytes < sizeof(InternalThunk)) - return false; - - InternalThunk* thunk = new(storage, NT_PLACE) InternalThunk; - -#pragma warning(push) -#pragma warning(disable: 4311) - // These casts generate warnings because they are 32 bit specific. - thunk->interceptor_function = reinterpret_cast<ULONG>(interceptor); - thunk->extra_argument = reinterpret_cast<ULONG>(original_function); -#pragma warning(pop) - - return true; -} - NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, const char* interceptor_name, const void** address) { @@ -129,11 +59,4 @@ NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, return STATUS_SUCCESS; } -NTSTATUS ResolverThunk::ResolveTarget(const void* module, - const char* function_name, - void** address) { - const void** casted = const_cast<const void**>(address); - return ResolverThunk::ResolveInterceptor(module, function_name, casted); -} - } // namespace sandbox diff --git a/sandbox/src/resolver_32.cc b/sandbox/src/resolver_32.cc new file mode 100644 index 0000000..ae5c168 --- /dev/null +++ b/sandbox/src/resolver_32.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2006-2010 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/src/resolver.h" + +#include "sandbox/src/sandbox_nt_util.h" + +namespace { + +#pragma pack(push, 1) +struct InternalThunk { + // This struct contains roughly the following code: + // sub esp, 8 // Create working space + // push edx // Save register + // mov edx, [esp + 0xc] // Get return adddress + // mov [esp + 8], edx // Store return address + // mov dword ptr [esp + 0xc], 0x7c401200 // Store extra argument + // mov dword ptr [esp + 4], 0x40010203 // Store address to jump to + // pop edx // Restore register + // ret // Jump to interceptor + // + // This code only modifies esp and eip so it must work with to normal calling + // convention. It is assembled as: + // + // 00 83ec08 sub esp,8 + // 03 52 push edx + // 04 8b54240c mov edx,dword ptr [esp + 0Ch] + // 08 89542408 mov dword ptr [esp + 8], edx + // 0c c744240c0012407c mov dword ptr [esp + 0Ch], 7C401200h + // 14 c744240403020140 mov dword ptr [esp + 4], 40010203h + // 1c 5a pop edx + // 1d c3 ret + InternalThunk() { + opcodes_1 = 0x5208ec83; + opcodes_2 = 0x0c24548b; + opcodes_3 = 0x08245489; + opcodes_4 = 0x0c2444c7; + opcodes_5 = 0x042444c7; + opcodes_6 = 0xc35a; + extra_argument = 0; + interceptor_function = 0; + }; + ULONG opcodes_1; // = 0x5208ec83 + ULONG opcodes_2; // = 0x0c24548b + ULONG opcodes_3; // = 0x08245489 + ULONG opcodes_4; // = 0x0c2444c7 + ULONG extra_argument; + ULONG opcodes_5; // = 0x042444c7 + ULONG interceptor_function; + USHORT opcodes_6; // = 0xc35a +}; +#pragma pack(pop) + +}; // namespace + +namespace sandbox { + +bool ResolverThunk::SetInternalThunk(void* storage, size_t storage_bytes, + const void* original_function, + const void* interceptor) { + if (storage_bytes < sizeof(InternalThunk)) + return false; + + InternalThunk* thunk = new(storage, NT_PLACE) InternalThunk; + +#pragma warning(push) +#pragma warning(disable: 4311) + // These casts generate warnings because they are 32 bit specific. + thunk->interceptor_function = reinterpret_cast<ULONG>(interceptor); + thunk->extra_argument = reinterpret_cast<ULONG>(original_function); +#pragma warning(pop) + + return true; +} + +size_t ResolverThunk::GetInternalThunkSize() const { + return sizeof(InternalThunk); +} + +NTSTATUS ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + const void** casted = const_cast<const void**>(address); + return ResolverThunk::ResolveInterceptor(module, function_name, casted); +} + +} // namespace sandbox diff --git a/sandbox/src/resolver_64.cc b/sandbox/src/resolver_64.cc index 4c736c2..96d039b 100644 --- a/sandbox/src/resolver_64.cc +++ b/sandbox/src/resolver_64.cc @@ -4,7 +4,6 @@ #include "sandbox/src/resolver.h" -#include "base/pe_image.h" #include "sandbox/src/sandbox_nt_util.h" namespace { @@ -44,37 +43,6 @@ struct InternalThunk { 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; -} - size_t ResolverThunk::GetInternalThunkSize() const { return sizeof(InternalThunk); } @@ -91,25 +59,6 @@ bool ResolverThunk::SetInternalThunk(void* storage, size_t storage_bytes, return true; } -NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, - const char* interceptor_name, - const void** address) { - DCHECK_NT(address); - if (!interceptor_module) - return STATUS_INVALID_PARAMETER; - - PEImage pe(interceptor_module); - if (!pe.VerifyMagic()) - return STATUS_INVALID_IMAGE_FORMAT; - - *address = pe.GetProcAddress(interceptor_name); - - if (!(*address)) - return STATUS_PROCEDURE_NOT_FOUND; - - return STATUS_SUCCESS; -} - NTSTATUS ResolverThunk::ResolveTarget(const void* module, const char* function_name, void** address) { diff --git a/sandbox/src/service_resolver.cc b/sandbox/src/service_resolver.cc index 069b37f..728555e 100644 --- a/sandbox/src/service_resolver.cc +++ b/sandbox/src/service_resolver.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2006-2010 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. @@ -6,163 +6,9 @@ #include "base/logging.h" #include "base/pe_image.h" -#include "base/scoped_ptr.h" -#include "sandbox/src/sandbox_types.h" -#include "sandbox/src/sandbox_utils.h" - - -namespace { -#pragma pack(push, 1) - -const BYTE kMovEax = 0xB8; -const BYTE kMovEdx = 0xBA; -const USHORT kCallPtrEdx = 0x12FF; -const USHORT kCallEdx = 0xD2FF; -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 USHORT kCallFs2 = 0; -const BYTE kCallFs3 = 0; -const BYTE kAddEsp1 = 0x83; -const USHORT kAddEsp2 = 0x4C4; -const BYTE kJmp32 = 0xE9; - -const int kMaxService = 1000; - -// Service code for 32 bit systems. -// NOTE: on win2003 "call dword ptr [edx]" is "call edx". -struct ServiceEntry { - // this struct contains roughly the following code: - // 00 mov eax,25h - // 05 mov edx,offset SharedUserData!SystemCallStub (7ffe0300) - // 0a call dword ptr [edx] - // 0c ret 2Ch - // 0f nop - BYTE mov_eax; // = B8 - ULONG service_id; - BYTE mov_edx; // = BA - ULONG stub; - USHORT call_ptr_edx; // = FF 12 - BYTE ret; // = C2 - USHORT num_params; - BYTE nop; - ULONG pad1; // Extend the structure to be the same size as the - ULONG pad2; // 64 version (Wow64Entry) -}; - -// Service code for a 32 bit process running on a 64 bit os. -struct Wow64Entry { - // This struct may contain one of two versions of code: - // 1. For XP, Vista and 2K3: - // 00 b852000000 mov eax, 25h - // 05 33c9 xor ecx, ecx - // 07 8d542404 lea edx, [esp + 4] - // 0b 64ff15c0000000 call dword ptr fs:[0C0h] - // 12 c22c00 ret 2Ch - // - // 2. For Windows 7: - // 00 b852000000 mov eax, 25h - // 05 33c9 xor ecx, ecx - // 07 8d542404 lea edx, [esp + 4] - // 0b 64ff15c0000000 call dword ptr fs:[0C0h] - // 12 83c404 add esp, 4 - // 15 c22c00 ret 2Ch - // - // So we base the structure on the bigger one: - BYTE mov_eax; // = B8 - ULONG service_id; - USHORT xor_ecx; // = 33 C9 - ULONG lea_edx; // = 8D 54 24 04 - ULONG call_fs1; // = 64 FF 15 C0 - USHORT call_fs2; // = 00 00 - BYTE call_fs3; // = 00 - BYTE add_esp1; // = 83 or ret - USHORT add_esp2; // = C4 04 or num_params - BYTE ret; // = C2 - USHORT num_params; -}; - -// Make sure that relaxed patching works as expected. -COMPILE_ASSERT(sizeof(ServiceEntry) == sizeof(Wow64Entry), wrong_service_len); - -struct ServiceFullThunk { - union { - ServiceEntry original; - Wow64Entry wow_64; - }; - int internal_thunk; // Dummy member to the beginning of the 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; -} - -}; // namespace namespace sandbox { -NTSTATUS ServiceResolverThunk::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_array<char> thunk_buffer(new char[thunk_bytes]); - ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>( - thunk_buffer.get()); - - if (!IsFunctionAService(&thunk->original) && - (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) - return STATUS_UNSUCCESSFUL; - - ret = PerformPatch(thunk, thunk_storage); - - if (NULL != storage_used) - *storage_used = thunk_bytes; - - return ret; -} - NTSTATUS ServiceResolverThunk::ResolveInterceptor( const void* interceptor_module, const char* interceptor_name, @@ -174,7 +20,7 @@ NTSTATUS ServiceResolverThunk::ResolveInterceptor( } // In this case all the work is done from the parent, so resolve is -// just a simple GetProcAddress +// just a simple GetProcAddress. NTSTATUS ServiceResolverThunk::ResolveTarget(const void* module, const char* function_name, void** address) { @@ -185,217 +31,12 @@ NTSTATUS ServiceResolverThunk::ResolveTarget(const void* module, PEImage module_image(module); *address = module_image.GetProcAddress(function_name); - if (NULL == *address) + if (NULL == *address) { + NOTREACHED(); return STATUS_UNSUCCESSFUL; - - return STATUS_SUCCESS; -} - -size_t ServiceResolverThunk::GetThunkSize() const { - return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); -} - -bool ServiceResolverThunk::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 (kMovEax != function_code.mov_eax || - kMovEdx != function_code.mov_edx || - (kCallPtrEdx != function_code.call_ptr_edx && - kCallEdx != function_code.call_ptr_edx) || - kRet != function_code.ret) - return false; - - // Find the system call pointer if we don't already have it. - if (kCallEdx != function_code.call_ptr_edx) { - DWORD ki_system_call; - if (!::ReadProcessMemory(process_, - bit_cast<const void*>(function_code.stub), - &ki_system_call, sizeof(ki_system_call), &read)) - return false; - - if (sizeof(ki_system_call) != read) - return false; - - HMODULE module_1, module_2; - // last check, call_stub should point to a KiXXSystemCall function on ntdll - if (!GetModuleHandleHelper(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | - GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - bit_cast<const wchar_t*>(ki_system_call), - &module_1)) - return false; - - if (NULL != ntdll_base_) { - // This path is only taken when running the unit tests. We want to be - // able to patch a buffer in memory, so target_ is not inside ntdll. - module_2 = ntdll_base_; - } else { - if (!GetModuleHandleHelper( - GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | - GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - reinterpret_cast<const wchar_t*>(target_), - &module_2)) { - return false; - } - } - - if (module_1 != module_2) - return false; - } - - // Save the verified code - memcpy(local_thunk, &function_code, sizeof(function_code)); - - return true; -} - -NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, - void* remote_thunk) { - ServiceEntry intercepted_code; - size_t bytes_to_write = sizeof(intercepted_code); - ServiceFullThunk *full_local_thunk = reinterpret_cast<ServiceFullThunk*>( - local_thunk); - ServiceFullThunk *full_remote_thunk = reinterpret_cast<ServiceFullThunk*>( - remote_thunk); - - // patch the original code - memcpy(&intercepted_code, &full_local_thunk->original, - sizeof(intercepted_code)); - intercepted_code.mov_eax = kMovEax; - intercepted_code.service_id = full_local_thunk->original.service_id; - intercepted_code.mov_edx = kMovEdx; - intercepted_code.stub = bit_cast<ULONG>(&full_remote_thunk->internal_thunk); - intercepted_code.call_ptr_edx = kJmpEdx; - if (!win2k_) { - intercepted_code.ret = kRet; - intercepted_code.num_params = full_local_thunk->original.num_params; - intercepted_code.nop = kNop; - } else { - bytes_to_write = offsetof(ServiceEntry, ret); - } - - if (relative_jump_) { - intercepted_code.mov_eax = kJmp32; - intercepted_code.service_id = relative_jump_; - bytes_to_write = offsetof(ServiceEntry, mov_edx); - } - - // setup the thunk - SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(), - remote_thunk, interceptor_); - - size_t thunk_size = GetThunkSize(); - - // copy the local thunk buffer to the child - SIZE_T written; - if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, - thunk_size, &written)) - return STATUS_UNSUCCESSFUL; - - if (thunk_size != written) - return STATUS_UNSUCCESSFUL; - - // and now change the function to intercept, on the child - if (NULL != ntdll_base_) { - // running a unit test - if (!::WriteProcessMemory(process_, target_, &intercepted_code, - bytes_to_write, &written)) - return STATUS_UNSUCCESSFUL; - } else { - if (!::WriteProtectedChildMemory(process_, target_, &intercepted_code, - bytes_to_write)) - return STATUS_UNSUCCESSFUL; } return STATUS_SUCCESS; } -bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk, - void* remote_thunk) { - 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 (kJmp32 == function_code.mov_eax) { - // Plain old entry point patch. The relative jump address follows it. - ULONG relative = function_code.service_id; - - // First, fix our copy of their patch. - relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk); - - function_code.service_id = relative; - - // And now, remember how to re-patch it. - ServiceFullThunk *full_thunk = - reinterpret_cast<ServiceFullThunk*>(remote_thunk); - - const ULONG kJmp32Size = 5; - - relative_jump_ = bit_cast<ULONG>(&full_thunk->internal_thunk) - - bit_cast<ULONG>(target_) - kJmp32Size; - } - - // Save the verified code - memcpy(local_thunk, &function_code, sizeof(function_code)); - - return true; -} - -bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { - Wow64Entry 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 (kMovEax != function_code.mov_eax || kXorEcx != function_code.xor_ecx || - kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 || - kCallFs2 != function_code.call_fs2 || kCallFs3 != function_code.call_fs3) - return false; - - if ((kAddEsp1 == function_code.add_esp1 && - kAddEsp2 == function_code.add_esp2 && - kRet == function_code.ret) || kRet == function_code.add_esp1) { - // Save the verified code - memcpy(local_thunk, &function_code, sizeof(function_code)); - return true; - } - - return false; -} - -bool Win2kResolverThunk::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 (kMovEax != function_code.mov_eax || - function_code.service_id > kMaxService) - return false; - - // Save the verified code - memcpy(local_thunk, &function_code, sizeof(function_code)); - - return true; -} - } // namespace sandbox diff --git a/sandbox/src/service_resolver_32.cc b/sandbox/src/service_resolver_32.cc new file mode 100644 index 0000000..aaa2c49 --- /dev/null +++ b/sandbox/src/service_resolver_32.cc @@ -0,0 +1,341 @@ +// Copyright (c) 2006-2010 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/src/service_resolver.h" + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "sandbox/src/sandbox_utils.h" +#include "sandbox/src/win_utils.h" + +namespace { +#pragma pack(push, 1) + +const BYTE kMovEax = 0xB8; +const BYTE kMovEdx = 0xBA; +const USHORT kCallPtrEdx = 0x12FF; +const USHORT kCallEdx = 0xD2FF; +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 USHORT kCallFs2 = 0; +const BYTE kCallFs3 = 0; +const BYTE kAddEsp1 = 0x83; +const USHORT kAddEsp2 = 0x4C4; +const BYTE kJmp32 = 0xE9; + +const int kMaxService = 1000; + +// Service code for 32 bit systems. +// NOTE: on win2003 "call dword ptr [edx]" is "call edx". +struct ServiceEntry { + // this struct contains roughly the following code: + // 00 mov eax,25h + // 05 mov edx,offset SharedUserData!SystemCallStub (7ffe0300) + // 0a call dword ptr [edx] + // 0c ret 2Ch + // 0f nop + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE mov_edx; // = BA + ULONG stub; + USHORT call_ptr_edx; // = FF 12 + BYTE ret; // = C2 + USHORT num_params; + BYTE nop; + ULONG pad1; // Extend the structure to be the same size as the + ULONG pad2; // 64 version (Wow64Entry) +}; + +// Service code for a 32 bit process running on a 64 bit os. +struct Wow64Entry { + // This struct may contain one of two versions of code: + // 1. For XP, Vista and 2K3: + // 00 b852000000 mov eax, 25h + // 05 33c9 xor ecx, ecx + // 07 8d542404 lea edx, [esp + 4] + // 0b 64ff15c0000000 call dword ptr fs:[0C0h] + // 12 c22c00 ret 2Ch + // + // 2. For Windows 7: + // 00 b852000000 mov eax, 25h + // 05 33c9 xor ecx, ecx + // 07 8d542404 lea edx, [esp + 4] + // 0b 64ff15c0000000 call dword ptr fs:[0C0h] + // 12 83c404 add esp, 4 + // 15 c22c00 ret 2Ch + // + // So we base the structure on the bigger one: + BYTE mov_eax; // = B8 + ULONG service_id; + USHORT xor_ecx; // = 33 C9 + ULONG lea_edx; // = 8D 54 24 04 + ULONG call_fs1; // = 64 FF 15 C0 + USHORT call_fs2; // = 00 00 + BYTE call_fs3; // = 00 + BYTE add_esp1; // = 83 or ret + USHORT add_esp2; // = C4 04 or num_params + BYTE ret; // = C2 + USHORT num_params; +}; + +// Make sure that relaxed patching works as expected. +COMPILE_ASSERT(sizeof(ServiceEntry) == sizeof(Wow64Entry), wrong_service_len); + +struct ServiceFullThunk { + union { + ServiceEntry original; + Wow64Entry wow_64; + }; + int internal_thunk; // Dummy member to the beginning of the internal thunk. +}; + +#pragma pack(pop) + +}; // namespace + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::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_array<char> thunk_buffer(new char[thunk_bytes]); + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>( + thunk_buffer.get()); + + if (!IsFunctionAService(&thunk->original) && + (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) + return STATUS_UNSUCCESSFUL; + + ret = PerformPatch(thunk, thunk_storage); + + if (NULL != storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +size_t ServiceResolverThunk::GetThunkSize() const { + return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); +} + +bool ServiceResolverThunk::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 (kMovEax != function_code.mov_eax || + kMovEdx != function_code.mov_edx || + (kCallPtrEdx != function_code.call_ptr_edx && + kCallEdx != function_code.call_ptr_edx) || + kRet != function_code.ret) + return false; + + // Find the system call pointer if we don't already have it. + if (kCallEdx != function_code.call_ptr_edx) { + DWORD ki_system_call; + if (!::ReadProcessMemory(process_, + bit_cast<const void*>(function_code.stub), + &ki_system_call, sizeof(ki_system_call), &read)) + return false; + + if (sizeof(ki_system_call) != read) + return false; + + HMODULE module_1, module_2; + // last check, call_stub should point to a KiXXSystemCall function on ntdll + if (!GetModuleHandleHelper(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + bit_cast<const wchar_t*>(ki_system_call), + &module_1)) + return false; + + if (NULL != ntdll_base_) { + // This path is only taken when running the unit tests. We want to be + // able to patch a buffer in memory, so target_ is not inside ntdll. + module_2 = ntdll_base_; + } else { + if (!GetModuleHandleHelper( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<const wchar_t*>(target_), + &module_2)) { + return false; + } + } + + if (module_1 != module_2) + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, + void* remote_thunk) { + ServiceEntry intercepted_code; + size_t bytes_to_write = sizeof(intercepted_code); + ServiceFullThunk *full_local_thunk = reinterpret_cast<ServiceFullThunk*>( + local_thunk); + ServiceFullThunk *full_remote_thunk = reinterpret_cast<ServiceFullThunk*>( + remote_thunk); + + // patch the original code + memcpy(&intercepted_code, &full_local_thunk->original, + sizeof(intercepted_code)); + intercepted_code.mov_eax = kMovEax; + intercepted_code.service_id = full_local_thunk->original.service_id; + intercepted_code.mov_edx = kMovEdx; + intercepted_code.stub = bit_cast<ULONG>(&full_remote_thunk->internal_thunk); + intercepted_code.call_ptr_edx = kJmpEdx; + if (!win2k_) { + intercepted_code.ret = kRet; + intercepted_code.num_params = full_local_thunk->original.num_params; + intercepted_code.nop = kNop; + } else { + bytes_to_write = offsetof(ServiceEntry, ret); + } + + if (relative_jump_) { + intercepted_code.mov_eax = kJmp32; + intercepted_code.service_id = relative_jump_; + bytes_to_write = offsetof(ServiceEntry, mov_edx); + } + + // setup the thunk + SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(), + remote_thunk, interceptor_); + + size_t thunk_size = GetThunkSize(); + + // copy the local thunk buffer to the child + SIZE_T written; + if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, + thunk_size, &written)) + return STATUS_UNSUCCESSFUL; + + if (thunk_size != written) + return STATUS_UNSUCCESSFUL; + + // and now change the function to intercept, on the child + if (NULL != ntdll_base_) { + // running a unit test + if (!::WriteProcessMemory(process_, target_, &intercepted_code, + bytes_to_write, &written)) + return STATUS_UNSUCCESSFUL; + } else { + if (!WriteProtectedChildMemory(process_, target_, &intercepted_code, + bytes_to_write)) + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk, + void* remote_thunk) { + 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 (kJmp32 == function_code.mov_eax) { + // Plain old entry point patch. The relative jump address follows it. + ULONG relative = function_code.service_id; + + // First, fix our copy of their patch. + relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk); + + function_code.service_id = relative; + + // And now, remember how to re-patch it. + ServiceFullThunk *full_thunk = + reinterpret_cast<ServiceFullThunk*>(remote_thunk); + + const ULONG kJmp32Size = 5; + + relative_jump_ = bit_cast<ULONG>(&full_thunk->internal_thunk) - + bit_cast<ULONG>(target_) - kJmp32Size; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64Entry 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 (kMovEax != function_code.mov_eax || kXorEcx != function_code.xor_ecx || + kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 || + kCallFs2 != function_code.call_fs2 || kCallFs3 != function_code.call_fs3) + return false; + + if ((kAddEsp1 == function_code.add_esp1 && + kAddEsp2 == function_code.add_esp2 && + kRet == function_code.ret) || kRet == function_code.add_esp1) { + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; + } + + return false; +} + +bool Win2kResolverThunk::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 (kMovEax != function_code.mov_eax || + function_code.service_id > kMaxService) + return false; + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +} // namespace sandbox diff --git a/sandbox/src/service_resolver_64.cc b/sandbox/src/service_resolver_64.cc index c6e5b41..7194ee4 100644 --- a/sandbox/src/service_resolver_64.cc +++ b/sandbox/src/service_resolver_64.cc @@ -5,8 +5,8 @@ #include "sandbox/src/service_resolver.h" #include "base/logging.h" -#include "base/pe_image.h" #include "base/scoped_ptr.h" +#include "sandbox/src/win_utils.h" namespace { #pragma pack(push, 1) @@ -41,36 +41,6 @@ struct ServiceFullThunk { #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; -} - }; // namespace namespace sandbox { @@ -105,36 +75,6 @@ NTSTATUS ServiceResolverThunk::Setup(const void* target_module, return ret; } -NTSTATUS ServiceResolverThunk::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<void**>(address)); -} - -// In this case all the work is done from the parent, so resolve is -// just a simple GetProcAddress. -NTSTATUS ServiceResolverThunk::ResolveTarget(const void* module, - const char* function_name, - void** address) { - DCHECK(address); - if (NULL == module) - return STATUS_UNSUCCESSFUL; - - PEImage module_image(module); - *address = module_image.GetProcAddress(function_name); - - if (NULL == *address) { - NOTREACHED(); - return STATUS_UNSUCCESSFUL; - } - - return STATUS_SUCCESS; -} - size_t ServiceResolverThunk::GetThunkSize() const { return sizeof(ServiceFullThunk); } diff --git a/sandbox/src/win_utils.cc b/sandbox/src/win_utils.cc index bf4936b..8be9ce4 100644 --- a/sandbox/src/win_utils.cc +++ b/sandbox/src/win_utils.cc @@ -265,6 +265,26 @@ bool GetPathFromHandle(HANDLE handle, std::wstring* path) { return true; } +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; +} + }; // namespace sandbox // TODO(cpu): This is not the final code we want here but we are yet diff --git a/sandbox/src/win_utils.h b/sandbox/src/win_utils.h index c52f3e3..8bc44b7 100644 --- a/sandbox/src/win_utils.h +++ b/sandbox/src/win_utils.h @@ -88,6 +88,12 @@ HKEY GetReservedKeyFromName(const std::wstring& name); // cannot be resolved. bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name); +// Writes |length| bytes from the provided |buffer| into the address space of +// |child_process|, at the specified |address|, preserving the original write +// protection attributes. Returns true on success. +bool WriteProtectedChildMemory(HANDLE child_process, void* address, + const void* buffer, size_t length); + } // namespace sandbox // Resolves a function name in NTDLL to a function pointer. The second parameter |