xpcom/build/nsWindowsDllInterceptor.h

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
michael@0 7 #define NS_WINDOWS_DLL_INTERCEPTOR_H_
michael@0 8 #include <windows.h>
michael@0 9 #include <winternl.h>
michael@0 10
michael@0 11 /*
michael@0 12 * Simple function interception.
michael@0 13 *
michael@0 14 * We have two separate mechanisms for intercepting a function: We can use the
michael@0 15 * built-in nop space, if it exists, or we can create a detour.
michael@0 16 *
michael@0 17 * Using the built-in nop space works as follows: On x86-32, DLL functions
michael@0 18 * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
michael@0 19 * NOP instructions.
michael@0 20 *
michael@0 21 * When we detect a function with this prelude, we do the following:
michael@0 22 *
michael@0 23 * 1. Write a long jump to our interceptor function into the five bytes of NOPs
michael@0 24 * before the function.
michael@0 25 *
michael@0 26 * 2. Write a short jump -5 into the two-byte nop at the beginning of the function.
michael@0 27 *
michael@0 28 * This mechanism is nice because it's thread-safe. It's even safe to do if
michael@0 29 * another thread is currently running the function we're modifying!
michael@0 30 *
michael@0 31 * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
michael@0 32 * but not the long jump, so re-intercepting the same function won't work,
michael@0 33 * because its prelude won't match.
michael@0 34 *
michael@0 35 *
michael@0 36 * Unfortunately nop space patching doesn't work on functions which don't have
michael@0 37 * this magic prelude (and in particular, x86-64 never has the prelude). So
michael@0 38 * when we can't use the built-in nop space, we fall back to using a detour,
michael@0 39 * which works as follows:
michael@0 40 *
michael@0 41 * 1. Save first N bytes of OrigFunction to trampoline, where N is a
michael@0 42 * number of bytes >= 5 that are instruction aligned.
michael@0 43 *
michael@0 44 * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
michael@0 45 * function.
michael@0 46 *
michael@0 47 * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
michael@0 48 * continue original program flow.
michael@0 49 *
michael@0 50 * 4. Hook function needs to call the trampoline during its execution,
michael@0 51 * to invoke the original function (so address of trampoline is
michael@0 52 * returned).
michael@0 53 *
michael@0 54 * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
michael@0 55 * patched again to jump directly to the trampoline instead of going through
michael@0 56 * the hook function. As such, re-intercepting the same function won't work, as
michael@0 57 * jump instructions are not supported.
michael@0 58 *
michael@0 59 * Note that this is not thread-safe. Sad day.
michael@0 60 *
michael@0 61 */
michael@0 62
michael@0 63 #include <stdint.h>
michael@0 64
michael@0 65 namespace mozilla {
michael@0 66 namespace internal {
michael@0 67
michael@0 68 class WindowsDllNopSpacePatcher
michael@0 69 {
michael@0 70 typedef unsigned char *byteptr_t;
michael@0 71 HMODULE mModule;
michael@0 72
michael@0 73 // Dumb array for remembering the addresses of functions we've patched.
michael@0 74 // (This should be nsTArray, but non-XPCOM code uses this class.)
michael@0 75 static const size_t maxPatchedFns = 128;
michael@0 76 byteptr_t mPatchedFns[maxPatchedFns];
michael@0 77 int mPatchedFnsLen;
michael@0 78
michael@0 79 public:
michael@0 80 WindowsDllNopSpacePatcher()
michael@0 81 : mModule(0)
michael@0 82 , mPatchedFnsLen(0)
michael@0 83 {}
michael@0 84
michael@0 85 ~WindowsDllNopSpacePatcher()
michael@0 86 {
michael@0 87 // Restore the mov edi, edi to the beginning of each function we patched.
michael@0 88
michael@0 89 for (int i = 0; i < mPatchedFnsLen; i++) {
michael@0 90 byteptr_t fn = mPatchedFns[i];
michael@0 91
michael@0 92 // Ensure we can write to the code.
michael@0 93 DWORD op;
michael@0 94 if (!VirtualProtectEx(GetCurrentProcess(), fn, 2, PAGE_EXECUTE_READWRITE, &op)) {
michael@0 95 // printf("VirtualProtectEx failed! %d\n", GetLastError());
michael@0 96 continue;
michael@0 97 }
michael@0 98
michael@0 99 // mov edi, edi
michael@0 100 *((uint16_t*)fn) = 0xff8b;
michael@0 101
michael@0 102 // Restore the old protection.
michael@0 103 VirtualProtectEx(GetCurrentProcess(), fn, 2, op, &op);
michael@0 104
michael@0 105 // I don't think this is actually necessary, but it can't hurt.
michael@0 106 FlushInstructionCache(GetCurrentProcess(),
michael@0 107 /* ignored */ nullptr,
michael@0 108 /* ignored */ 0);
michael@0 109 }
michael@0 110 }
michael@0 111
michael@0 112 void Init(const char *modulename)
michael@0 113 {
michael@0 114 mModule = LoadLibraryExA(modulename, nullptr, 0);
michael@0 115 if (!mModule) {
michael@0 116 //printf("LoadLibraryEx for '%s' failed\n", modulename);
michael@0 117 return;
michael@0 118 }
michael@0 119 }
michael@0 120
michael@0 121 #if defined(_M_IX86)
michael@0 122 bool AddHook(const char *pname, intptr_t hookDest, void **origFunc)
michael@0 123 {
michael@0 124 if (!mModule)
michael@0 125 return false;
michael@0 126
michael@0 127 if (mPatchedFnsLen == maxPatchedFns) {
michael@0 128 // printf ("No space for hook in mPatchedFns.\n");
michael@0 129 return false;
michael@0 130 }
michael@0 131
michael@0 132 byteptr_t fn = reinterpret_cast<byteptr_t>(GetProcAddress(mModule, pname));
michael@0 133 if (!fn) {
michael@0 134 //printf ("GetProcAddress failed\n");
michael@0 135 return false;
michael@0 136 }
michael@0 137
michael@0 138 // Ensure we can read and write starting at fn - 5 (for the long jmp we're
michael@0 139 // going to write) and ending at fn + 2 (for the short jmp up to the long
michael@0 140 // jmp).
michael@0 141 DWORD op;
michael@0 142 if (!VirtualProtectEx(GetCurrentProcess(), fn - 5, 7, PAGE_EXECUTE_READWRITE, &op)) {
michael@0 143 //printf ("VirtualProtectEx failed! %d\n", GetLastError());
michael@0 144 return false;
michael@0 145 }
michael@0 146
michael@0 147 bool rv = WriteHook(fn, hookDest, origFunc);
michael@0 148
michael@0 149 // Re-protect, and we're done.
michael@0 150 VirtualProtectEx(GetCurrentProcess(), fn - 5, 7, op, &op);
michael@0 151
michael@0 152 if (rv) {
michael@0 153 mPatchedFns[mPatchedFnsLen] = fn;
michael@0 154 mPatchedFnsLen++;
michael@0 155 }
michael@0 156
michael@0 157 return rv;
michael@0 158 }
michael@0 159
michael@0 160 bool WriteHook(byteptr_t fn, intptr_t hookDest, void **origFunc)
michael@0 161 {
michael@0 162 // Check that the 5 bytes before fn are NOP's or INT 3's,
michael@0 163 // and that the 2 bytes after fn are mov(edi, edi).
michael@0 164 //
michael@0 165 // It's safe to read fn[-5] because we set it to PAGE_EXECUTE_READWRITE
michael@0 166 // before calling WriteHook.
michael@0 167
michael@0 168 for (int i = -5; i <= -1; i++) {
michael@0 169 if (fn[i] != 0x90 && fn[i] != 0xcc) // nop or int 3
michael@0 170 return false;
michael@0 171 }
michael@0 172
michael@0 173 // mov edi, edi. Yes, there are two ways to encode the same thing:
michael@0 174 //
michael@0 175 // 0x89ff == mov r/m, r
michael@0 176 // 0x8bff == mov r, r/m
michael@0 177 //
michael@0 178 // where "r" is register and "r/m" is register or memory. Windows seems to
michael@0 179 // use 8bff; I include 89ff out of paranoia.
michael@0 180 if ((fn[0] != 0x8b && fn[0] != 0x89) || fn[1] != 0xff) {
michael@0 181 return false;
michael@0 182 }
michael@0 183
michael@0 184 // Write a long jump into the space above the function.
michael@0 185 fn[-5] = 0xe9; // jmp
michael@0 186 *((intptr_t*)(fn - 4)) = hookDest - (uintptr_t)(fn); // target displacement
michael@0 187
michael@0 188 // Set origFunc here, because after this point, hookDest might be called,
michael@0 189 // and hookDest might use the origFunc pointer.
michael@0 190 *origFunc = fn + 2;
michael@0 191
michael@0 192 // Short jump up into our long jump.
michael@0 193 *((uint16_t*)(fn)) = 0xf9eb; // jmp $-5
michael@0 194
michael@0 195 // I think this routine is safe without this, but it can't hurt.
michael@0 196 FlushInstructionCache(GetCurrentProcess(),
michael@0 197 /* ignored */ nullptr,
michael@0 198 /* ignored */ 0);
michael@0 199
michael@0 200 return true;
michael@0 201 }
michael@0 202 #else
michael@0 203 bool AddHook(const char *pname, intptr_t hookDest, void **origFunc)
michael@0 204 {
michael@0 205 // Not implemented except on x86-32.
michael@0 206 return false;
michael@0 207 }
michael@0 208 #endif
michael@0 209 };
michael@0 210
michael@0 211 class WindowsDllDetourPatcher
michael@0 212 {
michael@0 213 typedef unsigned char *byteptr_t;
michael@0 214 public:
michael@0 215 WindowsDllDetourPatcher()
michael@0 216 : mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0)
michael@0 217 {
michael@0 218 }
michael@0 219
michael@0 220 ~WindowsDllDetourPatcher()
michael@0 221 {
michael@0 222 int i;
michael@0 223 byteptr_t p;
michael@0 224 for (i = 0, p = mHookPage; i < mCurHooks; i++, p += kHookSize) {
michael@0 225 #if defined(_M_IX86)
michael@0 226 size_t nBytes = 1 + sizeof(intptr_t);
michael@0 227 #elif defined(_M_X64)
michael@0 228 size_t nBytes = 2 + sizeof(intptr_t);
michael@0 229 #else
michael@0 230 #error "Unknown processor type"
michael@0 231 #endif
michael@0 232 byteptr_t origBytes = *((byteptr_t *)p);
michael@0 233 // ensure we can modify the original code
michael@0 234 DWORD op;
michael@0 235 if (!VirtualProtectEx(GetCurrentProcess(), origBytes, nBytes, PAGE_EXECUTE_READWRITE, &op)) {
michael@0 236 //printf ("VirtualProtectEx failed! %d\n", GetLastError());
michael@0 237 continue;
michael@0 238 }
michael@0 239 // Remove the hook by making the original function jump directly
michael@0 240 // in the trampoline.
michael@0 241 intptr_t dest = (intptr_t)(p + sizeof(void *));
michael@0 242 #if defined(_M_IX86)
michael@0 243 *((intptr_t*)(origBytes+1)) = dest - (intptr_t)(origBytes+5); // target displacement
michael@0 244 #elif defined(_M_X64)
michael@0 245 *((intptr_t*)(origBytes+2)) = dest;
michael@0 246 #else
michael@0 247 #error "Unknown processor type"
michael@0 248 #endif
michael@0 249 // restore protection; if this fails we can't really do anything about it
michael@0 250 VirtualProtectEx(GetCurrentProcess(), origBytes, nBytes, op, &op);
michael@0 251 }
michael@0 252 }
michael@0 253
michael@0 254 void Init(const char *modulename, int nhooks = 0)
michael@0 255 {
michael@0 256 if (mModule)
michael@0 257 return;
michael@0 258
michael@0 259 mModule = LoadLibraryExA(modulename, nullptr, 0);
michael@0 260 if (!mModule) {
michael@0 261 //printf("LoadLibraryEx for '%s' failed\n", modulename);
michael@0 262 return;
michael@0 263 }
michael@0 264
michael@0 265 int hooksPerPage = 4096 / kHookSize;
michael@0 266 if (nhooks == 0)
michael@0 267 nhooks = hooksPerPage;
michael@0 268
michael@0 269 mMaxHooks = nhooks + (hooksPerPage % nhooks);
michael@0 270
michael@0 271 mHookPage = (byteptr_t) VirtualAllocEx(GetCurrentProcess(), nullptr,
michael@0 272 mMaxHooks * kHookSize,
michael@0 273 MEM_COMMIT | MEM_RESERVE,
michael@0 274 PAGE_EXECUTE_READWRITE);
michael@0 275
michael@0 276 if (!mHookPage) {
michael@0 277 mModule = 0;
michael@0 278 return;
michael@0 279 }
michael@0 280 }
michael@0 281
michael@0 282 bool Initialized()
michael@0 283 {
michael@0 284 return !!mModule;
michael@0 285 }
michael@0 286
michael@0 287 void LockHooks()
michael@0 288 {
michael@0 289 if (!mModule)
michael@0 290 return;
michael@0 291
michael@0 292 DWORD op;
michael@0 293 VirtualProtectEx(GetCurrentProcess(), mHookPage, mMaxHooks * kHookSize, PAGE_EXECUTE_READ, &op);
michael@0 294
michael@0 295 mModule = 0;
michael@0 296 }
michael@0 297
michael@0 298 bool AddHook(const char *pname, intptr_t hookDest, void **origFunc)
michael@0 299 {
michael@0 300 if (!mModule)
michael@0 301 return false;
michael@0 302
michael@0 303 void *pAddr = (void *) GetProcAddress(mModule, pname);
michael@0 304 if (!pAddr) {
michael@0 305 //printf ("GetProcAddress failed\n");
michael@0 306 return false;
michael@0 307 }
michael@0 308
michael@0 309 CreateTrampoline(pAddr, hookDest, origFunc);
michael@0 310 if (!*origFunc) {
michael@0 311 //printf ("CreateTrampoline failed\n");
michael@0 312 return false;
michael@0 313 }
michael@0 314
michael@0 315 return true;
michael@0 316 }
michael@0 317
michael@0 318 protected:
michael@0 319 const static int kPageSize = 4096;
michael@0 320 const static int kHookSize = 128;
michael@0 321
michael@0 322 HMODULE mModule;
michael@0 323 byteptr_t mHookPage;
michael@0 324 int mMaxHooks;
michael@0 325 int mCurHooks;
michael@0 326
michael@0 327 void CreateTrampoline(void *origFunction,
michael@0 328 intptr_t dest,
michael@0 329 void **outTramp)
michael@0 330 {
michael@0 331 *outTramp = nullptr;
michael@0 332
michael@0 333 byteptr_t tramp = FindTrampolineSpace();
michael@0 334 if (!tramp)
michael@0 335 return;
michael@0 336
michael@0 337 byteptr_t origBytes = (byteptr_t) origFunction;
michael@0 338
michael@0 339 int nBytes = 0;
michael@0 340 int pJmp32 = -1;
michael@0 341
michael@0 342 #if defined(_M_IX86)
michael@0 343 while (nBytes < 5) {
michael@0 344 // Understand some simple instructions that might be found in a
michael@0 345 // prologue; we might need to extend this as necessary.
michael@0 346 //
michael@0 347 // Note! If we ever need to understand jump instructions, we'll
michael@0 348 // need to rewrite the displacement argument.
michael@0 349 if (origBytes[nBytes] >= 0x88 && origBytes[nBytes] <= 0x8B) {
michael@0 350 // various MOVs
michael@0 351 unsigned char b = origBytes[nBytes+1];
michael@0 352 if (((b & 0xc0) == 0xc0) ||
michael@0 353 (((b & 0xc0) == 0x00) &&
michael@0 354 ((b & 0x07) != 0x04) && ((b & 0x07) != 0x05)))
michael@0 355 {
michael@0 356 // REG=r, R/M=r or REG=r, R/M=[r]
michael@0 357 nBytes += 2;
michael@0 358 } else if (((b & 0xc0) == 0x40) && ((b & 0x38) != 0x20)) {
michael@0 359 // REG=r, R/M=[r + disp8]
michael@0 360 nBytes += 3;
michael@0 361 } else {
michael@0 362 // complex MOV, bail
michael@0 363 return;
michael@0 364 }
michael@0 365 } else if (origBytes[nBytes] == 0xB8) {
michael@0 366 // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
michael@0 367 nBytes += 5;
michael@0 368 } else if (origBytes[nBytes] == 0x83) {
michael@0 369 // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r/m, imm8
michael@0 370 unsigned char b = origBytes[nBytes+1];
michael@0 371 if ((b & 0xc0) == 0xc0) {
michael@0 372 // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
michael@0 373 nBytes += 3;
michael@0 374 } else {
michael@0 375 // bail
michael@0 376 return;
michael@0 377 }
michael@0 378 } else if (origBytes[nBytes] == 0x68) {
michael@0 379 // PUSH with 4-byte operand
michael@0 380 nBytes += 5;
michael@0 381 } else if ((origBytes[nBytes] & 0xf0) == 0x50) {
michael@0 382 // 1-byte PUSH/POP
michael@0 383 nBytes++;
michael@0 384 } else if (origBytes[nBytes] == 0x6A) {
michael@0 385 // PUSH imm8
michael@0 386 nBytes += 2;
michael@0 387 } else if (origBytes[nBytes] == 0xe9) {
michael@0 388 pJmp32 = nBytes;
michael@0 389 // jmp 32bit offset
michael@0 390 nBytes += 5;
michael@0 391 } else {
michael@0 392 //printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", origBytes[nBytes]);
michael@0 393 return;
michael@0 394 }
michael@0 395 }
michael@0 396 #elif defined(_M_X64)
michael@0 397 byteptr_t directJmpAddr;
michael@0 398
michael@0 399 while (nBytes < 13) {
michael@0 400
michael@0 401 // if found JMP 32bit offset, next bytes must be NOP
michael@0 402 if (pJmp32 >= 0) {
michael@0 403 if (origBytes[nBytes++] != 0x90)
michael@0 404 return;
michael@0 405
michael@0 406 continue;
michael@0 407 }
michael@0 408 if (origBytes[nBytes] == 0x0f) {
michael@0 409 nBytes++;
michael@0 410 if (origBytes[nBytes] == 0x1f) {
michael@0 411 // nop (multibyte)
michael@0 412 nBytes++;
michael@0 413 if ((origBytes[nBytes] & 0xc0) == 0x40 &&
michael@0 414 (origBytes[nBytes] & 0x7) == 0x04) {
michael@0 415 nBytes += 3;
michael@0 416 } else {
michael@0 417 return;
michael@0 418 }
michael@0 419 } else if (origBytes[nBytes] == 0x05) {
michael@0 420 // syscall
michael@0 421 nBytes++;
michael@0 422 } else {
michael@0 423 return;
michael@0 424 }
michael@0 425 } else if (origBytes[nBytes] == 0x41) {
michael@0 426 // REX.B
michael@0 427 nBytes++;
michael@0 428
michael@0 429 if ((origBytes[nBytes] & 0xf0) == 0x50) {
michael@0 430 // push/pop with Rx register
michael@0 431 nBytes++;
michael@0 432 } else if (origBytes[nBytes] >= 0xb8 && origBytes[nBytes] <= 0xbf) {
michael@0 433 // mov r32, imm32
michael@0 434 nBytes += 5;
michael@0 435 } else {
michael@0 436 return;
michael@0 437 }
michael@0 438 } else if (origBytes[nBytes] == 0x45) {
michael@0 439 // REX.R & REX.B
michael@0 440 nBytes++;
michael@0 441
michael@0 442 if (origBytes[nBytes] == 0x33) {
michael@0 443 // xor r32, r32
michael@0 444 nBytes += 2;
michael@0 445 } else {
michael@0 446 return;
michael@0 447 }
michael@0 448 } else if ((origBytes[nBytes] & 0xfb) == 0x48) {
michael@0 449 // REX.W | REX.WR
michael@0 450 nBytes++;
michael@0 451
michael@0 452 if (origBytes[nBytes] == 0x81 && (origBytes[nBytes+1] & 0xf8) == 0xe8) {
michael@0 453 // sub r, dword
michael@0 454 nBytes += 6;
michael@0 455 } else if (origBytes[nBytes] == 0x83 &&
michael@0 456 (origBytes[nBytes+1] & 0xf8) == 0xe8) {
michael@0 457 // sub r, byte
michael@0 458 nBytes += 3;
michael@0 459 } else if (origBytes[nBytes] == 0x83 &&
michael@0 460 (origBytes[nBytes+1] & 0xf8) == 0x60) {
michael@0 461 // and [r+d], imm8
michael@0 462 nBytes += 5;
michael@0 463 } else if ((origBytes[nBytes] & 0xfd) == 0x89) {
michael@0 464 // MOV r/m64, r64 | MOV r64, r/m64
michael@0 465 if ((origBytes[nBytes+1] & 0xc0) == 0x40) {
michael@0 466 if ((origBytes[nBytes+1] & 0x7) == 0x04) {
michael@0 467 // R/M=[SIB+disp8], REG=r64
michael@0 468 nBytes += 4;
michael@0 469 } else {
michael@0 470 // R/M=[r64+disp8], REG=r64
michael@0 471 nBytes += 3;
michael@0 472 }
michael@0 473 } else if (((origBytes[nBytes+1] & 0xc0) == 0xc0) ||
michael@0 474 (((origBytes[nBytes+1] & 0xc0) == 0x00) &&
michael@0 475 ((origBytes[nBytes+1] & 0x07) != 0x04) && ((origBytes[nBytes+1] & 0x07) != 0x05))) {
michael@0 476 // REG=r64, R/M=r64 or REG=r64, R/M=[r64]
michael@0 477 nBytes += 2;
michael@0 478 } else {
michael@0 479 // complex MOV
michael@0 480 return;
michael@0 481 }
michael@0 482 } else if (origBytes[nBytes] == 0xc7) {
michael@0 483 // MOV r/m64, imm32
michael@0 484 if (origBytes[nBytes + 1] == 0x44) {
michael@0 485 // MOV [r64+disp8], imm32
michael@0 486 // ModR/W + SIB + disp8 + imm32
michael@0 487 nBytes += 8;
michael@0 488 } else {
michael@0 489 return;
michael@0 490 }
michael@0 491 } else if (origBytes[nBytes] == 0xff) {
michael@0 492 pJmp32 = nBytes - 1;
michael@0 493 // JMP /4
michael@0 494 if ((origBytes[nBytes+1] & 0xc0) == 0x0 &&
michael@0 495 (origBytes[nBytes+1] & 0x07) == 0x5) {
michael@0 496 // [rip+disp32]
michael@0 497 // convert JMP 32bit offset to JMP 64bit direct
michael@0 498 directJmpAddr = (byteptr_t)*((uint64_t*)(origBytes + nBytes + 6 + (*((int32_t*)(origBytes + nBytes + 2)))));
michael@0 499 nBytes += 6;
michael@0 500 } else {
michael@0 501 // not support yet!
michael@0 502 return;
michael@0 503 }
michael@0 504 } else {
michael@0 505 // not support yet!
michael@0 506 return;
michael@0 507 }
michael@0 508 } else if ((origBytes[nBytes] & 0xf0) == 0x50) {
michael@0 509 // 1-byte push/pop
michael@0 510 nBytes++;
michael@0 511 } else if (origBytes[nBytes] == 0x90) {
michael@0 512 // nop
michael@0 513 nBytes++;
michael@0 514 } else if (origBytes[nBytes] == 0xb8) {
michael@0 515 // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
michael@0 516 nBytes += 5;
michael@0 517 } else if (origBytes[nBytes] == 0xc3) {
michael@0 518 // ret
michael@0 519 nBytes++;
michael@0 520 } else if (origBytes[nBytes] == 0xe9) {
michael@0 521 pJmp32 = nBytes;
michael@0 522 // convert JMP 32bit offset to JMP 64bit direct
michael@0 523 directJmpAddr = origBytes + pJmp32 + 5 + (*((int32_t*)(origBytes + pJmp32 + 1)));
michael@0 524 // jmp 32bit offset
michael@0 525 nBytes += 5;
michael@0 526 } else if (origBytes[nBytes] == 0xff) {
michael@0 527 nBytes++;
michael@0 528 if ((origBytes[nBytes] & 0xf8) == 0xf0) {
michael@0 529 // push r64
michael@0 530 nBytes++;
michael@0 531 } else {
michael@0 532 return;
michael@0 533 }
michael@0 534 } else {
michael@0 535 return;
michael@0 536 }
michael@0 537 }
michael@0 538 #else
michael@0 539 #error "Unknown processor type"
michael@0 540 #endif
michael@0 541
michael@0 542 if (nBytes > 100) {
michael@0 543 //printf ("Too big!");
michael@0 544 return;
michael@0 545 }
michael@0 546
michael@0 547 // We keep the address of the original function in the first bytes of
michael@0 548 // the trampoline buffer
michael@0 549 *((void **)tramp) = origFunction;
michael@0 550 tramp += sizeof(void *);
michael@0 551
michael@0 552 memcpy(tramp, origFunction, nBytes);
michael@0 553
michael@0 554 // OrigFunction+N, the target of the trampoline
michael@0 555 byteptr_t trampDest = origBytes + nBytes;
michael@0 556
michael@0 557 #if defined(_M_IX86)
michael@0 558 if (pJmp32 >= 0) {
michael@0 559 // Jump directly to the original target of the jump instead of jumping to the
michael@0 560 // original function.
michael@0 561 // Adjust jump target displacement to jump location in the trampoline.
michael@0 562 *((intptr_t*)(tramp+pJmp32+1)) += origBytes - tramp;
michael@0 563 } else {
michael@0 564 tramp[nBytes] = 0xE9; // jmp
michael@0 565 *((intptr_t*)(tramp+nBytes+1)) = (intptr_t)trampDest - (intptr_t)(tramp+nBytes+5); // target displacement
michael@0 566 }
michael@0 567 #elif defined(_M_X64)
michael@0 568 // If JMP32 opcode found, we don't insert to trampoline jump
michael@0 569 if (pJmp32 >= 0) {
michael@0 570 // mov r11, address
michael@0 571 tramp[pJmp32] = 0x49;
michael@0 572 tramp[pJmp32+1] = 0xbb;
michael@0 573 *((intptr_t*)(tramp+pJmp32+2)) = (intptr_t)directJmpAddr;
michael@0 574
michael@0 575 // jmp r11
michael@0 576 tramp[pJmp32+10] = 0x41;
michael@0 577 tramp[pJmp32+11] = 0xff;
michael@0 578 tramp[pJmp32+12] = 0xe3;
michael@0 579 } else {
michael@0 580 // mov r11, address
michael@0 581 tramp[nBytes] = 0x49;
michael@0 582 tramp[nBytes+1] = 0xbb;
michael@0 583 *((intptr_t*)(tramp+nBytes+2)) = (intptr_t)trampDest;
michael@0 584
michael@0 585 // jmp r11
michael@0 586 tramp[nBytes+10] = 0x41;
michael@0 587 tramp[nBytes+11] = 0xff;
michael@0 588 tramp[nBytes+12] = 0xe3;
michael@0 589 }
michael@0 590 #endif
michael@0 591
michael@0 592 // The trampoline is now valid.
michael@0 593 *outTramp = tramp;
michael@0 594
michael@0 595 // ensure we can modify the original code
michael@0 596 DWORD op;
michael@0 597 if (!VirtualProtectEx(GetCurrentProcess(), origFunction, nBytes, PAGE_EXECUTE_READWRITE, &op)) {
michael@0 598 //printf ("VirtualProtectEx failed! %d\n", GetLastError());
michael@0 599 return;
michael@0 600 }
michael@0 601
michael@0 602 #if defined(_M_IX86)
michael@0 603 // now modify the original bytes
michael@0 604 origBytes[0] = 0xE9; // jmp
michael@0 605 *((intptr_t*)(origBytes+1)) = dest - (intptr_t)(origBytes+5); // target displacement
michael@0 606 #elif defined(_M_X64)
michael@0 607 // mov r11, address
michael@0 608 origBytes[0] = 0x49;
michael@0 609 origBytes[1] = 0xbb;
michael@0 610
michael@0 611 *((intptr_t*)(origBytes+2)) = dest;
michael@0 612
michael@0 613 // jmp r11
michael@0 614 origBytes[10] = 0x41;
michael@0 615 origBytes[11] = 0xff;
michael@0 616 origBytes[12] = 0xe3;
michael@0 617 #endif
michael@0 618
michael@0 619 // restore protection; if this fails we can't really do anything about it
michael@0 620 VirtualProtectEx(GetCurrentProcess(), origFunction, nBytes, op, &op);
michael@0 621 }
michael@0 622
michael@0 623 byteptr_t FindTrampolineSpace()
michael@0 624 {
michael@0 625 if (mCurHooks >= mMaxHooks)
michael@0 626 return 0;
michael@0 627
michael@0 628 byteptr_t p = mHookPage + mCurHooks*kHookSize;
michael@0 629
michael@0 630 mCurHooks++;
michael@0 631
michael@0 632 return p;
michael@0 633 }
michael@0 634 };
michael@0 635
michael@0 636 } // namespace internal
michael@0 637
michael@0 638 class WindowsDllInterceptor
michael@0 639 {
michael@0 640 internal::WindowsDllNopSpacePatcher mNopSpacePatcher;
michael@0 641 internal::WindowsDllDetourPatcher mDetourPatcher;
michael@0 642
michael@0 643 const char *mModuleName;
michael@0 644 int mNHooks;
michael@0 645
michael@0 646 public:
michael@0 647 WindowsDllInterceptor()
michael@0 648 : mModuleName(nullptr)
michael@0 649 , mNHooks(0)
michael@0 650 {}
michael@0 651
michael@0 652 void Init(const char *moduleName, int nhooks = 0)
michael@0 653 {
michael@0 654 if (mModuleName) {
michael@0 655 return;
michael@0 656 }
michael@0 657
michael@0 658 mModuleName = moduleName;
michael@0 659 mNHooks = nhooks;
michael@0 660 mNopSpacePatcher.Init(moduleName);
michael@0 661
michael@0 662 // Lazily initialize mDetourPatcher, since it allocates memory and we might
michael@0 663 // not need it.
michael@0 664 }
michael@0 665
michael@0 666 void LockHooks()
michael@0 667 {
michael@0 668 if (mDetourPatcher.Initialized())
michael@0 669 mDetourPatcher.LockHooks();
michael@0 670 }
michael@0 671
michael@0 672 bool AddHook(const char *pname, intptr_t hookDest, void **origFunc)
michael@0 673 {
michael@0 674 // Use a nop space patch if possible, otherwise fall back to a detour.
michael@0 675 // This should be the preferred method for adding hooks.
michael@0 676
michael@0 677 if (!mModuleName) {
michael@0 678 return false;
michael@0 679 }
michael@0 680
michael@0 681 if (mNopSpacePatcher.AddHook(pname, hookDest, origFunc)) {
michael@0 682 return true;
michael@0 683 }
michael@0 684
michael@0 685 return AddDetour(pname, hookDest, origFunc);
michael@0 686 }
michael@0 687
michael@0 688 bool AddDetour(const char *pname, intptr_t hookDest, void **origFunc)
michael@0 689 {
michael@0 690 // Generally, code should not call this method directly. Use AddHook unless
michael@0 691 // there is a specific need to avoid nop space patches.
michael@0 692
michael@0 693 if (!mModuleName) {
michael@0 694 return false;
michael@0 695 }
michael@0 696
michael@0 697 if (!mDetourPatcher.Initialized()) {
michael@0 698 mDetourPatcher.Init(mModuleName, mNHooks);
michael@0 699 }
michael@0 700
michael@0 701 return mDetourPatcher.AddHook(pname, hookDest, origFunc);
michael@0 702 }
michael@0 703 };
michael@0 704
michael@0 705 } // namespace mozilla
michael@0 706
michael@0 707 #endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */

mercurial