Permalink
Cannot retrieve contributors at this time
819 lines (682 sloc)
26.1 KB
| /* -*- 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 <wchar.h> | |
| #include <windows.h> | |
| #include <winternl.h> | |
| #include <utility> | |
| #include "mozilla/ArrayUtils.h" | |
| #include "mozilla/Assertions.h" | |
| #include "mozilla/Atomics.h" | |
| #include "mozilla/Attributes.h" | |
| #include "mozilla/CheckedInt.h" | |
| #include "mozilla/DebugOnly.h" | |
| #include "mozilla/NativeNt.h" | |
| #include "mozilla/Tuple.h" | |
| #include "mozilla/Types.h" | |
| #include "mozilla/UniquePtr.h" | |
| #include "mozilla/Vector.h" | |
| #include "mozilla/interceptor/MMPolicies.h" | |
| #include "mozilla/interceptor/PatcherDetour.h" | |
| #include "mozilla/interceptor/PatcherNopSpace.h" | |
| #include "mozilla/interceptor/VMSharingPolicies.h" | |
| #include "nsWindowsHelpers.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. | |
| * | |
| */ | |
| #if defined(_M_IX86) && defined(__clang__) && __has_declspec_attribute(guard) | |
| // On x86, nop-space patches return to the second instruction of their target. | |
| // This is a deliberate violation of Control Flow Guard, so disable the check. | |
| # define INTERCEPTOR_DISABLE_CFGUARD __declspec(guard(nocf)) | |
| #else | |
| # define INTERCEPTOR_DISABLE_CFGUARD /* nothing */ | |
| #endif | |
| namespace mozilla { | |
| namespace interceptor { | |
| template <typename T> | |
| struct OriginalFunctionPtrTraits; | |
| template <typename R, typename... Args> | |
| struct OriginalFunctionPtrTraits<R (*)(Args...)> { | |
| using ReturnType = R; | |
| }; | |
| #if defined(_M_IX86) | |
| template <typename R, typename... Args> | |
| struct OriginalFunctionPtrTraits<R(__stdcall*)(Args...)> { | |
| using ReturnType = R; | |
| }; | |
| template <typename R, typename... Args> | |
| struct OriginalFunctionPtrTraits<R(__fastcall*)(Args...)> { | |
| using ReturnType = R; | |
| }; | |
| #endif // defined(_M_IX86) | |
| template <typename InterceptorT, typename FuncPtrT> | |
| class FuncHook final { | |
| public: | |
| using ThisType = FuncHook<InterceptorT, FuncPtrT>; | |
| using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType; | |
| constexpr FuncHook() : mOrigFunc(nullptr), mInitOnce(INIT_ONCE_STATIC_INIT) {} | |
| ~FuncHook() = default; | |
| bool Set(InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) { | |
| LPVOID addHookOk = nullptr; | |
| InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, false); | |
| return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx, | |
| &addHookOk) && | |
| addHookOk; | |
| } | |
| bool SetDetour(InterceptorT& aInterceptor, const char* aName, | |
| FuncPtrT aHookDest) { | |
| LPVOID addHookOk = nullptr; | |
| InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, true); | |
| return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx, | |
| &addHookOk) && | |
| addHookOk; | |
| } | |
| explicit operator bool() const { return !!mOrigFunc; } | |
| template <typename... ArgsType> | |
| INTERCEPTOR_DISABLE_CFGUARD ReturnType operator()(ArgsType&&... aArgs) const { | |
| return mOrigFunc(std::forward<ArgsType>(aArgs)...); | |
| } | |
| FuncPtrT GetStub() const { return mOrigFunc; } | |
| // One-time init stuff cannot be moved or copied | |
| FuncHook(const FuncHook&) = delete; | |
| FuncHook(FuncHook&&) = delete; | |
| FuncHook& operator=(const FuncHook&) = delete; | |
| FuncHook& operator=(FuncHook&& aOther) = delete; | |
| private: | |
| struct MOZ_RAII InitOnceContext final { | |
| InitOnceContext(ThisType* aHook, InterceptorT* aInterceptor, | |
| const char* aName, FuncPtrT aHookDest, bool aForceDetour) | |
| : mHook(aHook), | |
| mInterceptor(aInterceptor), | |
| mName(aName), | |
| mHookDest(reinterpret_cast<void*>(aHookDest)), | |
| mForceDetour(aForceDetour) {} | |
| ThisType* mHook; | |
| InterceptorT* mInterceptor; | |
| const char* mName; | |
| void* mHookDest; | |
| bool mForceDetour; | |
| }; | |
| private: | |
| bool Apply(InterceptorT* aInterceptor, const char* aName, void* aHookDest) { | |
| return aInterceptor->AddHook(aName, reinterpret_cast<intptr_t>(aHookDest), | |
| reinterpret_cast<void**>(&mOrigFunc)); | |
| } | |
| bool ApplyDetour(InterceptorT* aInterceptor, const char* aName, | |
| void* aHookDest) { | |
| return aInterceptor->AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest), | |
| reinterpret_cast<void**>(&mOrigFunc)); | |
| } | |
| static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam, | |
| PVOID* aOutContext) { | |
| MOZ_ASSERT(aOutContext); | |
| bool result; | |
| auto ctx = reinterpret_cast<InitOnceContext*>(aParam); | |
| if (ctx->mForceDetour) { | |
| result = ctx->mHook->ApplyDetour(ctx->mInterceptor, ctx->mName, | |
| ctx->mHookDest); | |
| } else { | |
| result = ctx->mHook->Apply(ctx->mInterceptor, ctx->mName, ctx->mHookDest); | |
| } | |
| *aOutContext = | |
| result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS) | |
| : nullptr; | |
| return TRUE; | |
| } | |
| private: | |
| FuncPtrT mOrigFunc; | |
| INIT_ONCE mInitOnce; | |
| }; | |
| template <typename InterceptorT, typename FuncPtrT> | |
| class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FuncHookCrossProcess final { | |
| public: | |
| using ThisType = FuncHookCrossProcess<InterceptorT, FuncPtrT>; | |
| using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType; | |
| #if defined(DEBUG) | |
| FuncHookCrossProcess() {} | |
| #endif // defined(DEBUG) | |
| bool Set(nt::CrossExecTransferManager& aTransferMgr, | |
| InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) { | |
| FuncPtrT origFunc; | |
| if (!aInterceptor.AddHook(aName, reinterpret_cast<intptr_t>(aHookDest), | |
| reinterpret_cast<void**>(&origFunc))) { | |
| return false; | |
| } | |
| return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc); | |
| } | |
| bool SetDetour(nt::CrossExecTransferManager& aTransferMgr, | |
| InterceptorT& aInterceptor, const char* aName, | |
| FuncPtrT aHookDest) { | |
| FuncPtrT origFunc; | |
| if (!aInterceptor.AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest), | |
| reinterpret_cast<void**>(&origFunc))) { | |
| return false; | |
| } | |
| return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc); | |
| } | |
| explicit operator bool() const { return !!mOrigFunc; } | |
| /** | |
| * NB: This operator is only meaningful when invoked in the target process! | |
| */ | |
| template <typename... ArgsType> | |
| ReturnType operator()(ArgsType&&... aArgs) const { | |
| return mOrigFunc(std::forward<ArgsType>(aArgs)...); | |
| } | |
| #if defined(DEBUG) | |
| FuncHookCrossProcess(const FuncHookCrossProcess&) = delete; | |
| FuncHookCrossProcess(FuncHookCrossProcess&&) = delete; | |
| FuncHookCrossProcess& operator=(const FuncHookCrossProcess&) = delete; | |
| FuncHookCrossProcess& operator=(FuncHookCrossProcess&& aOther) = delete; | |
| #endif // defined(DEBUG) | |
| private: | |
| bool CopyStubToChildProcess(nt::CrossExecTransferManager& aTransferMgr, | |
| InterceptorT& aInterceptor, FuncPtrT aStub) { | |
| LauncherVoidResult writeResult = | |
| aTransferMgr.Transfer(&mOrigFunc, &aStub, sizeof(FuncPtrT)); | |
| if (writeResult.isErr()) { | |
| #ifdef MOZ_USE_LAUNCHER_ERROR | |
| const mozilla::WindowsError& err = writeResult.inspectErr().mError; | |
| #else | |
| const mozilla::WindowsError& err = writeResult.inspectErr(); | |
| #endif | |
| aInterceptor.SetLastDetourError(FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR, | |
| err.AsHResult()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| private: | |
| FuncPtrT mOrigFunc; | |
| }; | |
| template <typename MMPolicyT, typename InterceptorT> | |
| struct TypeResolver; | |
| template <typename InterceptorT> | |
| struct TypeResolver<mozilla::interceptor::MMPolicyInProcess, InterceptorT> { | |
| template <typename FuncPtrT> | |
| using FuncHookType = FuncHook<InterceptorT, FuncPtrT>; | |
| }; | |
| template <typename InterceptorT> | |
| struct TypeResolver<mozilla::interceptor::MMPolicyOutOfProcess, InterceptorT> { | |
| template <typename FuncPtrT> | |
| using FuncHookType = FuncHookCrossProcess<InterceptorT, FuncPtrT>; | |
| }; | |
| template <typename VMPolicy = mozilla::interceptor::VMSharingPolicyShared> | |
| class WindowsDllInterceptor final | |
| : public TypeResolver<typename VMPolicy::MMPolicyT, | |
| WindowsDllInterceptor<VMPolicy>> { | |
| typedef WindowsDllInterceptor<VMPolicy> ThisType; | |
| interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher; | |
| #if defined(_M_IX86) | |
| interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT> | |
| mNopSpacePatcher; | |
| #endif // defined(_M_IX86) | |
| HMODULE mModule; | |
| public: | |
| template <typename... Args> | |
| explicit WindowsDllInterceptor(Args&&... aArgs) | |
| : mDetourPatcher(std::forward<Args>(aArgs)...) | |
| #if defined(_M_IX86) | |
| , | |
| mNopSpacePatcher(std::forward<Args>(aArgs)...) | |
| #endif // defined(_M_IX86) | |
| , | |
| mModule(nullptr) { | |
| } | |
| 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]) { | |
| 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); | |
| } | |
| void Init(const wchar_t* aModuleName) { | |
| if (mModule) { | |
| return; | |
| } | |
| mModule = ::LoadLibraryW(aModuleName); | |
| } | |
| /** Force a specific configuration for testing purposes. NOT to be used in | |
| production code! **/ | |
| void TestOnlyDetourInit(const wchar_t* aModuleName, DetourFlags aFlags) { | |
| Init(aModuleName); | |
| mDetourPatcher.Init(aFlags); | |
| } | |
| void Clear() { | |
| if (!mModule) { | |
| return; | |
| } | |
| #if defined(_M_IX86) | |
| mNopSpacePatcher.Clear(); | |
| #endif // defined(_M_IX86) | |
| mDetourPatcher.Clear(); | |
| // NB: We intentionally leak mModule | |
| } | |
| #if defined(NIGHTLY_BUILD) | |
| const Maybe<DetourError>& GetLastDetourError() const { | |
| return mDetourPatcher.GetLastDetourError(); | |
| } | |
| #endif // defined(NIGHTLY_BUILD) | |
| template <typename... Args> | |
| void SetLastDetourError(Args&&... aArgs) { | |
| return mDetourPatcher.SetLastDetourError(std::forward<Args>(aArgs)...); | |
| } | |
| constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() { | |
| return WindowsDllDetourPatcherPrimitive< | |
| typename VMPolicy::MMPolicyT>::GetWorstCaseRequiredBytesToPatch(); | |
| } | |
| private: | |
| /** | |
| * 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) { | |
| mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL); | |
| return false; | |
| } | |
| if (!mDetourPatcher.IsPageAccessible( | |
| nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) { | |
| mDetourPatcher.SetLastDetourError( | |
| DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE); | |
| return false; | |
| } | |
| FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName); | |
| if (!proc) { | |
| mDetourPatcher.SetLastDetourError( | |
| DetourResultCode::INTERCEPTOR_PROC_NULL); | |
| return false; | |
| } | |
| if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) { | |
| mDetourPatcher.SetLastDetourError( | |
| DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE); | |
| 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) { | |
| mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL); | |
| return false; | |
| } | |
| if (!mDetourPatcher.IsPageAccessible( | |
| nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) { | |
| mDetourPatcher.SetLastDetourError( | |
| DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE); | |
| return false; | |
| } | |
| FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName); | |
| if (!proc) { | |
| mDetourPatcher.SetLastDetourError( | |
| DetourResultCode::INTERCEPTOR_PROC_NULL); | |
| return false; | |
| } | |
| if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) { | |
| mDetourPatcher.SetLastDetourError( | |
| DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE); | |
| return false; | |
| } | |
| return AddDetour(proc, aHookDest, aOrigFunc); | |
| } | |
| bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc) { | |
| MOZ_ASSERT(mModule && aProc); | |
| if (!mDetourPatcher.Initialized()) { | |
| DetourFlags flags = DetourFlags::eDefault; | |
| #if defined(_M_X64) | |
| // NTDLL hooks should attempt to use a 10-byte patch because some | |
| // injected DLLs do the same and interfere with our stuff. | |
| bool needs10BytePatch = (mModule == ::GetModuleHandleW(L"ntdll.dll")); | |
| bool isWin8Or81 = IsWin8OrLater() && (!IsWin10OrLater()); | |
| bool isWin8 = IsWin8OrLater() && (!IsWin8Point1OrLater()); | |
| bool isKernel32Dll = (mModule == ::GetModuleHandleW(L"kernel32.dll")); | |
| bool isDuplicateHandle = (reinterpret_cast<void*>(aProc) == | |
| reinterpret_cast<void*>(&::DuplicateHandle)); | |
| // CloseHandle on Windows 8/8.1 only accomodates 10-byte patches. | |
| needs10BytePatch |= isWin8Or81 && isKernel32Dll && | |
| (reinterpret_cast<void*>(aProc) == | |
| reinterpret_cast<void*>(&CloseHandle)); | |
| // CreateFileA and DuplicateHandle on Windows 8 require 10-byte patches. | |
| needs10BytePatch |= isWin8 && isKernel32Dll && | |
| ((reinterpret_cast<void*>(aProc) == | |
| reinterpret_cast<void*>(&::CreateFileA)) || | |
| isDuplicateHandle); | |
| if (needs10BytePatch) { | |
| flags |= DetourFlags::eEnable10BytePatch; | |
| } | |
| if (isWin8 && isDuplicateHandle) { | |
| // Because we can't detour Win8's KERNELBASE!DuplicateHandle, | |
| // we detour kernel32!DuplicateHandle (See bug 1659398). | |
| flags |= DetourFlags::eDontResolveRedirection; | |
| } | |
| #endif // defined(_M_X64) | |
| mDetourPatcher.Init(flags); | |
| } | |
| return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc); | |
| } | |
| private: | |
| template <typename InterceptorT, typename FuncPtrT> | |
| friend class FuncHook; | |
| template <typename InterceptorT, typename FuncPtrT> | |
| friend class FuncHookCrossProcess; | |
| }; | |
| /** | |
| * IAT patching is intended for use when we only want to intercept a function | |
| * call originating from a specific module. | |
| */ | |
| class WindowsIATPatcher final { | |
| public: | |
| template <typename FuncPtrT> | |
| using FuncHookType = FuncHook<WindowsIATPatcher, FuncPtrT>; | |
| private: | |
| static bool CheckASCII(const char* aInStr) { | |
| while (*aInStr) { | |
| if (*aInStr & 0x80) { | |
| return false; | |
| } | |
| ++aInStr; | |
| } | |
| return true; | |
| } | |
| static bool AddHook(HMODULE aFromModule, const char* aToModuleName, | |
| const char* aTargetFnName, void* aHookDest, | |
| Atomic<void*>* aOutOrigFunc) { | |
| if (!aFromModule || !aToModuleName || !aTargetFnName || !aOutOrigFunc) { | |
| return false; | |
| } | |
| // PE Spec requires ASCII names for imported module names | |
| const bool isModuleNameAscii = CheckASCII(aToModuleName); | |
| MOZ_ASSERT(isModuleNameAscii); | |
| if (!isModuleNameAscii) { | |
| return false; | |
| } | |
| // PE Spec requires ASCII names for imported function names | |
| const bool isTargetFnNameAscii = CheckASCII(aTargetFnName); | |
| MOZ_ASSERT(isTargetFnNameAscii); | |
| if (!isTargetFnNameAscii) { | |
| return false; | |
| } | |
| nt::PEHeaders headers(aFromModule); | |
| if (!headers) { | |
| return false; | |
| } | |
| PIMAGE_IMPORT_DESCRIPTOR impDesc = | |
| headers.GetImportDescriptor(aToModuleName); | |
| if (!nt::PEHeaders::IsValid(impDesc)) { | |
| // Either aFromModule does not import aToModuleName at load-time, or | |
| // aToModuleName is a (currently unsupported) delay-load import. | |
| return false; | |
| } | |
| // Resolve the import name table (INT). | |
| auto firstINTThunk = headers.template RVAToPtr<PIMAGE_THUNK_DATA>( | |
| impDesc->OriginalFirstThunk); | |
| if (!nt::PEHeaders::IsValid(firstINTThunk)) { | |
| return false; | |
| } | |
| Maybe<ptrdiff_t> thunkIndex; | |
| // Scan the INT for the location of the thunk for the function named | |
| // 'aTargetFnName'. | |
| for (PIMAGE_THUNK_DATA curINTThunk = firstINTThunk; | |
| nt::PEHeaders::IsValid(curINTThunk); ++curINTThunk) { | |
| if (IMAGE_SNAP_BY_ORDINAL(curINTThunk->u1.Ordinal)) { | |
| // Currently not supporting import by ordinal; this isn't hard to add, | |
| // but we won't bother unless necessary. | |
| continue; | |
| } | |
| PIMAGE_IMPORT_BY_NAME curThunkFnName = | |
| headers.template RVAToPtr<PIMAGE_IMPORT_BY_NAME>( | |
| curINTThunk->u1.AddressOfData); | |
| MOZ_ASSERT(curThunkFnName); | |
| if (!curThunkFnName) { | |
| // Looks like we have a bad name descriptor. Try to continue. | |
| continue; | |
| } | |
| // Function name checks MUST be case-sensitive! | |
| if (!strcmp(aTargetFnName, curThunkFnName->Name)) { | |
| // We found the thunk. Save the index of this thunk, as the IAT thunk | |
| // is located at the same index in that table as in the INT. | |
| thunkIndex = Some(curINTThunk - firstINTThunk); | |
| break; | |
| } | |
| } | |
| if (thunkIndex.isNothing()) { | |
| // We never found a thunk for that function. Perhaps it's not imported? | |
| return false; | |
| } | |
| if (thunkIndex.value() < 0) { | |
| // That's just wrong. | |
| return false; | |
| } | |
| auto firstIATThunk = | |
| headers.template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk); | |
| if (!nt::PEHeaders::IsValid(firstIATThunk)) { | |
| return false; | |
| } | |
| // Resolve the IAT thunk for the function we want | |
| PIMAGE_THUNK_DATA targetThunk = &firstIATThunk[thunkIndex.value()]; | |
| if (!nt::PEHeaders::IsValid(targetThunk)) { | |
| return false; | |
| } | |
| auto fnPtr = reinterpret_cast<Atomic<void*>*>(&targetThunk->u1.Function); | |
| // Now we can just change out its pointer with our hook function. | |
| AutoVirtualProtect prot(fnPtr, sizeof(void*), PAGE_EXECUTE_READWRITE); | |
| if (!prot) { | |
| return false; | |
| } | |
| // We do the exchange this way to ensure that *aOutOrigFunc is always valid | |
| // once the atomic exchange has taken place. | |
| void* tmp; | |
| do { | |
| tmp = *fnPtr; | |
| *aOutOrigFunc = tmp; | |
| } while (!fnPtr->compareExchange(tmp, aHookDest)); | |
| return true; | |
| } | |
| template <typename InterceptorT, typename FuncPtrT> | |
| friend class FuncHook; | |
| }; | |
| template <typename FuncPtrT> | |
| class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS | |
| FuncHook<WindowsIATPatcher, FuncPtrT> | |
| final { | |
| public: | |
| using ThisType = FuncHook<WindowsIATPatcher, FuncPtrT>; | |
| using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType; | |
| constexpr FuncHook() | |
| : mInitOnce(INIT_ONCE_STATIC_INIT), | |
| mFromModule(nullptr), | |
| mOrigFunc(nullptr) {} | |
| #if defined(DEBUG) | |
| ~FuncHook() = default; | |
| #endif // defined(DEBUG) | |
| bool Set(const wchar_t* aFromModuleName, const char* aToModuleName, | |
| const char* aFnName, FuncPtrT aHookDest) { | |
| nsModuleHandle fromModule(::LoadLibraryW(aFromModuleName)); | |
| if (!fromModule) { | |
| return false; | |
| } | |
| return Set(fromModule, aToModuleName, aFnName, aHookDest); | |
| } | |
| // We offer this overload in case the client wants finer-grained control over | |
| // loading aFromModule. | |
| bool Set(nsModuleHandle& aFromModule, const char* aToModuleName, | |
| const char* aFnName, FuncPtrT aHookDest) { | |
| LPVOID addHookOk = nullptr; | |
| InitOnceContext ctx(this, aFromModule, aToModuleName, aFnName, aHookDest); | |
| bool result = ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx, | |
| &addHookOk) && | |
| addHookOk; | |
| if (!result) { | |
| return result; | |
| } | |
| // If we successfully set the hook then we must retain a strong reference | |
| // to the module that we modified. | |
| mFromModule = aFromModule.disown(); | |
| return result; | |
| } | |
| explicit operator bool() const { return !!mOrigFunc; } | |
| template <typename... ArgsType> | |
| ReturnType operator()(ArgsType&&... aArgs) const { | |
| return mOrigFunc(std::forward<ArgsType>(aArgs)...); | |
| } | |
| FuncPtrT GetStub() const { return mOrigFunc; } | |
| #if defined(DEBUG) | |
| // One-time init stuff cannot be moved or copied | |
| FuncHook(const FuncHook&) = delete; | |
| FuncHook(FuncHook&&) = delete; | |
| FuncHook& operator=(const FuncHook&) = delete; | |
| FuncHook& operator=(FuncHook&& aOther) = delete; | |
| #endif // defined(DEBUG) | |
| private: | |
| struct MOZ_RAII InitOnceContext final { | |
| InitOnceContext(ThisType* aHook, const nsModuleHandle& aFromModule, | |
| const char* aToModuleName, const char* aFnName, | |
| FuncPtrT aHookDest) | |
| : mHook(aHook), | |
| mFromModule(aFromModule), | |
| mToModuleName(aToModuleName), | |
| mFnName(aFnName), | |
| mHookDest(reinterpret_cast<void*>(aHookDest)) {} | |
| ThisType* mHook; | |
| const nsModuleHandle& mFromModule; | |
| const char* mToModuleName; | |
| const char* mFnName; | |
| void* mHookDest; | |
| }; | |
| private: | |
| bool Apply(const nsModuleHandle& aFromModule, const char* aToModuleName, | |
| const char* aFnName, void* aHookDest) { | |
| return WindowsIATPatcher::AddHook( | |
| aFromModule, aToModuleName, aFnName, aHookDest, | |
| reinterpret_cast<Atomic<void*>*>(&mOrigFunc)); | |
| } | |
| static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam, | |
| PVOID* aOutContext) { | |
| MOZ_ASSERT(aOutContext); | |
| auto ctx = reinterpret_cast<InitOnceContext*>(aParam); | |
| bool result = ctx->mHook->Apply(ctx->mFromModule, ctx->mToModuleName, | |
| ctx->mFnName, ctx->mHookDest); | |
| *aOutContext = | |
| result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS) | |
| : nullptr; | |
| return TRUE; | |
| } | |
| private: | |
| INIT_ONCE mInitOnce; | |
| HMODULE mFromModule; // never freed | |
| FuncPtrT mOrigFunc; | |
| }; | |
| /** | |
| * This class applies an irreversible patch to jump to a target function | |
| * without backing up the original function. | |
| */ | |
| class WindowsDllEntryPointInterceptor final { | |
| using DllMainFn = BOOL(WINAPI*)(HINSTANCE, DWORD, LPVOID); | |
| using MMPolicyT = MMPolicyInProcessEarlyStage; | |
| MMPolicyT mMMPolicy; | |
| public: | |
| explicit WindowsDllEntryPointInterceptor( | |
| const MMPolicyT::Kernel32Exports& aK32Exports) | |
| : mMMPolicy(aK32Exports) {} | |
| bool Set(const nt::PEHeaders& aHeaders, DllMainFn aDestination) { | |
| if (!aHeaders) { | |
| return false; | |
| } | |
| WindowsDllDetourPatcherPrimitive<MMPolicyT> patcher; | |
| return patcher.AddIrreversibleHook( | |
| mMMPolicy, aHeaders.GetEntryPoint(), | |
| reinterpret_cast<uintptr_t>(aDestination)); | |
| } | |
| }; | |
| } // namespace interceptor | |
| using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>; | |
| using CrossProcessDllInterceptor = interceptor::WindowsDllInterceptor< | |
| mozilla::interceptor::VMSharingPolicyUnique< | |
| mozilla::interceptor::MMPolicyOutOfProcess>>; | |
| using WindowsIATPatcher = interceptor::WindowsIATPatcher; | |
| } // namespace mozilla | |
| #endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */ |