From 88fca26a2867683aa583d8a01e0b8dc4c9ee9afe Mon Sep 17 00:00:00 2001 From: boykopovar Date: Tue, 4 Aug 2026 15:28:10 +0300 Subject: [PATCH] refactor(scanner): move scanner from relinker to separated module --- core/CMakeLists.txt | 3 + .../include/codegen/CodegenException.hpp | 19 ++ core/codegen/include/codegen/CodegenTypes.hpp | 38 ++++ .../include/codegen/IInstructionDecoder.hpp | 21 ++ .../include/codegen/IInstructionRewriter.hpp | 21 ++ .../include/codegen/IInstructionScanner.hpp | 26 +++ .../codegen/x86/X64InstructionDecoder.hpp | 18 ++ .../codegen/x86/X64InstructionRewriter.hpp | 18 ++ core/codegen/src/InstructionScanner.cpp | 51 +++++ .../codegen/src/x86/X64InstructionDecoder.cpp | 183 +++++++++++++++ core/relinker/src/analysis/SyscallScanner.cpp | 213 +----------------- 11 files changed, 407 insertions(+), 204 deletions(-) create mode 100644 core/codegen/include/codegen/CodegenException.hpp create mode 100644 core/codegen/include/codegen/CodegenTypes.hpp create mode 100644 core/codegen/include/codegen/IInstructionDecoder.hpp create mode 100644 core/codegen/include/codegen/IInstructionRewriter.hpp create mode 100644 core/codegen/include/codegen/IInstructionScanner.hpp create mode 100644 core/codegen/include/codegen/x86/X64InstructionDecoder.hpp create mode 100644 core/codegen/include/codegen/x86/X64InstructionRewriter.hpp create mode 100644 core/codegen/src/InstructionScanner.cpp create mode 100644 core/codegen/src/x86/X64InstructionDecoder.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 3a5145034..184675fce 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -9,6 +9,8 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileWriter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/InstructionScanner.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/X64InstructionDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp @@ -21,5 +23,6 @@ target_include_directories(relinker PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/domain/include ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include ${CMAKE_CURRENT_SOURCE_DIR}/io/include + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/include ${CMAKE_CURRENT_SOURCE_DIR}/relinker/include ) diff --git a/core/codegen/include/codegen/CodegenException.hpp b/core/codegen/include/codegen/CodegenException.hpp new file mode 100644 index 000000000..5f9eb0008 --- /dev/null +++ b/core/codegen/include/codegen/CodegenException.hpp @@ -0,0 +1,19 @@ +#ifndef CODEGEN_CODEGENEXCEPTION_HPP +#define CODEGEN_CODEGENEXCEPTION_HPP + +#include +#include +#include + +namespace Codegen { + +struct CodegenException : std::runtime_error { + Domain::FileByteOffset FailureOffset; + + explicit CodegenException(const std::string& message, const Domain::FileByteOffset failureOffset = 0) + : std::runtime_error(message), FailureOffset(failureOffset) {} +}; + +} + +#endif diff --git a/core/codegen/include/codegen/CodegenTypes.hpp b/core/codegen/include/codegen/CodegenTypes.hpp new file mode 100644 index 000000000..e87d8dfaf --- /dev/null +++ b/core/codegen/include/codegen/CodegenTypes.hpp @@ -0,0 +1,38 @@ +#ifndef CODEGEN_CODEGENTYPES_HPP +#define CODEGEN_CODEGENTYPES_HPP + +#include +#include +#include + +namespace Codegen { + +struct DecodedInstruction { + std::size_t Length; + bool Valid; +}; + +struct InstructionMatch { + Domain::FileByteOffset Offset; + std::size_t Length; +}; + +struct RewriteRequest { + Domain::FileByteOffset Offset; + std::vector NewBytes; +}; + +struct AddressAdjustment { + Domain::FileByteOffset OriginalOffset; + Domain::FileByteOffset AdjustedOffset; + std::int64_t Delta; +}; + +struct RewriteResult { + std::vector Bytes; + std::vector Adjustments; +}; + +} + +#endif diff --git a/core/codegen/include/codegen/IInstructionDecoder.hpp b/core/codegen/include/codegen/IInstructionDecoder.hpp new file mode 100644 index 000000000..d833ef26d --- /dev/null +++ b/core/codegen/include/codegen/IInstructionDecoder.hpp @@ -0,0 +1,21 @@ +#ifndef CODEGEN_IINSTRUCTIONDECODER_HPP +#define CODEGEN_IINSTRUCTIONDECODER_HPP + +#include +#include + +namespace Codegen { + +class IInstructionDecoder { +public: + virtual ~IInstructionDecoder() = default; + + [[nodiscard]] virtual DecodedInstruction Decode( + const std::uint8_t* data, + std::size_t available + ) const = 0; +}; + +} + +#endif diff --git a/core/codegen/include/codegen/IInstructionRewriter.hpp b/core/codegen/include/codegen/IInstructionRewriter.hpp new file mode 100644 index 000000000..12a3fbc45 --- /dev/null +++ b/core/codegen/include/codegen/IInstructionRewriter.hpp @@ -0,0 +1,21 @@ +#ifndef CODEGEN_IINSTRUCTIONREWRITER_HPP +#define CODEGEN_IINSTRUCTIONREWRITER_HPP + +#include +#include + +namespace Codegen { + +class IInstructionRewriter { +public: + virtual ~IInstructionRewriter() = default; + + [[nodiscard]] virtual RewriteResult Rewrite( + const std::vector& codeSection, + const RewriteRequest& request + ) const = 0; +}; + +} + +#endif diff --git a/core/codegen/include/codegen/IInstructionScanner.hpp b/core/codegen/include/codegen/IInstructionScanner.hpp new file mode 100644 index 000000000..1f639525f --- /dev/null +++ b/core/codegen/include/codegen/IInstructionScanner.hpp @@ -0,0 +1,26 @@ +#ifndef CODEGEN_IINSTRUCTIONSCANNER_HPP +#define CODEGEN_IINSTRUCTIONSCANNER_HPP + +#include +#include +#include +#include + +namespace Codegen { + +class IInstructionScanner { +public: + virtual ~IInstructionScanner() = default; + + [[nodiscard]] virtual std::vector ScanCodeSection( + const std::vector& codeSection, + Domain::FileByteOffset codeSectionOffset, + Domain::FileByteOffset codeSectionSize + ) const = 0; +}; + +std::unique_ptr MakeInstructionScanner(); + +} + +#endif diff --git a/core/codegen/include/codegen/x86/X64InstructionDecoder.hpp b/core/codegen/include/codegen/x86/X64InstructionDecoder.hpp new file mode 100644 index 000000000..ee8d8447d --- /dev/null +++ b/core/codegen/include/codegen/x86/X64InstructionDecoder.hpp @@ -0,0 +1,18 @@ +#ifndef CODEGEN_X86_X64INSTRUCTIONDECODER_HPP +#define CODEGEN_X86_X64INSTRUCTIONDECODER_HPP + +#include + +namespace Codegen { + +class X64InstructionDecoder : public IInstructionDecoder { +public: + [[nodiscard]] DecodedInstruction Decode( + const std::uint8_t* data, + std::size_t available + ) const override; +}; + +} + +#endif diff --git a/core/codegen/include/codegen/x86/X64InstructionRewriter.hpp b/core/codegen/include/codegen/x86/X64InstructionRewriter.hpp new file mode 100644 index 000000000..e0da8063a --- /dev/null +++ b/core/codegen/include/codegen/x86/X64InstructionRewriter.hpp @@ -0,0 +1,18 @@ +#ifndef CODEGEN_X86_X64INSTRUCTIONREWRITER_HPP +#define CODEGEN_X86_X64INSTRUCTIONREWRITER_HPP + +#include + +namespace Codegen { + +class X64InstructionRewriter : public IInstructionRewriter { +public: + [[nodiscard]] RewriteResult Rewrite( + const std::vector& codeSection, + const RewriteRequest& request + ) const override; +}; + +} + +#endif diff --git a/core/codegen/src/InstructionScanner.cpp b/core/codegen/src/InstructionScanner.cpp new file mode 100644 index 000000000..080fcee34 --- /dev/null +++ b/core/codegen/src/InstructionScanner.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +namespace Codegen { + +class InstructionScanner : public IInstructionScanner { +public: + [[nodiscard]] std::vector ScanCodeSection( + const std::vector& codeSection, + Domain::FileByteOffset codeSectionOffset, + Domain::FileByteOffset codeSectionSize + ) const override; +}; + +std::vector InstructionScanner::ScanCodeSection( + const std::vector& codeSection, + const Domain::FileByteOffset codeSectionOffset, + const Domain::FileByteOffset codeSectionSize +) const { + const std::size_t limit = std::min(codeSection.size(), static_cast(codeSectionSize)); + + std::vector matches; + std::size_t i = 0; + + while (i < limit) { + X64InstructionDecoder decoder; + const std::uint8_t* cursor = codeSection.data() + i; + const std::size_t available = limit - i; + + const auto decoded = decoder.Decode(cursor, available); + + if (!decoded.Valid || decoded.Length == 0) { + i += 1; + continue; + } + + matches.push_back({codeSectionOffset + i, decoded.Length}); + + i += decoded.Length; + } + + return matches; +} + +std::unique_ptr MakeInstructionScanner() { + return std::make_unique(); +} + +} diff --git a/core/codegen/src/x86/X64InstructionDecoder.cpp b/core/codegen/src/x86/X64InstructionDecoder.cpp new file mode 100644 index 000000000..df8ec0cba --- /dev/null +++ b/core/codegen/src/x86/X64InstructionDecoder.cpp @@ -0,0 +1,183 @@ +#include + +namespace Codegen { + +DecodedInstruction X64InstructionDecoder::Decode(const std::uint8_t* data, std::size_t available) const { + std::size_t pos = 0; + bool rexPresent = false; + std::uint8_t rex = 0; + bool operandSizeOverride = false; + + while (pos < available) { + const std::uint8_t b = data[pos]; + + if (b == 0xF0 || b == 0xF2 || b == 0xF3 || + b == 0x2E || b == 0x36 || b == 0x3E || b == 0x26 || + b == 0x64 || b == 0x65) { + pos += 1; + continue; + } + + if (b == 0x66) { + operandSizeOverride = true; + pos += 1; + continue; + } + + if (b == 0x67) { + pos += 1; + continue; + } + + break; + } + + if (pos < available && data[pos] >= 0x40 && data[pos] <= 0x4F) { + rexPresent = true; + rex = data[pos]; + pos += 1; + } + + if (pos >= available) { + return {1, false}; + } + + std::uint8_t opcode = data[pos]; + pos += 1; + bool twoByteOpcode = false; + + if (opcode == 0x0F) { + if (pos >= available) { + return {1, false}; + } + twoByteOpcode = true; + opcode = data[pos]; + pos += 1; + } + + bool hasModRm = false; + std::size_t immediateSize = 0; + + if (!twoByteOpcode) { + if ((opcode >= 0x00 && opcode <= 0x03) || (opcode >= 0x08 && opcode <= 0x0B) || + (opcode >= 0x10 && opcode <= 0x13) || (opcode >= 0x18 && opcode <= 0x1B) || + (opcode >= 0x20 && opcode <= 0x23) || (opcode >= 0x28 && opcode <= 0x2B) || + (opcode >= 0x30 && opcode <= 0x33) || (opcode >= 0x38 && opcode <= 0x3B) || + opcode == 0x62 || opcode == 0x63 || + (opcode >= 0x84 && opcode <= 0x8F) || + opcode == 0xC0 || opcode == 0xC1 || + opcode == 0xC4 || opcode == 0xC5 || opcode == 0xC6 || opcode == 0xC7 || + opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3 || + opcode == 0xF6 || opcode == 0xF7 || + opcode == 0xFE || opcode == 0xFF || + (opcode >= 0x88 && opcode <= 0x8B)) { + hasModRm = true; + } + + if (opcode == 0xC6 || opcode == 0x80 || + opcode == 0x04 || opcode == 0x0C || + opcode == 0x14 || opcode == 0x1C || + opcode == 0x24 || opcode == 0x2C || + opcode == 0x34 || opcode == 0x3C || + (opcode >= 0xB0 && opcode <= 0xB7) || + opcode == 0x6A || + opcode == 0xA8) { + immediateSize = 1; + } else if (opcode == 0xC7 || opcode == 0x81 || + opcode == 0x05 || opcode == 0x0D || + opcode == 0x15 || opcode == 0x1D || + opcode == 0x25 || opcode == 0x2D || + opcode == 0x35 || opcode == 0x3D || + opcode == 0x68 || + opcode == 0xA9 || + (opcode >= 0xB8 && opcode <= 0xBF)) { + if (opcode >= 0xB8 && opcode <= 0xBF) { + immediateSize = (rexPresent && (rex & 0x08) != 0) ? 8 : (operandSizeOverride ? 2 : 4); + } else { + immediateSize = operandSizeOverride ? 2 : 4; + } + } else if (opcode == 0x83) { + immediateSize = 1; + } + + if (opcode >= 0x70 && opcode <= 0x7F) { + immediateSize = 1; + } + + if (opcode == 0xE8 || opcode == 0xE9) { + immediateSize = 4; + } + + if (opcode == 0xEB) { + immediateSize = 1; + } + } else { + if (opcode >= 0x80 && opcode <= 0x8F) { + immediateSize = 4; + } else if (opcode == 0xBA) { + hasModRm = true; + immediateSize = 1; + } else if (opcode == 0xA4 || opcode == 0xAC) { + immediateSize = 1; + } else if ((opcode >= 0x40 && opcode <= 0x4F) || + (opcode >= 0xA3 && opcode <= 0xAB) || + (opcode >= 0xB0 && opcode <= 0xB7) || + (opcode >= 0xBC && opcode <= 0xBF) || + (opcode >= 0x10 && opcode <= 0x17) || + (opcode >= 0x28 && opcode <= 0x2F) || + (opcode >= 0x38 && opcode <= 0x3A) || + (opcode >= 0x54 && opcode <= 0x77) || + (opcode >= 0xD0 && opcode <= 0xFE) || + opcode == 0x1F) { + hasModRm = true; + } + } + + if (!hasModRm) { + if (pos + immediateSize > available) { + return {pos + immediateSize, false}; + } + return {pos + immediateSize, true}; + } + + if (pos >= available) { + return {pos, false}; + } + + const std::uint8_t modrm = data[pos]; + pos += 1; + + const std::uint8_t mod = static_cast((modrm >> 6) & 0x03); + const std::uint8_t rm = static_cast(modrm & 0x07); + + if (mod != 0x03 && rm == 0x04) { + if (pos >= available) { + return {pos, false}; + } + const std::uint8_t sib = data[pos]; + const std::uint8_t base = static_cast(sib & 0x07); + pos += 1; + + if (mod == 0x00 && base == 0x05) { + pos += 4; + } + } + + if (mod == 0x01) { + pos += 1; + } else if (mod == 0x02) { + pos += 4; + } else if (mod == 0x00 && rm == 0x05) { + pos += 4; + } + + pos += immediateSize; + + if (pos > available) { + return {pos, false}; + } + + return {pos, true}; +} + +} diff --git a/core/relinker/src/analysis/SyscallScanner.cpp b/core/relinker/src/analysis/SyscallScanner.cpp index 787428bd8..f57cede70 100644 --- a/core/relinker/src/analysis/SyscallScanner.cpp +++ b/core/relinker/src/analysis/SyscallScanner.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -13,192 +14,6 @@ static constexpr std::uint8_t SYSENTER_BYTE1 = 0x34; static constexpr std::uint8_t SYSRET_BYTE0 = 0x0F; static constexpr std::uint8_t SYSRET_BYTE1 = 0x07; -class X64InstructionLengthDecoder { -public: - struct DecodedInstruction { - std::size_t Length; - bool Valid; - }; - - [[nodiscard]] DecodedInstruction Decode(const std::uint8_t* data, std::size_t available) const { - std::size_t pos = 0; - bool rexPresent = false; - std::uint8_t rex = 0; - bool operandSizeOverride = false; - - while (pos < available) { - const std::uint8_t b = data[pos]; - - if (b == 0xF0 || b == 0xF2 || b == 0xF3 || - b == 0x2E || b == 0x36 || b == 0x3E || b == 0x26 || - b == 0x64 || b == 0x65) { - pos += 1; - continue; - } - - if (b == 0x66) { - operandSizeOverride = true; - pos += 1; - continue; - } - - if (b == 0x67) { - pos += 1; - continue; - } - - break; - } - - if (pos < available && data[pos] >= 0x40 && data[pos] <= 0x4F) { - rexPresent = true; - rex = data[pos]; - pos += 1; - } - - if (pos >= available) { - return {1, false}; - } - - std::uint8_t opcode = data[pos]; - pos += 1; - bool twoByteOpcode = false; - - if (opcode == 0x0F) { - if (pos >= available) { - return {1, false}; - } - twoByteOpcode = true; - opcode = data[pos]; - pos += 1; - } - - bool hasModRm = false; - std::size_t immediateSize = 0; - - if (!twoByteOpcode) { - if ((opcode >= 0x00 && opcode <= 0x03) || (opcode >= 0x08 && opcode <= 0x0B) || - (opcode >= 0x10 && opcode <= 0x13) || (opcode >= 0x18 && opcode <= 0x1B) || - (opcode >= 0x20 && opcode <= 0x23) || (opcode >= 0x28 && opcode <= 0x2B) || - (opcode >= 0x30 && opcode <= 0x33) || (opcode >= 0x38 && opcode <= 0x3B) || - opcode == 0x62 || opcode == 0x63 || - (opcode >= 0x84 && opcode <= 0x8F) || - opcode == 0xC0 || opcode == 0xC1 || - opcode == 0xC4 || opcode == 0xC5 || opcode == 0xC6 || opcode == 0xC7 || - opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3 || - opcode == 0xF6 || opcode == 0xF7 || - opcode == 0xFE || opcode == 0xFF || - (opcode >= 0x88 && opcode <= 0x8B)) { - hasModRm = true; - } - - if (opcode == 0xC6 || opcode == 0x80 || - opcode == 0x04 || opcode == 0x0C || - opcode == 0x14 || opcode == 0x1C || - opcode == 0x24 || opcode == 0x2C || - opcode == 0x34 || opcode == 0x3C || - (opcode >= 0xB0 && opcode <= 0xB7) || - opcode == 0x6A || - opcode == 0xA8) { - immediateSize = 1; - } else if (opcode == 0xC7 || opcode == 0x81 || - opcode == 0x05 || opcode == 0x0D || - opcode == 0x15 || opcode == 0x1D || - opcode == 0x25 || opcode == 0x2D || - opcode == 0x35 || opcode == 0x3D || - opcode == 0x68 || - opcode == 0xA9 || - (opcode >= 0xB8 && opcode <= 0xBF)) { - if (opcode >= 0xB8 && opcode <= 0xBF) { - immediateSize = (rexPresent && (rex & 0x08) != 0) ? 8 : (operandSizeOverride ? 2 : 4); - } else { - immediateSize = operandSizeOverride ? 2 : 4; - } - } else if (opcode == 0x83) { - immediateSize = 1; - } - - if (opcode >= 0x70 && opcode <= 0x7F) { - immediateSize = 1; - } - - if (opcode == 0xE8 || opcode == 0xE9) { - immediateSize = 4; - } - - if (opcode == 0xEB) { - immediateSize = 1; - } - } else { - if (opcode >= 0x80 && opcode <= 0x8F) { - immediateSize = 4; - } else if (opcode == 0xBA) { - hasModRm = true; - immediateSize = 1; - } else if (opcode == 0xA4 || opcode == 0xAC) { - immediateSize = 1; - } else if ((opcode >= 0x40 && opcode <= 0x4F) || - (opcode >= 0xA3 && opcode <= 0xAB) || - (opcode >= 0xB0 && opcode <= 0xB7) || - (opcode >= 0xBC && opcode <= 0xBF) || - (opcode >= 0x10 && opcode <= 0x17) || - (opcode >= 0x28 && opcode <= 0x2F) || - (opcode >= 0x38 && opcode <= 0x3A) || - (opcode >= 0x54 && opcode <= 0x77) || - (opcode >= 0xD0 && opcode <= 0xFE) || - opcode == 0x1F) { - hasModRm = true; - } - } - - if (!hasModRm) { - if (pos + immediateSize > available) { - return {pos + immediateSize, false}; - } - return {pos + immediateSize, true}; - } - - if (pos >= available) { - return {pos, false}; - } - - const std::uint8_t modrm = data[pos]; - pos += 1; - - const std::uint8_t mod = static_cast((modrm >> 6) & 0x03); - const std::uint8_t rm = static_cast(modrm & 0x07); - - if (mod != 0x03 && rm == 0x04) { - if (pos >= available) { - return {pos, false}; - } - const std::uint8_t sib = data[pos]; - const std::uint8_t base = static_cast(sib & 0x07); - pos += 1; - - if (mod == 0x00 && base == 0x05) { - pos += 4; - } - } - - if (mod == 0x01) { - pos += 1; - } else if (mod == 0x02) { - pos += 4; - } else if (mod == 0x00 && rm == 0x05) { - pos += 4; - } - - pos += immediateSize; - - if (pos > available) { - return {pos, false}; - } - - return {pos, true}; - } -}; - class SyscallScanner : public ISyscallScanner { public: void ScanCodeSectionForSyscalls( @@ -213,16 +28,15 @@ void SyscallScanner::ScanCodeSectionForSyscalls( const FileByteOffset codeSectionSize) { const std::size_t limit = std::min(codeSection.size(), static_cast(codeSectionSize)); - std::size_t i = 0; + const auto scanner = Codegen::MakeInstructionScanner(); + const auto matches = scanner->ScanCodeSection(codeSection, codeSectionOffset, codeSectionSize); - while (i < limit) { - X64InstructionLengthDecoder decoder; - const std::uint8_t* cursor = codeSection.data() + i; - const std::size_t available = limit - i; + for (const auto& [Offset, Length] : matches) { + const std::size_t i = Offset - codeSectionOffset; - if (available >= 2) { - const std::uint8_t b0 = cursor[0]; - const std::uint8_t b1 = cursor[1]; + if (i + 1 < limit) { + const std::uint8_t b0 = codeSection[i]; + const std::uint8_t b1 = codeSection[i + 1]; const bool isSyscall = (b0 == SYSCALL_BYTE0 && b1 == SYSCALL_BYTE1); const bool isInt80 = (b0 == INT80_BYTE0 && b1 == INT80_BYTE1); @@ -230,21 +44,12 @@ void SyscallScanner::ScanCodeSectionForSyscalls( const bool isSysret = (b0 == SYSRET_BYTE0 && b1 == SYSRET_BYTE1); if (isSyscall || isInt80 || isSysenter || isSysret) { - FileByteOffset instrOffset = codeSectionOffset + i; + const FileByteOffset instrOffset = codeSectionOffset + i; std::ostringstream msg; msg << "Forbidden syscall instruction at code offset 0x" << std::hex << instrOffset; throw RelinkerException(msg.str(), instrOffset); } } - - const auto decoded = decoder.Decode(cursor, available); - - if (!decoded.Valid || decoded.Length == 0) { - i += 1; - continue; - } - - i += decoded.Length; } }