// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sandbox/win/src/service_resolver.h"
#include <windows.h>
#include <ntstatus.h>
#include <stddef.h>
#include <winternl.h>
#include "base/containers/heap_array.h"
namespace {
#pragma pack(push, 1)
const BYTE kMovEax = 0xB8;
const BYTE kMovEdx = 0xBA;
const USHORT kMovEdxEsp = 0xD48B;
const USHORT kCallEdx = 0xD2FF;
const BYTE kCallEip = 0xE8;
const BYTE kRet = 0xC2;
const BYTE kRet2 = 0xC3;
const USHORT kJmpEdx = 0xE2FF;
const BYTE kJmp32 = 0xE9;
const USHORT kSysenter = 0x340F;
// Service code for 32 bit Windows. Introduced in Windows 8.
struct ServiceEntry32 {
// This struct contains the following code:
// 00 b825000000 mov eax,25h
// 05 e803000000 call eip+3
// 0a c22c00 ret 2Ch
// 0d 8bd4 mov edx,esp
// 0f 0f34 sysenter
// 11 c3 ret
// 12 8bff mov edi,edi
BYTE mov_eax; // = B8
ULONG service_id;
BYTE call_eip; // = E8
ULONG call_offset;
BYTE ret_p; // = C2
USHORT num_params;
USHORT mov_edx_esp; // = BD D4
USHORT sysenter; // = 0F 34
BYTE ret; // = C3
USHORT nop;
};
// Service code for a 32 bit process under Wow64. Introduced in Windows 10.
// Also used for the patching process.
struct ServiceEntryWow64 {
// 00 b828000000 mov eax, 28h
// 05 bab0d54877 mov edx, 7748D5B0h
// 09 ffd2 call edx
// 0c c22800 ret 28h
BYTE mov_eax; // = B8
ULONG service_id;
BYTE mov_edx; // = BA
ULONG mov_edx_param;
USHORT call_edx; // = FF D2
BYTE ret; // = C2
USHORT num_params;
BYTE nop;
};
// Make sure that relaxed patching works as expected.
const size_t kMinServiceSize = offsetof(ServiceEntryWow64, ret);
// Maximum size of the entry, was the size of the Windows Vista WoW64 entry.
// Keep this fixed for compatibility reasons.
const size_t kMaxServiceSize = 24;
static_assert(sizeof(ServiceEntry32) >= kMinServiceSize,
"wrong minimum service length");
static_assert(sizeof(ServiceEntry32) < kMaxServiceSize,
"wrong maximum service length");
static_assert(sizeof(ServiceEntryWow64) >= kMinServiceSize,
"wrong minimum service length");
static_assert(sizeof(ServiceEntryWow64) < kMaxServiceSize,
"wrong maximum service length");
struct ServiceFullThunk {
union {
ServiceEntryWow64 original;
// Pad the entry to the maximum size.
char dummy[kMaxServiceSize];
};
int internal_thunk; // Dummy member to the beginning of the internal thunk.
};
#pragma pack(pop)
bool IsWow64Process() {
// We don't need to use IsWow64Process2 as this returns the expected result
// when running in the ARM64 x86 emulator.
BOOL is_wow64 = FALSE;
return ::IsWow64Process(::GetCurrentProcess(), &is_wow64) && is_wow64;
}
bool IsFunctionAService32(HANDLE process, void* target, void* local_thunk) {
ServiceEntry32 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 || kCallEip != function_code.call_eip ||
function_code.call_offset != 3 || kRet != function_code.ret_p ||
kMovEdxEsp != function_code.mov_edx_esp ||
kSysenter != function_code.sysenter || kRet2 != function_code.ret) {
return false;
}
// Save the verified code
memcpy(local_thunk, &function_code, sizeof(function_code));
return true;
}
bool IsFunctionAServiceWow64(HANDLE process, void* target, void* local_thunk) {
ServiceEntryWow64 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 ||
kCallEdx != function_code.call_edx || kRet != function_code.ret) {
return false;
}
// Save the verified code
memcpy(local_thunk, &function_code, sizeof(function_code));
return true;
}
} // 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;
relative_jump_ = 0;
size_t thunk_bytes = GetThunkSize();
base::HeapArray<char> thunk_buffer =
base::HeapArray<char>::Uninit(thunk_bytes);
ServiceFullThunk* thunk =
reinterpret_cast<ServiceFullThunk*>(thunk_buffer.data());
if (!IsFunctionAService(&thunk->original) &&
(!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) {
return STATUS_OBJECT_NAME_COLLISION;
}
ret = PerformPatch(thunk, thunk_storage);
if (storage_used)
*storage_used = thunk_bytes;
return ret;
}
size_t ServiceResolverThunk::GetThunkSize() const {
return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize();
}
NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module,
const char* target_name,
BYTE* thunk_storage,
size_t storage_bytes,
size_t* storage_used) {
NTSTATUS ret = ResolveTarget(target_module, target_name, &target_);
if (!NT_SUCCESS(ret))
return ret;
size_t thunk_bytes = GetThunkSize();
if (storage_bytes < thunk_bytes)
return STATUS_UNSUCCESSFUL;
ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage);
if (!IsFunctionAService(&thunk->original) &&
(!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) {
return STATUS_OBJECT_NAME_COLLISION;
}
if (storage_used)
*storage_used = thunk_bytes;
return ret;
}
bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const {
static bool is_wow64 = IsWow64Process();
return is_wow64 ? IsFunctionAServiceWow64(process_, target_, local_thunk)
: IsFunctionAService32(process_, target_, local_thunk);
}
NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk,
void* remote_thunk) {
ServiceEntryWow64 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.mov_edx_param =
reinterpret_cast<ULONG>(&full_remote_thunk->internal_thunk);
intercepted_code.call_edx = kJmpEdx;
bytes_to_write = kMinServiceSize;
if (relative_jump_) {
intercepted_code.mov_eax = kJmp32;
intercepted_code.service_id = relative_jump_;
bytes_to_write = offsetof(ServiceEntryWow64, 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 (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) {
ServiceEntryWow64 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 += reinterpret_cast<ULONG>(target_) -
reinterpret_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_ = reinterpret_cast<ULONG>(&full_thunk->internal_thunk) -
reinterpret_cast<ULONG>(target_) - kJmp32Size;
}
// Save the verified code
memcpy(local_thunk, &function_code, sizeof(function_code));
return true;
}
bool ServiceResolverThunk::VerifyJumpTargetForTesting(
void* thunk_storage) const {
const size_t kJmp32Size = 5;
ServiceEntryWow64* patched = static_cast<ServiceEntryWow64*>(target_);
if (kJmp32 != patched->mov_eax) {
return false;
}
ULONG source_addr = reinterpret_cast<ULONG>(target_);
ULONG target_addr = reinterpret_cast<ULONG>(thunk_storage);
return target_addr + kMaxServiceSize - kJmp32Size - source_addr ==
patched->service_id;
}
} // namespace sandbox