1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_interceptor_PatcherNopSpace_h 8 #define mozilla_interceptor_PatcherNopSpace_h 9 10 #if defined(_M_IX86) 11 12 # include "mozilla/interceptor/PatcherBase.h" 13 14 namespace mozilla { 15 namespace interceptor { 16 17 template <typename VMPolicy> 18 class WindowsDllNopSpacePatcher final : public WindowsDllPatcherBase<VMPolicy> { 19 typedef typename VMPolicy::MMPolicyT MMPolicyT; 20 21 // For remembering the addresses of functions we've patched. 22 mozilla::Vector<void*> mPatchedFns; 23 24 public: 25 template <typename... Args> WindowsDllNopSpacePatcher(Args &&...aArgs)26 explicit WindowsDllNopSpacePatcher(Args&&... aArgs) 27 : WindowsDllPatcherBase<VMPolicy>(std::forward<Args>(aArgs)...) {} 28 ~WindowsDllNopSpacePatcher()29 ~WindowsDllNopSpacePatcher() { Clear(); } 30 31 WindowsDllNopSpacePatcher(const WindowsDllNopSpacePatcher&) = delete; 32 WindowsDllNopSpacePatcher(WindowsDllNopSpacePatcher&&) = delete; 33 WindowsDllNopSpacePatcher& operator=(const WindowsDllNopSpacePatcher&) = 34 delete; 35 WindowsDllNopSpacePatcher& operator=(WindowsDllNopSpacePatcher&&) = delete; 36 Clear()37 void Clear() { 38 // Restore the mov edi, edi to the beginning of each function we patched. 39 40 for (auto&& ptr : mPatchedFns) { 41 WritableTargetFunction<MMPolicyT> fn( 42 this->mVMPolicy, reinterpret_cast<uintptr_t>(ptr), sizeof(uint16_t)); 43 if (!fn) { 44 continue; 45 } 46 47 // mov edi, edi 48 fn.CommitAndWriteShort(0xff8b); 49 } 50 51 mPatchedFns.clear(); 52 } 53 54 /** 55 * NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions 56 * in our address space. There is a bug in Detours 2.x that causes it to 57 * patch at the wrong address when attempting to detour code that is already 58 * NOP space patched. This function is an effort to detect the presence of 59 * this NVIDIA code in our address space and disable NOP space patching if it 60 * is. We also check AppInit_DLLs since this is the mechanism that the Optimus 61 * drivers use to inject into our process. 62 */ IsCompatible()63 static bool IsCompatible() { 64 // These DLLs are known to have bad interactions with this style of patching 65 const wchar_t* kIncompatibleDLLs[] = {L"detoured.dll", L"_etoured.dll", 66 L"nvd3d9wrap.dll", L"nvdxgiwrap.dll"}; 67 // See if the infringing DLLs are already loaded 68 for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) { 69 if (GetModuleHandleW(kIncompatibleDLLs[i])) { 70 return false; 71 } 72 } 73 if (GetModuleHandleW(L"user32.dll")) { 74 // user32 is loaded but the infringing DLLs are not, assume we're safe to 75 // proceed. 76 return true; 77 } 78 // If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus 79 // won't be loaded once user32 is initialized. 80 HKEY hkey = NULL; 81 if (!RegOpenKeyExW( 82 HKEY_LOCAL_MACHINE, 83 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", 0, 84 KEY_QUERY_VALUE, &hkey)) { 85 nsAutoRegKey key(hkey); 86 DWORD numBytes = 0; 87 const wchar_t kAppInitDLLs[] = L"AppInit_DLLs"; 88 // Query for required buffer size 89 LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr, 90 nullptr, &numBytes); 91 mozilla::UniquePtr<wchar_t[]> data; 92 if (!status) { 93 // Allocate the buffer and query for the actual data 94 data = mozilla::MakeUnique<wchar_t[]>((numBytes + 1) / sizeof(wchar_t)); 95 status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr, 96 (LPBYTE)data.get(), &numBytes); 97 } 98 if (!status) { 99 // For each token, split up the filename components and then check the 100 // name of the file. 101 const wchar_t kDelimiters[] = L", "; 102 wchar_t* tokenContext = nullptr; 103 wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext); 104 while (token) { 105 wchar_t fname[_MAX_FNAME] = {0}; 106 if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0, fname, 107 mozilla::ArrayLength(fname), nullptr, 0)) { 108 // nvinit.dll is responsible for bootstrapping the DLL injection, so 109 // that is the library that we check for here 110 const wchar_t kNvInitName[] = L"nvinit"; 111 if (!_wcsnicmp(fname, kNvInitName, 112 mozilla::ArrayLength(kNvInitName))) { 113 return false; 114 } 115 } 116 token = wcstok_s(nullptr, kDelimiters, &tokenContext); 117 } 118 } 119 } 120 return true; 121 } 122 AddHook(FARPROC aTargetFn,intptr_t aHookDest,void ** aOrigFunc)123 bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc) { 124 if (!IsCompatible()) { 125 # if defined(MOZILLA_INTERNAL_API) 126 NS_WARNING("NOP space patching is unavailable for compatibility reasons"); 127 # endif 128 return false; 129 } 130 131 MOZ_ASSERT(aTargetFn); 132 if (!aTargetFn) { 133 return false; 134 } 135 136 ReadOnlyTargetFunction<MMPolicyT> readOnlyTargetFn( 137 this->ResolveRedirectedAddress(aTargetFn)); 138 139 if (!WriteHook(readOnlyTargetFn, aHookDest, aOrigFunc)) { 140 return false; 141 } 142 143 return mPatchedFns.append( 144 reinterpret_cast<void*>(readOnlyTargetFn.GetBaseAddress())); 145 } 146 WriteHook(const ReadOnlyTargetFunction<MMPolicyT> & aFn,intptr_t aHookDest,void ** aOrigFunc)147 bool WriteHook(const ReadOnlyTargetFunction<MMPolicyT>& aFn, 148 intptr_t aHookDest, void** aOrigFunc) { 149 // Ensure we can read and write starting at fn - 5 (for the long jmp we're 150 // going to write) and ending at fn + 2 (for the short jmp up to the long 151 // jmp). These bytes may span two pages with different protection. 152 WritableTargetFunction<MMPolicyT> writableFn(aFn.Promote(7, -5)); 153 if (!writableFn) { 154 return false; 155 } 156 157 // Check that the 5 bytes before the function are NOP's or INT 3's, 158 const uint8_t nopOrBp[] = {0x90, 0xCC}; 159 if (!writableFn.template VerifyValuesAreOneOf<uint8_t, 5>(nopOrBp)) { 160 return false; 161 } 162 163 // ... and that the first 2 bytes of the function are mov(edi, edi). 164 // There are two ways to encode the same thing: 165 // 166 // 0x89 0xff == mov r/m, r 167 // 0x8b 0xff == mov r, r/m 168 // 169 // where "r" is register and "r/m" is register or memory. 170 // Windows seems to use 0x8B 0xFF. We include 0x89 0xFF out of paranoia. 171 172 // (These look backwards because little-endian) 173 const uint16_t possibleEncodings[] = {0xFF8B, 0xFF89}; 174 if (!writableFn.template VerifyValuesAreOneOf<uint16_t, 1>( 175 possibleEncodings, 5)) { 176 return false; 177 } 178 179 // Write a long jump into the space above the function. 180 writableFn.WriteByte(0xe9); // jmp 181 if (!writableFn) { 182 return false; 183 } 184 185 writableFn.WriteDisp32(aHookDest); // target 186 if (!writableFn) { 187 return false; 188 } 189 190 // Set aOrigFunc here, because after this point, aHookDest might be called, 191 // and aHookDest might use the aOrigFunc pointer. 192 *aOrigFunc = reinterpret_cast<void*>(writableFn.GetCurrentAddress() + 193 sizeof(uint16_t)); 194 195 // Short jump up into our long jump. 196 return writableFn.CommitAndWriteShort(0xF9EB); // jmp $-5 197 } 198 }; 199 200 } // namespace interceptor 201 } // namespace mozilla 202 203 #endif // defined(_M_IX86) 204 205 #endif // mozilla_interceptor_PatcherNopSpace_h 206