2015-04-09 20:25:05 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2012-05-21 15:12:37 +04:00
|
|
|
/* 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/. */
|
2009-11-04 09:35:20 +03:00
|
|
|
|
|
|
|
#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
|
|
|
|
#define NS_WINDOWS_DLL_INTERCEPTOR_H_
|
2016-09-07 10:15:58 +03:00
|
|
|
|
2016-10-14 00:15:22 +03:00
|
|
|
#include "mozilla/Assertions.h"
|
2016-10-14 02:10:52 +03:00
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
|
|
#include "mozilla/UniquePtr.h"
|
|
|
|
#include "nsWindowsHelpers.h"
|
2016-10-14 00:15:22 +03:00
|
|
|
|
2016-10-14 02:10:52 +03:00
|
|
|
#include <wchar.h>
|
2009-11-04 09:35:20 +03:00
|
|
|
#include <windows.h>
|
|
|
|
#include <winternl.h>
|
|
|
|
|
|
|
|
/*
|
2012-04-10 23:52:56 +04:00
|
|
|
* 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:
|
2009-11-04 09:35:20 +03:00
|
|
|
*
|
|
|
|
* 1. Save first N bytes of OrigFunction to trampoline, where N is a
|
|
|
|
* number of bytes >= 5 that are instruction aligned.
|
|
|
|
*
|
2014-02-24 23:52:14 +04:00
|
|
|
* 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
|
2009-11-04 09:35:20 +03:00
|
|
|
* 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).
|
2012-04-10 23:52:56 +04:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
2009-11-04 09:35:20 +03:00
|
|
|
*/
|
|
|
|
|
2013-07-30 18:25:31 +04:00
|
|
|
#include <stdint.h>
|
2012-04-10 23:52:56 +04:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace internal {
|
|
|
|
|
2015-09-04 21:23:33 +03:00
|
|
|
class AutoVirtualProtect
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
AutoVirtualProtect(void* aFunc, size_t aSize, DWORD aProtect)
|
|
|
|
: mFunc(aFunc), mSize(aSize), mNewProtect(aProtect), mOldProtect(0),
|
|
|
|
mSuccess(false)
|
|
|
|
{}
|
|
|
|
|
|
|
|
~AutoVirtualProtect()
|
|
|
|
{
|
|
|
|
if (mSuccess) {
|
|
|
|
VirtualProtectEx(GetCurrentProcess(), mFunc, mSize, mOldProtect,
|
|
|
|
&mOldProtect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Protect()
|
|
|
|
{
|
|
|
|
mSuccess = !!VirtualProtectEx(GetCurrentProcess(), mFunc, mSize,
|
|
|
|
mNewProtect, &mOldProtect);
|
|
|
|
return mSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void* const mFunc;
|
|
|
|
size_t const mSize;
|
|
|
|
DWORD const mNewProtect;
|
|
|
|
DWORD mOldProtect;
|
|
|
|
bool mSuccess;
|
|
|
|
};
|
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
class WindowsDllNopSpacePatcher
|
2009-11-04 09:35:20 +03:00
|
|
|
{
|
2015-12-29 16:57:38 +03:00
|
|
|
typedef uint8_t* byteptr_t;
|
2012-04-10 23:52:56 +04:00
|
|
|
HMODULE mModule;
|
|
|
|
|
|
|
|
// Dumb array for remembering the addresses of functions we've patched.
|
|
|
|
// (This should be nsTArray, but non-XPCOM code uses this class.)
|
|
|
|
static const size_t maxPatchedFns = 128;
|
|
|
|
byteptr_t mPatchedFns[maxPatchedFns];
|
|
|
|
int mPatchedFnsLen;
|
|
|
|
|
2009-11-04 09:35:20 +03:00
|
|
|
public:
|
2012-04-10 23:52:56 +04:00
|
|
|
WindowsDllNopSpacePatcher()
|
2009-11-04 09:35:20 +03:00
|
|
|
: mModule(0)
|
2012-04-10 23:52:56 +04:00
|
|
|
, mPatchedFnsLen(0)
|
|
|
|
{}
|
|
|
|
|
2016-10-14 02:10:52 +03:00
|
|
|
#if defined(_M_IX86)
|
2012-04-10 23:52:56 +04:00
|
|
|
~WindowsDllNopSpacePatcher()
|
|
|
|
{
|
|
|
|
// Restore the mov edi, edi to the beginning of each function we patched.
|
|
|
|
|
|
|
|
for (int i = 0; i < mPatchedFnsLen; i++) {
|
|
|
|
byteptr_t fn = mPatchedFns[i];
|
|
|
|
|
|
|
|
// Ensure we can write to the code.
|
2015-09-04 21:23:33 +03:00
|
|
|
AutoVirtualProtect protect(fn, 2, PAGE_EXECUTE_READWRITE);
|
|
|
|
if (!protect.Protect()) {
|
2012-04-10 23:52:56 +04:00
|
|
|
// printf("VirtualProtectEx failed! %d\n", GetLastError());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// mov edi, edi
|
|
|
|
*((uint16_t*)fn) = 0xff8b;
|
|
|
|
|
|
|
|
// I don't think this is actually necessary, but it can't hurt.
|
|
|
|
FlushInstructionCache(GetCurrentProcess(),
|
2013-10-11 00:36:42 +04:00
|
|
|
/* ignored */ nullptr,
|
2012-04-10 23:52:56 +04:00
|
|
|
/* ignored */ 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
void Init(const char* aModuleName)
|
2012-04-10 23:52:56 +04:00
|
|
|
{
|
2016-10-14 02:10:52 +03:00
|
|
|
if (!IsCompatible()) {
|
|
|
|
#if defined(MOZILLA_INTERNAL_API)
|
|
|
|
NS_WARNING("NOP space patching is unavailable for compatibility reasons");
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
mModule = LoadLibraryExA(aModuleName, nullptr, 0);
|
2012-04-10 23:52:56 +04:00
|
|
|
if (!mModule) {
|
2014-08-13 22:45:37 +04:00
|
|
|
//printf("LoadLibraryEx for '%s' failed\n", aModuleName);
|
2012-04-10 23:52:56 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-14 02:10:52 +03:00
|
|
|
/**
|
|
|
|
* 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 / 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;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
|
2012-04-10 23:52:56 +04:00
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
if (!mModule) {
|
2012-04-10 23:52:56 +04:00
|
|
|
return false;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2012-04-10 23:52:56 +04:00
|
|
|
|
2016-10-14 02:10:52 +03:00
|
|
|
if (!IsCompatible()) {
|
|
|
|
#if defined(MOZILLA_INTERNAL_API)
|
|
|
|
NS_WARNING("NOP space patching is unavailable for compatibility reasons");
|
|
|
|
#endif
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
if (mPatchedFnsLen == maxPatchedFns) {
|
|
|
|
// printf ("No space for hook in mPatchedFns.\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
byteptr_t fn = reinterpret_cast<byteptr_t>(GetProcAddress(mModule, aName));
|
2012-04-10 23:52:56 +04:00
|
|
|
if (!fn) {
|
|
|
|
//printf ("GetProcAddress failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
2014-08-13 22:45:37 +04:00
|
|
|
|
2015-01-16 17:07:09 +03:00
|
|
|
fn = ResolveRedirectedAddress(fn);
|
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
// 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
|
2015-09-04 21:24:05 +03:00
|
|
|
// jmp). These bytes may span two pages with different protection.
|
|
|
|
AutoVirtualProtect protectBefore(fn - 5, 5, PAGE_EXECUTE_READWRITE);
|
|
|
|
AutoVirtualProtect protectAfter(fn, 2, PAGE_EXECUTE_READWRITE);
|
|
|
|
if (!protectBefore.Protect() || !protectAfter.Protect()) {
|
2012-04-10 23:52:56 +04:00
|
|
|
//printf ("VirtualProtectEx failed! %d\n", GetLastError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
bool rv = WriteHook(fn, aHookDest, aOrigFunc);
|
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
if (rv) {
|
|
|
|
mPatchedFns[mPatchedFnsLen] = fn;
|
|
|
|
mPatchedFnsLen++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
bool WriteHook(byteptr_t aFn, intptr_t aHookDest, void** aOrigFunc)
|
2009-11-04 09:35:20 +03:00
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
// Check that the 5 bytes before aFn are NOP's or INT 3's,
|
|
|
|
// and that the 2 bytes after aFn are mov(edi, edi).
|
2012-04-10 23:52:56 +04:00
|
|
|
//
|
2014-08-13 22:45:37 +04:00
|
|
|
// It's safe to read aFn[-5] because we set it to PAGE_EXECUTE_READWRITE
|
2012-04-10 23:52:56 +04:00
|
|
|
// before calling WriteHook.
|
|
|
|
|
|
|
|
for (int i = -5; i <= -1; i++) {
|
2014-08-13 22:45:37 +04:00
|
|
|
if (aFn[i] != 0x90 && aFn[i] != 0xcc) { // nop or int 3
|
2012-04-10 23:52:56 +04:00
|
|
|
return false;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2012-04-10 23:52:56 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// mov edi, edi. Yes, there are two ways to encode the same thing:
|
|
|
|
//
|
|
|
|
// 0x89ff == mov r/m, r
|
|
|
|
// 0x8bff == mov r, r/m
|
|
|
|
//
|
|
|
|
// where "r" is register and "r/m" is register or memory. Windows seems to
|
|
|
|
// use 8bff; I include 89ff out of paranoia.
|
2014-08-13 22:45:37 +04:00
|
|
|
if ((aFn[0] != 0x8b && aFn[0] != 0x89) || aFn[1] != 0xff) {
|
2012-04-10 23:52:56 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write a long jump into the space above the function.
|
2014-08-13 22:45:37 +04:00
|
|
|
aFn[-5] = 0xe9; // jmp
|
|
|
|
*((intptr_t*)(aFn - 4)) = aHookDest - (uintptr_t)(aFn); // target displacement
|
2012-04-10 23:52:56 +04:00
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
// Set aOrigFunc here, because after this point, aHookDest might be called,
|
|
|
|
// and aHookDest might use the aOrigFunc pointer.
|
|
|
|
*aOrigFunc = aFn + 2;
|
2012-04-10 23:52:56 +04:00
|
|
|
|
|
|
|
// Short jump up into our long jump.
|
2014-08-13 22:45:37 +04:00
|
|
|
*((uint16_t*)(aFn)) = 0xf9eb; // jmp $-5
|
2012-04-10 23:52:56 +04:00
|
|
|
|
|
|
|
// I think this routine is safe without this, but it can't hurt.
|
|
|
|
FlushInstructionCache(GetCurrentProcess(),
|
2013-10-11 00:36:42 +04:00
|
|
|
/* ignored */ nullptr,
|
2012-04-10 23:52:56 +04:00
|
|
|
/* ignored */ 0);
|
|
|
|
|
|
|
|
return true;
|
2009-11-04 09:35:20 +03:00
|
|
|
}
|
2015-01-16 17:07:09 +03:00
|
|
|
|
|
|
|
private:
|
|
|
|
static byteptr_t ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
|
|
|
|
{
|
|
|
|
// If function entry is jmp [disp32] such as used by kernel32,
|
|
|
|
// we resolve redirected address from import table.
|
|
|
|
if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
|
|
|
|
return (byteptr_t)(**((uint32_t**) (aOriginalFunction + 2)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return aOriginalFunction;
|
|
|
|
}
|
2012-04-10 23:52:56 +04:00
|
|
|
#else
|
2016-10-14 02:10:52 +03:00
|
|
|
void Init(const char* aModuleName)
|
|
|
|
{
|
|
|
|
// Not implemented except on x86-32.
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
|
2012-04-10 23:52:56 +04:00
|
|
|
{
|
|
|
|
// Not implemented except on x86-32.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
};
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
class WindowsDllDetourPatcher
|
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
typedef unsigned char* byteptr_t;
|
2012-04-10 23:52:56 +04:00
|
|
|
public:
|
2014-08-13 22:45:37 +04:00
|
|
|
WindowsDllDetourPatcher()
|
2012-04-10 23:52:56 +04:00
|
|
|
: mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0)
|
|
|
|
{
|
2009-11-04 09:35:20 +03:00
|
|
|
}
|
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
~WindowsDllDetourPatcher()
|
|
|
|
{
|
2011-08-11 09:54:57 +04:00
|
|
|
int i;
|
|
|
|
byteptr_t p;
|
|
|
|
for (i = 0, p = mHookPage; i < mCurHooks; i++, p += kHookSize) {
|
|
|
|
#if defined(_M_IX86)
|
|
|
|
size_t nBytes = 1 + sizeof(intptr_t);
|
|
|
|
#elif defined(_M_X64)
|
|
|
|
size_t nBytes = 2 + sizeof(intptr_t);
|
|
|
|
#else
|
|
|
|
#error "Unknown processor type"
|
|
|
|
#endif
|
2014-08-13 22:45:37 +04:00
|
|
|
byteptr_t origBytes = *((byteptr_t*)p);
|
2015-09-04 21:23:33 +03:00
|
|
|
|
2011-08-11 09:54:57 +04:00
|
|
|
// ensure we can modify the original code
|
2015-09-04 21:23:33 +03:00
|
|
|
AutoVirtualProtect protect(origBytes, nBytes, PAGE_EXECUTE_READWRITE);
|
|
|
|
if (!protect.Protect()) {
|
2011-08-11 09:54:57 +04:00
|
|
|
//printf ("VirtualProtectEx failed! %d\n", GetLastError());
|
|
|
|
continue;
|
|
|
|
}
|
2015-09-04 21:23:33 +03:00
|
|
|
|
2011-08-11 09:54:57 +04:00
|
|
|
// Remove the hook by making the original function jump directly
|
|
|
|
// in the trampoline.
|
2014-08-13 22:45:37 +04:00
|
|
|
intptr_t dest = (intptr_t)(p + sizeof(void*));
|
2011-08-11 09:54:57 +04:00
|
|
|
#if defined(_M_IX86)
|
2014-08-13 22:45:37 +04:00
|
|
|
*((intptr_t*)(origBytes + 1)) =
|
|
|
|
dest - (intptr_t)(origBytes + 5); // target displacement
|
2011-08-11 09:54:57 +04:00
|
|
|
#elif defined(_M_X64)
|
2014-08-13 22:45:37 +04:00
|
|
|
*((intptr_t*)(origBytes + 2)) = dest;
|
2011-08-11 09:54:57 +04:00
|
|
|
#else
|
|
|
|
#error "Unknown processor type"
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
void Init(const char* aModuleName, int aNumHooks = 0)
|
2012-04-10 23:52:56 +04:00
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
if (mModule) {
|
2009-11-04 09:35:20 +03:00
|
|
|
return;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
mModule = LoadLibraryExA(aModuleName, nullptr, 0);
|
2009-11-04 09:35:20 +03:00
|
|
|
if (!mModule) {
|
2014-08-13 22:45:37 +04:00
|
|
|
//printf("LoadLibraryEx for '%s' failed\n", aModuleName);
|
2009-11-04 09:35:20 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int hooksPerPage = 4096 / kHookSize;
|
2014-08-13 22:45:37 +04:00
|
|
|
if (aNumHooks == 0) {
|
|
|
|
aNumHooks = hooksPerPage;
|
|
|
|
}
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
mMaxHooks = aNumHooks + (hooksPerPage % aNumHooks);
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
mHookPage = (byteptr_t)VirtualAllocEx(GetCurrentProcess(), nullptr,
|
|
|
|
mMaxHooks * kHookSize,
|
|
|
|
MEM_COMMIT | MEM_RESERVE,
|
|
|
|
PAGE_EXECUTE_READWRITE);
|
2009-11-04 09:35:20 +03:00
|
|
|
if (!mHookPage) {
|
|
|
|
mModule = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
bool Initialized() { return !!mModule; }
|
2012-04-10 23:52:56 +04:00
|
|
|
|
|
|
|
void LockHooks()
|
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
if (!mModule) {
|
2009-11-04 09:35:20 +03:00
|
|
|
return;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2009-11-04 09:35:20 +03:00
|
|
|
|
|
|
|
DWORD op;
|
2014-08-13 22:45:37 +04:00
|
|
|
VirtualProtectEx(GetCurrentProcess(), mHookPage, mMaxHooks * kHookSize,
|
|
|
|
PAGE_EXECUTE_READ, &op);
|
2009-11-04 09:35:20 +03:00
|
|
|
|
|
|
|
mModule = 0;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
|
2009-11-04 09:35:20 +03:00
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
if (!mModule) {
|
2009-11-04 09:35:20 +03:00
|
|
|
return false;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
void* pAddr = (void*)GetProcAddress(mModule, aName);
|
2009-11-04 09:35:20 +03:00
|
|
|
if (!pAddr) {
|
|
|
|
//printf ("GetProcAddress failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-01-16 17:07:09 +03:00
|
|
|
pAddr = ResolveRedirectedAddress((byteptr_t)pAddr);
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
CreateTrampoline(pAddr, aHookDest, aOrigFunc);
|
|
|
|
if (!*aOrigFunc) {
|
2009-11-04 09:35:20 +03:00
|
|
|
//printf ("CreateTrampoline failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
const static int kPageSize = 4096;
|
|
|
|
const static int kHookSize = 128;
|
2014-02-15 02:52:29 +04:00
|
|
|
|
2009-11-04 09:35:20 +03:00
|
|
|
HMODULE mModule;
|
|
|
|
byteptr_t mHookPage;
|
|
|
|
int mMaxHooks;
|
|
|
|
int mCurHooks;
|
|
|
|
|
2016-10-14 00:15:22 +03:00
|
|
|
// rex bits
|
|
|
|
static const BYTE kMaskHighNibble = 0xF0;
|
|
|
|
static const BYTE kRexOpcode = 0x40;
|
|
|
|
static const BYTE kMaskRexW = 0x08;
|
|
|
|
static const BYTE kMaskRexR = 0x04;
|
|
|
|
static const BYTE kMaskRexX = 0x02;
|
|
|
|
static const BYTE kMaskRexB = 0x01;
|
|
|
|
|
|
|
|
// mod r/m bits
|
|
|
|
static const BYTE kRegFieldShift = 3;
|
|
|
|
static const BYTE kMaskMod = 0xC0;
|
|
|
|
static const BYTE kMaskReg = 0x38;
|
|
|
|
static const BYTE kMaskRm = 0x07;
|
|
|
|
static const BYTE kRmNeedSib = 0x04;
|
|
|
|
static const BYTE kModReg = 0xC0;
|
|
|
|
static const BYTE kModDisp32 = 0x80;
|
|
|
|
static const BYTE kModDisp8 = 0x40;
|
|
|
|
static const BYTE kModNoRegDisp = 0x00;
|
|
|
|
static const BYTE kRmNoRegDispDisp32 = 0x05;
|
|
|
|
|
|
|
|
// sib bits
|
|
|
|
static const BYTE kMaskSibScale = 0xC0;
|
|
|
|
static const BYTE kMaskSibIndex = 0x38;
|
|
|
|
static const BYTE kMaskSibBase = 0x07;
|
|
|
|
static const BYTE kSibBaseEbp = 0x05;
|
|
|
|
|
|
|
|
int CountModRmSib(const BYTE *aModRm, BYTE* aSubOpcode = nullptr)
|
|
|
|
{
|
|
|
|
if (!aModRm) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
int numBytes = 1; // Start with 1 for mod r/m byte itself
|
|
|
|
switch (*aModRm & kMaskMod) {
|
|
|
|
case kModReg:
|
|
|
|
return numBytes;
|
|
|
|
case kModDisp8:
|
|
|
|
numBytes += 1;
|
|
|
|
break;
|
|
|
|
case kModDisp32:
|
|
|
|
numBytes += 4;
|
|
|
|
break;
|
|
|
|
case kModNoRegDisp:
|
|
|
|
if ((*aModRm & kMaskRm) == kRmNoRegDispDisp32 ||
|
|
|
|
((*aModRm & kMaskRm) == kRmNeedSib &&
|
|
|
|
(*(aModRm + 1) & kMaskSibBase) == kSibBaseEbp)) {
|
|
|
|
numBytes += 4;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// This should not be reachable
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Impossible value for modr/m byte mod bits");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if ((*aModRm & kMaskRm) == kRmNeedSib) {
|
|
|
|
// SIB byte
|
|
|
|
numBytes += 1;
|
|
|
|
}
|
|
|
|
if (aSubOpcode) {
|
|
|
|
*aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
|
|
|
|
}
|
|
|
|
return numBytes;
|
|
|
|
}
|
|
|
|
|
2015-12-29 16:57:38 +03:00
|
|
|
#if defined(_M_X64)
|
|
|
|
// To patch for JMP and JE
|
|
|
|
|
|
|
|
enum JumpType {
|
|
|
|
Je,
|
|
|
|
Jmp
|
|
|
|
};
|
|
|
|
|
|
|
|
struct JumpPatch {
|
|
|
|
JumpPatch()
|
|
|
|
: mHookOffset(0), mJumpAddress(0), mType(JumpType::Jmp)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
JumpPatch(size_t aOffset, intptr_t aAddress, JumpType aType = JumpType::Jmp)
|
|
|
|
: mHookOffset(aOffset), mJumpAddress(aAddress), mType(aType)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddJumpPatch(size_t aHookOffset, intptr_t aAbsJumpAddress,
|
|
|
|
JumpType aType = JumpType::Jmp)
|
|
|
|
{
|
|
|
|
mHookOffset = aHookOffset;
|
|
|
|
mJumpAddress = aAbsJumpAddress;
|
|
|
|
mType = aType;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t GenerateJump(uint8_t* aCode)
|
|
|
|
{
|
|
|
|
size_t offset = mHookOffset;
|
|
|
|
if (mType == JumpType::Je) {
|
|
|
|
// JNE RIP+14
|
|
|
|
aCode[offset] = 0x75;
|
|
|
|
aCode[offset + 1] = 14;
|
|
|
|
offset += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// JMP [RIP+0]
|
|
|
|
aCode[offset] = 0xff;
|
|
|
|
aCode[offset + 1] = 0x25;
|
|
|
|
*reinterpret_cast<int32_t*>(aCode + offset + 2) = 0;
|
|
|
|
|
|
|
|
// Jump table
|
|
|
|
*reinterpret_cast<int64_t*>(aCode + offset + 2 + 4) = mJumpAddress;
|
|
|
|
|
|
|
|
return offset + 2 + 4 + 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HasJumpPatch() const
|
|
|
|
{
|
|
|
|
return !!mJumpAddress;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t mHookOffset;
|
|
|
|
intptr_t mJumpAddress;
|
|
|
|
JumpType mType;
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2016-10-14 00:04:48 +03:00
|
|
|
enum ePrefixGroupBits
|
|
|
|
{
|
|
|
|
eNoPrefixes = 0,
|
|
|
|
ePrefixGroup1 = (1 << 0),
|
|
|
|
ePrefixGroup2 = (1 << 1),
|
|
|
|
ePrefixGroup3 = (1 << 2),
|
|
|
|
ePrefixGroup4 = (1 << 3)
|
|
|
|
};
|
|
|
|
|
|
|
|
int CountPrefixBytes(byteptr_t aBytes, const int aBytesIndex,
|
|
|
|
unsigned char* aOutGroupBits)
|
|
|
|
{
|
|
|
|
unsigned char& groupBits = *aOutGroupBits;
|
|
|
|
groupBits = eNoPrefixes;
|
|
|
|
int index = aBytesIndex;
|
|
|
|
while (true) {
|
|
|
|
switch (aBytes[index]) {
|
|
|
|
// Group 1
|
|
|
|
case 0xF0: // LOCK
|
|
|
|
case 0xF2: // REPNZ
|
|
|
|
case 0xF3: // REP / REPZ
|
|
|
|
if (groupBits & ePrefixGroup1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
groupBits |= ePrefixGroup1;
|
|
|
|
++index;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Group 2
|
|
|
|
case 0x2E: // CS override / branch not taken
|
|
|
|
case 0x36: // SS override
|
|
|
|
case 0x3E: // DS override / branch taken
|
|
|
|
case 0x64: // FS override
|
|
|
|
case 0x65: // GS override
|
|
|
|
if (groupBits & ePrefixGroup2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
groupBits |= ePrefixGroup2;
|
|
|
|
++index;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Group 3
|
|
|
|
case 0x66: // operand size override
|
|
|
|
if (groupBits & ePrefixGroup3) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
groupBits |= ePrefixGroup3;
|
|
|
|
++index;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Group 4
|
|
|
|
case 0x67: // Address size override
|
|
|
|
if (groupBits & ePrefixGroup4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
groupBits |= ePrefixGroup4;
|
|
|
|
++index;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return index - aBytesIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
void CreateTrampoline(void* aOrigFunction, intptr_t aDest, void** aOutTramp)
|
2009-11-04 09:35:20 +03:00
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
*aOutTramp = nullptr;
|
2012-04-10 23:43:04 +04:00
|
|
|
|
2014-02-24 23:52:14 +04:00
|
|
|
byteptr_t tramp = FindTrampolineSpace();
|
2014-08-13 22:45:37 +04:00
|
|
|
if (!tramp) {
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
byteptr_t origBytes = (byteptr_t)aOrigFunction;
|
2009-11-04 09:35:20 +03:00
|
|
|
|
|
|
|
int nBytes = 0;
|
2011-08-29 15:23:21 +04:00
|
|
|
|
2011-04-25 05:02:07 +04:00
|
|
|
#if defined(_M_IX86)
|
2015-12-29 16:57:38 +03:00
|
|
|
int pJmp32 = -1;
|
2014-02-24 23:52:14 +04:00
|
|
|
while (nBytes < 5) {
|
2009-11-04 09:35:20 +03:00
|
|
|
// Understand some simple instructions that might be found in a
|
|
|
|
// prologue; we might need to extend this as necessary.
|
|
|
|
//
|
|
|
|
// Note! If we ever need to understand jump instructions, we'll
|
|
|
|
// need to rewrite the displacement argument.
|
2016-10-14 00:04:48 +03:00
|
|
|
unsigned char prefixGroups;
|
|
|
|
int numPrefixBytes = CountPrefixBytes(origBytes, nBytes, &prefixGroups);
|
|
|
|
if (numPrefixBytes < 0 || (prefixGroups & (ePrefixGroup3 | ePrefixGroup4))) {
|
|
|
|
// Either the prefix sequence was bad, or there are prefixes that
|
|
|
|
// we don't currently support (groups 3 and 4)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
nBytes += numPrefixBytes;
|
2009-11-04 09:35:20 +03:00
|
|
|
if (origBytes[nBytes] >= 0x88 && origBytes[nBytes] <= 0x8B) {
|
2011-08-29 15:23:21 +04:00
|
|
|
// various MOVs
|
2016-10-26 02:57:16 +03:00
|
|
|
++nBytes;
|
|
|
|
int len = CountModRmSib(origBytes + nBytes);
|
|
|
|
if (len < 0) {
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2009-11-04 09:35:20 +03:00
|
|
|
}
|
2016-10-26 02:57:16 +03:00
|
|
|
nBytes += len;
|
|
|
|
} else if (origBytes[nBytes] == 0xA1) {
|
|
|
|
// MOV eax, [seg:offset]
|
|
|
|
nBytes += 5;
|
2013-01-11 01:50:16 +04:00
|
|
|
} else if (origBytes[nBytes] == 0xB8) {
|
|
|
|
// MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
|
|
|
|
nBytes += 5;
|
2011-08-29 15:23:21 +04:00
|
|
|
} else if (origBytes[nBytes] == 0x83) {
|
|
|
|
// ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r/m, imm8
|
2014-08-13 22:45:37 +04:00
|
|
|
unsigned char b = origBytes[nBytes + 1];
|
2011-08-29 15:23:21 +04:00
|
|
|
if ((b & 0xc0) == 0xc0) {
|
|
|
|
// ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
|
|
|
|
nBytes += 3;
|
|
|
|
} else {
|
|
|
|
// bail
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2011-08-29 15:23:21 +04:00
|
|
|
}
|
2009-11-04 09:35:20 +03:00
|
|
|
} else if (origBytes[nBytes] == 0x68) {
|
|
|
|
// PUSH with 4-byte operand
|
|
|
|
nBytes += 5;
|
|
|
|
} else if ((origBytes[nBytes] & 0xf0) == 0x50) {
|
|
|
|
// 1-byte PUSH/POP
|
|
|
|
nBytes++;
|
2010-11-12 05:39:05 +03:00
|
|
|
} else if (origBytes[nBytes] == 0x6A) {
|
|
|
|
// PUSH imm8
|
|
|
|
nBytes += 2;
|
2011-08-29 15:23:21 +04:00
|
|
|
} else if (origBytes[nBytes] == 0xe9) {
|
|
|
|
pJmp32 = nBytes;
|
|
|
|
// jmp 32bit offset
|
|
|
|
nBytes += 5;
|
2014-12-24 07:23:36 +03:00
|
|
|
} else if (origBytes[nBytes] == 0xff && origBytes[nBytes + 1] == 0x25) {
|
|
|
|
// jmp [disp32]
|
|
|
|
nBytes += 6;
|
2009-11-04 09:35:20 +03:00
|
|
|
} else {
|
|
|
|
//printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", origBytes[nBytes]);
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2009-11-04 09:35:20 +03:00
|
|
|
}
|
|
|
|
}
|
2011-04-25 05:02:07 +04:00
|
|
|
#elif defined(_M_X64)
|
2015-12-29 16:57:38 +03:00
|
|
|
JumpPatch jump;
|
2013-08-05 06:13:53 +04:00
|
|
|
|
2011-04-25 05:02:07 +04:00
|
|
|
while (nBytes < 13) {
|
|
|
|
|
2015-08-12 20:10:04 +03:00
|
|
|
// if found JMP 32bit offset, next bytes must be NOP or INT3
|
2015-12-29 16:57:38 +03:00
|
|
|
if (jump.HasJumpPatch()) {
|
2015-08-12 20:10:04 +03:00
|
|
|
if (origBytes[nBytes] == 0x90 || origBytes[nBytes] == 0xcc) {
|
|
|
|
nBytes++;
|
|
|
|
continue;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2015-08-12 20:10:04 +03:00
|
|
|
return;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2013-06-12 23:22:55 +04:00
|
|
|
if (origBytes[nBytes] == 0x0f) {
|
|
|
|
nBytes++;
|
|
|
|
if (origBytes[nBytes] == 0x1f) {
|
|
|
|
// nop (multibyte)
|
|
|
|
nBytes++;
|
|
|
|
if ((origBytes[nBytes] & 0xc0) == 0x40 &&
|
|
|
|
(origBytes[nBytes] & 0x7) == 0x04) {
|
|
|
|
nBytes += 3;
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (origBytes[nBytes] == 0x05) {
|
|
|
|
// syscall
|
|
|
|
nBytes++;
|
2015-12-29 16:57:38 +03:00
|
|
|
} else if (origBytes[nBytes] == 0x84) {
|
|
|
|
// je rel32
|
|
|
|
jump.AddJumpPatch(nBytes - 1,
|
|
|
|
(intptr_t)
|
|
|
|
origBytes + nBytes + 5 +
|
|
|
|
*(reinterpret_cast<int32_t*>(origBytes +
|
|
|
|
nBytes + 1)),
|
|
|
|
JumpType::Je);
|
|
|
|
nBytes += 5;
|
2013-06-12 23:22:55 +04:00
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
2014-11-26 05:41:21 +03:00
|
|
|
} else if (origBytes[nBytes] == 0x40 ||
|
|
|
|
origBytes[nBytes] == 0x41) {
|
|
|
|
// Plain REX or REX.B
|
2011-04-25 05:02:07 +04:00
|
|
|
nBytes++;
|
|
|
|
|
|
|
|
if ((origBytes[nBytes] & 0xf0) == 0x50) {
|
|
|
|
// push/pop with Rx register
|
|
|
|
nBytes++;
|
|
|
|
} else if (origBytes[nBytes] >= 0xb8 && origBytes[nBytes] <= 0xbf) {
|
|
|
|
// mov r32, imm32
|
|
|
|
nBytes += 5;
|
|
|
|
} else {
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2011-04-25 05:02:07 +04:00
|
|
|
}
|
|
|
|
} else if (origBytes[nBytes] == 0x45) {
|
|
|
|
// REX.R & REX.B
|
|
|
|
nBytes++;
|
|
|
|
|
|
|
|
if (origBytes[nBytes] == 0x33) {
|
|
|
|
// xor r32, r32
|
|
|
|
nBytes += 2;
|
|
|
|
} else {
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2011-04-25 05:02:07 +04:00
|
|
|
}
|
2011-08-29 15:23:21 +04:00
|
|
|
} else if ((origBytes[nBytes] & 0xfb) == 0x48) {
|
|
|
|
// REX.W | REX.WR
|
2011-04-25 05:02:07 +04:00
|
|
|
nBytes++;
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
if (origBytes[nBytes] == 0x81 &&
|
|
|
|
(origBytes[nBytes + 1] & 0xf8) == 0xe8) {
|
2011-04-25 05:02:07 +04:00
|
|
|
// sub r, dword
|
|
|
|
nBytes += 6;
|
|
|
|
} else if (origBytes[nBytes] == 0x83 &&
|
2014-08-13 22:45:37 +04:00
|
|
|
(origBytes[nBytes + 1] & 0xf8) == 0xe8) {
|
2011-04-25 05:02:07 +04:00
|
|
|
// sub r, byte
|
|
|
|
nBytes += 3;
|
|
|
|
} else if (origBytes[nBytes] == 0x83 &&
|
2014-08-13 22:45:37 +04:00
|
|
|
(origBytes[nBytes + 1] & 0xf8) == 0x60) {
|
2011-04-25 05:02:07 +04:00
|
|
|
// and [r+d], imm8
|
|
|
|
nBytes += 5;
|
2015-12-29 16:57:38 +03:00
|
|
|
} else if (origBytes[nBytes] == 0x85) {
|
|
|
|
// 85 /r => TEST r/m32, r32
|
|
|
|
if ((origBytes[nBytes + 1] & 0xc0) == 0xc0) {
|
|
|
|
nBytes += 2;
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
2011-08-29 15:23:21 +04:00
|
|
|
} else if ((origBytes[nBytes] & 0xfd) == 0x89) {
|
2016-10-26 02:57:16 +03:00
|
|
|
++nBytes;
|
2011-08-29 15:23:21 +04:00
|
|
|
// MOV r/m64, r64 | MOV r64, r/m64
|
2016-10-26 02:57:16 +03:00
|
|
|
int len = CountModRmSib(origBytes + nBytes);
|
|
|
|
if (len < 0) {
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2011-04-25 05:02:07 +04:00
|
|
|
}
|
2016-10-26 02:57:16 +03:00
|
|
|
nBytes += len;
|
2013-06-12 23:22:55 +04:00
|
|
|
} else if (origBytes[nBytes] == 0xc7) {
|
|
|
|
// MOV r/m64, imm32
|
2013-11-19 10:47:03 +04:00
|
|
|
if (origBytes[nBytes + 1] == 0x44) {
|
|
|
|
// MOV [r64+disp8], imm32
|
|
|
|
// ModR/W + SIB + disp8 + imm32
|
|
|
|
nBytes += 8;
|
2013-08-05 06:13:53 +04:00
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (origBytes[nBytes] == 0xff) {
|
|
|
|
// JMP /4
|
2014-08-13 22:45:37 +04:00
|
|
|
if ((origBytes[nBytes + 1] & 0xc0) == 0x0 &&
|
|
|
|
(origBytes[nBytes + 1] & 0x07) == 0x5) {
|
2013-08-05 06:13:53 +04:00
|
|
|
// [rip+disp32]
|
|
|
|
// convert JMP 32bit offset to JMP 64bit direct
|
2015-12-29 16:57:38 +03:00
|
|
|
jump.AddJumpPatch(nBytes - 1,
|
|
|
|
*reinterpret_cast<intptr_t*>(
|
|
|
|
origBytes + nBytes + 6 +
|
|
|
|
*reinterpret_cast<int32_t*>(origBytes + nBytes +
|
|
|
|
2)));
|
2013-06-12 23:22:55 +04:00
|
|
|
nBytes += 6;
|
|
|
|
} else {
|
2013-08-05 06:13:53 +04:00
|
|
|
// not support yet!
|
2013-06-12 23:22:55 +04:00
|
|
|
return;
|
|
|
|
}
|
2011-04-25 05:02:07 +04:00
|
|
|
} else {
|
|
|
|
// not support yet!
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2011-04-25 05:02:07 +04:00
|
|
|
}
|
2016-10-13 23:56:23 +03:00
|
|
|
} else if (origBytes[nBytes] == 0x66) {
|
|
|
|
// operand override prefix
|
|
|
|
nBytes += 1;
|
|
|
|
// This is the same as the x86 version
|
|
|
|
if (origBytes[nBytes] >= 0x88 && origBytes[nBytes] <= 0x8B) {
|
|
|
|
// various MOVs
|
|
|
|
unsigned char b = origBytes[nBytes + 1];
|
|
|
|
if (((b & 0xc0) == 0xc0) ||
|
|
|
|
(((b & 0xc0) == 0x00) &&
|
|
|
|
((b & 0x07) != 0x04) && ((b & 0x07) != 0x05))) {
|
|
|
|
// REG=r, R/M=r or REG=r, R/M=[r]
|
|
|
|
nBytes += 2;
|
|
|
|
} else if ((b & 0xc0) == 0x40) {
|
|
|
|
if ((b & 0x07) == 0x04) {
|
|
|
|
// REG=r, R/M=[SIB + disp8]
|
|
|
|
nBytes += 4;
|
|
|
|
} else {
|
|
|
|
// REG=r, R/M=[r + disp8]
|
|
|
|
nBytes += 3;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// complex MOV, bail
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2011-04-25 05:02:07 +04:00
|
|
|
} else if ((origBytes[nBytes] & 0xf0) == 0x50) {
|
|
|
|
// 1-byte push/pop
|
|
|
|
nBytes++;
|
2016-09-07 10:15:58 +03:00
|
|
|
} else if (origBytes[nBytes] == 0x65) {
|
|
|
|
// GS prefix
|
|
|
|
//
|
|
|
|
// The entry of GetKeyState on Windows 10 has the following code.
|
|
|
|
// 65 48 8b 04 25 30 00 00 00 mov rax,qword ptr gs:[30h]
|
|
|
|
// (GS prefix + REX + MOV (0x8b) ...)
|
|
|
|
if (origBytes[nBytes + 1] == 0x48 &&
|
|
|
|
(origBytes[nBytes + 2] >= 0x88 && origBytes[nBytes + 2] <= 0x8b)) {
|
|
|
|
nBytes += 3;
|
2016-10-14 00:15:22 +03:00
|
|
|
int len = CountModRmSib(origBytes + nBytes);
|
|
|
|
if (len < 0) {
|
2016-09-07 10:15:58 +03:00
|
|
|
// no way to support this yet.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
nBytes += len;
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
2011-04-25 05:02:07 +04:00
|
|
|
} else if (origBytes[nBytes] == 0x90) {
|
|
|
|
// nop
|
|
|
|
nBytes++;
|
2013-06-12 23:22:55 +04:00
|
|
|
} else if (origBytes[nBytes] == 0xb8) {
|
|
|
|
// MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
|
|
|
|
nBytes += 5;
|
2016-10-13 23:56:23 +03:00
|
|
|
} else if (origBytes[nBytes] == 0x33) {
|
|
|
|
// xor r32, r/m32
|
|
|
|
nBytes += 2;
|
2016-10-14 00:15:22 +03:00
|
|
|
} else if (origBytes[nBytes] == 0xf6) {
|
|
|
|
// test r/m8, imm8 (used by ntdll on Windows 10 x64)
|
|
|
|
// (no flags are affected by near jmp since there is no task switch,
|
|
|
|
// so it is ok for a jmp to be written immediately after a test)
|
|
|
|
BYTE subOpcode = 0;
|
|
|
|
int nModRmSibBytes = CountModRmSib(&origBytes[nBytes + 1], &subOpcode);
|
|
|
|
if (nModRmSibBytes < 0 || subOpcode != 0) {
|
|
|
|
// Unsupported
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
nBytes += 2 + nModRmSibBytes;
|
2013-06-12 23:22:55 +04:00
|
|
|
} else if (origBytes[nBytes] == 0xc3) {
|
|
|
|
// ret
|
|
|
|
nBytes++;
|
2015-12-29 16:57:38 +03:00
|
|
|
} else if (origBytes[nBytes] == 0xcc) {
|
|
|
|
// int 3
|
|
|
|
nBytes++;
|
2011-04-25 05:02:07 +04:00
|
|
|
} else if (origBytes[nBytes] == 0xe9) {
|
|
|
|
// jmp 32bit offset
|
2015-12-29 16:57:38 +03:00
|
|
|
jump.AddJumpPatch(nBytes,
|
|
|
|
// convert JMP 32bit offset to JMP 64bit direct
|
|
|
|
(intptr_t)
|
|
|
|
origBytes + nBytes + 5 +
|
|
|
|
*(reinterpret_cast<int32_t*>(origBytes + nBytes + 1)));
|
2011-04-25 05:02:07 +04:00
|
|
|
nBytes += 5;
|
|
|
|
} else if (origBytes[nBytes] == 0xff) {
|
|
|
|
nBytes++;
|
|
|
|
if ((origBytes[nBytes] & 0xf8) == 0xf0) {
|
|
|
|
// push r64
|
|
|
|
nBytes++;
|
|
|
|
} else {
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2011-04-25 05:02:07 +04:00
|
|
|
}
|
|
|
|
} else {
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2011-04-25 05:02:07 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#error "Unknown processor type"
|
|
|
|
#endif
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2014-02-24 23:52:14 +04:00
|
|
|
if (nBytes > 100) {
|
2009-11-04 09:35:20 +03:00
|
|
|
//printf ("Too big!");
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2009-11-04 09:35:20 +03:00
|
|
|
}
|
|
|
|
|
2011-08-11 09:54:57 +04:00
|
|
|
// We keep the address of the original function in the first bytes of
|
|
|
|
// the trampoline buffer
|
2014-08-13 22:45:37 +04:00
|
|
|
*((void**)tramp) = aOrigFunction;
|
|
|
|
tramp += sizeof(void*);
|
2011-08-11 09:54:57 +04:00
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
memcpy(tramp, aOrigFunction, nBytes);
|
2009-11-04 09:35:20 +03:00
|
|
|
|
|
|
|
// OrigFunction+N, the target of the trampoline
|
|
|
|
byteptr_t trampDest = origBytes + nBytes;
|
|
|
|
|
2011-04-25 05:02:07 +04:00
|
|
|
#if defined(_M_IX86)
|
2011-08-29 15:23:21 +04:00
|
|
|
if (pJmp32 >= 0) {
|
|
|
|
// Jump directly to the original target of the jump instead of jumping to the
|
|
|
|
// original function.
|
|
|
|
// Adjust jump target displacement to jump location in the trampoline.
|
2014-08-13 22:45:37 +04:00
|
|
|
*((intptr_t*)(tramp + pJmp32 + 1)) += origBytes - tramp;
|
2011-08-29 15:23:21 +04:00
|
|
|
} else {
|
2014-02-24 23:52:14 +04:00
|
|
|
tramp[nBytes] = 0xE9; // jmp
|
2014-08-13 22:45:37 +04:00
|
|
|
*((intptr_t*)(tramp + nBytes + 1)) =
|
|
|
|
(intptr_t)trampDest - (intptr_t)(tramp + nBytes + 5); // target displacement
|
2011-08-29 15:23:21 +04:00
|
|
|
}
|
2011-04-25 05:02:07 +04:00
|
|
|
#elif defined(_M_X64)
|
2015-12-29 16:57:38 +03:00
|
|
|
// If JMP/JE opcode found, we don't insert to trampoline jump
|
|
|
|
if (jump.HasJumpPatch()) {
|
|
|
|
size_t offset = jump.GenerateJump(tramp);
|
|
|
|
if (jump.mType != JumpType::Jmp) {
|
|
|
|
JumpPatch patch(offset, reinterpret_cast<intptr_t>(trampDest));
|
|
|
|
patch.GenerateJump(tramp);
|
|
|
|
}
|
2011-04-25 05:02:07 +04:00
|
|
|
} else {
|
2015-12-29 16:57:38 +03:00
|
|
|
JumpPatch patch(nBytes, reinterpret_cast<intptr_t>(trampDest));
|
|
|
|
patch.GenerateJump(tramp);
|
2011-04-25 05:02:07 +04:00
|
|
|
}
|
|
|
|
#endif
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2012-04-10 23:43:04 +04:00
|
|
|
// The trampoline is now valid.
|
2014-08-13 22:45:37 +04:00
|
|
|
*aOutTramp = tramp;
|
2012-04-10 23:43:04 +04:00
|
|
|
|
2009-11-04 09:35:20 +03:00
|
|
|
// ensure we can modify the original code
|
2015-09-04 21:23:33 +03:00
|
|
|
AutoVirtualProtect protect(aOrigFunction, nBytes, PAGE_EXECUTE_READWRITE);
|
|
|
|
if (!protect.Protect()) {
|
2009-11-04 09:35:20 +03:00
|
|
|
//printf ("VirtualProtectEx failed! %d\n", GetLastError());
|
2012-04-10 23:43:04 +04:00
|
|
|
return;
|
2009-11-04 09:35:20 +03:00
|
|
|
}
|
|
|
|
|
2011-04-25 05:02:07 +04:00
|
|
|
#if defined(_M_IX86)
|
2009-11-04 09:35:20 +03:00
|
|
|
// now modify the original bytes
|
2014-02-24 23:52:14 +04:00
|
|
|
origBytes[0] = 0xE9; // jmp
|
2014-08-13 22:45:37 +04:00
|
|
|
*((intptr_t*)(origBytes + 1)) =
|
|
|
|
aDest - (intptr_t)(origBytes + 5); // target displacement
|
2011-04-25 05:02:07 +04:00
|
|
|
#elif defined(_M_X64)
|
|
|
|
// mov r11, address
|
2014-02-24 23:52:14 +04:00
|
|
|
origBytes[0] = 0x49;
|
|
|
|
origBytes[1] = 0xbb;
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
*((intptr_t*)(origBytes + 2)) = aDest;
|
2011-04-25 05:02:07 +04:00
|
|
|
|
|
|
|
// jmp r11
|
|
|
|
origBytes[10] = 0x41;
|
|
|
|
origBytes[11] = 0xff;
|
|
|
|
origBytes[12] = 0xe3;
|
|
|
|
#endif
|
2009-11-04 09:35:20 +03:00
|
|
|
}
|
|
|
|
|
2014-02-24 23:52:14 +04:00
|
|
|
byteptr_t FindTrampolineSpace()
|
2012-04-10 23:52:56 +04:00
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
if (mCurHooks >= mMaxHooks) {
|
2014-02-24 23:52:14 +04:00
|
|
|
return 0;
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2009-11-04 09:35:20 +03:00
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
byteptr_t p = mHookPage + mCurHooks * kHookSize;
|
2009-11-04 09:35:20 +03:00
|
|
|
|
|
|
|
mCurHooks++;
|
|
|
|
|
2014-02-24 23:52:14 +04:00
|
|
|
return p;
|
2009-11-04 09:35:20 +03:00
|
|
|
}
|
2015-01-16 17:07:09 +03:00
|
|
|
|
|
|
|
static void* ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
|
|
|
|
{
|
|
|
|
#if defined(_M_IX86)
|
|
|
|
// If function entry is jmp [disp32] such as used by kernel32,
|
|
|
|
// we resolve redirected address from import table.
|
|
|
|
if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
|
|
|
|
return (void*)(**((uint32_t**) (aOriginalFunction + 2)));
|
|
|
|
}
|
2015-02-12 09:07:57 +03:00
|
|
|
#elif defined(_M_X64)
|
|
|
|
if (aOriginalFunction[0] == 0xe9) {
|
|
|
|
// require for TestDllInterceptor with --disable-optimize
|
|
|
|
int32_t offset = *((int32_t*)(aOriginalFunction + 1));
|
|
|
|
return aOriginalFunction + 5 + offset;
|
|
|
|
}
|
2015-01-16 17:07:09 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return aOriginalFunction;
|
|
|
|
}
|
2009-11-04 09:35:20 +03:00
|
|
|
};
|
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
} // namespace internal
|
|
|
|
|
|
|
|
class WindowsDllInterceptor
|
|
|
|
{
|
|
|
|
internal::WindowsDllNopSpacePatcher mNopSpacePatcher;
|
|
|
|
internal::WindowsDllDetourPatcher mDetourPatcher;
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
const char* mModuleName;
|
2012-04-10 23:52:56 +04:00
|
|
|
int mNHooks;
|
|
|
|
|
|
|
|
public:
|
|
|
|
WindowsDllInterceptor()
|
2013-10-11 00:36:42 +04:00
|
|
|
: mModuleName(nullptr)
|
2012-04-10 23:52:56 +04:00
|
|
|
, mNHooks(0)
|
|
|
|
{}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
void Init(const char* aModuleName, int aNumHooks = 0)
|
2012-04-10 23:52:56 +04:00
|
|
|
{
|
|
|
|
if (mModuleName) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
mModuleName = aModuleName;
|
|
|
|
mNHooks = aNumHooks;
|
|
|
|
mNopSpacePatcher.Init(aModuleName);
|
2012-04-10 23:52:56 +04:00
|
|
|
|
|
|
|
// Lazily initialize mDetourPatcher, since it allocates memory and we might
|
|
|
|
// not need it.
|
|
|
|
}
|
|
|
|
|
|
|
|
void LockHooks()
|
|
|
|
{
|
2014-08-13 22:45:37 +04:00
|
|
|
if (mDetourPatcher.Initialized()) {
|
2012-04-10 23:52:56 +04:00
|
|
|
mDetourPatcher.LockHooks();
|
2014-08-13 22:45:37 +04:00
|
|
|
}
|
2012-04-10 23:52:56 +04:00
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
|
2012-04-10 23:52:56 +04:00
|
|
|
{
|
2014-03-03 19:27:21 +04:00
|
|
|
// Use a nop space patch if possible, otherwise fall back to a detour.
|
|
|
|
// This should be the preferred method for adding hooks.
|
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
if (!mModuleName) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
if (mNopSpacePatcher.AddHook(aName, aHookDest, aOrigFunc)) {
|
2012-04-10 23:52:56 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
return AddDetour(aName, aHookDest, aOrigFunc);
|
2014-03-03 19:27:21 +04:00
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc)
|
2014-03-03 19:27:21 +04:00
|
|
|
{
|
|
|
|
// Generally, code should not call this method directly. Use AddHook unless
|
|
|
|
// there is a specific need to avoid nop space patches.
|
|
|
|
|
|
|
|
if (!mModuleName) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-04-10 23:52:56 +04:00
|
|
|
if (!mDetourPatcher.Initialized()) {
|
|
|
|
mDetourPatcher.Init(mModuleName, mNHooks);
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:45:37 +04:00
|
|
|
return mDetourPatcher.AddHook(aName, aHookDest, aOrigFunc);
|
2012-04-10 23:52:56 +04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace mozilla
|
2009-11-04 09:35:20 +03:00
|
|
|
|
|
|
|
#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
|