[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:
Etienne Bergeron
2016-07-11 23:02:18 +00:00
parent de1be7171a
commit f5525670ad
3 changed files with 1088 additions and 349 deletions

View File

@@ -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;

View File

@@ -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)

View File

@@ -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