mirror of
https://github.com/intel/llvm.git
synced 2026-01-16 05:32:28 +08:00
[compiler-rt] Refactor the interception code on windows.
Summary:
This is a cleanup and refactoring of the interception code on windows
Enhancement:
* Adding the support for 64-bits code
* Adding several hooking technique:
* Detour
* JumpRedirect
* HotPatch
* Trampoline
* Adding a trampoline memory pool (64-bits) and release the allocated memory in unittests
Cleanup:
* Adding unittests for 64-bits hooking techniques
* Enhancing the RoundUpInstruction by sharing common decoder
Reviewers: rnk
Subscribers: llvm-commits, wang0109, chrisha
Differential Revision: http://reviews.llvm.org/D22111
llvm-svn: 275123
This commit is contained in:
@@ -10,16 +10,160 @@
|
||||
// This file is a part of AddressSanitizer, an address sanity checker.
|
||||
//
|
||||
// Windows-specific interception methods.
|
||||
//
|
||||
// This file is implementing several hooking techniques to intercept calls
|
||||
// to functions. The hooks are dynamically installed by modifying the assembly
|
||||
// code.
|
||||
//
|
||||
// The hooking techniques are making assumptions on the way the code is
|
||||
// generated and are safe under these assumptions.
|
||||
//
|
||||
// On 64-bit architecture, there is no direct 64-bit jump instruction. To allow
|
||||
// arbitrary branching on the whole memory space, the notion of trampoline
|
||||
// region is used. A trampoline region is a memory space withing 2G boundary
|
||||
// where it is safe to add custom assembly code to build 64-bit jumps.
|
||||
//
|
||||
// Hooking techniques
|
||||
// ==================
|
||||
//
|
||||
// 1) Detour
|
||||
//
|
||||
// The Detour hooking technique is assuming the presence of an header with
|
||||
// padding and an overridable 2-bytes nop instruction (mov edi, edi). The
|
||||
// nop instruction can safely be replaced by a 2-bytes jump without any need
|
||||
// to save the instruction. A jump to the target is encoded in the function
|
||||
// header and the nop instruction is replaced by a short jump to the header.
|
||||
//
|
||||
// head: 5 x nop head: jmp <hook>
|
||||
// func: mov edi, edi --> func: jmp short <head>
|
||||
// [...] real: [...]
|
||||
//
|
||||
// This technique is only implemented on 32-bit architecture.
|
||||
// Most of the time, Windows API are hookable with the detour technique.
|
||||
//
|
||||
// 2) Redirect Jump
|
||||
//
|
||||
// The redirect jump is applicable when the first instruction is a direct
|
||||
// jump. The instruction is replaced by jump to the hook.
|
||||
//
|
||||
// func: jmp <label> --> func: jmp <hook>
|
||||
//
|
||||
// On an 64-bit architecture, a trampoline is inserted.
|
||||
//
|
||||
// func: jmp <label> --> func: jmp <tramp>
|
||||
// [...]
|
||||
//
|
||||
// [trampoline]
|
||||
// tramp: jmp QWORD [addr]
|
||||
// addr: .bytes <hook>
|
||||
//
|
||||
// Note: <real> is equilavent to <label>.
|
||||
//
|
||||
// 3) HotPatch
|
||||
//
|
||||
// The HotPatch hooking is assuming the presence of an header with padding
|
||||
// and a first instruction with at least 2-bytes.
|
||||
//
|
||||
// The reason to enforce the 2-bytes limitation is to provide the minimal
|
||||
// space to encode a short jump. HotPatch technique is only rewriting one
|
||||
// instruction to avoid breaking a sequence of instructions containing a
|
||||
// branching target.
|
||||
//
|
||||
// Assumptions are enforced by MSVC compiler by using the /HOTPATCH flag.
|
||||
// see: https://msdn.microsoft.com/en-us/library/ms173507.aspx
|
||||
// Default padding length is 5 bytes in 32-bits and 6 bytes in 64-bits.
|
||||
//
|
||||
// head: 5 x nop head: jmp <hook>
|
||||
// func: <instr> --> func: jmp short <head>
|
||||
// [...] body: [...]
|
||||
//
|
||||
// [trampoline]
|
||||
// real: <instr>
|
||||
// jmp <body>
|
||||
//
|
||||
// On an 64-bit architecture:
|
||||
//
|
||||
// head: 6 x nop head: jmp QWORD [addr1]
|
||||
// func: <instr> --> func: jmp short <head>
|
||||
// [...] body: [...]
|
||||
//
|
||||
// [trampoline]
|
||||
// addr1: .bytes <hook>
|
||||
// real: <instr>
|
||||
// jmp QWORD [addr2]
|
||||
// addr2: .bytes <body>
|
||||
//
|
||||
// 4) Trampoline
|
||||
//
|
||||
// The Trampoline hooking technique is the most aggressive one. It is
|
||||
// assuming that there is a sequence of instructions that can be safely
|
||||
// replaced by a jump (enough room and no incoming branches).
|
||||
//
|
||||
// Unfortunately, these assumptions can't be safely presumed and code may
|
||||
// be broken after hooking.
|
||||
//
|
||||
// func: <instr> --> func: jmp <hook>
|
||||
// <instr>
|
||||
// [...] body: [...]
|
||||
//
|
||||
// [trampoline]
|
||||
// real: <instr>
|
||||
// <instr>
|
||||
// jmp <body>
|
||||
//
|
||||
// On an 64-bit architecture:
|
||||
//
|
||||
// func: <instr> --> func: jmp QWORD [addr1]
|
||||
// <instr>
|
||||
// [...] body: [...]
|
||||
//
|
||||
// [trampoline]
|
||||
// addr1: .bytes <hook>
|
||||
// real: <instr>
|
||||
// <instr>
|
||||
// jmp QWORD [addr2]
|
||||
// addr2: .bytes <body>
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "interception.h"
|
||||
#include "sanitizer_common/sanitizer_platform.h"
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
namespace __interception {
|
||||
|
||||
static const int kAddressLength = FIRST_32_SECOND_64(4, 8);
|
||||
static const int kJumpInstructionLength = 5;
|
||||
static const int kShortJumpInstructionLength = 2;
|
||||
static const int kIndirectJumpInstructionLength = 6;
|
||||
static const int kBranchLength =
|
||||
FIRST_32_SECOND_64(kJumpInstructionLength, kIndirectJumpInstructionLength);
|
||||
static const int kDirectBranchLength = kBranchLength + kAddressLength;
|
||||
|
||||
static void InterceptionFailed() {
|
||||
// Do we have a good way to abort with an error message here?
|
||||
__debugbreak();
|
||||
}
|
||||
|
||||
static bool DistanceIsWithin2Gig(uptr from, uptr target) {
|
||||
if (from < target)
|
||||
return target - from <= (uptr)0x7FFFFFFFU;
|
||||
else
|
||||
return from - target <= (uptr)0x80000000U;
|
||||
}
|
||||
|
||||
static uptr GetMmapGranularity() {
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwAllocationGranularity;
|
||||
}
|
||||
|
||||
static uptr RoundUpTo(uptr size, uptr boundary) {
|
||||
return (size + boundary - 1) & ~(boundary - 1);
|
||||
}
|
||||
|
||||
// FIXME: internal_str* and internal_mem* functions should be moved from the
|
||||
// ASan sources into interception/.
|
||||
|
||||
@@ -35,334 +179,553 @@ static void _memcpy(void *dst, void *src, size_t sz) {
|
||||
dst_c[i] = src_c[i];
|
||||
}
|
||||
|
||||
#if SANITIZER_WINDOWS64
|
||||
static void WriteIndirectJumpInstruction(char *jmp_from, uptr *indirect_target) { // NOLINT
|
||||
// jmp [rip + XXYYZZWW] = FF 25 WW ZZ YY XX, where
|
||||
// XXYYZZWW is an offset from jmp_from.
|
||||
// The displacement is still 32-bit in x64, so indirect_target must be located
|
||||
// within +/- 2GB range.
|
||||
int offset = (int)(indirect_target - (uptr *)jmp_from);
|
||||
jmp_from[0] = '\xFF';
|
||||
jmp_from[1] = '\x25';
|
||||
*(int*)(jmp_from + 2) = offset;
|
||||
static bool ChangeMemoryProtection(
|
||||
uptr address, uptr size, DWORD *old_protection) {
|
||||
return ::VirtualProtect((void*)address, size,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
old_protection) != FALSE;
|
||||
}
|
||||
#else
|
||||
static void WriteJumpInstruction(char *jmp_from, char *to) {
|
||||
// jmp XXYYZZWW = E9 WW ZZ YY XX, where XXYYZZWW is an offset from jmp_from
|
||||
// to the next instruction to the destination.
|
||||
ptrdiff_t offset = to - jmp_from - 5;
|
||||
*jmp_from = '\xE9';
|
||||
*(ptrdiff_t*)(jmp_from + 1) = offset;
|
||||
|
||||
static bool RestoreMemoryProtection(
|
||||
uptr address, uptr size, DWORD old_protection) {
|
||||
DWORD unused;
|
||||
return ::VirtualProtect((void*)address, size,
|
||||
old_protection,
|
||||
&unused) != FALSE;
|
||||
}
|
||||
|
||||
static bool IsMemoryPadding(uptr address, uptr size) {
|
||||
u8* function = (u8*)address;
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
if (function[i] != 0x90 && function[i] != 0xCC)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WritePadding(uptr from, uptr size) {
|
||||
_memset((void*)from, 0xCC, (size_t)size);
|
||||
}
|
||||
|
||||
static void CopyInstructions(uptr from, uptr to, uptr size) {
|
||||
_memcpy((void*)from, (void*)to, (size_t)size);
|
||||
}
|
||||
|
||||
static void WriteJumpInstruction(uptr from, uptr target) {
|
||||
if (!DistanceIsWithin2Gig(from + kJumpInstructionLength, target))
|
||||
InterceptionFailed();
|
||||
ptrdiff_t offset = target - from - kJumpInstructionLength;
|
||||
*(u8*)from = 0xE9;
|
||||
*(u32*)(from + 1) = offset;
|
||||
}
|
||||
|
||||
static void WriteShortJumpInstruction(uptr from, uptr target) {
|
||||
sptr offset = target - from - kShortJumpInstructionLength;
|
||||
if (offset < -128 || offset > 127)
|
||||
InterceptionFailed();
|
||||
*(u8*)from = 0xEB;
|
||||
*(u8*)(from + 1) = (u8)offset;
|
||||
}
|
||||
|
||||
#if SANITIZER_WINDOWS64
|
||||
static void WriteIndirectJumpInstruction(uptr from, uptr indirect_target) {
|
||||
// jmp [rip + <offset>] = FF 25 <offset> where <offset> is a relative
|
||||
// offset.
|
||||
// The offset is the distance from then end of the jump instruction to the
|
||||
// memory location containing the targeted address. The displacement is still
|
||||
// 32-bit in x64, so indirect_target must be located within +/- 2GB range.
|
||||
int offset = indirect_target - from - kIndirectJumpInstructionLength;
|
||||
if (!DistanceIsWithin2Gig(from + kIndirectJumpInstructionLength,
|
||||
indirect_target)) {
|
||||
InterceptionFailed();
|
||||
}
|
||||
*(u16*)from = 0x25FF;
|
||||
*(u32*)(from + 2) = offset;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void WriteTrampolineJumpInstruction(char *jmp_from, char *to) {
|
||||
static void WriteBranch(
|
||||
uptr from, uptr indirect_target, uptr target) {
|
||||
#if SANITIZER_WINDOWS64
|
||||
WriteIndirectJumpInstruction(from, indirect_target);
|
||||
*(u64*)indirect_target = target;
|
||||
#else
|
||||
(void)indirect_target;
|
||||
WriteJumpInstruction(from, target);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void WriteDirectBranch(uptr from, uptr target) {
|
||||
#if SANITIZER_WINDOWS64
|
||||
// Emit an indirect jump through immediately following bytes:
|
||||
// jmp_from:
|
||||
// jmp [rip + 6]
|
||||
// .quad to
|
||||
// Store the address.
|
||||
uptr *indirect_target = (uptr *)(jmp_from + 6);
|
||||
*indirect_target = (uptr)to;
|
||||
// Write the indirect jump.
|
||||
WriteIndirectJumpInstruction(jmp_from, indirect_target);
|
||||
// jmp [rip + kBranchLength]
|
||||
// .quad <target>
|
||||
WriteBranch(from, from + kBranchLength, target);
|
||||
#else
|
||||
WriteJumpInstruction(jmp_from, to);
|
||||
WriteJumpInstruction(from, target);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void WriteInterceptorJumpInstruction(char *jmp_from, char *to) {
|
||||
struct TrampolineMemoryRegion {
|
||||
uptr content;
|
||||
uptr allocated_size;
|
||||
uptr max_size;
|
||||
};
|
||||
|
||||
static const uptr kTrampolineScanLimitRange = 1 << 30; // 1 gig
|
||||
static const int kMaxTrampolineRegion = 1024;
|
||||
static TrampolineMemoryRegion TrampolineRegions[kMaxTrampolineRegion];
|
||||
|
||||
static void *AllocateTrampolineRegion(uptr image_address, size_t granularity) {
|
||||
#if SANITIZER_WINDOWS64
|
||||
// Emit an indirect jump through immediately following bytes:
|
||||
// jmp_from:
|
||||
// jmp [rip - 8]
|
||||
// .quad to
|
||||
// Store the address.
|
||||
uptr *indirect_target = (uptr *)(jmp_from - 8);
|
||||
*indirect_target = (uptr)to;
|
||||
// Write the indirect jump.
|
||||
WriteIndirectJumpInstruction(jmp_from, indirect_target);
|
||||
uptr address = image_address;
|
||||
uptr scanned = 0;
|
||||
while (scanned < kTrampolineScanLimitRange) {
|
||||
MEMORY_BASIC_INFORMATION info;
|
||||
if (!::VirtualQuery((void*)address, &info, sizeof(info)))
|
||||
return nullptr;
|
||||
|
||||
// Check whether a region can be allocated at |address|.
|
||||
if (info.State == MEM_FREE && info.RegionSize >= granularity) {
|
||||
void *page = ::VirtualAlloc((void*)RoundUpTo(address, granularity),
|
||||
granularity,
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_EXECUTE_READWRITE);
|
||||
return page;
|
||||
}
|
||||
|
||||
// Move to the next region.
|
||||
address = (uptr)info.BaseAddress + info.RegionSize;
|
||||
scanned += info.RegionSize;
|
||||
}
|
||||
return nullptr;
|
||||
#else
|
||||
WriteJumpInstruction(jmp_from, to);
|
||||
return ::VirtualAlloc(nullptr,
|
||||
granularity,
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_EXECUTE_READWRITE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static char *GetMemoryForTrampoline(size_t size) {
|
||||
// Trampolines are allocated from a common pool.
|
||||
const int POOL_SIZE = 1024;
|
||||
static char *pool = NULL;
|
||||
static size_t pool_used = 0;
|
||||
if (!pool) {
|
||||
pool = (char *)VirtualAlloc(NULL, POOL_SIZE, MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_EXECUTE_READWRITE);
|
||||
// FIXME: Might want to apply PAGE_EXECUTE_READ access after all the
|
||||
// interceptors are in place.
|
||||
if (!pool)
|
||||
return NULL;
|
||||
_memset(pool, 0xCC /* int 3 */, POOL_SIZE);
|
||||
// Used by unittests to release mapped memory space.
|
||||
void TestOnlyReleaseTrampolineRegions() {
|
||||
for (size_t bucket = 0; bucket < kMaxTrampolineRegion; ++bucket) {
|
||||
TrampolineMemoryRegion *current = &TrampolineRegions[bucket];
|
||||
if (current->content == 0)
|
||||
return;
|
||||
::VirtualFree((void*)current->content, 0, MEM_RELEASE);
|
||||
current->content = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uptr AllocateMemoryForTrampoline(uptr image_address, size_t size) {
|
||||
// Find a region within 2G with enough space to allocate |size| bytes.
|
||||
TrampolineMemoryRegion *region = nullptr;
|
||||
for (size_t bucket = 0; bucket < kMaxTrampolineRegion; ++bucket) {
|
||||
TrampolineMemoryRegion* current = &TrampolineRegions[bucket];
|
||||
if (current->content == 0) {
|
||||
// No valid region found, allocate a new region.
|
||||
size_t bucket_size = GetMmapGranularity();
|
||||
void *content = AllocateTrampolineRegion(image_address, bucket_size);
|
||||
if (content == nullptr)
|
||||
return 0U;
|
||||
|
||||
current->content = (uptr)content;
|
||||
current->allocated_size = 0;
|
||||
current->max_size = bucket_size;
|
||||
region = current;
|
||||
break;
|
||||
} else if (current->max_size - current->allocated_size > size) {
|
||||
#if SANITIZER_WINDOWS64
|
||||
// In 64-bits, the memory space must be allocated within 2G boundary.
|
||||
uptr next_address = current->content + current->allocated_size;
|
||||
if (next_address < image_address ||
|
||||
next_address - image_address >= 0x7FFFF0000)
|
||||
continue;
|
||||
#endif
|
||||
// The space can be allocated in the current region.
|
||||
region = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pool_used + size > POOL_SIZE)
|
||||
return NULL;
|
||||
// Failed to find a region.
|
||||
if (region == nullptr)
|
||||
return 0U;
|
||||
|
||||
char *ret = pool + pool_used;
|
||||
pool_used += size;
|
||||
return ret;
|
||||
// Allocate the space in the current region.
|
||||
uptr allocated_space = region->content + region->allocated_size;
|
||||
region->allocated_size += size;
|
||||
WritePadding(allocated_space, size);
|
||||
|
||||
return allocated_space;
|
||||
}
|
||||
|
||||
// Returns 0 on error.
|
||||
static size_t RoundUpToInstrBoundary(size_t size, char *code) {
|
||||
static size_t GetInstructionSize(uptr address) {
|
||||
switch (*(u8*)address) {
|
||||
case 0x90: // 90 : nop
|
||||
return 1;
|
||||
|
||||
case 0x50: // push eax / rax
|
||||
case 0x51: // push ecx / rcx
|
||||
case 0x52: // push edx / rdx
|
||||
case 0x53: // push ebx / rbx
|
||||
case 0x54: // push esp / rsp
|
||||
case 0x55: // push ebp / rbp
|
||||
case 0x56: // push esi / rsi
|
||||
case 0x57: // push edi / rdi
|
||||
case 0x5D: // pop ebp / rbp
|
||||
return 1;
|
||||
|
||||
case 0x6A: // 6A XX = push XX
|
||||
return 2;
|
||||
|
||||
case 0xb8: // b8 XX XX XX XX : mov eax, XX XX XX XX
|
||||
case 0xB9: // b9 XX XX XX XX : mov ecx, XX XX XX XX
|
||||
case 0xA1: // A1 XX XX XX XX : mov eax, dword ptr ds:[XXXXXXXX]
|
||||
return 5;
|
||||
|
||||
// Cannot overwrite control-instruction. Return 0 to indicate failure.
|
||||
case 0xE9: // E9 XX XX XX XX : jmp <label>
|
||||
case 0xE8: // E8 XX XX XX XX : call <func>
|
||||
case 0xC3: // C3 : ret
|
||||
case 0xEB: // EB XX : jmp XX (short jump)
|
||||
case 0x70: // 7Y YY : jy XX (short conditional jump)
|
||||
case 0x71:
|
||||
case 0x72:
|
||||
case 0x73:
|
||||
case 0x74:
|
||||
case 0x75:
|
||||
case 0x76:
|
||||
case 0x77:
|
||||
case 0x78:
|
||||
case 0x79:
|
||||
case 0x7A:
|
||||
case 0x7B:
|
||||
case 0x7C:
|
||||
case 0x7D:
|
||||
case 0x7E:
|
||||
case 0x7F:
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (*(u16*)(address)) {
|
||||
case 0xFF8B: // 8B FF : mov edi, edi
|
||||
case 0xEC8B: // 8B EC : mov ebp, esp
|
||||
case 0xc889: // 89 C8 : mov eax, ecx
|
||||
case 0xC18B: // 8B C1 : mov eax, ecx
|
||||
case 0xC033: // 33 C0 : xor eax, eax
|
||||
case 0xC933: // 33 C9 : xor ecx, ecx
|
||||
case 0xD233: // 33 D2 : xor edx, edx
|
||||
return 2;
|
||||
|
||||
// Cannot overwrite control-instruction. Return 0 to indicate failure.
|
||||
case 0x25FF: // FF 25 XX XX XX XX : jmp [XXXXXXXX]
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if SANITIZER_WINDOWS64
|
||||
// Win64 RoundUpToInstrBoundary is a work in progress.
|
||||
size_t cursor = 0;
|
||||
while (cursor < size) {
|
||||
switch (code[cursor]) {
|
||||
case '\x57': // 57 : push rdi
|
||||
cursor++;
|
||||
continue;
|
||||
case '\x90': // 90 : nop
|
||||
cursor++;
|
||||
continue;
|
||||
case '\xb8': // b8 XX XX XX XX : mov eax, XX XX XX XX
|
||||
cursor += 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (*(u16*)(code + cursor)) { // NOLINT
|
||||
case 0x5540: // 40 55 : rex push rbp
|
||||
case 0x5340: // 40 53 : rex push rbx
|
||||
cursor += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (0x00FFFFFF & *(u32*)(code + cursor)) {
|
||||
case 0xc18b48: // 48 8b c1 : mov rax, rcx
|
||||
case 0xc48b48: // 48 8b c4 : mov rax, rsp
|
||||
case 0xd9f748: // 48 f7 d9 : neg rcx
|
||||
case 0xd12b48: // 48 2b d1 : sub rdx, rcx
|
||||
case 0x07c1f6: // f6 c1 07 : test cl, 0x7
|
||||
case 0xc0854d: // 4d 85 c0 : test r8, r8
|
||||
case 0xc2b60f: // 0f b6 c2 : movzx eax, dl
|
||||
case 0xc03345: // 45 33 c0 : xor r8d, r8d
|
||||
case 0xd98b4c: // 4c 8b d9 : mov r11, rcx
|
||||
case 0xd28b4c: // 4c 8b d2 : mov r10, rdx
|
||||
case 0xd2b60f: // 0f b6 d2 : movzx edx, dl
|
||||
case 0xca2b48: // 48 2b ca : sub rcx, rdx
|
||||
case 0x10b70f: // 0f b7 10 : movzx edx, WORD PTR [rax]
|
||||
case 0xc00b4d: // 3d 0b c0 : or r8, r8
|
||||
case 0xd18b48: // 48 8b d1 : mov rdx, rcx
|
||||
case 0xdc8b4c: // 4c 8b dc : mov r11,rsp
|
||||
case 0xd18b4c: // 4c 8b d1 : mov r10, rcx
|
||||
cursor += 3;
|
||||
continue;
|
||||
|
||||
case 0xec8348: // 48 83 ec XX : sub rsp, 0xXX
|
||||
case 0xf88349: // 49 83 f8 XX : cmp r8, XX
|
||||
case 0x588948: // 48 89 58 XX : mov QWORD PTR[rax + XX], rbx
|
||||
cursor += 4;
|
||||
continue;
|
||||
|
||||
case 0x058b48: // 48 8b 05 XX XX XX XX
|
||||
// = mov rax, QWORD PTR [rip+ 0xXXXXXXXX]
|
||||
case 0x25ff48: // 48 ff 25 XX XX XX XX
|
||||
// = rex.W jmp QWORD PTR [rip + 0xXXXXXXXX]
|
||||
cursor += 7;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (*(u32*)(code + cursor)) {
|
||||
case 0x24448b48: // 48 8b 44 24 XX : mov rax, qword ptr [rsp + 0xXX]
|
||||
cursor += 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check first 5 bytes.
|
||||
switch (0xFFFFFFFFFFull & *(u64*)(code + cursor)) {
|
||||
case 0x08245c8948: // 48 89 5c 24 08 : mov QWORD PTR [rsp+0x8], rbx
|
||||
case 0x1024748948: // 48 89 74 24 10 : mov QWORD PTR [rsp+0x10], rsi
|
||||
cursor += 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check 8 bytes.
|
||||
switch (*(u64*)(code + cursor)) {
|
||||
case 0x90909090909006EBull: // JMP +6, 6x NOP
|
||||
cursor += 8;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unknown instructions!
|
||||
__debugbreak();
|
||||
switch (*(u16*)address) {
|
||||
case 0x5040: // push rax
|
||||
case 0x5140: // push rcx
|
||||
case 0x5240: // push rdx
|
||||
case 0x5340: // push rbx
|
||||
case 0x5440: // push rsp
|
||||
case 0x5540: // push rbp
|
||||
case 0x5640: // push rsi
|
||||
case 0x5740: // push rdi
|
||||
case 0x5441: // push r12
|
||||
case 0x5541: // push r13
|
||||
case 0x5641: // push r14
|
||||
case 0x5741: // push r15
|
||||
return 2;
|
||||
}
|
||||
|
||||
switch (0x00FFFFFF & *(u32*)address) {
|
||||
case 0xe58948: // 48 8b c4 : mov rbp, rsp
|
||||
case 0xc18b48: // 48 8b c1 : mov rax, rcx
|
||||
case 0xc48b48: // 48 8b c4 : mov rax, rsp
|
||||
case 0xd9f748: // 48 f7 d9 : neg rcx
|
||||
case 0xd12b48: // 48 2b d1 : sub rdx, rcx
|
||||
case 0x07c1f6: // f6 c1 07 : test cl, 0x7
|
||||
case 0xc0854d: // 4d 85 c0 : test r8, r8
|
||||
case 0xc2b60f: // 0f b6 c2 : movzx eax, dl
|
||||
case 0xc03345: // 45 33 c0 : xor r8d, r8d
|
||||
case 0xd98b4c: // 4c 8b d9 : mov r11, rcx
|
||||
case 0xd28b4c: // 4c 8b d2 : mov r10, rdx
|
||||
case 0xd2b60f: // 0f b6 d2 : movzx edx, dl
|
||||
case 0xca2b48: // 48 2b ca : sub rcx, rdx
|
||||
case 0x10b70f: // 0f b7 10 : movzx edx, WORD PTR [rax]
|
||||
case 0xc00b4d: // 3d 0b c0 : or r8, r8
|
||||
case 0xd18b48: // 48 8b d1 : mov rdx, rcx
|
||||
case 0xdc8b4c: // 4c 8b dc : mov r11,rsp
|
||||
case 0xd18b4c: // 4c 8b d1 : mov r10, rcx
|
||||
return 3;
|
||||
|
||||
case 0xec8348: // 48 83 ec XX : sub rsp, XX
|
||||
case 0xf88349: // 49 83 f8 XX : cmp r8, XX
|
||||
case 0x588948: // 48 89 58 XX : mov QWORD PTR[rax + XX], rbx
|
||||
return 4;
|
||||
|
||||
case 0x058b48: // 48 8b 05 XX XX XX XX :
|
||||
// mov rax, QWORD PTR [rip + XXXXXXXX]
|
||||
case 0x25ff48: // 48 ff 25 XX XX XX XX :
|
||||
// rex.W jmp QWORD PTR [rip + XXXXXXXX]
|
||||
return 7;
|
||||
}
|
||||
|
||||
switch (*(u32*)(address)) {
|
||||
case 0x24448b48: // 48 8b 44 24 XX : mov rax, qword ptr [rsp + XX]
|
||||
case 0x245c8948: // 48 89 5c 24 XX : mov QWORD PTR [rsp + XX], rbx
|
||||
case 0x24748948: // 48 89 74 24 XX : mov QWORD PTR [rsp + XX], rsi
|
||||
return 5;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
#else
|
||||
size_t cursor = 0;
|
||||
while (cursor < size) {
|
||||
switch (code[cursor]) {
|
||||
case '\xE8': // E8 XX XX XX XX = call <func>
|
||||
case '\xE9': // E9 XX XX XX XX = jmp <label>
|
||||
case '\xC3': // C3 = ret
|
||||
case '\xEB': // EB XX = jmp XX (short jump)
|
||||
case '\x70': // 7X YY = jx XX (short conditional jump)
|
||||
case '\x71':
|
||||
case '\x72':
|
||||
case '\x73':
|
||||
case '\x74':
|
||||
case '\x75':
|
||||
case '\x76':
|
||||
case '\x77':
|
||||
case '\x78':
|
||||
case '\x79':
|
||||
case '\x7A':
|
||||
case '\x7B':
|
||||
case '\x7C':
|
||||
case '\x7D':
|
||||
case '\x7E':
|
||||
case '\x7F':
|
||||
return 0;
|
||||
|
||||
case '\x50': // push eax
|
||||
case '\x51': // push ecx
|
||||
case '\x52': // push edx
|
||||
case '\x53': // push ebx
|
||||
case '\x54': // push esp
|
||||
case '\x55': // push ebp
|
||||
case '\x56': // push esi
|
||||
case '\x57': // push edi
|
||||
case '\x5D': // pop ebp
|
||||
cursor++;
|
||||
continue;
|
||||
case '\x6A': // 6A XX = push XX
|
||||
cursor += 2;
|
||||
continue;
|
||||
case '\xB8': // B8 XX YY ZZ WW = mov eax, WWZZYYXX
|
||||
cursor += 5;
|
||||
continue;
|
||||
}
|
||||
switch (*(u16*)(code + cursor)) { // NOLINT
|
||||
case 0xFF8B: // 8B FF = mov edi, edi
|
||||
case 0xEC8B: // 8B EC = mov ebp, esp
|
||||
case 0xC033: // 33 C0 = xor eax, eax
|
||||
case 0xC933: // 33 C9 = xor ecx, ecx
|
||||
cursor += 2;
|
||||
continue;
|
||||
case 0x458B: // 8B 45 XX = mov eax, dword ptr [ebp+XXh]
|
||||
case 0x5D8B: // 8B 5D XX = mov ebx, dword ptr [ebp+XXh]
|
||||
case 0x7D8B: // 8B 7D XX = mov edi, dword ptr [ebp+XXh]
|
||||
case 0xEC83: // 83 EC XX = sub esp, XX
|
||||
case 0x75FF: // FF 75 XX = push dword ptr [ebp+XXh]
|
||||
cursor += 3;
|
||||
continue;
|
||||
case 0xC1F7: // F7 C1 XX YY ZZ WW = test ecx, WWZZYYXX
|
||||
case 0x25FF: // FF 25 XX YY ZZ WW = jmp dword ptr ds:[WWZZYYXX]
|
||||
cursor += 6;
|
||||
continue;
|
||||
case 0x3D83: // 83 3D XX YY ZZ WW TT = cmp TT, WWZZYYXX
|
||||
cursor += 7;
|
||||
continue;
|
||||
case 0x7D83: // 83 7D XX YY = cmp dword ptr [ebp+XXh], YY
|
||||
cursor += 4;
|
||||
continue;
|
||||
}
|
||||
switch (0x00FFFFFF & *(u32*)(code + cursor)) {
|
||||
case 0x24448A: // 8A 44 24 XX = mov eal, dword ptr [esp+XXh]
|
||||
case 0x24448B: // 8B 44 24 XX = mov eax, dword ptr [esp+XXh]
|
||||
case 0x244C8B: // 8B 4C 24 XX = mov ecx, dword ptr [esp+XXh]
|
||||
case 0x24548B: // 8B 54 24 XX = mov edx, dword ptr [esp+XXh]
|
||||
case 0x24748B: // 8B 74 24 XX = mov esi, dword ptr [esp+XXh]
|
||||
case 0x247C8B: // 8B 7C 24 XX = mov edi, dword ptr [esp+XXh]
|
||||
cursor += 4;
|
||||
continue;
|
||||
}
|
||||
switch (*(u32*)(code + cursor)) {
|
||||
case 0x2444B60F: // 0F B6 44 24 XX = movzx eax, byte ptr [esp+XXh]
|
||||
cursor += 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unknown instruction!
|
||||
// FIXME: Unknown instruction failures might happen when we add a new
|
||||
// interceptor or a new compiler version. In either case, they should result
|
||||
// in visible and readable error messages. However, merely calling abort()
|
||||
// leads to an infinite recursion in CheckFailed.
|
||||
// Do we have a good way to abort with an error message here?
|
||||
__debugbreak();
|
||||
return 0;
|
||||
switch (*(u16*)address) {
|
||||
case 0x458B: // 8B 45 XX : mov eax, dword ptr [ebp + XX]
|
||||
case 0x5D8B: // 8B 5D XX : mov ebx, dword ptr [ebp + XX]
|
||||
case 0x7D8B: // 8B 7D XX : mov edi, dword ptr [ebp + XX]
|
||||
case 0xEC83: // 83 EC XX : sub esp, XX
|
||||
case 0x75FF: // FF 75 XX : push dword ptr [ebp + XX]
|
||||
return 3;
|
||||
case 0xC1F7: // F7 C1 XX YY ZZ WW : test ecx, WWZZYYXX
|
||||
case 0x25FF: // FF 25 XX YY ZZ WW : jmp dword ptr ds:[WWZZYYXX]
|
||||
return 6;
|
||||
case 0x3D83: // 83 3D XX YY ZZ WW TT : cmp TT, WWZZYYXX
|
||||
return 7;
|
||||
case 0x7D83: // 83 7D XX YY : cmp dword ptr [ebp + XX], YY
|
||||
return 4;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
switch (0x00FFFFFF & *(u32*)address) {
|
||||
case 0x24448A: // 8A 44 24 XX : mov eal, dword ptr [esp + XX]
|
||||
case 0x24448B: // 8B 44 24 XX : mov eax, dword ptr [esp + XX]
|
||||
case 0x244C8B: // 8B 4C 24 XX : mov ecx, dword ptr [esp + XX]
|
||||
case 0x24548B: // 8B 54 24 XX : mov edx, dword ptr [esp + XX]
|
||||
case 0x24748B: // 8B 74 24 XX : mov esi, dword ptr [esp + XX]
|
||||
case 0x247C8B: // 8B 7C 24 XX : mov edi, dword ptr [esp + XX]
|
||||
return 4;
|
||||
}
|
||||
|
||||
switch (*(u32*)address) {
|
||||
case 0x2444B60F: // 0F B6 44 24 XX : movzx eax, byte ptr [esp + XX]
|
||||
return 5;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Unknown instruction!
|
||||
// FIXME: Unknown instruction failures might happen when we add a new
|
||||
// interceptor or a new compiler version. In either case, they should result
|
||||
// in visible and readable error messages. However, merely calling abort()
|
||||
// leads to an infinite recursion in CheckFailed.
|
||||
InterceptionFailed();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool OverrideFunction(uptr old_func, uptr new_func, uptr *orig_old_func) {
|
||||
// Function overriding works basically like this:
|
||||
// On Win32, We write "jmp <new_func>" (5 bytes) at the beginning of
|
||||
// the 'old_func' to override it.
|
||||
// On Win64, We write "jmp [rip -8]" (6 bytes) at the beginning of
|
||||
// the 'old_func' to override it, and use 8 bytes of data to store
|
||||
// the full 64-bit address for new_func.
|
||||
// We might want to be able to execute the original 'old_func' from the
|
||||
// wrapper, in this case we need to keep the leading 5+ (6+ on Win64)
|
||||
// bytes ('head') of the original code somewhere with a "jmp <old_func+head>".
|
||||
// We call these 'head'+5/6 bytes of instructions a "trampoline".
|
||||
char *old_bytes = (char *)old_func;
|
||||
// Returns 0 on error.
|
||||
static size_t RoundUpToInstrBoundary(size_t size, uptr address) {
|
||||
size_t cursor = 0;
|
||||
while (cursor < size) {
|
||||
size_t instruction_size = GetInstructionSize(address + cursor);
|
||||
if (!instruction_size)
|
||||
return 0;
|
||||
cursor += instruction_size;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
#if !SANITIZER_WINDOWS64
|
||||
bool OverrideFunctionWithDetour(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func) {
|
||||
const int kDetourHeaderLen = 5;
|
||||
const u16 kDetourInstruction = 0xFF8B;
|
||||
|
||||
uptr header = (uptr)old_func - kDetourHeaderLen;
|
||||
uptr patch_length = kDetourHeaderLen + kShortJumpInstructionLength;
|
||||
|
||||
// Validate that the function is hookable.
|
||||
if (*(u16*)old_func != kDetourInstruction ||
|
||||
!IsMemoryPadding(header, kDetourHeaderLen))
|
||||
return false;
|
||||
|
||||
// Change memory protection to writable.
|
||||
DWORD protection = 0;
|
||||
if (!ChangeMemoryProtection(header, patch_length, &protection))
|
||||
return false;
|
||||
|
||||
// Write a relative jump to the redirected function.
|
||||
WriteJumpInstruction(header, new_func);
|
||||
|
||||
// Write the short jump to the function prefix.
|
||||
WriteShortJumpInstruction(old_func, header);
|
||||
|
||||
// Restore previous memory protection.
|
||||
if (!RestoreMemoryProtection(header, patch_length, protection))
|
||||
return false;
|
||||
|
||||
if (orig_old_func)
|
||||
*orig_old_func = old_func + kShortJumpInstructionLength;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool OverrideFunctionWithRedirectJump(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func) {
|
||||
// Check whether the first instruction is a relative jump.
|
||||
if (*(u8*)old_func != 0xE9)
|
||||
return false;
|
||||
|
||||
if (orig_old_func) {
|
||||
uptr relative_offset = *(u32*)(old_func + 1);
|
||||
uptr absolute_target = old_func + relative_offset + kJumpInstructionLength;
|
||||
*orig_old_func = absolute_target;
|
||||
}
|
||||
|
||||
#if SANITIZER_WINDOWS64
|
||||
size_t kHeadMin = 6; // The minimum size of the head to contain the 'jmp'.
|
||||
size_t kTrampolineJumpSize = 14; // The total bytes used at the end of
|
||||
// trampoline for jumping back to the
|
||||
// remains of original function.
|
||||
size_t kExtraPrevBytes = 8; // The extra bytes we need to mark READWRITE for
|
||||
// page access, that is preceeding the begin
|
||||
// of function.
|
||||
#else
|
||||
size_t kHeadMin = 5;
|
||||
size_t kTrampolineJumpSize = 5;
|
||||
size_t kExtraPrevBytes = 0;
|
||||
// If needed, get memory space for a trampoline jump.
|
||||
uptr trampoline = AllocateMemoryForTrampoline(old_func, kDirectBranchLength);
|
||||
if (!trampoline)
|
||||
return false;
|
||||
WriteDirectBranch(trampoline, new_func);
|
||||
#endif
|
||||
size_t head = kHeadMin;
|
||||
|
||||
// Change memory protection to writable.
|
||||
DWORD protection = 0;
|
||||
if (!ChangeMemoryProtection(old_func, kJumpInstructionLength, &protection))
|
||||
return false;
|
||||
|
||||
// Write a relative jump to the redirected function.
|
||||
WriteJumpInstruction(old_func, FIRST_32_SECOND_64(new_func, trampoline));
|
||||
|
||||
// Restore previous memory protection.
|
||||
if (!RestoreMemoryProtection(old_func, kJumpInstructionLength, protection))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OverrideFunctionWithHotPatch(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func) {
|
||||
const int kHotPatchHeaderLen = kBranchLength;
|
||||
|
||||
uptr header = (uptr)old_func - kHotPatchHeaderLen;
|
||||
uptr patch_length = kHotPatchHeaderLen + kShortJumpInstructionLength;
|
||||
|
||||
// Validate that the function is hot patchable.
|
||||
size_t instruction_size = GetInstructionSize(old_func);
|
||||
if (instruction_size < kShortJumpInstructionLength ||
|
||||
!IsMemoryPadding(header, kHotPatchHeaderLen))
|
||||
return false;
|
||||
|
||||
if (orig_old_func) {
|
||||
// Put the needed instructions into the trampoline bytes.
|
||||
uptr trampoline_length = instruction_size + kDirectBranchLength;
|
||||
uptr trampoline = AllocateMemoryForTrampoline(old_func, trampoline_length);
|
||||
if (!trampoline)
|
||||
return false;
|
||||
CopyInstructions(trampoline, old_func, instruction_size);
|
||||
WriteDirectBranch(trampoline + instruction_size,
|
||||
old_func + instruction_size);
|
||||
*orig_old_func = trampoline;
|
||||
}
|
||||
|
||||
// If needed, get memory space for indirect address.
|
||||
uptr indirect_address = 0;
|
||||
#if SANITIZER_WINDOWS64
|
||||
indirect_address = AllocateMemoryForTrampoline(old_func, kAddressLength);
|
||||
if (!indirect_address)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// Change memory protection to writable.
|
||||
DWORD protection = 0;
|
||||
if (!ChangeMemoryProtection(header, patch_length, &protection))
|
||||
return false;
|
||||
|
||||
// Write jumps to the redirected function.
|
||||
WriteBranch(header, indirect_address, new_func);
|
||||
WriteShortJumpInstruction(old_func, header);
|
||||
|
||||
// Restore previous memory protection.
|
||||
if (!RestoreMemoryProtection(header, patch_length, protection))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OverrideFunctionWithTrampoline(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func) {
|
||||
|
||||
size_t instructions_length = kBranchLength;
|
||||
size_t padding_length = 0;
|
||||
uptr indirect_address = 0;
|
||||
|
||||
if (orig_old_func) {
|
||||
// Find out the number of bytes of the instructions we need to copy
|
||||
// to the trampoline and store it in 'head'.
|
||||
head = RoundUpToInstrBoundary(kHeadMin, old_bytes);
|
||||
if (!head)
|
||||
// to the trampoline.
|
||||
instructions_length = RoundUpToInstrBoundary(kBranchLength, old_func);
|
||||
if (!instructions_length)
|
||||
return false;
|
||||
|
||||
// Put the needed instructions into the trampoline bytes.
|
||||
char *trampoline = GetMemoryForTrampoline(head + kTrampolineJumpSize);
|
||||
uptr trampoline_length = instructions_length + kDirectBranchLength;
|
||||
uptr trampoline = AllocateMemoryForTrampoline(old_func, trampoline_length);
|
||||
if (!trampoline)
|
||||
return false;
|
||||
_memcpy(trampoline, old_bytes, head);
|
||||
WriteTrampolineJumpInstruction(trampoline + head, old_bytes + head);
|
||||
*orig_old_func = (uptr)trampoline;
|
||||
CopyInstructions(trampoline, old_func, instructions_length);
|
||||
WriteDirectBranch(trampoline + instructions_length,
|
||||
old_func + instructions_length);
|
||||
*orig_old_func = trampoline;
|
||||
}
|
||||
|
||||
// Now put the "jmp <new_func>" instruction at the original code location.
|
||||
// We should preserve the EXECUTE flag as some of our own code might be
|
||||
// located in the same page (sic!). FIXME: might consider putting the
|
||||
// __interception code into a separate section or something?
|
||||
DWORD old_prot, unused_prot;
|
||||
// TODO(wwchrome): Properly handle access violations when finding a safe
|
||||
// region to store the indirect jump target address.
|
||||
// Need to mark extra 8 bytes for Win64 because jmp [rip -8]
|
||||
if (!VirtualProtect((void *)(old_bytes - kExtraPrevBytes),
|
||||
head + kExtraPrevBytes, PAGE_EXECUTE_READWRITE,
|
||||
&old_prot))
|
||||
#if SANITIZER_WINDOWS64
|
||||
// Check if the targeted address can be encoded in the function padding.
|
||||
// Otherwise, allocate it in the trampoline region.
|
||||
if (IsMemoryPadding(old_func - kAddressLength, kAddressLength)) {
|
||||
indirect_address = old_func - kAddressLength;
|
||||
padding_length = kAddressLength;
|
||||
} else {
|
||||
indirect_address = AllocateMemoryForTrampoline(old_func, kAddressLength);
|
||||
if (!indirect_address)
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Change memory protection to writable.
|
||||
uptr patch_address = old_func - padding_length;
|
||||
uptr patch_length = instructions_length + padding_length;
|
||||
DWORD protection = 0;
|
||||
if (!ChangeMemoryProtection(patch_address, patch_length, &protection))
|
||||
return false;
|
||||
|
||||
WriteInterceptorJumpInstruction(old_bytes, (char *)new_func);
|
||||
_memset(old_bytes + kHeadMin, 0xCC /* int 3 */, head - kHeadMin);
|
||||
// Patch the original function.
|
||||
WriteBranch(old_func, indirect_address, new_func);
|
||||
|
||||
// Restore the original permissions.
|
||||
if (!VirtualProtect((void *)(old_bytes - kExtraPrevBytes),
|
||||
head + kExtraPrevBytes, old_prot, &unused_prot))
|
||||
return false; // not clear if this failure bothers us.
|
||||
// Restore previous memory protection.
|
||||
if (!RestoreMemoryProtection(patch_address, patch_length, protection))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OverrideFunction(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func) {
|
||||
#if !SANITIZER_WINDOWS64
|
||||
if (OverrideFunctionWithDetour(old_func, new_func, orig_old_func))
|
||||
return true;
|
||||
#endif
|
||||
if (OverrideFunctionWithRedirectJump(old_func, new_func, orig_old_func))
|
||||
return true;
|
||||
if (OverrideFunctionWithHotPatch(old_func, new_func, orig_old_func))
|
||||
return true;
|
||||
if (OverrideFunctionWithTrampoline(old_func, new_func, orig_old_func))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void **InterestingDLLsAvailable() {
|
||||
static const char *InterestingDLLs[] = {
|
||||
"kernel32.dll",
|
||||
@@ -461,7 +824,7 @@ bool OverrideImportedFunction(const char *module_to_patch,
|
||||
RVAPtr<IMAGE_DOS_HEADER> dos_stub(module, 0);
|
||||
RVAPtr<IMAGE_NT_HEADERS> headers(module, dos_stub->e_lfanew);
|
||||
if (!module || dos_stub->e_magic != IMAGE_DOS_SIGNATURE || // "MZ"
|
||||
headers->Signature != IMAGE_NT_SIGNATURE || // "PE\0\0"
|
||||
headers->Signature != IMAGE_NT_SIGNATURE || // "PE\0\0"
|
||||
headers->FileHeader.SizeOfOptionalHeader <
|
||||
sizeof(IMAGE_OPTIONAL_HEADER)) {
|
||||
return false;
|
||||
|
||||
@@ -42,6 +42,23 @@ bool OverrideImportedFunction(const char *module_to_patch,
|
||||
const char *function_name, uptr new_function,
|
||||
uptr *orig_old_func);
|
||||
|
||||
#if !SANITIZER_WINDOWS64
|
||||
// Exposed for unittests
|
||||
bool OverrideFunctionWithDetour(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func);
|
||||
#endif
|
||||
|
||||
// Exposed for unittests
|
||||
bool OverrideFunctionWithRedirectJump(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func);
|
||||
bool OverrideFunctionWithHotPatch(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func);
|
||||
bool OverrideFunctionWithTrampoline(
|
||||
uptr old_func, uptr new_func, uptr *orig_old_func);
|
||||
|
||||
// Exposed for unittests
|
||||
void TestOnlyReleaseTrampolineRegions();
|
||||
|
||||
} // namespace __interception
|
||||
|
||||
#if defined(INTERCEPTION_DYNAMIC_CRT)
|
||||
|
||||
@@ -22,32 +22,121 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
namespace __interception {
|
||||
namespace {
|
||||
|
||||
enum FunctionPrefixKind {
|
||||
FunctionPrefixNone,
|
||||
FunctionPrefixPadding,
|
||||
FunctionPrefixHotPatch,
|
||||
FunctionPrefixDetour,
|
||||
};
|
||||
|
||||
typedef bool (*TestOverrideFunction)(uptr, uptr, uptr*);
|
||||
typedef int (*IdentityFunction)(int);
|
||||
|
||||
#if !SANITIZER_WINDOWS64
|
||||
#if SANITIZER_WINDOWS64
|
||||
|
||||
const u8 kIdentityCodeWithPrologue[] = {
|
||||
0x55, // push ebp
|
||||
0x8B, 0xEC, // mov ebp,esp
|
||||
0x8B, 0x45, 0x08, // mov eax,dword ptr [ebp + 8]
|
||||
0x5D, // pop ebp
|
||||
0xC3, // ret
|
||||
0x55, // push rbp
|
||||
0x48, 0x89, 0xE5, // mov rbp,rsp
|
||||
0x8B, 0xC1, // mov eax,ecx
|
||||
0x5D, // pop rbp
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
const u8 kIdentityCodeWithPushPop[] = {
|
||||
0x55, // push ebp
|
||||
0x8B, 0xEC, // mov ebp,esp
|
||||
0x53, // push ebx
|
||||
0x50, // push eax
|
||||
0x58, // pop eax
|
||||
0x8B, 0x45, 0x08, // mov eax,dword ptr [ebp + 8]
|
||||
0x5B, // pop ebx
|
||||
0x5D, // pop ebp
|
||||
0xC3, // ret
|
||||
0x55, // push rbp
|
||||
0x48, 0x89, 0xE5, // mov rbp,rsp
|
||||
0x53, // push rbx
|
||||
0x50, // push rax
|
||||
0x58, // pop rax
|
||||
0x8B, 0xC1, // mov rax,rcx
|
||||
0x5B, // pop rbx
|
||||
0x5D, // pop rbp
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
const u8 kIdentityTwiceOffset = 16;
|
||||
const u8 kIdentityTwice[] = {
|
||||
0x55, // push rbp
|
||||
0x48, 0x89, 0xE5, // mov rbp,rsp
|
||||
0x8B, 0xC1, // mov eax,ecx
|
||||
0x5D, // pop rbp
|
||||
0xC3, // ret
|
||||
0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90,
|
||||
0x55, // push rbp
|
||||
0x48, 0x89, 0xE5, // mov rbp,rsp
|
||||
0x8B, 0xC1, // mov eax,ecx
|
||||
0x5D, // pop rbp
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
const u8 kIdentityCodeWithMov[] = {
|
||||
0x89, 0xC8, // mov eax, ecx
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
const u8 kIdentityCodeWithJump[] = {
|
||||
0xE9, 0x04, 0x00, 0x00,
|
||||
0x00, // jmp + 4
|
||||
0xCC, 0xCC, 0xCC, 0xCC,
|
||||
0x89, 0xC8, // mov eax, ecx
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
const u8 kIdentityCodeWithPrologue[] = {
|
||||
0x55, // push ebp
|
||||
0x8B, 0xEC, // mov ebp,esp
|
||||
0x8B, 0x45, 0x08, // mov eax,dword ptr [ebp + 8]
|
||||
0x5D, // pop ebp
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
const u8 kIdentityCodeWithPushPop[] = {
|
||||
0x55, // push ebp
|
||||
0x8B, 0xEC, // mov ebp,esp
|
||||
0x53, // push ebx
|
||||
0x50, // push eax
|
||||
0x58, // pop eax
|
||||
0x8B, 0x45, 0x08, // mov eax,dword ptr [ebp + 8]
|
||||
0x5B, // pop ebx
|
||||
0x5D, // pop ebp
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
const u8 kIdentityTwiceOffset = 8;
|
||||
const u8 kIdentityTwice[] = {
|
||||
0x55, // push ebp
|
||||
0x8B, 0xEC, // mov ebp,esp
|
||||
0x8B, 0x45, 0x08, // mov eax,dword ptr [ebp + 8]
|
||||
0x5D, // pop ebp
|
||||
0xC3, // ret
|
||||
0x55, // push ebp
|
||||
0x8B, 0xEC, // mov ebp,esp
|
||||
0x8B, 0x45, 0x08, // mov eax,dword ptr [ebp + 8]
|
||||
0x5D, // pop ebp
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
const u8 kIdentityCodeWithMov[] = {
|
||||
0x8B, 0x44, 0x24, 0x04, // mov eax,dword ptr [esp + 4]
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
const u8 kIdentityCodeWithJump[] = {
|
||||
0xE9, 0x04, 0x00, 0x00,
|
||||
0x00, // jmp + 4
|
||||
0xCC, 0xCC, 0xCC, 0xCC,
|
||||
0x8B, 0x44, 0x24, 0x04, // mov eax,dword ptr [esp + 4]
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
const u8 kPatchableCode1[] = {
|
||||
0xB8, 0x4B, 0x00, 0x00, 0x00, // mov eax,4B
|
||||
0x33, 0xC9, // xor ecx,ecx
|
||||
@@ -69,6 +158,11 @@ const u8 kPatchableCode3[] = {
|
||||
0xE8, 0x3D, 0xFF, 0xFF, 0xFF, // call <func>
|
||||
};
|
||||
|
||||
const u8 kPatchableCode4[] = {
|
||||
0xE9, 0xCC, 0xCC, 0xCC, 0xCC, // jmp <label>
|
||||
0x90, 0x90, 0x90, 0x90,
|
||||
};
|
||||
|
||||
const u8 kUnpatchableCode1[] = {
|
||||
0xC3, // ret
|
||||
};
|
||||
@@ -97,49 +191,68 @@ const u8 kUnpatchableCode5[] = {
|
||||
};
|
||||
|
||||
const u8 kUnpatchableCode6[] = {
|
||||
0xE9, 0xCC, 0xCC, 0xCC, 0xCC, // jmp <label>
|
||||
0x90, 0x90, 0x90, 0x90,
|
||||
};
|
||||
|
||||
const u8 kUnpatchableCode7[] = {
|
||||
0xE8, 0xCC, 0xCC, 0xCC, 0xCC, // call <func>
|
||||
0x90, 0x90, 0x90, 0x90,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// A buffer holding the dynamically generated code under test.
|
||||
u8* ActiveCode;
|
||||
size_t ActiveCodeLength = 4096;
|
||||
|
||||
template<class T>
|
||||
void LoadActiveCode(const T &Code, uptr *EntryPoint) {
|
||||
static void LoadActiveCode(
|
||||
const T &code,
|
||||
uptr *entry_point,
|
||||
FunctionPrefixKind prefix_kind = FunctionPrefixNone) {
|
||||
if (ActiveCode == nullptr) {
|
||||
ActiveCode =
|
||||
(u8*)::VirtualAlloc(nullptr, ActiveCodeLength, MEM_COMMIT | MEM_RESERVE,
|
||||
(u8*)::VirtualAlloc(nullptr, ActiveCodeLength,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE);
|
||||
ASSERT_NE(ActiveCode, nullptr);
|
||||
}
|
||||
|
||||
size_t Position = 0;
|
||||
*EntryPoint = (uptr)&ActiveCode[0];
|
||||
size_t position = 0;
|
||||
|
||||
// Add padding to avoid memory violation when scanning the prefix.
|
||||
for (int i = 0; i < 16; ++i)
|
||||
ActiveCode[position++] = 0xC3; // Instruction 'ret'.
|
||||
|
||||
// Add function padding.
|
||||
size_t padding = 0;
|
||||
if (prefix_kind == FunctionPrefixPadding)
|
||||
padding = 16;
|
||||
else if (prefix_kind == FunctionPrefixDetour ||
|
||||
prefix_kind == FunctionPrefixHotPatch)
|
||||
padding = FIRST_32_SECOND_64(5, 6);
|
||||
// Insert |padding| instructions 'nop'.
|
||||
for (size_t i = 0; i < padding; ++i)
|
||||
ActiveCode[position++] = 0x90;
|
||||
|
||||
// Keep track of the entry point.
|
||||
*entry_point = (uptr)&ActiveCode[position];
|
||||
|
||||
// Add the detour instruction (i.e. mov edi, edi)
|
||||
if (prefix_kind == FunctionPrefixDetour) {
|
||||
ActiveCode[position++] = 0x8B;
|
||||
ActiveCode[position++] = 0xFF;
|
||||
}
|
||||
|
||||
// Copy the function body.
|
||||
for (size_t i = 0; i < sizeof(T); ++i)
|
||||
ActiveCode[Position++] = Code[i];
|
||||
ActiveCode[position++] = code[i];
|
||||
}
|
||||
|
||||
int InterceptorFunctionCalled;
|
||||
IdentityFunction InterceptedRealFunction;
|
||||
|
||||
NOINLINE int InterceptorFunction(int x) {
|
||||
int InterceptorFunction(int x) {
|
||||
++InterceptorFunctionCalled;
|
||||
return x;
|
||||
return InterceptedRealFunction(x);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace __interception {
|
||||
|
||||
// Tests for interception_win.h
|
||||
TEST(Interception, InternalGetProcAddress) {
|
||||
HMODULE ntdll_handle = ::GetModuleHandle("ntdll");
|
||||
@@ -155,67 +268,313 @@ TEST(Interception, InternalGetProcAddress) {
|
||||
}
|
||||
|
||||
template<class T>
|
||||
bool TestFunctionPatching(const T &Code) {
|
||||
uptr Address;
|
||||
int x = sizeof(T);
|
||||
|
||||
LoadActiveCode<T>(Code, &Address);
|
||||
uptr UnusedRealAddress = 0;
|
||||
return OverrideFunction(Address, (uptr)&InterceptorFunction,
|
||||
&UnusedRealAddress);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void TestIdentityFunctionPatching(const T &IdentityCode) {
|
||||
uptr IdentityAddress;
|
||||
LoadActiveCode<T>(IdentityCode, &IdentityAddress);
|
||||
IdentityFunction Identity = (IdentityFunction)IdentityAddress;
|
||||
static void TestIdentityFunctionPatching(
|
||||
const T &code,
|
||||
TestOverrideFunction override,
|
||||
FunctionPrefixKind prefix_kind = FunctionPrefixNone) {
|
||||
uptr identity_address;
|
||||
LoadActiveCode(code, &identity_address, prefix_kind);
|
||||
IdentityFunction identity = (IdentityFunction)identity_address;
|
||||
|
||||
// Validate behavior before dynamic patching.
|
||||
InterceptorFunctionCalled = 0;
|
||||
EXPECT_EQ(0, Identity(0));
|
||||
EXPECT_EQ(42, Identity(42));
|
||||
EXPECT_EQ(0, identity(0));
|
||||
EXPECT_EQ(42, identity(42));
|
||||
EXPECT_EQ(0, InterceptorFunctionCalled);
|
||||
|
||||
// Patch the function.
|
||||
uptr RealIdentityAddress = 0;
|
||||
EXPECT_TRUE(OverrideFunction(IdentityAddress, (uptr)&InterceptorFunction,
|
||||
&RealIdentityAddress));
|
||||
IdentityFunction RealIdentity = (IdentityFunction)RealIdentityAddress;
|
||||
uptr real_identity_address = 0;
|
||||
bool success = override(identity_address,
|
||||
(uptr)&InterceptorFunction,
|
||||
&real_identity_address);
|
||||
EXPECT_TRUE(success);
|
||||
EXPECT_NE(0U, real_identity_address);
|
||||
IdentityFunction real_identity = (IdentityFunction)real_identity_address;
|
||||
InterceptedRealFunction = real_identity;
|
||||
|
||||
// Don't run tests if hooking failed or the real function is not valid.
|
||||
if (!success || !real_identity_address)
|
||||
return;
|
||||
|
||||
// Calling the redirected function.
|
||||
InterceptorFunctionCalled = 0;
|
||||
EXPECT_EQ(0, Identity(0));
|
||||
EXPECT_EQ(42, Identity(42));
|
||||
EXPECT_EQ(0, identity(0));
|
||||
EXPECT_EQ(42, identity(42));
|
||||
EXPECT_EQ(2, InterceptorFunctionCalled);
|
||||
|
||||
// Calling the real function.
|
||||
InterceptorFunctionCalled = 0;
|
||||
EXPECT_EQ(0, RealIdentity(0));
|
||||
EXPECT_EQ(42, RealIdentity(42));
|
||||
EXPECT_EQ(0, real_identity(0));
|
||||
EXPECT_EQ(42, real_identity(42));
|
||||
EXPECT_EQ(0, InterceptorFunctionCalled);
|
||||
|
||||
TestOnlyReleaseTrampolineRegions();
|
||||
}
|
||||
|
||||
#if !SANITIZER_WINDOWS64
|
||||
TEST(Interception, OverrideFunctionWithDetour) {
|
||||
TestOverrideFunction override = OverrideFunctionWithDetour;
|
||||
FunctionPrefixKind prefix = FunctionPrefixDetour;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPrologue, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPushPop, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithMov, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithJump, override, prefix);
|
||||
}
|
||||
#endif // !SANITIZER_WINDOWS64
|
||||
|
||||
TEST(Interception, OverrideFunctionWithRedirectJump) {
|
||||
TestOverrideFunction override = OverrideFunctionWithRedirectJump;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithJump, override);
|
||||
}
|
||||
|
||||
TEST(Interception, OverrideFunctionWithHotPatch) {
|
||||
TestOverrideFunction override = OverrideFunctionWithHotPatch;
|
||||
FunctionPrefixKind prefix = FunctionPrefixHotPatch;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithMov, override, prefix);
|
||||
}
|
||||
|
||||
TEST(Interception, OverrideFunctionWithTrampoline) {
|
||||
TestOverrideFunction override = OverrideFunctionWithTrampoline;
|
||||
FunctionPrefixKind prefix = FunctionPrefixNone;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPrologue, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPushPop, override, prefix);
|
||||
|
||||
prefix = FunctionPrefixPadding;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPrologue, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPushPop, override, prefix);
|
||||
}
|
||||
|
||||
TEST(Interception, OverrideFunction) {
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPrologue);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPushPop);
|
||||
TestOverrideFunction override = OverrideFunction;
|
||||
FunctionPrefixKind prefix = FunctionPrefixNone;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPrologue, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPushPop, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithJump, override, prefix);
|
||||
|
||||
prefix = FunctionPrefixPadding;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPrologue, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPushPop, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithMov, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithJump, override, prefix);
|
||||
|
||||
prefix = FunctionPrefixHotPatch;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPrologue, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPushPop, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithMov, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithJump, override, prefix);
|
||||
|
||||
prefix = FunctionPrefixDetour;
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPrologue, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithPushPop, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithMov, override, prefix);
|
||||
TestIdentityFunctionPatching(kIdentityCodeWithJump, override, prefix);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void TestIdentityFunctionMultiplePatching(
|
||||
const T &code,
|
||||
TestOverrideFunction override,
|
||||
FunctionPrefixKind prefix_kind = FunctionPrefixNone) {
|
||||
uptr identity_address;
|
||||
LoadActiveCode(code, &identity_address, prefix_kind);
|
||||
|
||||
// Patch the function.
|
||||
uptr real_identity_address = 0;
|
||||
bool success = override(identity_address,
|
||||
(uptr)&InterceptorFunction,
|
||||
&real_identity_address);
|
||||
EXPECT_TRUE(success);
|
||||
EXPECT_NE(0U, real_identity_address);
|
||||
|
||||
// Re-patching the function should not work.
|
||||
success = override(identity_address,
|
||||
(uptr)&InterceptorFunction,
|
||||
&real_identity_address);
|
||||
EXPECT_FALSE(success);
|
||||
|
||||
TestOnlyReleaseTrampolineRegions();
|
||||
}
|
||||
|
||||
TEST(Interception, OverrideFunctionMultiplePatchingIsFailing) {
|
||||
#if !SANITIZER_WINDOWS64
|
||||
TestIdentityFunctionMultiplePatching(kIdentityCodeWithPrologue,
|
||||
OverrideFunctionWithDetour,
|
||||
FunctionPrefixDetour);
|
||||
#endif
|
||||
|
||||
TestIdentityFunctionMultiplePatching(kIdentityCodeWithMov,
|
||||
OverrideFunctionWithHotPatch,
|
||||
FunctionPrefixHotPatch);
|
||||
|
||||
TestIdentityFunctionMultiplePatching(kIdentityCodeWithPushPop,
|
||||
OverrideFunctionWithTrampoline,
|
||||
FunctionPrefixPadding);
|
||||
}
|
||||
|
||||
TEST(Interception, OverrideFunctionTwice) {
|
||||
uptr identity_address1;
|
||||
LoadActiveCode(kIdentityTwice, &identity_address1);
|
||||
uptr identity_address2 = identity_address1 + kIdentityTwiceOffset;
|
||||
IdentityFunction identity1 = (IdentityFunction)identity_address1;
|
||||
IdentityFunction identity2 = (IdentityFunction)identity_address2;
|
||||
|
||||
// Patch the two functions.
|
||||
uptr real_identity_address = 0;
|
||||
EXPECT_TRUE(OverrideFunction(identity_address1,
|
||||
(uptr)&InterceptorFunction,
|
||||
&real_identity_address));
|
||||
EXPECT_TRUE(OverrideFunction(identity_address2,
|
||||
(uptr)&InterceptorFunction,
|
||||
&real_identity_address));
|
||||
IdentityFunction real_identity = (IdentityFunction)real_identity_address;
|
||||
InterceptedRealFunction = real_identity;
|
||||
|
||||
// Calling the redirected function.
|
||||
InterceptorFunctionCalled = 0;
|
||||
EXPECT_EQ(42, identity1(42));
|
||||
EXPECT_EQ(42, identity2(42));
|
||||
EXPECT_EQ(2, InterceptorFunctionCalled);
|
||||
|
||||
TestOnlyReleaseTrampolineRegions();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static bool TestFunctionPatching(
|
||||
const T &code,
|
||||
TestOverrideFunction override,
|
||||
FunctionPrefixKind prefix_kind = FunctionPrefixNone) {
|
||||
uptr address;
|
||||
LoadActiveCode(code, &address, prefix_kind);
|
||||
uptr unused_real_address = 0;
|
||||
bool result = override(
|
||||
address, (uptr)&InterceptorFunction, &unused_real_address);
|
||||
|
||||
TestOnlyReleaseTrampolineRegions();
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(Interception, PatchableFunction) {
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode1));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode2));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode3));
|
||||
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode1));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode2));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode3));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode4));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode5));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode6));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode7));
|
||||
}
|
||||
TestOverrideFunction override = OverrideFunction;
|
||||
// Test without function padding.
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode1, override));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode2, override));
|
||||
#if SANITIZER_WINDOWS64
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode3, override));
|
||||
#else
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode3, override));
|
||||
#endif
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode4, override));
|
||||
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode1, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode2, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode3, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode4, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode5, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode6, override));
|
||||
}
|
||||
|
||||
#if !SANITIZER_WINDOWS64
|
||||
TEST(Interception, PatchableFunctionWithDetour) {
|
||||
TestOverrideFunction override = OverrideFunctionWithDetour;
|
||||
// Without the prefix, no function can be detoured.
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode1, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode2, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode3, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode4, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode1, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode2, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode3, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode4, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode5, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode6, override));
|
||||
|
||||
// With the prefix, all functions can be detoured.
|
||||
FunctionPrefixKind prefix = FunctionPrefixDetour;
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode1, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode2, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode3, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode4, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kUnpatchableCode1, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kUnpatchableCode2, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kUnpatchableCode3, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kUnpatchableCode4, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kUnpatchableCode5, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kUnpatchableCode6, override, prefix));
|
||||
}
|
||||
#endif // !SANITIZER_WINDOWS64
|
||||
|
||||
TEST(Interception, PatchableFunctionWithRedirectJump) {
|
||||
TestOverrideFunction override = OverrideFunctionWithRedirectJump;
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode1, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode2, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode3, override));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode4, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode1, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode2, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode3, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode4, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode5, override));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode6, override));
|
||||
}
|
||||
|
||||
TEST(Interception, PatchableFunctionWithHotPatch) {
|
||||
TestOverrideFunction override = OverrideFunctionWithHotPatch;
|
||||
FunctionPrefixKind prefix = FunctionPrefixHotPatch;
|
||||
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode1, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode2, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode3, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode4, override, prefix));
|
||||
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode1, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kUnpatchableCode2, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode3, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode4, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode5, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode6, override, prefix));
|
||||
}
|
||||
|
||||
TEST(Interception, PatchableFunctionWithTrampoline) {
|
||||
TestOverrideFunction override = OverrideFunctionWithTrampoline;
|
||||
FunctionPrefixKind prefix = FunctionPrefixPadding;
|
||||
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode1, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode2, override, prefix));
|
||||
#if SANITIZER_WINDOWS64
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode3, override, prefix));
|
||||
#else
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode3, override, prefix));
|
||||
#endif
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode4, override, prefix));
|
||||
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode1, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode2, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode3, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode4, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode5, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode6, override, prefix));
|
||||
}
|
||||
|
||||
TEST(Interception, PatchableFunctionPadding) {
|
||||
TestOverrideFunction override = OverrideFunction;
|
||||
FunctionPrefixKind prefix = FunctionPrefixPadding;
|
||||
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode1, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode2, override, prefix));
|
||||
#if SANITIZER_WINDOWS64
|
||||
EXPECT_FALSE(TestFunctionPatching(kPatchableCode3, override, prefix));
|
||||
#else
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode3, override, prefix));
|
||||
#endif
|
||||
EXPECT_TRUE(TestFunctionPatching(kPatchableCode4, override, prefix));
|
||||
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode1, override, prefix));
|
||||
EXPECT_TRUE(TestFunctionPatching(kUnpatchableCode2, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode3, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode4, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode5, override, prefix));
|
||||
EXPECT_FALSE(TestFunctionPatching(kUnpatchableCode6, override, prefix));
|
||||
}
|
||||
|
||||
} // namespace __interception
|
||||
|
||||
|
||||
Reference in New Issue
Block a user