// Copyright 2011-2020 Molecular Matters GmbH, all rights reserved. #if LC_VERSION == 1 // BEGIN EPIC MOD //#include PCH_INCLUDE // END EPIC MOD #include "LC_FunctionPatcher.h" #include "LC_Patch.h" #include "LC_Disassembler.h" #include "LC_NameMangling.h" #include "LC_AppSettings.h" #include "LC_Process.h" // BEGIN EPIC MOD #include "LC_Logging.h" // END EPIC MOD namespace { struct PatchTechnique { enum Enum { DIRECT_RELATIVE_JUMP, HOTPATCH_INDIRECTION }; }; static const uint8_t INT3_PADDING[16u] = { 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC }; // helper function that checks whether a certain number of bytes in memory are available for patching template static inline bool AreBytesAvailableForPatching(Process::Handle processHandle, const void* address) { // available bytes are either 0xCC (int 3) or 0x0 (page padding) uint8_t memory[N] = {}; Process::ReadProcessMemory(processHandle, address, memory, N); for (size_t i = 0u; i < N; ++i) { if ((memory[i] != 0xCC) && (memory[i] != 0x0)) { return false; } } return true; } } functions::Record functions::PatchFunction ( char* originalAddress, char* patchAddress, uint32_t functionRva, uint32_t patchFunctionRva, const symbols::ThunkDB* thunkDb, const symbols::Contribution* contribution, Process::Handle processHandle, void* moduleBase, uint16_t moduleIndex, types::unordered_set& patchedAddresses, const types::vector& threadIPs, // debug only Process::Id processId, const char* functionName ) { Record record = { thunkDb, functionRva, patchFunctionRva, moduleIndex, 0u }; // there are three ways to patch the old function so that it redirects to the new one: // 1) patch the incremental linking table to point to the new function. this is easiest because it // doesn't need any disassembling of instructions or instruction pointer checks. // 2) install a relative jump to the new function at the start of the old function. // this relative jump needs 5 bytes and can only be installed if no thread of this process is currently // reading instructions from the location in question. // 3) install a relative jump to the new function 5 bytes in front of the old function. // install a short jump to this relative jump at the start of the old function. // this needs support from the compiler (/hotpatch) to ensure that the first instruction of a // function is always at least 2 bytes long, and support from the linker (/FUNCTIONPADMIN) to ensure // that there are 5 unused bytes in front of each function. unsigned int patchTechniqueUsed = PatchTechnique::HOTPATCH_INDIRECTION; size_t wholeInstructionSize = 0u; // first check whether we can find an incremental linking thunk for this function. bool installedPatchToILT = false; const types::vector& thunkTableEntries = symbols::FindThunkTableEntriesByRVA(thunkDb, functionRva); if (thunkTableEntries.size() != 0u) { // patch the ILTs directly, but keep installing patches into the real function too. // this acts as a safety net, should any relocation or any function ever point to the real function // instead of the ILT. for (auto thunkRva : thunkTableEntries) { LC_LOG_DEV("Patching ILT 0x%X of function %s at 0x%p (0x%X) (PID: %d)", thunkRva, functionName, moduleBase, functionRva, +processId); void* incrementalLinkingThunk = pointer::Offset(moduleBase, thunkRva); patch::InstallRelativeNearJump(processHandle, incrementalLinkingThunk, patchAddress); } installedPatchToILT = true; } // second check whether the function is at least 5 bytes long to consider it for direct patching using // a single relative jump. if (contribution && contribution->size >= 5u) { // the function seems to be long enough. // disassemble the first instructions to see how many bytes we can patch. while (wholeInstructionSize < 5u) { const uint8_t instructionSize = Disassembler::FindInstructionSize(processHandle, originalAddress + wholeInstructionSize); if (instructionSize == 0u) { // dump raw code in case it could not be decoded LC_ERROR_DEV("Failed to disassemble code for function %s at 0x%p (0x%X) (PID: %d)", functionName, moduleBase, functionRva, +processId); Process::DumpMemory(processHandle, originalAddress, contribution->size); break; } wholeInstructionSize += instructionSize; } // did we disassemble at least 5 bytes worth of instructions? if (wholeInstructionSize >= 5u) { record.directJumpInstructionSize = static_cast(wholeInstructionSize); // yes, now check if a thread is currently reading from memory at the location where we // want to install a relative jump. bool anyThreadInside = false; const size_t threadCount = threadIPs.size(); for (size_t t = 0u; t < threadCount; ++t) { const void* instructionPointer = threadIPs[t]; if ((instructionPointer >= originalAddress) && (instructionPointer < originalAddress + wholeInstructionSize)) { anyThreadInside = true; break; } } if (!anyThreadInside) { // no thread currently reads from there, install a direct relative jump patchTechniqueUsed = PatchTechnique::DIRECT_RELATIVE_JUMP; } } } // now install a patch using the selected technique if (patchTechniqueUsed == PatchTechnique::DIRECT_RELATIVE_JUMP) { LC_LOG_DEV("Patching function %s directly at 0x%p (0x%X) (PID: %d)", functionName, moduleBase, functionRva, +processId); patch::InstallRelativeNearJump(processHandle, originalAddress, patchAddress); // are there remaining bytes straddling the last instruction? if (wholeInstructionSize > 5u) { // yes, overwrite those with int 3 Process::WriteProcessMemory(processHandle, originalAddress + 5u, INT3_PADDING, wholeInstructionSize - 5u); } } else if (patchTechniqueUsed == PatchTechnique::HOTPATCH_INDIRECTION) { const uint8_t instructionSize = Disassembler::FindInstructionSize(processHandle, originalAddress); if (instructionSize >= 2u) { // we need to go via an indirection, and install the relative jump to the patch // address right before the original function. this means we need 5 bytes to be // available in front of the function. const bool isAvailable = AreBytesAvailableForPatching<5u>(processHandle, originalAddress - 5u); const bool installedPatch = (patchedAddresses.find(originalAddress - 5u) != patchedAddresses.end()); if (isAvailable || installedPatch) { LC_LOG_DEV("Hot-patching function %s at 0x%p (0x%X) (PID: %d)", functionName, moduleBase, functionRva, +processId); // it is safe to install the relative jump right in front of the function patch::InstallRelativeNearJump(processHandle, originalAddress - 5u, patchAddress); // note that in very, very rare cases, the memory region in front of the function might not // be executable pages. this can only happen for the function right at the start of the // code segment, but it can happen. Process::MakePagesExecutable(processHandle, originalAddress - 5u, 5u); // jump to the relative jump we just installed using a short jump, using 2 bytes. // this memory region must always be executable already. patch::InstallRelativeShortJump(processHandle, originalAddress, originalAddress - 5u); patchedAddresses.insert(originalAddress - 5u); } else { // there is not enough space. // only emit a warning if the ILT also couldn't be patched. if (!installedPatchToILT) { if (appSettings::g_showUndecoratedNames->GetValue()) { LC_WARNING_USER("Not enough space near function '%s' at 0x%X to install patch (PID: %d). Changes to this function will not be observable.", nameMangling::UndecorateSymbol(functionName, 0u).c_str(), originalAddress, +processId); } else { LC_WARNING_USER("Not enough space near function '%s' at 0x%X to install patch (PID: %d). Changes to this function will not be observable.", functionName, originalAddress, +processId); } } } } else { // the instruction is too short. // only emit a warning if the ILT also couldn't be patched. if (!installedPatchToILT) { if (appSettings::g_showUndecoratedNames->GetValue()) { LC_WARNING_USER("Instruction in function '%s' at 0x%X is too short to install patch (PID: %d). Changes to this function will not be observable.", nameMangling::UndecorateSymbol(functionName, 0u).c_str(), originalAddress, +processId); } else { LC_WARNING_USER("Instruction in function '%s' at 0x%X is too short to install patch (PID: %d). Changes to this function will not be observable.", functionName, originalAddress, +processId); } } } } return record; } void functions::PatchFunction ( const Record& record, Process::Handle processHandle, void* processModuleBases[], void* newModuleBase, types::unordered_set& patchedAddresses, const types::vector& threadIPs ) { void* originalModuleBase = processModuleBases[record.patchIndex]; if (!originalModuleBase) { return; } char* originalAddress = pointer::Offset(originalModuleBase, record.functionRva); char* patchAddress = pointer::Offset(newModuleBase, record.patchFunctionRva); unsigned int patchTechniqueUsed = PatchTechnique::HOTPATCH_INDIRECTION; size_t wholeInstructionSize = 0u; // first check whether we can find an incremental linking thunk for this function. bool installedPatchToILT = false; const types::vector& thunkTableEntries = symbols::FindThunkTableEntriesByRVA(record.thunkDb, record.functionRva); if (thunkTableEntries.size() != 0u) { // patch the ILTs directly, but keep installing patches into the real function too. // this acts as a safety net, should any relocation or any function ever point to the real function // instead of the ILT. for (auto thunkRva : thunkTableEntries) { void* incrementalLinkingThunk = pointer::Offset(originalModuleBase, thunkRva); patch::InstallRelativeNearJump(processHandle, incrementalLinkingThunk, patchAddress); } installedPatchToILT = true; } // second check whether the function is at least 5 bytes long to consider it for direct patching using // a single relative jump. if (record.directJumpInstructionSize >= 5u) { // check if a thread is currently reading from memory at the location where we // want to install a relative jump. bool anyThreadInside = false; const size_t threadCount = threadIPs.size(); for (size_t t = 0u; t < threadCount; ++t) { const void* instructionPointer = threadIPs[t]; if ((instructionPointer >= originalAddress) && (instructionPointer < originalAddress + wholeInstructionSize)) { anyThreadInside = true; break; } } if (!anyThreadInside) { // no thread currently reads from there, install a direct relative jump patchTechniqueUsed = PatchTechnique::DIRECT_RELATIVE_JUMP; } } // now install a patch using the selected technique if (patchTechniqueUsed == PatchTechnique::DIRECT_RELATIVE_JUMP) { patch::InstallRelativeNearJump(processHandle, originalAddress, patchAddress); // are there remaining bytes straddling the last instruction? if (record.directJumpInstructionSize > 5u) { // yes, overwrite those with int 3 Process::WriteProcessMemory(processHandle, originalAddress + 5u, INT3_PADDING, record.directJumpInstructionSize - 5u); } } else if (patchTechniqueUsed == PatchTechnique::HOTPATCH_INDIRECTION) { const uint8_t instructionSize = Disassembler::FindInstructionSize(processHandle, originalAddress); if (instructionSize >= 2u) { // we need to go via an indirection, and install the relative jump to the patch // address right before the original function. this means we need 5 bytes to be // available in front of the function. const bool isAvailable = AreBytesAvailableForPatching<5u>(processHandle, originalAddress - 5u); const bool installedPatch = (patchedAddresses.find(originalAddress - 5u) != patchedAddresses.end()); if (isAvailable || installedPatch) { // it is safe to install the relative jump right in front of the function patch::InstallRelativeNearJump(processHandle, originalAddress - 5u, patchAddress); // note that in very, very rare cases, the memory region in front of the function might not // be executable pages. this can only happen for the function right at the start of the // code segment, but it can happen. Process::MakePagesExecutable(processHandle, originalAddress - 5u, 5u); // jump to the relative jump we just installed using a short jump, using 2 bytes. // this memory region must always be executable already. patch::InstallRelativeShortJump(processHandle, originalAddress, originalAddress - 5u); patchedAddresses.insert(originalAddress - 5u); } } } } functions::LibraryRecord functions::PatchLibraryFunction ( char* srcAddress, char* destAddress, uint32_t srcRva, uint32_t destRva, const symbols::Contribution* contribution, Process::Handle processHandle, uint16_t moduleIndex ) { LibraryRecord record = { srcRva, destRva, moduleIndex, 0u }; // patching of public functions that were pulled in from libraries is a bit different because those libraries // were probably not built with the /hotpatch and /FUNCTIONPADMIN switches. // therefore, we need to install a relative jump to the original function directly, without any indirection. // such a jump needs 5 bytes but is actually easier to install in this case due to the following constraints: // - if the function is shorter than 5 bytes it cannot contain a jump or a relocation to another symbol, because // both would need at least (1 + 4) bytes. the function therefore cannot access any data or other function, and // hence is of no relevance to us. // - the instruction pointer cannot be in any of these functions currently, because no code calling these functions // could have possibly been run at this point (the process is still suspended). // therefore, we analyze the instructions in the function until we have found at least 5 bytes. // these 5 bytes are then patched with a relative jump to the original function, and the remaining bytes (if any) // are patched with int 3. size_t wholeInstructionSize = 0u; while (wholeInstructionSize < 5u) { const uint8_t instructionSize = Disassembler::FindInstructionSize(processHandle, srcAddress + wholeInstructionSize); if (instructionSize == 0u) { // dump raw code in case it could not be decoded Process::DumpMemory(processHandle, srcAddress, contribution->size); break; } wholeInstructionSize += instructionSize; } // did we disassemble at least 5 bytes worth of instructions? if (wholeInstructionSize >= 5u) { record.wholeInstructionSize = static_cast(wholeInstructionSize); // install a relative jump to the destination right here patch::InstallRelativeNearJump(processHandle, srcAddress, destAddress); // are there remaining bytes straddling the last instruction? if (wholeInstructionSize > 5u) { // yes, overwrite those with int 3 Process::WriteProcessMemory(processHandle, srcAddress + 5u, INT3_PADDING, wholeInstructionSize - 5u); } } return record; } void functions::PatchLibraryFunction ( const LibraryRecord& record, Process::Handle processHandle, void* processModuleBases[], void* newModuleBase ) { void* originalModuleBase = processModuleBases[record.patchIndex]; if (!originalModuleBase) { return; } char* srcAddress = pointer::Offset(newModuleBase, record.srcRva); char* destAddress = pointer::Offset(originalModuleBase, record.destRva); if (record.wholeInstructionSize >= 5u) { // install a relative jump to the destination right here patch::InstallRelativeNearJump(processHandle, srcAddress, destAddress); // are there remaining bytes straddling the last instruction? if (record.wholeInstructionSize > 5u) { // yes, overwrite those with int 3 Process::WriteProcessMemory(processHandle, srcAddress + 5u, INT3_PADDING, record.wholeInstructionSize - 5u); } } } bool functions::IsValidRecord(const Record& record) { return (record.thunkDb != nullptr); } bool functions::IsValidRecord(const LibraryRecord& record) { return (record.wholeInstructionSize != 0u); } #endif