зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1432653: Refactor the DLL interceptor and parameterize its memory operations; r=handyman
MozReview-Commit-ID: EYxVsQ1kicy --HG-- rename : xpcom/build/nsWindowsDllInterceptor.h => mozglue/misc/interceptor/PatcherBase.h rename : xpcom/build/nsWindowsDllInterceptor.h => mozglue/misc/interceptor/PatcherDetour.h rename : xpcom/build/nsWindowsDllInterceptor.h => mozglue/misc/interceptor/PatcherNopSpace.h rename : xpcom/build/nsWindowsDllInterceptor.h => mozglue/misc/nsWindowsDllInterceptor.h rename : toolkit/xre/test/win/TestDllInterceptor.cpp => mozglue/tests/interceptor/TestDllInterceptor.cpp extra : amend_source : 84a7590b40a649f7321eb05feca4f9256ecc5d22
This commit is contained in:
Родитель
5936ccc549
Коммит
5317435ec0
|
@ -58,7 +58,7 @@ FunctionHook::HookFunctions(int aQuirks)
|
|||
// This cache is created when a DLL is registered with a FunctionHook.
|
||||
// It is cleared on a call to ClearDllInterceptorCache(). It
|
||||
// must be freed before exit to avoid leaks.
|
||||
typedef nsClassHashtable<nsCStringHashKey, WindowsDllInterceptor> DllInterceptors;
|
||||
typedef nsClassHashtable<nsStringHashKey, WindowsDllInterceptor> DllInterceptors;
|
||||
DllInterceptors* sDllInterceptorCache = nullptr;
|
||||
|
||||
WindowsDllInterceptor*
|
||||
|
@ -68,9 +68,13 @@ FunctionHook::GetDllInterceptorFor(const char* aModuleName)
|
|||
sDllInterceptorCache = new DllInterceptors();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(NS_IsAscii(aModuleName), "Non-ASCII module names are not supported");
|
||||
NS_ConvertASCIItoUTF16 moduleName(aModuleName);
|
||||
|
||||
WindowsDllInterceptor* ret =
|
||||
sDllInterceptorCache->LookupOrAdd(nsCString(aModuleName), aModuleName);
|
||||
sDllInterceptorCache->LookupOrAdd(moduleName);
|
||||
MOZ_ASSERT(ret);
|
||||
ret->Init(moduleName.get());
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_interceptor_MMPolicies_h
|
||||
#define mozilla_interceptor_MMPolicies_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Types.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace interceptor {
|
||||
|
||||
class MMPolicyBase
|
||||
{
|
||||
public:
|
||||
static DWORD ComputeAllocationSize(const uint32_t aRequestedSize)
|
||||
{
|
||||
MOZ_ASSERT(aRequestedSize);
|
||||
DWORD result = aRequestedSize;
|
||||
|
||||
const uint32_t granularity = GetAllocGranularity();
|
||||
|
||||
uint32_t mod = aRequestedSize % granularity;
|
||||
if (mod) {
|
||||
result += (granularity - mod);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static DWORD GetAllocGranularity()
|
||||
{
|
||||
static const DWORD kAllocGranularity = []() -> DWORD {
|
||||
SYSTEM_INFO sysInfo;
|
||||
::GetSystemInfo(&sysInfo);
|
||||
return sysInfo.dwAllocationGranularity;
|
||||
}();
|
||||
|
||||
return kAllocGranularity;
|
||||
}
|
||||
|
||||
static DWORD GetPageSize()
|
||||
{
|
||||
static const DWORD kPageSize = []() -> DWORD {
|
||||
SYSTEM_INFO sysInfo;
|
||||
::GetSystemInfo(&sysInfo);
|
||||
return sysInfo.dwPageSize;
|
||||
}();
|
||||
|
||||
return kPageSize;
|
||||
}
|
||||
};
|
||||
|
||||
class MMPolicyInProcess : public MMPolicyBase
|
||||
{
|
||||
public:
|
||||
typedef MMPolicyInProcess MMPolicyT;
|
||||
|
||||
explicit MMPolicyInProcess()
|
||||
: mBase(nullptr)
|
||||
, mReservationSize(0)
|
||||
, mCommitOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
MMPolicyInProcess(const MMPolicyInProcess&) = delete;
|
||||
MMPolicyInProcess& operator=(const MMPolicyInProcess&) = delete;
|
||||
|
||||
MMPolicyInProcess(MMPolicyInProcess&& aOther)
|
||||
: mBase(nullptr)
|
||||
, mReservationSize(0)
|
||||
, mCommitOffset(0)
|
||||
{
|
||||
*this = Move(aOther);
|
||||
}
|
||||
|
||||
MMPolicyInProcess& operator=(MMPolicyInProcess&& aOther)
|
||||
{
|
||||
mBase = aOther.mBase;
|
||||
aOther.mBase = nullptr;
|
||||
|
||||
mCommitOffset = aOther.mCommitOffset;
|
||||
aOther.mCommitOffset = 0;
|
||||
|
||||
mReservationSize = aOther.mReservationSize;
|
||||
aOther.mReservationSize = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// We always leak mBase
|
||||
~MMPolicyInProcess() = default;
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return !!mBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we unhook everything upon destruction?
|
||||
*/
|
||||
bool ShouldUnhookUponDestruction() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const
|
||||
{
|
||||
::memcpy(aToPtr, aFromPtr, aLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const
|
||||
{
|
||||
::memcpy(aToPtr, aFromPtr, aLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
|
||||
uint32_t* aPrevProtFlags) const
|
||||
{
|
||||
MOZ_ASSERT(aPrevProtFlags);
|
||||
BOOL ok = ::VirtualProtect(aVAddress, aSize, aProtFlags,
|
||||
reinterpret_cast<PDWORD>(aPrevProtFlags));
|
||||
MOZ_ASSERT(ok);
|
||||
return !!ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the page that hosts aVAddress is accessible.
|
||||
*/
|
||||
bool IsPageAccessible(void* aVAddress) const
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
SIZE_T result = ::VirtualQuery(aVAddress, &mbi, sizeof(mbi));
|
||||
|
||||
return result && mbi.AllocationProtect && mbi.State == MEM_COMMIT &&
|
||||
mbi.Protect != PAGE_NOACCESS;
|
||||
}
|
||||
|
||||
bool FlushInstructionCache() const
|
||||
{
|
||||
return !!::FlushInstructionCache(::GetCurrentProcess(), nullptr, 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t* GetLocalView() const
|
||||
{
|
||||
return mBase;
|
||||
}
|
||||
|
||||
uintptr_t GetRemoteView() const
|
||||
{
|
||||
// Same as local view for in-process
|
||||
return reinterpret_cast<uintptr_t>(mBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the effective number of bytes reserved, or 0 on failure
|
||||
*/
|
||||
uint32_t Reserve(const uint32_t aSize)
|
||||
{
|
||||
if (!aSize) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mBase) {
|
||||
MOZ_ASSERT(mReservationSize >= aSize);
|
||||
return mReservationSize;
|
||||
}
|
||||
|
||||
mReservationSize = ComputeAllocationSize(aSize);
|
||||
mBase = static_cast<uint8_t*>(::VirtualAlloc(nullptr, mReservationSize,
|
||||
MEM_RESERVE, PAGE_NOACCESS));
|
||||
if (!mBase) {
|
||||
return 0;
|
||||
}
|
||||
return mReservationSize;
|
||||
}
|
||||
|
||||
bool MaybeCommitNextPage(const uint32_t aRequestedOffset,
|
||||
const uint32_t aRequestedLength)
|
||||
{
|
||||
if (!(*this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t limit = aRequestedOffset + aRequestedLength - 1;
|
||||
if (limit < mCommitOffset) {
|
||||
// No commit required
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(mCommitOffset < mReservationSize);
|
||||
if (mCommitOffset >= mReservationSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PVOID local = ::VirtualAlloc(mBase + mCommitOffset, GetPageSize(),
|
||||
MEM_COMMIT, PAGE_EXECUTE_READ);
|
||||
if (!local) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mCommitOffset += GetPageSize();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* mBase;
|
||||
uint32_t mReservationSize;
|
||||
uint32_t mCommitOffset;
|
||||
};
|
||||
|
||||
} // namespace interceptor
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_interceptor_MMPolicies_h
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_interceptor_PatcherBase_h
|
||||
#define mozilla_interceptor_PatcherBase_h
|
||||
|
||||
#include "mozilla/interceptor/TargetFunction.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace interceptor {
|
||||
|
||||
template <typename VMPolicy>
|
||||
class WindowsDllPatcherBase
|
||||
{
|
||||
protected:
|
||||
typedef typename VMPolicy::MMPolicyT MMPolicyT;
|
||||
|
||||
template <typename... Args>
|
||||
explicit WindowsDllPatcherBase(Args... aArgs)
|
||||
: mVMPolicy(mozilla::Forward<Args>(aArgs)...)
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction<MMPolicyT>
|
||||
ResolveRedirectedAddress(const void* aOriginalFunction)
|
||||
{
|
||||
ReadOnlyTargetFunction<MMPolicyT> origFn(mVMPolicy, aOriginalFunction);
|
||||
// If function entry is jmp rel8 stub to the internal implementation, we
|
||||
// resolve redirected address from the jump target.
|
||||
if (origFn[0] == 0xeb) {
|
||||
int8_t offset = (int8_t)(origFn[1]);
|
||||
if (offset <= 0) {
|
||||
// Bail out for negative offset: probably already patched by some
|
||||
// third-party code.
|
||||
return Move(origFn);
|
||||
}
|
||||
|
||||
for (int8_t i = 0; i < offset; i++) {
|
||||
if (origFn[2 + i] != 0x90) {
|
||||
// Bail out on insufficient nop space.
|
||||
return Move(origFn);
|
||||
}
|
||||
}
|
||||
|
||||
origFn += 2 + offset;
|
||||
return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy, origFn.GetAddress());
|
||||
}
|
||||
|
||||
#if defined(_M_IX86)
|
||||
// If function entry is jmp [disp32] such as used by kernel32,
|
||||
// we resolve redirected address from import table.
|
||||
if (origFn[0] == 0xff && origFn[1] == 0x25) {
|
||||
return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy,
|
||||
reinterpret_cast<const void*>((origFn + 2).template ChasePointer<uintptr_t*>()));
|
||||
}
|
||||
#elif defined(_M_X64)
|
||||
// If function entry is jmp [disp32] such as used by kernel32,
|
||||
// we resolve redirected address from import table.
|
||||
if (origFn[0] == 0x48 && origFn[1] == 0xff && origFn[2] == 0x25) {
|
||||
return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy,
|
||||
reinterpret_cast<const void*>((origFn + 3).ChasePointerFromDisp()));
|
||||
}
|
||||
|
||||
if (origFn[0] == 0xe9) {
|
||||
// require for TestDllInterceptor with --disable-optimize
|
||||
uintptr_t abstarget = (origFn + 1).ReadDisp32AsAbsolute();
|
||||
return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy, abstarget);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Move(origFn);
|
||||
}
|
||||
|
||||
protected:
|
||||
VMPolicy mVMPolicy;
|
||||
};
|
||||
|
||||
} // namespace interceptor
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_interceptor_PatcherBase_h
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,217 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_interceptor_PatcherNopSpace_h
|
||||
#define mozilla_interceptor_PatcherNopSpace_h
|
||||
|
||||
#if defined(_M_IX86)
|
||||
|
||||
#include "mozilla/interceptor/PatcherBase.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace interceptor {
|
||||
|
||||
template <typename VMPolicy>
|
||||
class WindowsDllNopSpacePatcher final : public WindowsDllPatcherBase<VMPolicy>
|
||||
{
|
||||
// For remembering the addresses of functions we've patched.
|
||||
mozilla::Vector<void*> mPatchedFns;
|
||||
|
||||
public:
|
||||
template <typename... Args>
|
||||
explicit WindowsDllNopSpacePatcher(Args... aArgs)
|
||||
: WindowsDllPatcherBase<VMPolicy>(mozilla::Forward<Args>(aArgs)...)
|
||||
{}
|
||||
|
||||
~WindowsDllNopSpacePatcher()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
WindowsDllNopSpacePatcher(const WindowsDllNopSpacePatcher&) = delete;
|
||||
WindowsDllNopSpacePatcher(WindowsDllNopSpacePatcher&&) = delete;
|
||||
WindowsDllNopSpacePatcher& operator=(const WindowsDllNopSpacePatcher&) = delete;
|
||||
WindowsDllNopSpacePatcher& operator=(WindowsDllNopSpacePatcher&&) = delete;
|
||||
|
||||
void Clear()
|
||||
{
|
||||
// Restore the mov edi, edi to the beginning of each function we patched.
|
||||
|
||||
for (auto&& ptr : mPatchedFns) {
|
||||
WritableTargetFunction<MMPolicyT> fn(mVMPolicy,
|
||||
reinterpret_cast<uintptr_t>(ptr),
|
||||
sizeof(uint16_t));
|
||||
if (!fn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// mov edi, edi
|
||||
fn.WriteShort(0xff8b);
|
||||
fn.Commit();
|
||||
}
|
||||
|
||||
mPatchedFns.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
|
||||
* in our address space. There is a bug in Detours 2.x that causes it to
|
||||
* patch at the wrong address when attempting to detour code that is already
|
||||
* NOP space patched. This function is an effort to detect the presence of
|
||||
* this NVIDIA code in our address space and disable NOP space patching if it
|
||||
* is. We also check AppInit_DLLs since this is the mechanism that the Optimus
|
||||
* drivers use to inject into our process.
|
||||
*/
|
||||
static bool IsCompatible()
|
||||
{
|
||||
// These DLLs are known to have bad interactions with this style of patching
|
||||
const wchar_t* kIncompatibleDLLs[] = {
|
||||
L"detoured.dll",
|
||||
L"_etoured.dll",
|
||||
L"nvd3d9wrap.dll",
|
||||
L"nvdxgiwrap.dll"
|
||||
};
|
||||
// See if the infringing DLLs are already loaded
|
||||
for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) {
|
||||
if (GetModuleHandleW(kIncompatibleDLLs[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (GetModuleHandleW(L"user32.dll")) {
|
||||
// user32 is loaded but the infringing DLLs are not, assume we're safe to
|
||||
// proceed.
|
||||
return true;
|
||||
}
|
||||
// If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus
|
||||
// won't be loaded once user32 is initialized.
|
||||
HKEY hkey = NULL;
|
||||
if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
|
||||
0, KEY_QUERY_VALUE, &hkey)) {
|
||||
nsAutoRegKey key(hkey);
|
||||
DWORD numBytes = 0;
|
||||
const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
|
||||
// Query for required buffer size
|
||||
LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
|
||||
nullptr, nullptr, &numBytes);
|
||||
mozilla::UniquePtr<wchar_t[]> data;
|
||||
if (!status) {
|
||||
// Allocate the buffer and query for the actual data
|
||||
data = mozilla::MakeUnique<wchar_t[]>((numBytes + 1) / sizeof(wchar_t));
|
||||
status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
|
||||
nullptr, (LPBYTE)data.get(), &numBytes);
|
||||
}
|
||||
if (!status) {
|
||||
// For each token, split up the filename components and then check the
|
||||
// name of the file.
|
||||
const wchar_t kDelimiters[] = L", ";
|
||||
wchar_t* tokenContext = nullptr;
|
||||
wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
|
||||
while (token) {
|
||||
wchar_t fname[_MAX_FNAME] = {0};
|
||||
if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0,
|
||||
fname, mozilla::ArrayLength(fname),
|
||||
nullptr, 0)) {
|
||||
// nvinit.dll is responsible for bootstrapping the DLL injection, so
|
||||
// that is the library that we check for here
|
||||
const wchar_t kNvInitName[] = L"nvinit";
|
||||
if (!_wcsnicmp(fname, kNvInitName,
|
||||
mozilla::ArrayLength(kNvInitName))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
token = wcstok_s(nullptr, kDelimiters, &tokenContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc)
|
||||
{
|
||||
if (!IsCompatible()) {
|
||||
#if defined(MOZILLA_INTERNAL_API)
|
||||
NS_WARNING("NOP space patching is unavailable for compatibility reasons");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aTargetFn);
|
||||
if (!aTargetFn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction<MMPolicyT> readOnlyTargetFn(
|
||||
ResolveRedirectedAddress(aTargetFn));
|
||||
|
||||
if (!WriteHook(readOnlyTargetFn, aHookDest, aOrigFunc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mPatchedFns.append(reinterpret_cast<void*>(readOnlyTargetFn.GetBaseAddress()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteHook(const ReadOnlyTargetFunction<MMPolicyT>& aFn,
|
||||
intptr_t aHookDest, void** aOrigFunc)
|
||||
{
|
||||
// Ensure we can read and write starting at fn - 5 (for the long jmp we're
|
||||
// going to write) and ending at fn + 2 (for the short jmp up to the long
|
||||
// jmp). These bytes may span two pages with different protection.
|
||||
WritableTargetFunction<MMPolicyT> writableFn(aFn.Promote(7, -5));
|
||||
if (!writableFn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the 5 bytes before the function are NOP's or INT 3's,
|
||||
const uint8_t nopOrBp[] = { 0x90, 0xCC };
|
||||
if (!writableFn.VerifyValuesAreOneOf<uint8_t, 5>(nopOrBp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... and that the first 2 bytes of the function are mov(edi, edi).
|
||||
// There are two ways to encode the same thing:
|
||||
//
|
||||
// 0x89 0xff == mov r/m, r
|
||||
// 0x8b 0xff == mov r, r/m
|
||||
//
|
||||
// where "r" is register and "r/m" is register or memory.
|
||||
// Windows seems to use 0x8B 0xFF. We include 0x89 0xFF out of paranoia.
|
||||
|
||||
// (These look backwards because little-endian)
|
||||
const uint16_t possibleEncodings[] = { 0xFF8B, 0xFF89 };
|
||||
if (!writableFn.VerifyValuesAreOneOf<uint16_t, 1>(possibleEncodings, 5)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write a long jump into the space above the function.
|
||||
writableFn.WriteByte(0xe9); // jmp
|
||||
if (!writableFn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writableFn.WriteDisp32(aHookDest); // target
|
||||
if (!writableFn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set aOrigFunc here, because after this point, aHookDest might be called,
|
||||
// and aHookDest might use the aOrigFunc pointer.
|
||||
*aOrigFunc = reinterpret_cast<void*>(writableFn.GetCurrentAddress() +
|
||||
sizeof(uint16_t));
|
||||
|
||||
// Short jump up into our long jump.
|
||||
writableFn.WriteShort(0xF9EB); // jmp $-5
|
||||
return writableFn.Commit();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace interceptor
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // defined(_M_IX86)
|
||||
|
||||
#endif // mozilla_interceptor_PatcherNopSpace_h
|
|
@ -0,0 +1,552 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_interceptor_TargetFunction_h
|
||||
#define mozilla_interceptor_TargetFunction_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Types.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace mozilla {
|
||||
namespace interceptor {
|
||||
|
||||
template <typename MMPolicy>
|
||||
class MOZ_STACK_CLASS WritableTargetFunction final
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Used to initialize an invalid WritableTargetFunction, thus signalling an error.
|
||||
*/
|
||||
explicit WritableTargetFunction(const MMPolicy& aMMPolicy)
|
||||
: mMMPolicy(aMMPolicy)
|
||||
, mFunc(0)
|
||||
, mNumBytes(0)
|
||||
, mOffset(0)
|
||||
, mStartWriteOffset(0)
|
||||
, mPrevProt(0)
|
||||
, mAccumulatedStatus(false)
|
||||
{
|
||||
}
|
||||
|
||||
WritableTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc,
|
||||
size_t aNumBytes)
|
||||
: mMMPolicy(aMMPolicy)
|
||||
, mFunc(aFunc)
|
||||
, mNumBytes(aNumBytes)
|
||||
, mOffset(0)
|
||||
, mStartWriteOffset(0)
|
||||
, mPrevProt(0)
|
||||
, mAccumulatedStatus(true)
|
||||
{
|
||||
aMMPolicy.Protect(reinterpret_cast<void*>(aFunc), aNumBytes,
|
||||
PAGE_EXECUTE_READWRITE, &mPrevProt);
|
||||
}
|
||||
|
||||
WritableTargetFunction(WritableTargetFunction&& aOther)
|
||||
: mMMPolicy(aOther.mMMPolicy)
|
||||
, mFunc(aOther.mFunc)
|
||||
, mNumBytes(aOther.mNumBytes)
|
||||
, mOffset(aOther.mOffset)
|
||||
, mStartWriteOffset(aOther.mStartWriteOffset)
|
||||
, mPrevProt(aOther.mPrevProt)
|
||||
, mLocalBytes(Move(aOther.mLocalBytes))
|
||||
, mAccumulatedStatus(aOther.mAccumulatedStatus)
|
||||
{
|
||||
aOther.mPrevProt = 0;
|
||||
aOther.mAccumulatedStatus = false;
|
||||
}
|
||||
|
||||
~WritableTargetFunction()
|
||||
{
|
||||
if (!mPrevProt) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mLocalBytes.empty(), "Did you forget to call Commit?");
|
||||
|
||||
DebugOnly<bool> ok = mMMPolicy.Protect(reinterpret_cast<void*>(mFunc),
|
||||
mNumBytes, mPrevProt, &mPrevProt);
|
||||
MOZ_ASSERT(ok);
|
||||
}
|
||||
|
||||
WritableTargetFunction(const WritableTargetFunction&) = delete;
|
||||
WritableTargetFunction& operator=(const WritableTargetFunction&) = delete;
|
||||
WritableTargetFunction& operator=(WritableTargetFunction&&) = delete;
|
||||
|
||||
/**
|
||||
* @return true if data was successfully committed.
|
||||
*/
|
||||
bool Commit()
|
||||
{
|
||||
if (!(*this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mLocalBytes.empty()) {
|
||||
// Nothing to commit, treat like success
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ok = mMMPolicy.Write(reinterpret_cast<void*>(mFunc + mStartWriteOffset),
|
||||
mLocalBytes.begin(), mLocalBytes.length());
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mMMPolicy.FlushInstructionCache();
|
||||
mLocalBytes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return mPrevProt && mAccumulatedStatus;
|
||||
}
|
||||
|
||||
void WriteByte(const uint8_t& aValue)
|
||||
{
|
||||
if (!mLocalBytes.append(aValue)) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mOffset += sizeof(uint8_t);
|
||||
}
|
||||
|
||||
Maybe<uint8_t> ReadByte()
|
||||
{
|
||||
// Reading is only permitted prior to any writing
|
||||
MOZ_ASSERT(mOffset == mStartWriteOffset);
|
||||
if (mOffset > mStartWriteOffset) {
|
||||
mAccumulatedStatus = false;
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
uint8_t value;
|
||||
if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
|
||||
sizeof(uint8_t))) {
|
||||
mAccumulatedStatus = false;
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
mOffset += sizeof(uint8_t);
|
||||
mStartWriteOffset += sizeof(uint8_t);
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
void WriteShort(const uint16_t& aValue)
|
||||
{
|
||||
if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aValue),
|
||||
sizeof(uint16_t))) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mOffset += sizeof(uint16_t);
|
||||
}
|
||||
|
||||
void WriteDisp32(const uintptr_t aAbsTarget)
|
||||
{
|
||||
intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
|
||||
static_cast<intptr_t>(mFunc + mOffset + sizeof(int32_t));
|
||||
|
||||
CheckedInt<int32_t> checkedDisp(diff);
|
||||
MOZ_ASSERT(checkedDisp.isValid());
|
||||
if (!checkedDisp.isValid()) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t disp = checkedDisp.value();
|
||||
if (!mLocalBytes.append(reinterpret_cast<uint8_t*>(&disp), sizeof(int32_t))) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mOffset += sizeof(int32_t);
|
||||
}
|
||||
|
||||
void WritePointer(const uintptr_t aAbsTarget)
|
||||
{
|
||||
if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aAbsTarget),
|
||||
sizeof(uintptr_t))) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mOffset += sizeof(uintptr_t);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aValues N-sized array of type T that specifies the set of values
|
||||
* that are permissible in the first M bytes of the target
|
||||
* function at aOffset.
|
||||
* @return true if M values of type T in the function are members of the
|
||||
* set specified by aValues.
|
||||
*/
|
||||
template <typename T, size_t M, size_t N>
|
||||
bool VerifyValuesAreOneOf(const T (&aValues)[N], const uint8_t aOffset = 0)
|
||||
{
|
||||
T buf[M];
|
||||
if (!mMMPolicy.Read(buf, reinterpret_cast<const void*>(mFunc + mOffset + aOffset),
|
||||
M * sizeof(T))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto&& fnValue : buf) {
|
||||
bool match = false;
|
||||
for (auto&& testValue : aValues) {
|
||||
match |= (fnValue == testValue);
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uintptr_t GetCurrentAddress() const
|
||||
{
|
||||
return mFunc + mOffset;
|
||||
}
|
||||
|
||||
private:
|
||||
const MMPolicy& mMMPolicy;
|
||||
const uintptr_t mFunc;
|
||||
const size_t mNumBytes;
|
||||
uint32_t mOffset;
|
||||
uint32_t mStartWriteOffset;
|
||||
uint32_t mPrevProt;
|
||||
#if defined(_M_IX86)
|
||||
static const size_t kInlineStorage = 16;
|
||||
#elif defined(_M_X64)
|
||||
static const size_t kInlineStorage = 32;
|
||||
#endif
|
||||
Vector<uint8_t, kInlineStorage> mLocalBytes;
|
||||
bool mAccumulatedStatus;
|
||||
};
|
||||
|
||||
template <typename MMPolicy>
|
||||
class ReadOnlyTargetBytes;
|
||||
|
||||
template <>
|
||||
class ReadOnlyTargetBytes<MMPolicyInProcess>
|
||||
{
|
||||
public:
|
||||
ReadOnlyTargetBytes(const MMPolicyInProcess& aMMPolicy, const void* aBase)
|
||||
: mMMPolicy(aMMPolicy)
|
||||
, mBase(reinterpret_cast<const uint8_t*>(aBase))
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
|
||||
: mMMPolicy(aOther.mMMPolicy)
|
||||
, mBase(aOther.mBase)
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
|
||||
const uint32_t aOffsetFromOther = 0)
|
||||
: mMMPolicy(aOther.mMMPolicy)
|
||||
, mBase(aOther.mBase + aOffsetFromOther)
|
||||
{
|
||||
}
|
||||
|
||||
void EnsureLimit(uint32_t aDesiredLimit)
|
||||
{
|
||||
// In the out-proc case we use this function to read the target function's
|
||||
// bytes in the other process into a local buffer. We don't need that for
|
||||
// the in-process case because we already have direct access to our target
|
||||
// function's bytes.
|
||||
}
|
||||
|
||||
bool IsValidAtOffset(const int8_t aOffset) const
|
||||
{
|
||||
if (!aOffset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uintptr_t base = reinterpret_cast<uintptr_t>(mBase);
|
||||
uintptr_t adjusted = base + aOffset;
|
||||
uint32_t pageSize = mMMPolicy.GetPageSize();
|
||||
|
||||
// If |adjusted| is within the same page as |mBase|, we're still valid
|
||||
if ((base / pageSize) == (adjusted / pageSize)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, let's query |adjusted|
|
||||
return mMMPolicy.IsPageAccessible(reinterpret_cast<void*>(adjusted));
|
||||
}
|
||||
|
||||
const uint8_t* Get() const
|
||||
{
|
||||
return mBase;
|
||||
}
|
||||
|
||||
const MMPolicyInProcess& GetMMPolicy() const
|
||||
{
|
||||
return mMMPolicy;
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
|
||||
ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
|
||||
|
||||
private:
|
||||
const MMPolicyInProcess& mMMPolicy;
|
||||
uint8_t const * const mBase;
|
||||
};
|
||||
|
||||
template <typename MMPolicy>
|
||||
class MOZ_STACK_CLASS ReadOnlyTargetFunction final
|
||||
{
|
||||
template <typename TargetMMPolicy>
|
||||
class TargetBytesPtr
|
||||
{
|
||||
public:
|
||||
typedef TargetBytesPtr<TargetMMPolicy> Type;
|
||||
|
||||
static Type Make(const TargetMMPolicy& aMMPolicy, const void* aFunc)
|
||||
{
|
||||
return Move(TargetBytesPtr(aMMPolicy, aFunc));
|
||||
}
|
||||
|
||||
static Type CopyFromOffset(const TargetBytesPtr& aOther,
|
||||
const uint32_t aOffsetFromOther)
|
||||
{
|
||||
return Move(TargetBytesPtr(aOther, aOffsetFromOther));
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes<TargetMMPolicy>* operator->()
|
||||
{
|
||||
return &mTargetBytes;
|
||||
}
|
||||
|
||||
TargetBytesPtr(TargetBytesPtr&& aOther)
|
||||
: mTargetBytes(Move(aOther.mTargetBytes))
|
||||
{
|
||||
}
|
||||
|
||||
TargetBytesPtr(const TargetBytesPtr& aOther)
|
||||
: mTargetBytes(aOther.mTargetBytes)
|
||||
{
|
||||
}
|
||||
|
||||
TargetBytesPtr& operator=(const TargetBytesPtr&) = delete;
|
||||
TargetBytesPtr& operator=(TargetBytesPtr&&) = delete;
|
||||
|
||||
private:
|
||||
TargetBytesPtr(const TargetMMPolicy& aMMPolicy, const void* aFunc)
|
||||
: mTargetBytes(aMMPolicy, aFunc)
|
||||
{
|
||||
}
|
||||
|
||||
TargetBytesPtr(const TargetBytesPtr& aOther,
|
||||
const uint32_t aOffsetFromOther)
|
||||
: mTargetBytes(aOther.mTargetBytes, aOffsetFromOther)
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes<TargetMMPolicy> mTargetBytes;
|
||||
};
|
||||
|
||||
public:
|
||||
ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, const void* aFunc)
|
||||
: mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aFunc))
|
||||
, mOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc)
|
||||
: mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy,
|
||||
reinterpret_cast<const void*>(aFunc)))
|
||||
, mOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction(ReadOnlyTargetFunction&& aOther)
|
||||
: mTargetBytes(Move(aOther.mTargetBytes))
|
||||
, mOffset(aOther.mOffset)
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction& operator=(const ReadOnlyTargetFunction&) = delete;
|
||||
ReadOnlyTargetFunction& operator=(ReadOnlyTargetFunction&&) = delete;
|
||||
|
||||
~ReadOnlyTargetFunction() = default;
|
||||
|
||||
ReadOnlyTargetFunction operator+(const uint32_t aOffset) const
|
||||
{
|
||||
return ReadOnlyTargetFunction(*this, mOffset + aOffset);
|
||||
}
|
||||
|
||||
uintptr_t GetBaseAddress() const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mTargetBytes->Get());
|
||||
}
|
||||
|
||||
uintptr_t GetAddress() const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mTargetBytes->Get() + mOffset);
|
||||
}
|
||||
|
||||
uintptr_t AsEncodedPtr() const
|
||||
{
|
||||
return EncodePtr(const_cast<uint8_t*>(mTargetBytes->Get() + mOffset));
|
||||
}
|
||||
|
||||
static uintptr_t EncodePtr(void* aPtr)
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(::EncodePointer(aPtr));
|
||||
}
|
||||
|
||||
static uintptr_t DecodePtr(uintptr_t aEncodedPtr)
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(
|
||||
::DecodePointer(reinterpret_cast<PVOID>(aEncodedPtr)));
|
||||
}
|
||||
|
||||
uint8_t const & operator*() const
|
||||
{
|
||||
mTargetBytes->EnsureLimit(mOffset);
|
||||
return *(mTargetBytes->Get() + mOffset);
|
||||
}
|
||||
|
||||
uint8_t const & operator[](uint32_t aIndex) const
|
||||
{
|
||||
mTargetBytes->EnsureLimit(mOffset + aIndex);
|
||||
return *(mTargetBytes->Get() + mOffset + aIndex);
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction& operator++()
|
||||
{
|
||||
++mOffset;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction& operator+=(uint32_t aDelta)
|
||||
{
|
||||
mOffset += aDelta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint32_t GetOffset() const
|
||||
{
|
||||
return mOffset;
|
||||
}
|
||||
|
||||
uintptr_t ReadDisp32AsAbsolute()
|
||||
{
|
||||
mTargetBytes->EnsureLimit(mOffset + sizeof(int32_t));
|
||||
int32_t disp = *reinterpret_cast<const int32_t*>(mTargetBytes->Get() + mOffset);
|
||||
uintptr_t result = reinterpret_cast<uintptr_t>(
|
||||
mTargetBytes->Get() + mOffset + sizeof(int32_t) + disp);
|
||||
mOffset += sizeof(int32_t);
|
||||
return result;
|
||||
}
|
||||
|
||||
uintptr_t OffsetToAbsolute(const uint8_t aOffset) const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mTargetBytes->Get() + mOffset + aOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method promotes the code referenced by this object to be writable.
|
||||
*
|
||||
* @param aLen The length of the function's code to make writable. If set
|
||||
* to zero, this object's current offset is used as the length.
|
||||
* @param aOffset The result's base address will be offset from this
|
||||
* object's base address by |aOffset| bytes. This value may be
|
||||
* negative.
|
||||
*/
|
||||
WritableTargetFunction<MMPolicy> Promote(const uint32_t aLen = 0,
|
||||
const int8_t aOffset = 0) const
|
||||
{
|
||||
const uint32_t effectiveLength = aLen ? aLen : mOffset;
|
||||
MOZ_RELEASE_ASSERT(effectiveLength, "Cannot Promote a zero-length function");
|
||||
|
||||
if (!mTargetBytes->IsValidAtOffset(aOffset)) {
|
||||
return WritableTargetFunction<MMPolicy>(mTargetBytes->GetMMPolicy());
|
||||
}
|
||||
|
||||
WritableTargetFunction<MMPolicy> result(
|
||||
mTargetBytes->GetMMPolicy(),
|
||||
reinterpret_cast<uintptr_t>(mTargetBytes->Get() + aOffset),
|
||||
effectiveLength);
|
||||
|
||||
return Move(result);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
struct ChasePointerHelper
|
||||
{
|
||||
template <typename MMPolicy>
|
||||
static T Result(const MMPolicy&, T aValue)
|
||||
{
|
||||
return aValue;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ChasePointerHelper<T*>
|
||||
{
|
||||
template <typename MMPolicy>
|
||||
static auto Result(const MMPolicy& aPolicy, T* aValue)
|
||||
{
|
||||
ReadOnlyTargetFunction<MMPolicy> ptr(aPolicy, aValue);
|
||||
return ptr.ChasePointer<T>();
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
// Keep chasing pointers until T is not a pointer type anymore
|
||||
template <typename T>
|
||||
auto ChasePointer()
|
||||
{
|
||||
mTargetBytes->EnsureLimit(mOffset + sizeof(T));
|
||||
const typename RemoveCV<T>::Type result = *reinterpret_cast<const RemoveCV<T>::Type*>(mTargetBytes->Get() + mOffset);
|
||||
return ChasePointerHelper<typename RemoveCV<T>::Type>::Result(mTargetBytes->GetMMPolicy(), result);
|
||||
}
|
||||
|
||||
uintptr_t ChasePointerFromDisp()
|
||||
{
|
||||
uintptr_t ptrFromDisp = ReadDisp32AsAbsolute();
|
||||
ReadOnlyTargetFunction<MMPolicy> ptr(mTargetBytes->GetMMPolicy(),
|
||||
reinterpret_cast<const void*>(ptrFromDisp));
|
||||
return ptr.template ChasePointer<uintptr_t>();
|
||||
}
|
||||
|
||||
private:
|
||||
ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther)
|
||||
: mTargetBytes(aOther.mTargetBytes)
|
||||
, mOffset(aOther.mOffset)
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther,
|
||||
const uint32_t aOffsetFromOther)
|
||||
: mTargetBytes(TargetBytesPtr<MMPolicy>::CopyFromOffset(aOther.mTargetBytes,
|
||||
aOffsetFromOther))
|
||||
, mOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
|
||||
uint32_t mOffset;
|
||||
};
|
||||
|
||||
} // namespace interceptor
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_interceptor_TargetFunction_h
|
|
@ -0,0 +1,369 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_interceptor_Trampoline_h
|
||||
#define mozilla_interceptor_Trampoline_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Types.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace interceptor {
|
||||
|
||||
template <typename MMPolicy>
|
||||
class MOZ_STACK_CLASS Trampoline final
|
||||
{
|
||||
public:
|
||||
Trampoline(const MMPolicy* aMMPolicy, uint8_t* const aLocalBase,
|
||||
const uintptr_t aRemoteBase, const uint32_t aChunkSize)
|
||||
: mMMPolicy(aMMPolicy)
|
||||
, mPrevLocalProt(0)
|
||||
, mLocalBase(aLocalBase)
|
||||
, mRemoteBase(aRemoteBase)
|
||||
, mOffset(0)
|
||||
, mExeOffset(0)
|
||||
, mMaxOffset(aChunkSize)
|
||||
, mAccumulatedStatus(true)
|
||||
{
|
||||
::VirtualProtect(aLocalBase, aChunkSize, PAGE_EXECUTE_READWRITE,
|
||||
&mPrevLocalProt);
|
||||
}
|
||||
|
||||
Trampoline(Trampoline&& aOther)
|
||||
: mMMPolicy(aOther.mMMPolicy)
|
||||
, mPrevLocalProt(aOther.mPrevLocalProt)
|
||||
, mLocalBase(aOther.mLocalBase)
|
||||
, mRemoteBase(aOther.mRemoteBase)
|
||||
, mOffset(aOther.mOffset)
|
||||
, mExeOffset(aOther.mExeOffset)
|
||||
, mMaxOffset(aOther.mMaxOffset)
|
||||
, mAccumulatedStatus(aOther.mAccumulatedStatus)
|
||||
{
|
||||
aOther.mPrevLocalProt = 0;
|
||||
aOther.mAccumulatedStatus = false;
|
||||
}
|
||||
|
||||
MOZ_IMPLICIT Trampoline(decltype(nullptr))
|
||||
: mMMPolicy(nullptr)
|
||||
, mPrevLocalProt(0)
|
||||
, mLocalBase(nullptr)
|
||||
, mRemoteBase(0)
|
||||
, mOffset(0)
|
||||
, mExeOffset(0)
|
||||
, mMaxOffset(0)
|
||||
, mAccumulatedStatus(false)
|
||||
{
|
||||
}
|
||||
|
||||
Trampoline(const Trampoline&) = delete;
|
||||
Trampoline& operator=(const Trampoline&) = delete;
|
||||
Trampoline&& operator=(Trampoline&&) = delete;
|
||||
|
||||
~Trampoline()
|
||||
{
|
||||
if (!mLocalBase || !mPrevLocalProt) {
|
||||
return;
|
||||
}
|
||||
|
||||
::VirtualProtect(mLocalBase, mMaxOffset, mPrevLocalProt, &mPrevLocalProt);
|
||||
}
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return mLocalBase && mRemoteBase && mPrevLocalProt && mAccumulatedStatus;
|
||||
}
|
||||
|
||||
void WriteByte(uint8_t aValue)
|
||||
{
|
||||
if (mOffset >= mMaxOffset) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
*(mLocalBase + mOffset) = aValue;
|
||||
++mOffset;
|
||||
}
|
||||
|
||||
void WriteInteger(int32_t aValue)
|
||||
{
|
||||
if (mOffset + sizeof(int32_t) > mMaxOffset) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
*reinterpret_cast<int32_t*>(mLocalBase + mOffset) = aValue;
|
||||
mOffset += sizeof(int32_t);
|
||||
}
|
||||
|
||||
void WritePointer(uintptr_t aValue)
|
||||
{
|
||||
if (mOffset + sizeof(uintptr_t) > mMaxOffset) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
*reinterpret_cast<uintptr_t*>(mLocalBase + mOffset) = aValue;
|
||||
mOffset += sizeof(uintptr_t);
|
||||
}
|
||||
|
||||
void WriteEncodedPointer(void* aValue)
|
||||
{
|
||||
uintptr_t encoded = ReadOnlyTargetFunction<MMPolicy>::EncodePtr(aValue);
|
||||
WritePointer(encoded);
|
||||
}
|
||||
|
||||
Maybe<uintptr_t> ReadPointer()
|
||||
{
|
||||
if (mOffset + sizeof(uintptr_t) > mMaxOffset) {
|
||||
mAccumulatedStatus = false;
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
auto result = Some(*reinterpret_cast<uintptr_t*>(mLocalBase + mOffset));
|
||||
mOffset += sizeof(uintptr_t);
|
||||
return Move(result);
|
||||
}
|
||||
|
||||
Maybe<uintptr_t> ReadEncodedPointer()
|
||||
{
|
||||
Maybe<uintptr_t> encoded(ReadPointer());
|
||||
if (!encoded) {
|
||||
return encoded;
|
||||
}
|
||||
|
||||
return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(encoded.value()));
|
||||
}
|
||||
|
||||
void WriteDisp32(uintptr_t aAbsTarget)
|
||||
{
|
||||
if (mOffset + sizeof(int32_t) > mMaxOffset) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// This needs to be computed from the remote location
|
||||
intptr_t remoteTrampPosition = static_cast<intptr_t>(mRemoteBase + mOffset);
|
||||
|
||||
intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
|
||||
(remoteTrampPosition + sizeof(int32_t));
|
||||
|
||||
CheckedInt<int32_t> checkedDisp(diff);
|
||||
MOZ_ASSERT(checkedDisp.isValid());
|
||||
if (!checkedDisp.isValid()) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t disp = checkedDisp.value();
|
||||
*reinterpret_cast<int32_t*>(mLocalBase + mOffset) = disp;
|
||||
mOffset += sizeof(int32_t);
|
||||
}
|
||||
|
||||
#if defined(_M_IX86)
|
||||
// 32-bit only
|
||||
void AdjustDisp32AtOffset(uint32_t aOffset, uintptr_t aAbsTarget)
|
||||
{
|
||||
uint32_t effectiveOffset = mExeOffset + aOffset;
|
||||
|
||||
if (effectiveOffset + sizeof(int32_t) > mMaxOffset) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
|
||||
static_cast<intptr_t>(mRemoteBase + mExeOffset);
|
||||
*reinterpret_cast<int32_t*>(mLocalBase + effectiveOffset) += diff;
|
||||
}
|
||||
#endif // defined(_M_IX86)
|
||||
|
||||
void CopyFrom(uintptr_t aOrigBytes, uint32_t aNumBytes)
|
||||
{
|
||||
if (!mMMPolicy || mOffset + aNumBytes > mMaxOffset) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mMMPolicy->Read(mLocalBase + mOffset,
|
||||
reinterpret_cast<void*>(aOrigBytes), aNumBytes)) {
|
||||
mAccumulatedStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mOffset += aNumBytes;
|
||||
}
|
||||
|
||||
void Rewind()
|
||||
{
|
||||
mOffset = 0;
|
||||
}
|
||||
|
||||
uintptr_t GetCurrentRemoteAddress() const
|
||||
{
|
||||
return mRemoteBase + mOffset;
|
||||
}
|
||||
|
||||
void StartExecutableCode()
|
||||
{
|
||||
MOZ_ASSERT(!mExeOffset);
|
||||
mExeOffset = mOffset;
|
||||
}
|
||||
|
||||
void* EndExecutableCode() const
|
||||
{
|
||||
if (!mAccumulatedStatus) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This must always return the start address the executable code
|
||||
// *in the target process*
|
||||
return reinterpret_cast<void*>(mRemoteBase + mExeOffset);
|
||||
}
|
||||
|
||||
Trampoline<MMPolicy>& operator--()
|
||||
{
|
||||
MOZ_ASSERT(mOffset);
|
||||
--mOffset;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
const MMPolicy* mMMPolicy;
|
||||
DWORD mPrevLocalProt;
|
||||
uint8_t* const mLocalBase;
|
||||
const uintptr_t mRemoteBase;
|
||||
uint32_t mOffset;
|
||||
uint32_t mExeOffset;
|
||||
const uint32_t mMaxOffset;
|
||||
bool mAccumulatedStatus;
|
||||
};
|
||||
|
||||
template <typename MMPolicy>
|
||||
class MOZ_STACK_CLASS TrampolineCollection final
|
||||
{
|
||||
public:
|
||||
class MOZ_STACK_CLASS TrampolineIterator final
|
||||
{
|
||||
public:
|
||||
Trampoline<MMPolicy> operator*()
|
||||
{
|
||||
uint32_t offset = mCurTramp * mCollection.mTrampSize;
|
||||
return Trampoline<MMPolicy>(nullptr,
|
||||
mCollection.mLocalBase + offset,
|
||||
mCollection.mRemoteBase + offset,
|
||||
mCollection.mTrampSize);
|
||||
}
|
||||
|
||||
TrampolineIterator& operator++()
|
||||
{
|
||||
++mCurTramp;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!=(const TrampolineIterator& aOther) const
|
||||
{
|
||||
return mCurTramp != aOther.mCurTramp;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit TrampolineIterator(
|
||||
const TrampolineCollection<MMPolicy>& aCollection,
|
||||
const uint32_t aCurTramp = 0)
|
||||
: mCollection(aCollection)
|
||||
, mCurTramp(aCurTramp)
|
||||
{
|
||||
}
|
||||
|
||||
const TrampolineCollection<MMPolicy>& mCollection;
|
||||
uint32_t mCurTramp;
|
||||
|
||||
friend class TrampolineCollection<MMPolicy>;
|
||||
};
|
||||
|
||||
explicit TrampolineCollection(const MMPolicy& aMMPolicy)
|
||||
: mMMPolicy(aMMPolicy)
|
||||
, mLocalBase(0)
|
||||
, mRemoteBase(0)
|
||||
, mTrampSize(0)
|
||||
, mNumTramps(0)
|
||||
, mPrevProt(0)
|
||||
{
|
||||
}
|
||||
|
||||
TrampolineCollection(const MMPolicy& aMMPolicy, uint8_t* const aLocalBase,
|
||||
const uintptr_t aRemoteBase, const uint32_t aTrampSize,
|
||||
const uint32_t aNumTramps)
|
||||
: mMMPolicy(aMMPolicy)
|
||||
, mLocalBase(aLocalBase)
|
||||
, mRemoteBase(aRemoteBase)
|
||||
, mTrampSize(aTrampSize)
|
||||
, mNumTramps(aNumTramps)
|
||||
, mPrevProt(0)
|
||||
{
|
||||
if (!aNumTramps) {
|
||||
return;
|
||||
}
|
||||
|
||||
DebugOnly<BOOL> ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize,
|
||||
PAGE_EXECUTE_READWRITE, &mPrevProt);
|
||||
MOZ_ASSERT(ok);
|
||||
}
|
||||
|
||||
~TrampolineCollection()
|
||||
{
|
||||
if (!mPrevProt) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMMPolicy.Protect(mLocalBase, mNumTramps * mTrampSize,
|
||||
mPrevProt, &mPrevProt);
|
||||
}
|
||||
|
||||
TrampolineIterator begin() const
|
||||
{
|
||||
if (!mPrevProt) {
|
||||
return end();
|
||||
}
|
||||
|
||||
return TrampolineIterator(*this);
|
||||
}
|
||||
|
||||
TrampolineIterator end() const
|
||||
{
|
||||
return TrampolineIterator(*this, mNumTramps);
|
||||
}
|
||||
|
||||
TrampolineCollection(const TrampolineCollection&) = delete;
|
||||
TrampolineCollection& operator=(const TrampolineCollection&) = delete;
|
||||
TrampolineCollection& operator=(TrampolineCollection&&) = delete;
|
||||
|
||||
TrampolineCollection(TrampolineCollection&& aOther)
|
||||
: mMMPolicy(aOther.mMMPolicy)
|
||||
, mLocalBase(aOther.mLocalBase)
|
||||
, mRemoteBase(aOther.mRemoteBase)
|
||||
, mTrampSize(aOther.mTrampSize)
|
||||
, mNumTramps(aOther.mNumTramps)
|
||||
, mPrevProt(aOther.mPrevProt)
|
||||
{
|
||||
aOther.mPrevProt = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const MMPolicy& mMMPolicy;
|
||||
uint8_t* const mLocalBase;
|
||||
const uintptr_t mRemoteBase;
|
||||
const uint32_t mTrampSize;
|
||||
const uint32_t mNumTramps;
|
||||
uint32_t mPrevProt;
|
||||
|
||||
friend class TrampolineIterator;
|
||||
};
|
||||
|
||||
} // namespace interceptor
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_interceptor_Trampoline_h
|
|
@ -0,0 +1,90 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_interceptor_VMSharingPolicies_h
|
||||
#define mozilla_interceptor_VMSharingPolicies_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Types.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace interceptor {
|
||||
|
||||
template <typename MMPolicy, uint32_t kChunkSize>
|
||||
class VMSharingPolicyUnique : public MMPolicy
|
||||
{
|
||||
public:
|
||||
template <typename... Args>
|
||||
explicit VMSharingPolicyUnique(Args... aArgs)
|
||||
: MMPolicy(mozilla::Forward<Args>(aArgs)...)
|
||||
, mNextChunkIndex(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool Reserve(uint32_t aCount)
|
||||
{
|
||||
MOZ_ASSERT(aCount);
|
||||
uint32_t bytesReserved = MMPolicy::Reserve(aCount * kChunkSize);
|
||||
return !!bytesReserved;
|
||||
}
|
||||
|
||||
Trampoline<MMPolicy> GetNextTrampoline()
|
||||
{
|
||||
uint32_t offset = mNextChunkIndex * kChunkSize;
|
||||
if (!MaybeCommitNextPage(offset, kChunkSize)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
Trampoline<MMPolicy> result(this, GetLocalView() + offset,
|
||||
GetRemoteView() + offset, kChunkSize);
|
||||
if (!!result) {
|
||||
++mNextChunkIndex;
|
||||
}
|
||||
|
||||
return Move(result);
|
||||
}
|
||||
|
||||
TrampolineCollection<MMPolicy> Items() const
|
||||
{
|
||||
return TrampolineCollection<MMPolicy>(*this, GetLocalView(), GetRemoteView(),
|
||||
kChunkSize, mNextChunkIndex);
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
mNextChunkIndex = 0;
|
||||
}
|
||||
|
||||
~VMSharingPolicyUnique() = default;
|
||||
|
||||
VMSharingPolicyUnique(const VMSharingPolicyUnique&) = delete;
|
||||
VMSharingPolicyUnique& operator=(const VMSharingPolicyUnique&) = delete;
|
||||
|
||||
VMSharingPolicyUnique(VMSharingPolicyUnique&& aOther)
|
||||
: MMPolicy(Move(aOther))
|
||||
, mNextChunkIndex(aOther.mNextChunkIndex)
|
||||
{
|
||||
aOther.mNextChunkIndex = 0;
|
||||
}
|
||||
|
||||
VMSharingPolicyUnique& operator=(VMSharingPolicyUnique&& aOther)
|
||||
{
|
||||
static_cast<MMPolicy&>(*this) = Move(aOther);
|
||||
mNextChunkIndex = aOther.mNextChunkIndex;
|
||||
aOther.mNextChunkIndex = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mNextChunkIndex;
|
||||
};
|
||||
|
||||
} // namespace interceptor
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_interceptor_VMSharingPolicies_h
|
||||
|
|
@ -33,6 +33,18 @@ OS_LIBS += CONFIG['REALTIME_LIBS']
|
|||
DEFINES['IMPL_MFBT'] = True
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
EXPORTS += [
|
||||
'nsWindowsDllInterceptor.h',
|
||||
]
|
||||
EXPORTS.mozilla.interceptor += [
|
||||
'interceptor/MMPolicies.h',
|
||||
'interceptor/PatcherBase.h',
|
||||
'interceptor/PatcherDetour.h',
|
||||
'interceptor/PatcherNopSpace.h',
|
||||
'interceptor/TargetFunction.h',
|
||||
'interceptor/Trampoline.h',
|
||||
'interceptor/VMSharingPolicies.h',
|
||||
]
|
||||
SOURCES += [
|
||||
'TimeStamp_windows.cpp',
|
||||
]
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
|
||||
#define NS_WINDOWS_DLL_INTERCEPTOR_H_
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
#include "mozilla/Types.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
#include <wchar.h>
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include "mozilla/interceptor/MMPolicies.h"
|
||||
#include "mozilla/interceptor/PatcherDetour.h"
|
||||
#include "mozilla/interceptor/PatcherNopSpace.h"
|
||||
#include "mozilla/interceptor/VMSharingPolicies.h"
|
||||
|
||||
/*
|
||||
* Simple function interception.
|
||||
*
|
||||
* We have two separate mechanisms for intercepting a function: We can use the
|
||||
* built-in nop space, if it exists, or we can create a detour.
|
||||
*
|
||||
* Using the built-in nop space works as follows: On x86-32, DLL functions
|
||||
* begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
|
||||
* NOP instructions.
|
||||
*
|
||||
* When we detect a function with this prelude, we do the following:
|
||||
*
|
||||
* 1. Write a long jump to our interceptor function into the five bytes of NOPs
|
||||
* before the function.
|
||||
*
|
||||
* 2. Write a short jump -5 into the two-byte nop at the beginning of the function.
|
||||
*
|
||||
* This mechanism is nice because it's thread-safe. It's even safe to do if
|
||||
* another thread is currently running the function we're modifying!
|
||||
*
|
||||
* When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
|
||||
* but not the long jump, so re-intercepting the same function won't work,
|
||||
* because its prelude won't match.
|
||||
*
|
||||
*
|
||||
* Unfortunately nop space patching doesn't work on functions which don't have
|
||||
* this magic prelude (and in particular, x86-64 never has the prelude). So
|
||||
* when we can't use the built-in nop space, we fall back to using a detour,
|
||||
* which works as follows:
|
||||
*
|
||||
* 1. Save first N bytes of OrigFunction to trampoline, where N is a
|
||||
* number of bytes >= 5 that are instruction aligned.
|
||||
*
|
||||
* 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
|
||||
* function.
|
||||
*
|
||||
* 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
|
||||
* continue original program flow.
|
||||
*
|
||||
* 4. Hook function needs to call the trampoline during its execution,
|
||||
* to invoke the original function (so address of trampoline is
|
||||
* returned).
|
||||
*
|
||||
* When the WindowsDllDetourPatcher object is destructed, OrigFunction is
|
||||
* patched again to jump directly to the trampoline instead of going through
|
||||
* the hook function. As such, re-intercepting the same function won't work, as
|
||||
* jump instructions are not supported.
|
||||
*
|
||||
* Note that this is not thread-safe. Sad day.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
namespace interceptor {
|
||||
|
||||
enum
|
||||
{
|
||||
kDefaultTrampolineSize = 128
|
||||
};
|
||||
|
||||
template <typename VMPolicy =
|
||||
mozilla::interceptor::VMSharingPolicyUnique<
|
||||
mozilla::interceptor::MMPolicyInProcess, kDefaultTrampolineSize>>
|
||||
class WindowsDllInterceptor final
|
||||
{
|
||||
interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher;
|
||||
#if defined(_M_IX86)
|
||||
interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT> mNopSpacePatcher;
|
||||
#endif // defined(_M_IX86)
|
||||
|
||||
HMODULE mModule;
|
||||
int mNHooks;
|
||||
|
||||
public:
|
||||
template <typename... Args>
|
||||
explicit WindowsDllInterceptor(Args... aArgs)
|
||||
: mDetourPatcher(mozilla::Forward<Args>(aArgs)...)
|
||||
#if defined(_M_IX86)
|
||||
, mNopSpacePatcher(mozilla::Forward<Args>(aArgs)...)
|
||||
#endif // defined(_M_IX86)
|
||||
, mModule(nullptr)
|
||||
, mNHooks(0)
|
||||
{
|
||||
}
|
||||
|
||||
WindowsDllInterceptor(const WindowsDllInterceptor&) = delete;
|
||||
WindowsDllInterceptor(WindowsDllInterceptor&&) = delete;
|
||||
WindowsDllInterceptor& operator=(const WindowsDllInterceptor&) = delete;
|
||||
WindowsDllInterceptor& operator=(WindowsDllInterceptor&&) = delete;
|
||||
|
||||
~WindowsDllInterceptor()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void Init(const char (&aModuleName)[N], int aNumHooks = 0)
|
||||
{
|
||||
wchar_t moduleName[N];
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
MOZ_ASSERT(!(aModuleName[i] & 0x80),
|
||||
"Use wide-character overload for non-ASCII module names");
|
||||
moduleName[i] = aModuleName[i];
|
||||
}
|
||||
|
||||
Init(moduleName, aNumHooks);
|
||||
}
|
||||
|
||||
void Init(const wchar_t* aModuleName, int aNumHooks = 0)
|
||||
{
|
||||
if (mModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
mModule = ::LoadLibraryW(aModuleName);
|
||||
mNHooks = aNumHooks;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
if (!mModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(_M_IX86)
|
||||
mNopSpacePatcher.Clear();
|
||||
#endif // defined(_M_IX86)
|
||||
mDetourPatcher.Clear();
|
||||
|
||||
// NB: We intentionally leak mModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook/detour the method aName from the DLL we set in Init so that it calls
|
||||
* aHookDest instead. Returns the original method pointer in aOrigFunc
|
||||
* and returns true if successful.
|
||||
*
|
||||
* IMPORTANT: If you use this method, please add your case to the
|
||||
* TestDllInterceptor in order to detect future failures. Even if this
|
||||
* succeeds now, updates to the hooked DLL could cause it to fail in
|
||||
* the future.
|
||||
*/
|
||||
bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
|
||||
{
|
||||
// Use a nop space patch if possible, otherwise fall back to a detour.
|
||||
// This should be the preferred method for adding hooks.
|
||||
if (!mModule) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FARPROC proc = ::GetProcAddress(mModule, aName);
|
||||
if (!proc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(_M_IX86)
|
||||
if (mNopSpacePatcher.AddHook(proc, aHookDest, aOrigFunc)) {
|
||||
return true;
|
||||
}
|
||||
#endif // defined(_M_IX86)
|
||||
|
||||
return AddDetour(proc, aHookDest, aOrigFunc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detour the method aName from the DLL we set in Init so that it calls
|
||||
* aHookDest instead. Returns the original method pointer in aOrigFunc
|
||||
* and returns true if successful.
|
||||
*
|
||||
* IMPORTANT: If you use this method, please add your case to the
|
||||
* TestDllInterceptor in order to detect future failures. Even if this
|
||||
* succeeds now, updates to the detoured DLL could cause it to fail in
|
||||
* the future.
|
||||
*/
|
||||
bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc)
|
||||
{
|
||||
// Generally, code should not call this method directly. Use AddHook unless
|
||||
// there is a specific need to avoid nop space patches.
|
||||
|
||||
if (!mModule) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FARPROC proc = ::GetProcAddress(mModule, aName);
|
||||
if (!proc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return AddDetour(proc, aHookDest, aOrigFunc);
|
||||
}
|
||||
|
||||
private:
|
||||
bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc)
|
||||
{
|
||||
MOZ_ASSERT(mModule && aProc);
|
||||
|
||||
if (!mDetourPatcher.Initialized()) {
|
||||
mDetourPatcher.Init(mNHooks);
|
||||
}
|
||||
|
||||
return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace interceptor
|
||||
|
||||
using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
|
|
@ -61,7 +61,8 @@ bool CheckHook(HookTestFunc aHookTestFunc, void* aOrigFunc,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool TestHook(HookTestFunc funcTester, const char *dll, const char *func)
|
||||
template <size_t N>
|
||||
bool TestHook(HookTestFunc funcTester, const char (&dll)[N], const char *func)
|
||||
{
|
||||
void *orig_func;
|
||||
bool successful = false;
|
||||
|
@ -80,7 +81,8 @@ bool TestHook(HookTestFunc funcTester, const char *dll, const char *func)
|
|||
}
|
||||
}
|
||||
|
||||
bool TestDetour(const char *dll, const char *func)
|
||||
template <size_t N>
|
||||
bool TestDetour(const char (&dll)[N], const char *func)
|
||||
{
|
||||
void *orig_func;
|
||||
bool successful = false;
|
||||
|
@ -99,7 +101,8 @@ bool TestDetour(const char *dll, const char *func)
|
|||
}
|
||||
}
|
||||
|
||||
bool MaybeTestHook(const bool cond, HookTestFunc funcTester, const char* dll, const char* func)
|
||||
template <size_t N>
|
||||
bool MaybeTestHook(const bool cond, HookTestFunc funcTester, const char (&dll)[N], const char* func)
|
||||
{
|
||||
if (!cond) {
|
||||
printf("TEST-SKIPPED | WindowsDllInterceptor | Skipped hook test for %s from %s\n", func, dll);
|
||||
|
@ -595,6 +598,9 @@ bool TestFreeCredentialsHandle(void* aFunc)
|
|||
|
||||
int main()
|
||||
{
|
||||
LARGE_INTEGER start;
|
||||
QueryPerformanceCounter(&start);
|
||||
|
||||
// We disable this part of the test because the code coverage instrumentation
|
||||
// injects code in rotatePayload in a way that WindowsDllInterceptor doesn't
|
||||
// understand.
|
||||
|
@ -733,6 +739,19 @@ int main()
|
|||
TestDetour("kernel32.dll", "BaseThreadInitThunk") &&
|
||||
TestDetour("ntdll.dll", "LdrLoadDll")) {
|
||||
printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
|
||||
|
||||
LARGE_INTEGER end, freq;
|
||||
QueryPerformanceCounter(&end);
|
||||
|
||||
QueryPerformanceFrequency(&freq);
|
||||
|
||||
LARGE_INTEGER result;
|
||||
result.QuadPart = end.QuadPart - start.QuadPart;
|
||||
result.QuadPart *= 1000000;
|
||||
result.QuadPart /= freq.QuadPart;
|
||||
|
||||
printf("Elapsed time: %lld microseconds\n", result.QuadPart);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
CppUnitTests([
|
||||
'TestDllInterceptor',
|
||||
])
|
||||
|
||||
DEFINES['NS_NO_XPCOM'] = True
|
||||
|
||||
DisableStlWrapping()
|
||||
|
||||
OS_LIBS += [
|
||||
'ole32',
|
||||
]
|
||||
|
||||
USE_LIBS += [
|
||||
'mfbt',
|
||||
]
|
|
@ -13,3 +13,8 @@ GeckoCppUnitTests([
|
|||
CppUnitTests([
|
||||
'TestPrintf',
|
||||
])
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
TEST_DIRS += [
|
||||
'interceptor',
|
||||
]
|
||||
|
|
|
@ -8,10 +8,6 @@ SimplePrograms([
|
|||
'TestXREMakeCommandLineWin',
|
||||
])
|
||||
|
||||
CppUnitTests([
|
||||
'TestDllInterceptor',
|
||||
])
|
||||
|
||||
DEFINES['NS_NO_XPCOM'] = True
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
|
@ -24,7 +20,6 @@ USE_STATIC_LIBS = True
|
|||
|
||||
OS_LIBS += [
|
||||
'comctl32',
|
||||
'ole32',
|
||||
'shell32',
|
||||
'ws2_32',
|
||||
]
|
||||
|
|
|
@ -485,7 +485,7 @@ ClearPoisonIOInterposer()
|
|||
if (sIOPoisoned) {
|
||||
// Destroy the DLL interceptor
|
||||
sIOPoisoned = false;
|
||||
sNtDllInterceptor = WindowsDllInterceptor();
|
||||
sNtDllInterceptor.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,9 @@ EXPORTS.mozilla += [
|
|||
]
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
EXPORTS += ['nsWindowsDllInterceptor.h']
|
||||
EXPORTS.mozilla += ['perfprobe.h']
|
||||
EXPORTS.mozilla += [
|
||||
'perfprobe.h',
|
||||
]
|
||||
SOURCES += ['perfprobe.cpp']
|
||||
if CONFIG['CC_TYPE'] != 'gcc':
|
||||
SOURCES += [
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче