1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
7 #define NS_WINDOWS_DLL_INTERCEPTOR_H_
12 * Simple function interception.
14 * We have two separate mechanisms for intercepting a function: We can use the
15 * built-in nop space, if it exists, or we can create a detour.
17 * Using the built-in nop space works as follows: On x86-32, DLL functions
18 * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
21 * When we detect a function with this prelude, we do the following:
23 * 1. Write a long jump to our interceptor function into the five bytes of NOPs
24 * before the function.
26 * 2. Write a short jump -5 into the two-byte nop at the beginning of the function.
28 * This mechanism is nice because it's thread-safe. It's even safe to do if
29 * another thread is currently running the function we're modifying!
31 * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
32 * but not the long jump, so re-intercepting the same function won't work,
33 * because its prelude won't match.
36 * Unfortunately nop space patching doesn't work on functions which don't have
37 * this magic prelude (and in particular, x86-64 never has the prelude). So
38 * when we can't use the built-in nop space, we fall back to using a detour,
39 * which works as follows:
41 * 1. Save first N bytes of OrigFunction to trampoline, where N is a
42 * number of bytes >= 5 that are instruction aligned.
44 * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
47 * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
48 * continue original program flow.
50 * 4. Hook function needs to call the trampoline during its execution,
51 * to invoke the original function (so address of trampoline is
54 * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
55 * patched again to jump directly to the trampoline instead of going through
56 * the hook function. As such, re-intercepting the same function won't work, as
57 * jump instructions are not supported.
59 * Note that this is not thread-safe. Sad day.
68 class WindowsDllNopSpacePatcher
70 typedef unsigned char* byteptr_t
;
73 // Dumb array for remembering the addresses of functions we've patched.
74 // (This should be nsTArray, but non-XPCOM code uses this class.)
75 static const size_t maxPatchedFns
= 128;
76 byteptr_t mPatchedFns
[maxPatchedFns
];
80 WindowsDllNopSpacePatcher()
85 ~WindowsDllNopSpacePatcher()
87 // Restore the mov edi, edi to the beginning of each function we patched.
89 for (int i
= 0; i
< mPatchedFnsLen
; i
++) {
90 byteptr_t fn
= mPatchedFns
[i
];
92 // Ensure we can write to the code.
94 if (!VirtualProtectEx(GetCurrentProcess(), fn
, 2, PAGE_EXECUTE_READWRITE
, &op
)) {
95 // printf("VirtualProtectEx failed! %d\n", GetLastError());
100 *((uint16_t*)fn
) = 0xff8b;
102 // Restore the old protection.
103 VirtualProtectEx(GetCurrentProcess(), fn
, 2, op
, &op
);
105 // I don't think this is actually necessary, but it can't hurt.
106 FlushInstructionCache(GetCurrentProcess(),
107 /* ignored */ nullptr,
112 void Init(const char* aModuleName
)
114 mModule
= LoadLibraryExA(aModuleName
, nullptr, 0);
116 //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
122 bool AddHook(const char* aName
, intptr_t aHookDest
, void** aOrigFunc
)
128 if (mPatchedFnsLen
== maxPatchedFns
) {
129 // printf ("No space for hook in mPatchedFns.\n");
133 byteptr_t fn
= reinterpret_cast<byteptr_t
>(GetProcAddress(mModule
, aName
));
135 //printf ("GetProcAddress failed\n");
139 fn
= ResolveRedirectedAddress(fn
);
141 // Ensure we can read and write starting at fn - 5 (for the long jmp we're
142 // going to write) and ending at fn + 2 (for the short jmp up to the long
145 if (!VirtualProtectEx(GetCurrentProcess(), fn
- 5, 7,
146 PAGE_EXECUTE_READWRITE
, &op
)) {
147 //printf ("VirtualProtectEx failed! %d\n", GetLastError());
151 bool rv
= WriteHook(fn
, aHookDest
, aOrigFunc
);
153 // Re-protect, and we're done.
154 VirtualProtectEx(GetCurrentProcess(), fn
- 5, 7, op
, &op
);
157 mPatchedFns
[mPatchedFnsLen
] = fn
;
164 bool WriteHook(byteptr_t aFn
, intptr_t aHookDest
, void** aOrigFunc
)
166 // Check that the 5 bytes before aFn are NOP's or INT 3's,
167 // and that the 2 bytes after aFn are mov(edi, edi).
169 // It's safe to read aFn[-5] because we set it to PAGE_EXECUTE_READWRITE
170 // before calling WriteHook.
172 for (int i
= -5; i
<= -1; i
++) {
173 if (aFn
[i
] != 0x90 && aFn
[i
] != 0xcc) { // nop or int 3
178 // mov edi, edi. Yes, there are two ways to encode the same thing:
180 // 0x89ff == mov r/m, r
181 // 0x8bff == mov r, r/m
183 // where "r" is register and "r/m" is register or memory. Windows seems to
184 // use 8bff; I include 89ff out of paranoia.
185 if ((aFn
[0] != 0x8b && aFn
[0] != 0x89) || aFn
[1] != 0xff) {
189 // Write a long jump into the space above the function.
190 aFn
[-5] = 0xe9; // jmp
191 *((intptr_t*)(aFn
- 4)) = aHookDest
- (uintptr_t)(aFn
); // target displacement
193 // Set aOrigFunc here, because after this point, aHookDest might be called,
194 // and aHookDest might use the aOrigFunc pointer.
195 *aOrigFunc
= aFn
+ 2;
197 // Short jump up into our long jump.
198 *((uint16_t*)(aFn
)) = 0xf9eb; // jmp $-5
200 // I think this routine is safe without this, but it can't hurt.
201 FlushInstructionCache(GetCurrentProcess(),
202 /* ignored */ nullptr,
209 static byteptr_t
ResolveRedirectedAddress(const byteptr_t aOriginalFunction
)
211 // If function entry is jmp [disp32] such as used by kernel32,
212 // we resolve redirected address from import table.
213 if (aOriginalFunction
[0] == 0xff && aOriginalFunction
[1] == 0x25) {
214 return (byteptr_t
)(**((uint32_t**) (aOriginalFunction
+ 2)));
217 return aOriginalFunction
;
220 bool AddHook(const char* aName
, intptr_t aHookDest
, void** aOrigFunc
)
222 // Not implemented except on x86-32.
228 class WindowsDllDetourPatcher
230 typedef unsigned char* byteptr_t
;
232 WindowsDllDetourPatcher()
233 : mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0)
237 ~WindowsDllDetourPatcher()
241 for (i
= 0, p
= mHookPage
; i
< mCurHooks
; i
++, p
+= kHookSize
) {
243 size_t nBytes
= 1 + sizeof(intptr_t);
244 #elif defined(_M_X64)
245 size_t nBytes
= 2 + sizeof(intptr_t);
247 #error "Unknown processor type"
249 byteptr_t origBytes
= (byteptr_t
)DecodePointer(*((byteptr_t
*)p
));
250 // ensure we can modify the original code
252 if (!VirtualProtectEx(GetCurrentProcess(), origBytes
, nBytes
,
253 PAGE_EXECUTE_READWRITE
, &op
)) {
256 // Remove the hook by making the original function jump directly
257 // in the trampoline.
258 intptr_t dest
= (intptr_t)(p
+ sizeof(void*));
260 // Ensure the JMP from CreateTrampoline is where we expect it to be.
261 if (origBytes
[0] != 0xE9)
263 *((intptr_t*)(origBytes
+ 1)) =
264 dest
- (intptr_t)(origBytes
+ 5); // target displacement
265 #elif defined(_M_X64)
266 // Ensure the MOV R11 from CreateTrampoline is where we expect it to be.
267 if (origBytes
[0] != 0x49 || origBytes
[1] != 0xBB)
269 *((intptr_t*)(origBytes
+ 2)) = dest
;
271 #error "Unknown processor type"
273 // restore protection; if this fails we can't really do anything about it
274 VirtualProtectEx(GetCurrentProcess(), origBytes
, nBytes
, op
, &op
);
278 void Init(const char* aModuleName
, int aNumHooks
= 0)
284 mModule
= LoadLibraryExA(aModuleName
, nullptr, 0);
286 //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
290 int hooksPerPage
= 4096 / kHookSize
;
291 if (aNumHooks
== 0) {
292 aNumHooks
= hooksPerPage
;
295 mMaxHooks
= aNumHooks
+ (hooksPerPage
% aNumHooks
);
297 mHookPage
= (byteptr_t
)VirtualAllocEx(GetCurrentProcess(), nullptr,
298 mMaxHooks
* kHookSize
,
299 MEM_COMMIT
| MEM_RESERVE
,
300 PAGE_EXECUTE_READWRITE
);
307 bool Initialized() { return !!mModule
; }
316 VirtualProtectEx(GetCurrentProcess(), mHookPage
, mMaxHooks
* kHookSize
,
317 PAGE_EXECUTE_READ
, &op
);
322 bool AddHook(const char* aName
, intptr_t aHookDest
, void** aOrigFunc
)
328 void* pAddr
= (void*)GetProcAddress(mModule
, aName
);
330 //printf ("GetProcAddress failed\n");
334 pAddr
= ResolveRedirectedAddress((byteptr_t
)pAddr
);
336 CreateTrampoline(pAddr
, aHookDest
, aOrigFunc
);
338 //printf ("CreateTrampoline failed\n");
346 const static int kPageSize
= 4096;
347 const static int kHookSize
= 128;
354 void CreateTrampoline(void* aOrigFunction
, intptr_t aDest
, void** aOutTramp
)
356 *aOutTramp
= nullptr;
358 byteptr_t tramp
= FindTrampolineSpace();
363 byteptr_t origBytes
= (byteptr_t
)aOrigFunction
;
370 // Understand some simple instructions that might be found in a
371 // prologue; we might need to extend this as necessary.
373 // Note! If we ever need to understand jump instructions, we'll
374 // need to rewrite the displacement argument.
375 if (origBytes
[nBytes
] >= 0x88 && origBytes
[nBytes
] <= 0x8B) {
377 unsigned char b
= origBytes
[nBytes
+ 1];
378 if (((b
& 0xc0) == 0xc0) ||
379 (((b
& 0xc0) == 0x00) &&
380 ((b
& 0x07) != 0x04) && ((b
& 0x07) != 0x05))) {
381 // REG=r, R/M=r or REG=r, R/M=[r]
383 } else if ((b
& 0xc0) == 0x40) {
384 if ((b
& 0x07) == 0x04) {
385 // REG=r, R/M=[SIB + disp8]
388 // REG=r, R/M=[r + disp8]
395 } else if (origBytes
[nBytes
] == 0xB8) {
396 // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
398 } else if (origBytes
[nBytes
] == 0x83) {
399 // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r/m, imm8
400 unsigned char b
= origBytes
[nBytes
+ 1];
401 if ((b
& 0xc0) == 0xc0) {
402 // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
408 } else if (origBytes
[nBytes
] == 0x68) {
409 // PUSH with 4-byte operand
411 } else if ((origBytes
[nBytes
] & 0xf0) == 0x50) {
414 } else if (origBytes
[nBytes
] == 0x6A) {
417 } else if (origBytes
[nBytes
] == 0xe9) {
421 } else if (origBytes
[nBytes
] == 0xff && origBytes
[nBytes
+ 1] == 0x25) {
425 //printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", origBytes[nBytes]);
429 #elif defined(_M_X64)
430 byteptr_t directJmpAddr
;
432 while (nBytes
< 13) {
434 // if found JMP 32bit offset, next bytes must be NOP
436 if (origBytes
[nBytes
++] != 0x90) {
442 if (origBytes
[nBytes
] == 0x0f) {
444 if (origBytes
[nBytes
] == 0x1f) {
447 if ((origBytes
[nBytes
] & 0xc0) == 0x40 &&
448 (origBytes
[nBytes
] & 0x7) == 0x04) {
453 } else if (origBytes
[nBytes
] == 0x05) {
459 } else if (origBytes
[nBytes
] == 0x40 ||
460 origBytes
[nBytes
] == 0x41) {
461 // Plain REX or REX.B
464 if ((origBytes
[nBytes
] & 0xf0) == 0x50) {
465 // push/pop with Rx register
467 } else if (origBytes
[nBytes
] >= 0xb8 && origBytes
[nBytes
] <= 0xbf) {
473 } else if (origBytes
[nBytes
] == 0x45) {
477 if (origBytes
[nBytes
] == 0x33) {
483 } else if ((origBytes
[nBytes
] & 0xfb) == 0x48) {
487 if (origBytes
[nBytes
] == 0x81 &&
488 (origBytes
[nBytes
+ 1] & 0xf8) == 0xe8) {
491 } else if (origBytes
[nBytes
] == 0x83 &&
492 (origBytes
[nBytes
+ 1] & 0xf8) == 0xe8) {
495 } else if (origBytes
[nBytes
] == 0x83 &&
496 (origBytes
[nBytes
+ 1] & 0xf8) == 0x60) {
499 } else if ((origBytes
[nBytes
] & 0xfd) == 0x89) {
500 // MOV r/m64, r64 | MOV r64, r/m64
501 if ((origBytes
[nBytes
+ 1] & 0xc0) == 0x40) {
502 if ((origBytes
[nBytes
+ 1] & 0x7) == 0x04) {
503 // R/M=[SIB+disp8], REG=r64
506 // R/M=[r64+disp8], REG=r64
509 } else if (((origBytes
[nBytes
+ 1] & 0xc0) == 0xc0) ||
510 (((origBytes
[nBytes
+ 1] & 0xc0) == 0x00) &&
511 ((origBytes
[nBytes
+ 1] & 0x07) != 0x04) &&
512 ((origBytes
[nBytes
+ 1] & 0x07) != 0x05))) {
513 // REG=r64, R/M=r64 or REG=r64, R/M=[r64]
519 } else if (origBytes
[nBytes
] == 0xc7) {
521 if (origBytes
[nBytes
+ 1] == 0x44) {
522 // MOV [r64+disp8], imm32
523 // ModR/W + SIB + disp8 + imm32
528 } else if (origBytes
[nBytes
] == 0xff) {
531 if ((origBytes
[nBytes
+ 1] & 0xc0) == 0x0 &&
532 (origBytes
[nBytes
+ 1] & 0x07) == 0x5) {
534 // convert JMP 32bit offset to JMP 64bit direct
536 (byteptr_t
)*((uint64_t*)(origBytes
+ nBytes
+ 6 +
537 (*((int32_t*)(origBytes
+ nBytes
+ 2)))));
547 } else if ((origBytes
[nBytes
] & 0xf0) == 0x50) {
550 } else if (origBytes
[nBytes
] == 0x90) {
553 } else if (origBytes
[nBytes
] == 0xb8) {
554 // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
556 } else if (origBytes
[nBytes
] == 0xc3) {
559 } else if (origBytes
[nBytes
] == 0xe9) {
561 // convert JMP 32bit offset to JMP 64bit direct
562 directJmpAddr
= origBytes
+ pJmp32
+ 5 + (*((int32_t*)(origBytes
+ pJmp32
+ 1)));
565 } else if (origBytes
[nBytes
] == 0xff) {
567 if ((origBytes
[nBytes
] & 0xf8) == 0xf0) {
578 #error "Unknown processor type"
582 //printf ("Too big!");
586 // We keep the address of the original function in the first bytes of
587 // the trampoline buffer
588 *((void**)tramp
) = EncodePointer(aOrigFunction
);
589 tramp
+= sizeof(void*);
591 memcpy(tramp
, aOrigFunction
, nBytes
);
593 // OrigFunction+N, the target of the trampoline
594 byteptr_t trampDest
= origBytes
+ nBytes
;
598 // Jump directly to the original target of the jump instead of jumping to the
599 // original function.
600 // Adjust jump target displacement to jump location in the trampoline.
601 *((intptr_t*)(tramp
+ pJmp32
+ 1)) += origBytes
- tramp
;
603 tramp
[nBytes
] = 0xE9; // jmp
604 *((intptr_t*)(tramp
+ nBytes
+ 1)) =
605 (intptr_t)trampDest
- (intptr_t)(tramp
+ nBytes
+ 5); // target displacement
607 #elif defined(_M_X64)
608 // If JMP32 opcode found, we don't insert to trampoline jump
611 tramp
[pJmp32
] = 0x49;
612 tramp
[pJmp32
+ 1] = 0xbb;
613 *((intptr_t*)(tramp
+ pJmp32
+ 2)) = (intptr_t)directJmpAddr
;
616 tramp
[pJmp32
+ 10] = 0x41;
617 tramp
[pJmp32
+ 11] = 0xff;
618 tramp
[pJmp32
+ 12] = 0xe3;
621 tramp
[nBytes
] = 0x49;
622 tramp
[nBytes
+ 1] = 0xbb;
623 *((intptr_t*)(tramp
+ nBytes
+ 2)) = (intptr_t)trampDest
;
626 tramp
[nBytes
+ 10] = 0x41;
627 tramp
[nBytes
+ 11] = 0xff;
628 tramp
[nBytes
+ 12] = 0xe3;
632 // The trampoline is now valid.
635 // ensure we can modify the original code
637 if (!VirtualProtectEx(GetCurrentProcess(), aOrigFunction
, nBytes
,
638 PAGE_EXECUTE_READWRITE
, &op
)) {
639 //printf ("VirtualProtectEx failed! %d\n", GetLastError());
644 // now modify the original bytes
645 origBytes
[0] = 0xE9; // jmp
646 *((intptr_t*)(origBytes
+ 1)) =
647 aDest
- (intptr_t)(origBytes
+ 5); // target displacement
648 #elif defined(_M_X64)
653 *((intptr_t*)(origBytes
+ 2)) = aDest
;
656 origBytes
[10] = 0x41;
657 origBytes
[11] = 0xff;
658 origBytes
[12] = 0xe3;
661 // restore protection; if this fails we can't really do anything about it
662 VirtualProtectEx(GetCurrentProcess(), aOrigFunction
, nBytes
, op
, &op
);
665 byteptr_t
FindTrampolineSpace()
667 if (mCurHooks
>= mMaxHooks
) {
671 byteptr_t p
= mHookPage
+ mCurHooks
* kHookSize
;
678 static void* ResolveRedirectedAddress(const byteptr_t aOriginalFunction
)
681 // If function entry is jmp [disp32] such as used by kernel32,
682 // we resolve redirected address from import table.
683 if (aOriginalFunction
[0] == 0xff && aOriginalFunction
[1] == 0x25) {
684 return (void*)(**((uint32_t**) (aOriginalFunction
+ 2)));
686 #elif defined(_M_X64)
687 if (aOriginalFunction
[0] == 0xe9) {
688 // require for TestDllInterceptor with --disable-optimize
689 int32_t offset
= *((int32_t*)(aOriginalFunction
+ 1));
690 return aOriginalFunction
+ 5 + offset
;
694 return aOriginalFunction
;
698 } // namespace internal
700 class WindowsDllInterceptor
702 internal::WindowsDllNopSpacePatcher mNopSpacePatcher
;
703 internal::WindowsDllDetourPatcher mDetourPatcher
;
705 const char* mModuleName
;
709 WindowsDllInterceptor()
710 : mModuleName(nullptr)
714 void Init(const char* aModuleName
, int aNumHooks
= 0)
720 mModuleName
= aModuleName
;
722 mNopSpacePatcher
.Init(aModuleName
);
724 // Lazily initialize mDetourPatcher, since it allocates memory and we might
730 if (mDetourPatcher
.Initialized()) {
731 mDetourPatcher
.LockHooks();
735 bool AddHook(const char* aName
, intptr_t aHookDest
, void** aOrigFunc
)
737 // Use a nop space patch if possible, otherwise fall back to a detour.
738 // This should be the preferred method for adding hooks.
744 if (mNopSpacePatcher
.AddHook(aName
, aHookDest
, aOrigFunc
)) {
748 return AddDetour(aName
, aHookDest
, aOrigFunc
);
751 bool AddDetour(const char* aName
, intptr_t aHookDest
, void** aOrigFunc
)
753 // Generally, code should not call this method directly. Use AddHook unless
754 // there is a specific need to avoid nop space patches.
760 if (!mDetourPatcher
.Initialized()) {
761 mDetourPatcher
.Init(mModuleName
, mNHooks
);
764 return mDetourPatcher
.AddHook(aName
, aHookDest
, aOrigFunc
);
768 } // namespace mozilla
770 #endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */