Path: blob/main/contrib/llvm-project/compiler-rt/lib/interception/interception_win.cpp
35262 views
//===-- interception_win.cpp ------------------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file is a part of AddressSanitizer, an address sanity checker.9//10// Windows-specific interception methods.11//12// This file is implementing several hooking techniques to intercept calls13// to functions. The hooks are dynamically installed by modifying the assembly14// code.15//16// The hooking techniques are making assumptions on the way the code is17// generated and are safe under these assumptions.18//19// On 64-bit architecture, there is no direct 64-bit jump instruction. To allow20// arbitrary branching on the whole memory space, the notion of trampoline21// region is used. A trampoline region is a memory space withing 2G boundary22// where it is safe to add custom assembly code to build 64-bit jumps.23//24// Hooking techniques25// ==================26//27// 1) Detour28//29// The Detour hooking technique is assuming the presence of an header with30// padding and an overridable 2-bytes nop instruction (mov edi, edi). The31// nop instruction can safely be replaced by a 2-bytes jump without any need32// to save the instruction. A jump to the target is encoded in the function33// header and the nop instruction is replaced by a short jump to the header.34//35// head: 5 x nop head: jmp <hook>36// func: mov edi, edi --> func: jmp short <head>37// [...] real: [...]38//39// This technique is only implemented on 32-bit architecture.40// Most of the time, Windows API are hookable with the detour technique.41//42// 2) Redirect Jump43//44// The redirect jump is applicable when the first instruction is a direct45// jump. The instruction is replaced by jump to the hook.46//47// func: jmp <label> --> func: jmp <hook>48//49// On an 64-bit architecture, a trampoline is inserted.50//51// func: jmp <label> --> func: jmp <tramp>52// [...]53//54// [trampoline]55// tramp: jmp QWORD [addr]56// addr: .bytes <hook>57//58// Note: <real> is equivalent to <label>.59//60// 3) HotPatch61//62// The HotPatch hooking is assuming the presence of an header with padding63// and a first instruction with at least 2-bytes.64//65// The reason to enforce the 2-bytes limitation is to provide the minimal66// space to encode a short jump. HotPatch technique is only rewriting one67// instruction to avoid breaking a sequence of instructions containing a68// branching target.69//70// Assumptions are enforced by MSVC compiler by using the /HOTPATCH flag.71// see: https://msdn.microsoft.com/en-us/library/ms173507.aspx72// Default padding length is 5 bytes in 32-bits and 6 bytes in 64-bits.73//74// head: 5 x nop head: jmp <hook>75// func: <instr> --> func: jmp short <head>76// [...] body: [...]77//78// [trampoline]79// real: <instr>80// jmp <body>81//82// On an 64-bit architecture:83//84// head: 6 x nop head: jmp QWORD [addr1]85// func: <instr> --> func: jmp short <head>86// [...] body: [...]87//88// [trampoline]89// addr1: .bytes <hook>90// real: <instr>91// jmp QWORD [addr2]92// addr2: .bytes <body>93//94// 4) Trampoline95//96// The Trampoline hooking technique is the most aggressive one. It is97// assuming that there is a sequence of instructions that can be safely98// replaced by a jump (enough room and no incoming branches).99//100// Unfortunately, these assumptions can't be safely presumed and code may101// be broken after hooking.102//103// func: <instr> --> func: jmp <hook>104// <instr>105// [...] body: [...]106//107// [trampoline]108// real: <instr>109// <instr>110// jmp <body>111//112// On an 64-bit architecture:113//114// func: <instr> --> func: jmp QWORD [addr1]115// <instr>116// [...] body: [...]117//118// [trampoline]119// addr1: .bytes <hook>120// real: <instr>121// <instr>122// jmp QWORD [addr2]123// addr2: .bytes <body>124//===----------------------------------------------------------------------===//125126#include "interception.h"127128#if SANITIZER_WINDOWS129#include "sanitizer_common/sanitizer_platform.h"130#define WIN32_LEAN_AND_MEAN131#include <windows.h>132133namespace __interception {134135static const int kAddressLength = FIRST_32_SECOND_64(4, 8);136static const int kJumpInstructionLength = 5;137static const int kShortJumpInstructionLength = 2;138UNUSED static const int kIndirectJumpInstructionLength = 6;139static const int kBranchLength =140FIRST_32_SECOND_64(kJumpInstructionLength, kIndirectJumpInstructionLength);141static const int kDirectBranchLength = kBranchLength + kAddressLength;142143# if defined(_MSC_VER)144# define INTERCEPTION_FORMAT(f, a)145# else146# define INTERCEPTION_FORMAT(f, a) __attribute__((format(printf, f, a)))147# endif148149static void (*ErrorReportCallback)(const char *format, ...)150INTERCEPTION_FORMAT(1, 2);151152void SetErrorReportCallback(void (*callback)(const char *format, ...)) {153ErrorReportCallback = callback;154}155156# define ReportError(...) \157do { \158if (ErrorReportCallback) \159ErrorReportCallback(__VA_ARGS__); \160} while (0)161162static void InterceptionFailed() {163ReportError("interception_win: failed due to an unrecoverable error.\n");164// This acts like an abort when no debugger is attached. According to an old165// comment, calling abort() leads to an infinite recursion in CheckFailed.166__debugbreak();167}168169static bool DistanceIsWithin2Gig(uptr from, uptr target) {170#if SANITIZER_WINDOWS64171if (from < target)172return target - from <= (uptr)0x7FFFFFFFU;173else174return from - target <= (uptr)0x80000000U;175#else176// In a 32-bit address space, the address calculation will wrap, so this check177// is unnecessary.178return true;179#endif180}181182static uptr GetMmapGranularity() {183SYSTEM_INFO si;184GetSystemInfo(&si);185return si.dwAllocationGranularity;186}187188UNUSED static uptr RoundUpTo(uptr size, uptr boundary) {189return (size + boundary - 1) & ~(boundary - 1);190}191192// FIXME: internal_str* and internal_mem* functions should be moved from the193// ASan sources into interception/.194195static size_t _strlen(const char *str) {196const char* p = str;197while (*p != '\0') ++p;198return p - str;199}200201static char* _strchr(char* str, char c) {202while (*str) {203if (*str == c)204return str;205++str;206}207return nullptr;208}209210static void _memset(void *p, int value, size_t sz) {211for (size_t i = 0; i < sz; ++i)212((char*)p)[i] = (char)value;213}214215static void _memcpy(void *dst, void *src, size_t sz) {216char *dst_c = (char*)dst,217*src_c = (char*)src;218for (size_t i = 0; i < sz; ++i)219dst_c[i] = src_c[i];220}221222static bool ChangeMemoryProtection(223uptr address, uptr size, DWORD *old_protection) {224return ::VirtualProtect((void*)address, size,225PAGE_EXECUTE_READWRITE,226old_protection) != FALSE;227}228229static bool RestoreMemoryProtection(230uptr address, uptr size, DWORD old_protection) {231DWORD unused;232return ::VirtualProtect((void*)address, size,233old_protection,234&unused) != FALSE;235}236237static bool IsMemoryPadding(uptr address, uptr size) {238u8* function = (u8*)address;239for (size_t i = 0; i < size; ++i)240if (function[i] != 0x90 && function[i] != 0xCC)241return false;242return true;243}244245static const u8 kHintNop8Bytes[] = {2460x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00247};248249template<class T>250static bool FunctionHasPrefix(uptr address, const T &pattern) {251u8* function = (u8*)address - sizeof(pattern);252for (size_t i = 0; i < sizeof(pattern); ++i)253if (function[i] != pattern[i])254return false;255return true;256}257258static bool FunctionHasPadding(uptr address, uptr size) {259if (IsMemoryPadding(address - size, size))260return true;261if (size <= sizeof(kHintNop8Bytes) &&262FunctionHasPrefix(address, kHintNop8Bytes))263return true;264return false;265}266267static void WritePadding(uptr from, uptr size) {268_memset((void*)from, 0xCC, (size_t)size);269}270271static void WriteJumpInstruction(uptr from, uptr target) {272if (!DistanceIsWithin2Gig(from + kJumpInstructionLength, target)) {273ReportError(274"interception_win: cannot write jmp further than 2GB away, from %p to "275"%p.\n",276(void *)from, (void *)target);277InterceptionFailed();278}279ptrdiff_t offset = target - from - kJumpInstructionLength;280*(u8*)from = 0xE9;281*(u32*)(from + 1) = offset;282}283284static void WriteShortJumpInstruction(uptr from, uptr target) {285sptr offset = target - from - kShortJumpInstructionLength;286if (offset < -128 || offset > 127)287InterceptionFailed();288*(u8*)from = 0xEB;289*(u8*)(from + 1) = (u8)offset;290}291292#if SANITIZER_WINDOWS64293static void WriteIndirectJumpInstruction(uptr from, uptr indirect_target) {294// jmp [rip + <offset>] = FF 25 <offset> where <offset> is a relative295// offset.296// The offset is the distance from then end of the jump instruction to the297// memory location containing the targeted address. The displacement is still298// 32-bit in x64, so indirect_target must be located within +/- 2GB range.299int offset = indirect_target - from - kIndirectJumpInstructionLength;300if (!DistanceIsWithin2Gig(from + kIndirectJumpInstructionLength,301indirect_target)) {302ReportError(303"interception_win: cannot write indirect jmp with target further than "304"2GB away, from %p to %p.\n",305(void *)from, (void *)indirect_target);306InterceptionFailed();307}308*(u16*)from = 0x25FF;309*(u32*)(from + 2) = offset;310}311#endif312313static void WriteBranch(314uptr from, uptr indirect_target, uptr target) {315#if SANITIZER_WINDOWS64316WriteIndirectJumpInstruction(from, indirect_target);317*(u64*)indirect_target = target;318#else319(void)indirect_target;320WriteJumpInstruction(from, target);321#endif322}323324static void WriteDirectBranch(uptr from, uptr target) {325#if SANITIZER_WINDOWS64326// Emit an indirect jump through immediately following bytes:327// jmp [rip + kBranchLength]328// .quad <target>329WriteBranch(from, from + kBranchLength, target);330#else331WriteJumpInstruction(from, target);332#endif333}334335struct TrampolineMemoryRegion {336uptr content;337uptr allocated_size;338uptr max_size;339};340341UNUSED static const uptr kTrampolineScanLimitRange = 1ull << 31; // 2 gig342static const int kMaxTrampolineRegion = 1024;343static TrampolineMemoryRegion TrampolineRegions[kMaxTrampolineRegion];344345static void *AllocateTrampolineRegion(uptr image_address, size_t granularity) {346#if SANITIZER_WINDOWS64347uptr address = image_address;348uptr scanned = 0;349while (scanned < kTrampolineScanLimitRange) {350MEMORY_BASIC_INFORMATION info;351if (!::VirtualQuery((void*)address, &info, sizeof(info)))352return nullptr;353354// Check whether a region can be allocated at |address|.355if (info.State == MEM_FREE && info.RegionSize >= granularity) {356void *page = ::VirtualAlloc((void*)RoundUpTo(address, granularity),357granularity,358MEM_RESERVE | MEM_COMMIT,359PAGE_EXECUTE_READWRITE);360return page;361}362363// Move to the next region.364address = (uptr)info.BaseAddress + info.RegionSize;365scanned += info.RegionSize;366}367return nullptr;368#else369return ::VirtualAlloc(nullptr,370granularity,371MEM_RESERVE | MEM_COMMIT,372PAGE_EXECUTE_READWRITE);373#endif374}375376// Used by unittests to release mapped memory space.377void TestOnlyReleaseTrampolineRegions() {378for (size_t bucket = 0; bucket < kMaxTrampolineRegion; ++bucket) {379TrampolineMemoryRegion *current = &TrampolineRegions[bucket];380if (current->content == 0)381return;382::VirtualFree((void*)current->content, 0, MEM_RELEASE);383current->content = 0;384}385}386387static uptr AllocateMemoryForTrampoline(uptr image_address, size_t size) {388// Find a region within 2G with enough space to allocate |size| bytes.389TrampolineMemoryRegion *region = nullptr;390for (size_t bucket = 0; bucket < kMaxTrampolineRegion; ++bucket) {391TrampolineMemoryRegion* current = &TrampolineRegions[bucket];392if (current->content == 0) {393// No valid region found, allocate a new region.394size_t bucket_size = GetMmapGranularity();395void *content = AllocateTrampolineRegion(image_address, bucket_size);396if (content == nullptr)397return 0U;398399current->content = (uptr)content;400current->allocated_size = 0;401current->max_size = bucket_size;402region = current;403break;404} else if (current->max_size - current->allocated_size > size) {405#if SANITIZER_WINDOWS64406// In 64-bits, the memory space must be allocated within 2G boundary.407uptr next_address = current->content + current->allocated_size;408if (next_address < image_address ||409next_address - image_address >= 0x7FFF0000)410continue;411#endif412// The space can be allocated in the current region.413region = current;414break;415}416}417418// Failed to find a region.419if (region == nullptr)420return 0U;421422// Allocate the space in the current region.423uptr allocated_space = region->content + region->allocated_size;424region->allocated_size += size;425WritePadding(allocated_space, size);426427return allocated_space;428}429430// The following prologues cannot be patched because of the short jump431// jumping to the patching region.432433// Short jump patterns below are only for x86_64.434# if SANITIZER_WINDOWS_x64435// ntdll!wcslen in Win11436// 488bc1 mov rax,rcx437// 0fb710 movzx edx,word ptr [rax]438// 4883c002 add rax,2439// 6685d2 test dx,dx440// 75f4 jne -12441static const u8 kPrologueWithShortJump1[] = {4420x48, 0x8b, 0xc1, 0x0f, 0xb7, 0x10, 0x48, 0x83,4430xc0, 0x02, 0x66, 0x85, 0xd2, 0x75, 0xf4,444};445446// ntdll!strrchr in Win11447// 4c8bc1 mov r8,rcx448// 8a01 mov al,byte ptr [rcx]449// 48ffc1 inc rcx450// 84c0 test al,al451// 75f7 jne -9452static const u8 kPrologueWithShortJump2[] = {4530x4c, 0x8b, 0xc1, 0x8a, 0x01, 0x48, 0xff, 0xc1,4540x84, 0xc0, 0x75, 0xf7,455};456#endif457458// Returns 0 on error.459static size_t GetInstructionSize(uptr address, size_t* rel_offset = nullptr) {460#if SANITIZER_ARM64461// An ARM64 instruction is 4 bytes long.462return 4;463#endif464465# if SANITIZER_WINDOWS_x64466if (memcmp((u8*)address, kPrologueWithShortJump1,467sizeof(kPrologueWithShortJump1)) == 0 ||468memcmp((u8*)address, kPrologueWithShortJump2,469sizeof(kPrologueWithShortJump2)) == 0) {470return 0;471}472#endif473474switch (*(u64*)address) {475case 0x90909090909006EB: // stub: jmp over 6 x nop.476return 8;477}478479switch (*(u8*)address) {480case 0x90: // 90 : nop481case 0xC3: // C3 : ret (for small/empty function interception482case 0xCC: // CC : int 3 i.e. registering weak functions)483return 1;484485case 0x50: // push eax / rax486case 0x51: // push ecx / rcx487case 0x52: // push edx / rdx488case 0x53: // push ebx / rbx489case 0x54: // push esp / rsp490case 0x55: // push ebp / rbp491case 0x56: // push esi / rsi492case 0x57: // push edi / rdi493case 0x5D: // pop ebp / rbp494return 1;495496case 0x6A: // 6A XX = push XX497return 2;498499case 0xb8: // b8 XX XX XX XX : mov eax, XX XX XX XX500case 0xB9: // b9 XX XX XX XX : mov ecx, XX XX XX XX501return 5;502503// Cannot overwrite control-instruction. Return 0 to indicate failure.504case 0xE9: // E9 XX XX XX XX : jmp <label>505case 0xE8: // E8 XX XX XX XX : call <func>506case 0xEB: // EB XX : jmp XX (short jump)507case 0x70: // 7Y YY : jy XX (short conditional jump)508case 0x71:509case 0x72:510case 0x73:511case 0x74:512case 0x75:513case 0x76:514case 0x77:515case 0x78:516case 0x79:517case 0x7A:518case 0x7B:519case 0x7C:520case 0x7D:521case 0x7E:522case 0x7F:523return 0;524}525526switch (*(u16*)(address)) {527case 0x018A: // 8A 01 : mov al, byte ptr [ecx]528case 0xFF8B: // 8B FF : mov edi, edi529case 0xEC8B: // 8B EC : mov ebp, esp530case 0xc889: // 89 C8 : mov eax, ecx531case 0xE589: // 89 E5 : mov ebp, esp532case 0xC18B: // 8B C1 : mov eax, ecx533case 0xC033: // 33 C0 : xor eax, eax534case 0xC933: // 33 C9 : xor ecx, ecx535case 0xD233: // 33 D2 : xor edx, edx536return 2;537538// Cannot overwrite control-instruction. Return 0 to indicate failure.539case 0x25FF: // FF 25 XX XX XX XX : jmp [XXXXXXXX]540return 0;541}542543switch (0x00FFFFFF & *(u32*)address) {544case 0x24A48D: // 8D A4 24 XX XX XX XX : lea esp, [esp + XX XX XX XX]545return 7;546}547548switch (0x000000FF & *(u32 *)address) {549case 0xc2: // C2 XX XX : ret XX (needed for registering weak functions)550return 3;551}552553# if SANITIZER_WINDOWS_x64554switch (*(u8*)address) {555case 0xA1: // A1 XX XX XX XX XX XX XX XX :556// movabs eax, dword ptr ds:[XXXXXXXX]557return 9;558559case 0x83:560const u8 next_byte = *(u8*)(address + 1);561const u8 mod = next_byte >> 6;562const u8 rm = next_byte & 7;563if (mod == 1 && rm == 4)564return 5; // 83 ModR/M SIB Disp8 Imm8565// add|or|adc|sbb|and|sub|xor|cmp [r+disp8], imm8566}567568switch (*(u16*)address) {569case 0x5040: // push rax570case 0x5140: // push rcx571case 0x5240: // push rdx572case 0x5340: // push rbx573case 0x5440: // push rsp574case 0x5540: // push rbp575case 0x5640: // push rsi576case 0x5740: // push rdi577case 0x5441: // push r12578case 0x5541: // push r13579case 0x5641: // push r14580case 0x5741: // push r15581case 0x9066: // Two-byte NOP582case 0xc084: // test al, al583case 0x018a: // mov al, byte ptr [rcx]584return 2;585586case 0x058A: // 8A 05 XX XX XX XX : mov al, byte ptr [XX XX XX XX]587case 0x058B: // 8B 05 XX XX XX XX : mov eax, dword ptr [XX XX XX XX]588if (rel_offset)589*rel_offset = 2;590return 6;591}592593switch (0x00FFFFFF & *(u32*)address) {594case 0xe58948: // 48 8b c4 : mov rbp, rsp595case 0xc18b48: // 48 8b c1 : mov rax, rcx596case 0xc48b48: // 48 8b c4 : mov rax, rsp597case 0xd9f748: // 48 f7 d9 : neg rcx598case 0xd12b48: // 48 2b d1 : sub rdx, rcx599case 0x07c1f6: // f6 c1 07 : test cl, 0x7600case 0xc98548: // 48 85 C9 : test rcx, rcx601case 0xd28548: // 48 85 d2 : test rdx, rdx602case 0xc0854d: // 4d 85 c0 : test r8, r8603case 0xc2b60f: // 0f b6 c2 : movzx eax, dl604case 0xc03345: // 45 33 c0 : xor r8d, r8d605case 0xc93345: // 45 33 c9 : xor r9d, r9d606case 0xdb3345: // 45 33 DB : xor r11d, r11d607case 0xd98b4c: // 4c 8b d9 : mov r11, rcx608case 0xd28b4c: // 4c 8b d2 : mov r10, rdx609case 0xc98b4c: // 4C 8B C9 : mov r9, rcx610case 0xc18b4c: // 4C 8B C1 : mov r8, rcx611case 0xd2b60f: // 0f b6 d2 : movzx edx, dl612case 0xca2b48: // 48 2b ca : sub rcx, rdx613case 0xca3b48: // 48 3b ca : cmp rcx, rdx614case 0x10b70f: // 0f b7 10 : movzx edx, WORD PTR [rax]615case 0xc00b4d: // 3d 0b c0 : or r8, r8616case 0xc08b41: // 41 8b c0 : mov eax, r8d617case 0xd18b48: // 48 8b d1 : mov rdx, rcx618case 0xdc8b4c: // 4c 8b dc : mov r11, rsp619case 0xd18b4c: // 4c 8b d1 : mov r10, rcx620case 0xE0E483: // 83 E4 E0 : and esp, 0xFFFFFFE0621return 3;622623case 0xec8348: // 48 83 ec XX : sub rsp, XX624case 0xf88349: // 49 83 f8 XX : cmp r8, XX625case 0x588948: // 48 89 58 XX : mov QWORD PTR[rax + XX], rbx626return 4;627628case 0xec8148: // 48 81 EC XX XX XX XX : sub rsp, XXXXXXXX629return 7;630631case 0x058b48: // 48 8b 05 XX XX XX XX :632// mov rax, QWORD PTR [rip + XXXXXXXX]633case 0x058d48: // 48 8d 05 XX XX XX XX :634// lea rax, QWORD PTR [rip + XXXXXXXX]635case 0x25ff48: // 48 ff 25 XX XX XX XX :636// rex.W jmp QWORD PTR [rip + XXXXXXXX]637case 0x158D4C: // 4c 8d 15 XX XX XX XX : lea r10, [rip + XX]638// Instructions having offset relative to 'rip' need offset adjustment.639if (rel_offset)640*rel_offset = 3;641return 7;642643case 0x2444c7: // C7 44 24 XX YY YY YY YY644// mov dword ptr [rsp + XX], YYYYYYYY645return 8;646}647648switch (*(u32*)(address)) {649case 0x24448b48: // 48 8b 44 24 XX : mov rax, QWORD ptr [rsp + XX]650case 0x246c8948: // 48 89 6C 24 XX : mov QWORD ptr [rsp + XX], rbp651case 0x245c8948: // 48 89 5c 24 XX : mov QWORD PTR [rsp + XX], rbx652case 0x24748948: // 48 89 74 24 XX : mov QWORD PTR [rsp + XX], rsi653case 0x247c8948: // 48 89 7c 24 XX : mov QWORD PTR [rsp + XX], rdi654case 0x244C8948: // 48 89 4C 24 XX : mov QWORD PTR [rsp + XX], rcx655case 0x24548948: // 48 89 54 24 XX : mov QWORD PTR [rsp + XX], rdx656case 0x244c894c: // 4c 89 4c 24 XX : mov QWORD PTR [rsp + XX], r9657case 0x2444894c: // 4c 89 44 24 XX : mov QWORD PTR [rsp + XX], r8658return 5;659case 0x24648348: // 48 83 64 24 XX : and QWORD PTR [rsp + XX], YY660return 6;661}662663#else664665switch (*(u8*)address) {666case 0xA1: // A1 XX XX XX XX : mov eax, dword ptr ds:[XXXXXXXX]667return 5;668}669switch (*(u16*)address) {670case 0x458B: // 8B 45 XX : mov eax, dword ptr [ebp + XX]671case 0x5D8B: // 8B 5D XX : mov ebx, dword ptr [ebp + XX]672case 0x7D8B: // 8B 7D XX : mov edi, dword ptr [ebp + XX]673case 0xEC83: // 83 EC XX : sub esp, XX674case 0x75FF: // FF 75 XX : push dword ptr [ebp + XX]675return 3;676case 0xC1F7: // F7 C1 XX YY ZZ WW : test ecx, WWZZYYXX677case 0x25FF: // FF 25 XX YY ZZ WW : jmp dword ptr ds:[WWZZYYXX]678return 6;679case 0x3D83: // 83 3D XX YY ZZ WW TT : cmp TT, WWZZYYXX680return 7;681case 0x7D83: // 83 7D XX YY : cmp dword ptr [ebp + XX], YY682return 4;683}684685switch (0x00FFFFFF & *(u32*)address) {686case 0x24448A: // 8A 44 24 XX : mov eal, dword ptr [esp + XX]687case 0x24448B: // 8B 44 24 XX : mov eax, dword ptr [esp + XX]688case 0x244C8B: // 8B 4C 24 XX : mov ecx, dword ptr [esp + XX]689case 0x24548B: // 8B 54 24 XX : mov edx, dword ptr [esp + XX]690case 0x245C8B: // 8B 5C 24 XX : mov ebx, dword ptr [esp + XX]691case 0x246C8B: // 8B 6C 24 XX : mov ebp, dword ptr [esp + XX]692case 0x24748B: // 8B 74 24 XX : mov esi, dword ptr [esp + XX]693case 0x247C8B: // 8B 7C 24 XX : mov edi, dword ptr [esp + XX]694return 4;695}696697switch (*(u32*)address) {698case 0x2444B60F: // 0F B6 44 24 XX : movzx eax, byte ptr [esp + XX]699return 5;700}701#endif702703// Unknown instruction! This might happen when we add a new interceptor, use704// a new compiler version, or if Windows changed how some functions are705// compiled. In either case, we print the address and 8 bytes of instructions706// to notify the user about the error and to help identify the unknown707// instruction. Don't treat this as a fatal error, though we can break the708// debugger if one has been attached.709u8 *bytes = (u8 *)address;710ReportError(711"interception_win: unhandled instruction at %p: %02x %02x %02x %02x %02x "712"%02x %02x %02x\n",713(void *)address, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4],714bytes[5], bytes[6], bytes[7]);715if (::IsDebuggerPresent())716__debugbreak();717return 0;718}719720// Returns 0 on error.721static size_t RoundUpToInstrBoundary(size_t size, uptr address) {722size_t cursor = 0;723while (cursor < size) {724size_t instruction_size = GetInstructionSize(address + cursor);725if (!instruction_size)726return 0;727cursor += instruction_size;728}729return cursor;730}731732static bool CopyInstructions(uptr to, uptr from, size_t size) {733size_t cursor = 0;734while (cursor != size) {735size_t rel_offset = 0;736size_t instruction_size = GetInstructionSize(from + cursor, &rel_offset);737if (!instruction_size)738return false;739_memcpy((void *)(to + cursor), (void *)(from + cursor),740(size_t)instruction_size);741if (rel_offset) {742# if SANITIZER_WINDOWS64743// we want to make sure that the new relative offset still fits in 32-bits744// this will be untrue if relocated_offset \notin [-2**31, 2**31)745s64 delta = to - from;746s64 relocated_offset = *(s32 *)(to + cursor + rel_offset) - delta;747if (-0x8000'0000ll > relocated_offset || relocated_offset > 0x7FFF'FFFFll)748return false;749# else750// on 32-bit, the relative offset will always be correct751s32 delta = to - from;752s32 relocated_offset = *(s32 *)(to + cursor + rel_offset) - delta;753# endif754*(s32 *)(to + cursor + rel_offset) = relocated_offset;755}756cursor += instruction_size;757}758return true;759}760761762#if !SANITIZER_WINDOWS64763bool OverrideFunctionWithDetour(764uptr old_func, uptr new_func, uptr *orig_old_func) {765const int kDetourHeaderLen = 5;766const u16 kDetourInstruction = 0xFF8B;767768uptr header = (uptr)old_func - kDetourHeaderLen;769uptr patch_length = kDetourHeaderLen + kShortJumpInstructionLength;770771// Validate that the function is hookable.772if (*(u16*)old_func != kDetourInstruction ||773!IsMemoryPadding(header, kDetourHeaderLen))774return false;775776// Change memory protection to writable.777DWORD protection = 0;778if (!ChangeMemoryProtection(header, patch_length, &protection))779return false;780781// Write a relative jump to the redirected function.782WriteJumpInstruction(header, new_func);783784// Write the short jump to the function prefix.785WriteShortJumpInstruction(old_func, header);786787// Restore previous memory protection.788if (!RestoreMemoryProtection(header, patch_length, protection))789return false;790791if (orig_old_func)792*orig_old_func = old_func + kShortJumpInstructionLength;793794return true;795}796#endif797798bool OverrideFunctionWithRedirectJump(799uptr old_func, uptr new_func, uptr *orig_old_func) {800// Check whether the first instruction is a relative jump.801if (*(u8*)old_func != 0xE9)802return false;803804if (orig_old_func) {805sptr relative_offset = *(s32 *)(old_func + 1);806uptr absolute_target = old_func + relative_offset + kJumpInstructionLength;807*orig_old_func = absolute_target;808}809810#if SANITIZER_WINDOWS64811// If needed, get memory space for a trampoline jump.812uptr trampoline = AllocateMemoryForTrampoline(old_func, kDirectBranchLength);813if (!trampoline)814return false;815WriteDirectBranch(trampoline, new_func);816#endif817818// Change memory protection to writable.819DWORD protection = 0;820if (!ChangeMemoryProtection(old_func, kJumpInstructionLength, &protection))821return false;822823// Write a relative jump to the redirected function.824WriteJumpInstruction(old_func, FIRST_32_SECOND_64(new_func, trampoline));825826// Restore previous memory protection.827if (!RestoreMemoryProtection(old_func, kJumpInstructionLength, protection))828return false;829830return true;831}832833bool OverrideFunctionWithHotPatch(834uptr old_func, uptr new_func, uptr *orig_old_func) {835const int kHotPatchHeaderLen = kBranchLength;836837uptr header = (uptr)old_func - kHotPatchHeaderLen;838uptr patch_length = kHotPatchHeaderLen + kShortJumpInstructionLength;839840// Validate that the function is hot patchable.841size_t instruction_size = GetInstructionSize(old_func);842if (instruction_size < kShortJumpInstructionLength ||843!FunctionHasPadding(old_func, kHotPatchHeaderLen))844return false;845846if (orig_old_func) {847// Put the needed instructions into the trampoline bytes.848uptr trampoline_length = instruction_size + kDirectBranchLength;849uptr trampoline = AllocateMemoryForTrampoline(old_func, trampoline_length);850if (!trampoline)851return false;852if (!CopyInstructions(trampoline, old_func, instruction_size))853return false;854WriteDirectBranch(trampoline + instruction_size,855old_func + instruction_size);856*orig_old_func = trampoline;857}858859// If needed, get memory space for indirect address.860uptr indirect_address = 0;861#if SANITIZER_WINDOWS64862indirect_address = AllocateMemoryForTrampoline(old_func, kAddressLength);863if (!indirect_address)864return false;865#endif866867// Change memory protection to writable.868DWORD protection = 0;869if (!ChangeMemoryProtection(header, patch_length, &protection))870return false;871872// Write jumps to the redirected function.873WriteBranch(header, indirect_address, new_func);874WriteShortJumpInstruction(old_func, header);875876// Restore previous memory protection.877if (!RestoreMemoryProtection(header, patch_length, protection))878return false;879880return true;881}882883bool OverrideFunctionWithTrampoline(884uptr old_func, uptr new_func, uptr *orig_old_func) {885886size_t instructions_length = kBranchLength;887size_t padding_length = 0;888uptr indirect_address = 0;889890if (orig_old_func) {891// Find out the number of bytes of the instructions we need to copy892// to the trampoline.893instructions_length = RoundUpToInstrBoundary(kBranchLength, old_func);894if (!instructions_length)895return false;896897// Put the needed instructions into the trampoline bytes.898uptr trampoline_length = instructions_length + kDirectBranchLength;899uptr trampoline = AllocateMemoryForTrampoline(old_func, trampoline_length);900if (!trampoline)901return false;902if (!CopyInstructions(trampoline, old_func, instructions_length))903return false;904WriteDirectBranch(trampoline + instructions_length,905old_func + instructions_length);906*orig_old_func = trampoline;907}908909#if SANITIZER_WINDOWS64910// Check if the targeted address can be encoded in the function padding.911// Otherwise, allocate it in the trampoline region.912if (IsMemoryPadding(old_func - kAddressLength, kAddressLength)) {913indirect_address = old_func - kAddressLength;914padding_length = kAddressLength;915} else {916indirect_address = AllocateMemoryForTrampoline(old_func, kAddressLength);917if (!indirect_address)918return false;919}920#endif921922// Change memory protection to writable.923uptr patch_address = old_func - padding_length;924uptr patch_length = instructions_length + padding_length;925DWORD protection = 0;926if (!ChangeMemoryProtection(patch_address, patch_length, &protection))927return false;928929// Patch the original function.930WriteBranch(old_func, indirect_address, new_func);931932// Restore previous memory protection.933if (!RestoreMemoryProtection(patch_address, patch_length, protection))934return false;935936return true;937}938939bool OverrideFunction(940uptr old_func, uptr new_func, uptr *orig_old_func) {941#if !SANITIZER_WINDOWS64942if (OverrideFunctionWithDetour(old_func, new_func, orig_old_func))943return true;944#endif945if (OverrideFunctionWithRedirectJump(old_func, new_func, orig_old_func))946return true;947if (OverrideFunctionWithHotPatch(old_func, new_func, orig_old_func))948return true;949if (OverrideFunctionWithTrampoline(old_func, new_func, orig_old_func))950return true;951return false;952}953954static void **InterestingDLLsAvailable() {955static const char *InterestingDLLs[] = {956"kernel32.dll",957"msvcr100d.dll", // VS2010958"msvcr110d.dll", // VS2012959"msvcr120d.dll", // VS2013960"vcruntime140d.dll", // VS2015961"ucrtbased.dll", // Universal CRT962"msvcr100.dll", // VS2010963"msvcr110.dll", // VS2012964"msvcr120.dll", // VS2013965"vcruntime140.dll", // VS2015966"ucrtbase.dll", // Universal CRT967# if (defined(__MINGW32__) && defined(__i386__))968"libc++.dll", // libc++969"libunwind.dll", // libunwind970# endif971// NTDLL should go last as it exports some functions that we should972// override in the CRT [presumably only used internally].973"ntdll.dll",974NULL975};976static void *result[ARRAY_SIZE(InterestingDLLs)] = { 0 };977if (!result[0]) {978for (size_t i = 0, j = 0; InterestingDLLs[i]; ++i) {979if (HMODULE h = GetModuleHandleA(InterestingDLLs[i]))980result[j++] = (void *)h;981}982}983return &result[0];984}985986namespace {987// Utility for reading loaded PE images.988template <typename T> class RVAPtr {989public:990RVAPtr(void *module, uptr rva)991: ptr_(reinterpret_cast<T *>(reinterpret_cast<char *>(module) + rva)) {}992operator T *() { return ptr_; }993T *operator->() { return ptr_; }994T *operator++() { return ++ptr_; }995996private:997T *ptr_;998};999} // namespace10001001// Internal implementation of GetProcAddress. At least since Windows 8,1002// GetProcAddress appears to initialize DLLs before returning function pointers1003// into them. This is problematic for the sanitizers, because they typically1004// want to intercept malloc *before* MSVCRT initializes. Our internal1005// implementation walks the export list manually without doing initialization.1006uptr InternalGetProcAddress(void *module, const char *func_name) {1007// Check that the module header is full and present.1008RVAPtr<IMAGE_DOS_HEADER> dos_stub(module, 0);1009RVAPtr<IMAGE_NT_HEADERS> headers(module, dos_stub->e_lfanew);1010if (!module || dos_stub->e_magic != IMAGE_DOS_SIGNATURE || // "MZ"1011headers->Signature != IMAGE_NT_SIGNATURE || // "PE\0\0"1012headers->FileHeader.SizeOfOptionalHeader <1013sizeof(IMAGE_OPTIONAL_HEADER)) {1014return 0;1015}10161017IMAGE_DATA_DIRECTORY *export_directory =1018&headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];1019if (export_directory->Size == 0)1020return 0;1021RVAPtr<IMAGE_EXPORT_DIRECTORY> exports(module,1022export_directory->VirtualAddress);1023RVAPtr<DWORD> functions(module, exports->AddressOfFunctions);1024RVAPtr<DWORD> names(module, exports->AddressOfNames);1025RVAPtr<WORD> ordinals(module, exports->AddressOfNameOrdinals);10261027for (DWORD i = 0; i < exports->NumberOfNames; i++) {1028RVAPtr<char> name(module, names[i]);1029if (!strcmp(func_name, name)) {1030DWORD index = ordinals[i];1031RVAPtr<char> func(module, functions[index]);10321033// Handle forwarded functions.1034DWORD offset = functions[index];1035if (offset >= export_directory->VirtualAddress &&1036offset < export_directory->VirtualAddress + export_directory->Size) {1037// An entry for a forwarded function is a string with the following1038// format: "<module> . <function_name>" that is stored into the1039// exported directory.1040char function_name[256];1041size_t funtion_name_length = _strlen(func);1042if (funtion_name_length >= sizeof(function_name) - 1)1043InterceptionFailed();10441045_memcpy(function_name, func, funtion_name_length);1046function_name[funtion_name_length] = '\0';1047char* separator = _strchr(function_name, '.');1048if (!separator)1049InterceptionFailed();1050*separator = '\0';10511052void* redirected_module = GetModuleHandleA(function_name);1053if (!redirected_module)1054InterceptionFailed();1055return InternalGetProcAddress(redirected_module, separator + 1);1056}10571058return (uptr)(char *)func;1059}1060}10611062return 0;1063}10641065bool OverrideFunction(1066const char *func_name, uptr new_func, uptr *orig_old_func) {1067bool hooked = false;1068void **DLLs = InterestingDLLsAvailable();1069for (size_t i = 0; DLLs[i]; ++i) {1070uptr func_addr = InternalGetProcAddress(DLLs[i], func_name);1071if (func_addr &&1072OverrideFunction(func_addr, new_func, orig_old_func)) {1073hooked = true;1074}1075}1076return hooked;1077}10781079bool OverrideImportedFunction(const char *module_to_patch,1080const char *imported_module,1081const char *function_name, uptr new_function,1082uptr *orig_old_func) {1083HMODULE module = GetModuleHandleA(module_to_patch);1084if (!module)1085return false;10861087// Check that the module header is full and present.1088RVAPtr<IMAGE_DOS_HEADER> dos_stub(module, 0);1089RVAPtr<IMAGE_NT_HEADERS> headers(module, dos_stub->e_lfanew);1090if (!module || dos_stub->e_magic != IMAGE_DOS_SIGNATURE || // "MZ"1091headers->Signature != IMAGE_NT_SIGNATURE || // "PE\0\0"1092headers->FileHeader.SizeOfOptionalHeader <1093sizeof(IMAGE_OPTIONAL_HEADER)) {1094return false;1095}10961097IMAGE_DATA_DIRECTORY *import_directory =1098&headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];10991100// Iterate the list of imported DLLs. FirstThunk will be null for the last1101// entry.1102RVAPtr<IMAGE_IMPORT_DESCRIPTOR> imports(module,1103import_directory->VirtualAddress);1104for (; imports->FirstThunk != 0; ++imports) {1105RVAPtr<const char> modname(module, imports->Name);1106if (_stricmp(&*modname, imported_module) == 0)1107break;1108}1109if (imports->FirstThunk == 0)1110return false;11111112// We have two parallel arrays: the import address table (IAT) and the table1113// of names. They start out containing the same data, but the loader rewrites1114// the IAT to hold imported addresses and leaves the name table in1115// OriginalFirstThunk alone.1116RVAPtr<IMAGE_THUNK_DATA> name_table(module, imports->OriginalFirstThunk);1117RVAPtr<IMAGE_THUNK_DATA> iat(module, imports->FirstThunk);1118for (; name_table->u1.Ordinal != 0; ++name_table, ++iat) {1119if (!IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) {1120RVAPtr<IMAGE_IMPORT_BY_NAME> import_by_name(1121module, name_table->u1.ForwarderString);1122const char *funcname = &import_by_name->Name[0];1123if (strcmp(funcname, function_name) == 0)1124break;1125}1126}1127if (name_table->u1.Ordinal == 0)1128return false;11291130// Now we have the correct IAT entry. Do the swap. We have to make the page1131// read/write first.1132if (orig_old_func)1133*orig_old_func = iat->u1.AddressOfData;1134DWORD old_prot, unused_prot;1135if (!VirtualProtect(&iat->u1.AddressOfData, 4, PAGE_EXECUTE_READWRITE,1136&old_prot))1137return false;1138iat->u1.AddressOfData = new_function;1139if (!VirtualProtect(&iat->u1.AddressOfData, 4, old_prot, &unused_prot))1140return false; // Not clear if this failure bothers us.1141return true;1142}11431144} // namespace __interception11451146#endif // SANITIZER_APPLE114711481149