diff --git a/include/omath/utility/elf_pattern_scan.hpp b/include/omath/utility/elf_pattern_scan.hpp index 2198aab..b75d0eb 100644 --- a/include/omath/utility/elf_pattern_scan.hpp +++ b/include/omath/utility/elf_pattern_scan.hpp @@ -2,12 +2,13 @@ // Created by Vladislav on 30.12.2025. // #pragma once +#include "pattern_scan.hpp" +#include "section_scan_result.hpp" #include #include #include #include #include -#include "section_scan_result.hpp" namespace omath { class ElfPatternScanner final @@ -18,14 +19,74 @@ namespace omath scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, const std::string_view& target_section_name = ".text"); + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_loaded_module(const void* module_base_address, + const std::string_view& target_section_name = ".text") + { + return scan_for_pattern_in_loaded_module(module_base_address, target_section_name, + &ElfPatternScanner::scan_section_for_pattern); + } + [[nodiscard]] static std::optional scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, const std::string_view& target_section_name = ".text"); + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name = ".text") + { + return scan_for_pattern_in_file(path_to_file, target_section_name, + &ElfPatternScanner::scan_section_for_pattern); + } + [[nodiscard]] static std::optional scan_for_pattern_in_memory_file(std::span file_data, const std::string_view& pattern, const std::string_view& target_section_name = ".text"); + + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_memory_file(std::span file_data, + const std::string_view& target_section_name = ".text") + { + return scan_for_pattern_in_memory_file(file_data, target_section_name, + &ElfPatternScanner::scan_section_for_pattern); + } + + private: + using SectionScanFunction = std::optional (*)(std::span); + + template + [[nodiscard]] + static std::optional scan_section_for_pattern(const std::span section_data) + { + const auto result = PatternScanner::scan_for_pattern(section_data.begin(), section_data.end()); + + if (result == section_data.end()) + return std::nullopt; + + return result - section_data.begin(); + } + + [[nodiscard]] + static std::optional + scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name, + SectionScanFunction scan_pattern); + + [[nodiscard]] + static std::optional scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name, + SectionScanFunction scan_pattern); + + [[nodiscard]] + static std::optional + scan_for_pattern_in_memory_file(std::span file_data, + const std::string_view& target_section_name, SectionScanFunction scan_pattern); }; -} // namespace omath \ No newline at end of file +} // namespace omath diff --git a/include/omath/utility/macho_pattern_scan.hpp b/include/omath/utility/macho_pattern_scan.hpp index 0db3d90..aefd76f 100644 --- a/include/omath/utility/macho_pattern_scan.hpp +++ b/include/omath/utility/macho_pattern_scan.hpp @@ -2,12 +2,13 @@ // Created by Copilot on 04.02.2026. // #pragma once +#include "pattern_scan.hpp" +#include "section_scan_result.hpp" #include #include #include #include #include -#include "section_scan_result.hpp" namespace omath { class MachOPatternScanner final @@ -18,14 +19,74 @@ namespace omath scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, const std::string_view& target_section_name = "__text"); + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_loaded_module(const void* module_base_address, + const std::string_view& target_section_name = "__text") + { + return scan_for_pattern_in_loaded_module(module_base_address, target_section_name, + &MachOPatternScanner::scan_section_for_pattern); + } + [[nodiscard]] static std::optional scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, const std::string_view& target_section_name = "__text"); + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name = "__text") + { + return scan_for_pattern_in_file(path_to_file, target_section_name, + &MachOPatternScanner::scan_section_for_pattern); + } + [[nodiscard]] static std::optional scan_for_pattern_in_memory_file(std::span file_data, const std::string_view& pattern, const std::string_view& target_section_name = "__text"); + + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_memory_file(std::span file_data, + const std::string_view& target_section_name = "__text") + { + return scan_for_pattern_in_memory_file(file_data, target_section_name, + &MachOPatternScanner::scan_section_for_pattern); + } + + private: + using SectionScanFunction = std::optional (*)(std::span); + + template + [[nodiscard]] + static std::optional scan_section_for_pattern(const std::span section_data) + { + const auto result = PatternScanner::scan_for_pattern(section_data.begin(), section_data.end()); + + if (result == section_data.end()) + return std::nullopt; + + return result - section_data.begin(); + } + + [[nodiscard]] + static std::optional + scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name, + SectionScanFunction scan_pattern); + + [[nodiscard]] + static std::optional scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name, + SectionScanFunction scan_pattern); + + [[nodiscard]] + static std::optional + scan_for_pattern_in_memory_file(std::span file_data, + const std::string_view& target_section_name, SectionScanFunction scan_pattern); }; } // namespace omath diff --git a/include/omath/utility/pattern_scan.hpp b/include/omath/utility/pattern_scan.hpp index 9f8dd93..d3d5679 100644 --- a/include/omath/utility/pattern_scan.hpp +++ b/include/omath/utility/pattern_scan.hpp @@ -3,9 +3,12 @@ // #pragma once +#include +#include #include #include #include +#include #include #include @@ -15,6 +18,8 @@ class unit_test_pattern_scan_corner_case_1_Test; class unit_test_pattern_scan_corner_case_2_Test; class unit_test_pattern_scan_corner_case_3_Test; class unit_test_pattern_scan_corner_case_4_Test; +class unit_test_pattern_scan_consteval_read_test_Test; +class unit_test_pattern_scan_consteval_spacing_and_case_Test; // ReSharper restore CppInconsistentNaming namespace omath { @@ -29,8 +34,21 @@ namespace omath friend unit_test_pattern_scan_corner_case_2_Test; friend unit_test_pattern_scan_corner_case_3_Test; friend unit_test_pattern_scan_corner_case_4_Test; + friend unit_test_pattern_scan_consteval_read_test_Test; + friend unit_test_pattern_scan_consteval_spacing_and_case_Test; public: + template + struct fixed_string final + { + char value[N]{}; + + constexpr fixed_string(const char (&text)[N]) + { + std::ranges::copy(text, value); + } + }; + [[nodiscard]] static std::span::iterator scan_for_pattern(const std::span& range, const std::string_view& pattern); @@ -49,9 +67,26 @@ namespace omath if (!parsed_pattern) [[unlikely]] return end; + return scan_for_parsed_pattern(begin, end, parsed_pattern.value()); + } + template + requires std::input_or_output_iterator> + static IteratorType scan_for_pattern(const IteratorType& begin, const IteratorType& end) + { + constexpr auto parsed_pattern = parse_pattern(); + + return scan_for_parsed_pattern(begin, end, parsed_pattern); + } + + private: + template + requires std::input_or_output_iterator> + static IteratorType scan_for_parsed_pattern(const IteratorType& begin, const IteratorType& end, + const ParsedPattern& parsed_pattern) + { const auto whole_range_size = static_cast(std::distance(begin, end)); - const auto pattern_size = static_cast(parsed_pattern->size()); + const auto pattern_size = static_cast(parsed_pattern.size()); const std::ptrdiff_t scan_size = whole_range_size - pattern_size; if (scan_size < 0) @@ -61,9 +96,9 @@ namespace omath { bool found = true; - for (std::ptrdiff_t j = 0; j < static_cast(parsed_pattern->size()); j++) + for (std::ptrdiff_t j = 0; j < static_cast(parsed_pattern.size()); j++) { - found = parsed_pattern->at(j) == std::nullopt || parsed_pattern->at(j) == *(begin + i + j); + found = parsed_pattern.at(j) == std::nullopt || parsed_pattern.at(j) == *(begin + i + j); if (!found) break; @@ -73,10 +108,97 @@ namespace omath } return end; } - - private: [[nodiscard]] static std::expected>, PatternScanError> parse_pattern(const std::string_view& pattern_string); + + [[nodiscard]] + constexpr static bool is_space(const char c) + { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + + [[nodiscard]] + constexpr static int hex_value(const char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; + } + template + [[nodiscard]] + static consteval std::size_t signature_size() + { + std::size_t count = 0; + bool in_token = false; + + for (std::size_t i = 0; i + 1 < sizeof(Pattern.value); ++i) + { + if (is_space(Pattern.value[i])) + { + in_token = false; + } + else if (!in_token) + { + ++count; + in_token = true; + } + } + + return count; + } + + template + static consteval std::array, signature_size()> parse_pattern() + { + std::array, signature_size()> result{}; + std::size_t out = 0; + std::size_t i = 0; + + while (i + 1 < sizeof(Pattern.value)) + { + while (i + 1 < sizeof(Pattern.value) && is_space(Pattern.value[i])) + ++i; + + const std::size_t token_start = i; + + while (i + 1 < sizeof(Pattern.value) && !is_space(Pattern.value[i])) + ++i; + + const std::size_t token_size = i - token_start; + + if (token_size == 0) + continue; + + // ReSharper disable once CppTooWideScope + const bool is_wildcard = (token_size == 1 || token_size == 2) && Pattern.value[token_start] == '?'; + + if (is_wildcard) + { + if (token_size == 2 && Pattern.value[token_start + 1] != '?') + throw std::logic_error("invalid wildcard token"); + + result[out++] = std::nullopt; + continue; + } + + if (token_size != 2) + throw std::logic_error("invalid byte token"); + + const int high = hex_value(Pattern.value[token_start]); + const int low = hex_value(Pattern.value[token_start + 1]); + + if (high < 0 || low < 0) + throw std::logic_error("invalid hex byte"); + + result[out++] = static_cast((high << 4) | low); + } + + return result; + } }; -} // namespace omath \ No newline at end of file +} // namespace omath diff --git a/include/omath/utility/pe_pattern_scan.hpp b/include/omath/utility/pe_pattern_scan.hpp index 9e0b2d7..7a6067c 100644 --- a/include/omath/utility/pe_pattern_scan.hpp +++ b/include/omath/utility/pe_pattern_scan.hpp @@ -3,12 +3,13 @@ // #pragma once +#include "pattern_scan.hpp" +#include "section_scan_result.hpp" #include #include #include #include #include -#include "section_scan_result.hpp" namespace omath { @@ -20,14 +21,74 @@ namespace omath scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, const std::string_view& target_section_name = ".text"); + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_loaded_module(const void* module_base_address, + const std::string_view& target_section_name = ".text") + { + return scan_for_pattern_in_loaded_module(module_base_address, target_section_name, + &PePatternScanner::scan_section_for_pattern); + } + [[nodiscard]] static std::optional scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, const std::string_view& target_section_name = ".text"); + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name = ".text") + { + return scan_for_pattern_in_file(path_to_file, target_section_name, + &PePatternScanner::scan_section_for_pattern); + } + [[nodiscard]] static std::optional scan_for_pattern_in_memory_file(std::span file_data, const std::string_view& pattern, const std::string_view& target_section_name = ".text"); + + template + [[nodiscard]] + static std::optional + scan_for_pattern_in_memory_file(std::span file_data, + const std::string_view& target_section_name = ".text") + { + return scan_for_pattern_in_memory_file(file_data, target_section_name, + &PePatternScanner::scan_section_for_pattern); + } + + private: + using SectionScanFunction = std::optional (*)(std::span); + + template + [[nodiscard]] + static std::optional scan_section_for_pattern(const std::span section_data) + { + const auto result = PatternScanner::scan_for_pattern(section_data.begin(), section_data.end()); + + if (result == section_data.end()) + return std::nullopt; + + return result - section_data.begin(); + } + + [[nodiscard]] + static std::optional + scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name, + SectionScanFunction scan_pattern); + + [[nodiscard]] + static std::optional scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name, + SectionScanFunction scan_pattern); + + [[nodiscard]] + static std::optional + scan_for_pattern_in_memory_file(std::span file_data, + const std::string_view& target_section_name, SectionScanFunction scan_pattern); }; -} // namespace omath \ No newline at end of file +} // namespace omath diff --git a/source/utility/elf_pattern_scan.cpp b/source/utility/elf_pattern_scan.cpp index e2fcffd..3fce828 100644 --- a/source/utility/elf_pattern_scan.cpp +++ b/source/utility/elf_pattern_scan.cpp @@ -304,8 +304,8 @@ namespace elf_headers); } - template - std::optional scan_in_module_impl(const std::byte* base, const std::string_view pattern, + template + std::optional scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern, const std::string_view target_section_name) { const auto* file_header = reinterpret_cast(base); @@ -336,13 +336,14 @@ namespace continue; const auto* section_begin = base + static_cast(section->sh_addr); - const auto* section_end = section_begin + static_cast(section->sh_size); + const auto scan_range = + std::span{section_begin, static_cast(section->sh_size)}; - const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern); - if (scan_result == section_end) + const auto target_offset = scan_pattern(scan_range); + if (!target_offset.has_value()) return std::nullopt; - return reinterpret_cast(scan_result); + return reinterpret_cast(section_begin + target_offset.value()); } return std::nullopt; @@ -374,15 +375,58 @@ namespace omath if (!arch.has_value()) [[unlikely]] return std::nullopt; + const auto scan_pattern = + [&pattern](const std::span section_data) -> std::optional + { + const auto scan_result = + PatternScanner::scan_for_pattern(section_data.begin(), section_data.end(), pattern); + + if (scan_result == section_data.end()) + return std::nullopt; + + return scan_result - section_data.begin(); + }; + if (arch == FileArch::x64) return scan_in_module_impl(static_cast(module_base_address), - pattern, target_section_name); + scan_pattern, target_section_name); if (arch == FileArch::x32) return scan_in_module_impl(static_cast(module_base_address), - pattern, target_section_name); + scan_pattern, target_section_name); std::unreachable(); } + + std::optional + ElfPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + if (module_base_address == nullptr) [[unlikely]] + return std::nullopt; + + const auto* base = static_cast(module_base_address); + + constexpr std::string_view valid_elf_signature = "\x7F" + "ELF"; + if (std::string_view{reinterpret_cast(base), valid_elf_signature.size()} != valid_elf_signature) + return std::nullopt; + + const auto ei_class_value = static_cast(base[ei_class]); + const auto arch = ei_class_value == elfclass64 ? FileArch::x64 + : ei_class_value == elfclass32 ? FileArch::x32 + : std::optional{}; + if (!arch.has_value()) [[unlikely]] + return std::nullopt; + + if (arch == FileArch::x64) + return scan_in_module_impl(base, scan_pattern, target_section_name); + if (arch == FileArch::x32) + return scan_in_module_impl(base, scan_pattern, target_section_name); + + std::unreachable(); + } + std::optional ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, @@ -405,6 +449,26 @@ namespace omath .target_offset = offset}; } + std::optional + ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + const auto section = get_elf_section_by_name(path_to_file, target_section_name); + + if (!section.has_value()) [[unlikely]] + return std::nullopt; + + const auto target_offset = scan_pattern(std::span{section->data.data(), section->data.size()}); + + if (!target_offset.has_value()) + return std::nullopt; + + return SectionScanResult{.virtual_base_addr = section->virtual_base_addr, + .raw_base_addr = section->raw_base_addr, + .target_offset = target_offset.value()}; + } + std::optional ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span file_data, const std::string_view& pattern, @@ -427,4 +491,24 @@ namespace omath .raw_base_addr = section->raw_base_addr, .target_offset = offset}; } -} // namespace omath \ No newline at end of file + + std::optional + ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span file_data, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + const auto section = get_elf_section_by_name_from_memory(file_data, target_section_name); + + if (!section.has_value()) [[unlikely]] + return std::nullopt; + + const auto target_offset = scan_pattern(std::span{section->data.data(), section->data.size()}); + + if (!target_offset.has_value()) + return std::nullopt; + + return SectionScanResult{.virtual_base_addr = section->virtual_base_addr, + .raw_base_addr = section->raw_base_addr, + .target_offset = target_offset.value()}; + } +} // namespace omath diff --git a/source/utility/macho_pattern_scan.cpp b/source/utility/macho_pattern_scan.cpp index 2e518b4..a6eabd9 100644 --- a/source/utility/macho_pattern_scan.cpp +++ b/source/utility/macho_pattern_scan.cpp @@ -316,8 +316,8 @@ namespace data, section_name); if (magic == mh_magic_32 || magic == mh_cigam_32) - return extract_section_from_memory_impl(data, - section_name); + return extract_section_from_memory_impl( + data, section_name); return std::nullopt; } @@ -344,8 +344,9 @@ namespace return extract_section_impl(file, section_name); } - template - std::optional scan_in_module_impl(const std::byte* base, const std::string_view pattern, + template + std::optional scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern, const std::string_view target_section_name) { const auto* header = reinterpret_cast(base); @@ -374,12 +375,13 @@ namespace continue; } const auto* section_begin = base + static_cast(section->addr); - const auto* section_end = section_begin + static_cast(section->size); + const auto scan_range = + std::span{section_begin, static_cast(section->size)}; - const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern); + const auto target_offset = scan_pattern(scan_range); - if (scan_result != section_end) - return reinterpret_cast(scan_result); + if (target_offset.has_value()) + return reinterpret_cast(section_begin + target_offset.value()); } } @@ -404,12 +406,48 @@ namespace omath std::uint32_t magic{}; std::memcpy(&magic, base, sizeof(magic)); + const auto scan_pattern = + [&pattern](const std::span section_data) -> std::optional + { + const auto scan_result = + PatternScanner::scan_for_pattern(section_data.begin(), section_data.end(), pattern); + + if (scan_result == section_data.end()) + return std::nullopt; + + return scan_result - section_data.begin(); + }; + if (magic == mh_magic_64 || magic == mh_cigam_64) - return scan_in_module_impl(base, pattern, + return scan_in_module_impl(base, scan_pattern, target_section_name); if (magic == mh_magic_32 || magic == mh_cigam_32) - return scan_in_module_impl(base, pattern, + return scan_in_module_impl(base, scan_pattern, + target_section_name); + + return std::nullopt; + } + + std::optional + MachOPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + if (module_base_address == nullptr) [[unlikely]] + return std::nullopt; + + const auto* base = static_cast(module_base_address); + + std::uint32_t magic{}; + std::memcpy(&magic, base, sizeof(magic)); + + if (magic == mh_magic_64 || magic == mh_cigam_64) + return scan_in_module_impl(base, scan_pattern, + target_section_name); + + if (magic == mh_magic_32 || magic == mh_cigam_32) + return scan_in_module_impl(base, scan_pattern, target_section_name); return std::nullopt; @@ -438,6 +476,27 @@ namespace omath .target_offset = offset}; } + std::optional + MachOPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + const auto macho_section = get_macho_section_by_name(path_to_file, target_section_name); + + if (!macho_section.has_value()) [[unlikely]] + return std::nullopt; + + const auto target_offset = + scan_pattern(std::span{macho_section->data.data(), macho_section->data.size()}); + + if (!target_offset.has_value()) + return std::nullopt; + + return SectionScanResult{.virtual_base_addr = macho_section->virtual_base_addr, + .raw_base_addr = macho_section->raw_base_addr, + .target_offset = target_offset.value()}; + } + std::optional MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span file_data, const std::string_view& pattern, @@ -460,4 +519,24 @@ namespace omath .raw_base_addr = section->raw_base_addr, .target_offset = offset}; } + + std::optional + MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span file_data, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + const auto section = get_macho_section_by_name_from_memory(file_data, target_section_name); + + if (!section.has_value()) [[unlikely]] + return std::nullopt; + + const auto target_offset = scan_pattern(std::span{section->data.data(), section->data.size()}); + + if (!target_offset.has_value()) + return std::nullopt; + + return SectionScanResult{.virtual_base_addr = section->virtual_base_addr, + .raw_base_addr = section->raw_base_addr, + .target_offset = target_offset.value()}; + } } // namespace omath diff --git a/source/utility/pe_pattern_scan.cpp b/source/utility/pe_pattern_scan.cpp index ab40c12..08906a3 100644 --- a/source/utility/pe_pattern_scan.cpp +++ b/source/utility/pe_pattern_scan.cpp @@ -234,8 +234,12 @@ namespace constexpr bool invalid_nt_header_file(const NtHeaderVariant& variant) { constexpr std::uint32_t nt_hdr_magic = 0x4550; - return std::visit([&nt_hdr_magic](const auto& header) -> bool { return header.signature != nt_hdr_magic; }, - variant); + return std::visit( + [&nt_hdr_magic](const auto& header) -> bool + { + return header.signature != nt_hdr_magic; + }, + variant); } struct ExtractedSection final @@ -262,8 +266,7 @@ namespace if (nt_off + sizeof(ImageNtHeaders) > data.size()) return std::nullopt; - const auto* x86_hdrs = - reinterpret_cast*>(data.data() + nt_off); + const auto* x86_hdrs = reinterpret_cast*>(data.data() + nt_off); NtHeaderVariant nt_headers; if (x86_hdrs->optional_header.magic == opt_hdr32_magic) @@ -284,8 +287,8 @@ namespace [&data, §ion_name, nt_off](const auto& concrete_headers) -> std::optional { constexpr std::size_t sig_size = sizeof(concrete_headers.signature); - const auto section_table_off = nt_off + sig_size + sizeof(FileHeader) - + concrete_headers.file_header.size_optional_header; + const auto section_table_off = + nt_off + sig_size + sizeof(FileHeader) + concrete_headers.file_header.size_optional_header; for (std::size_t i = 0; i < concrete_headers.file_header.num_sections; ++i) { @@ -435,6 +438,65 @@ namespace omath }, nt_header_variant.value()); } + + std::optional + PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + const auto base_address = reinterpret_cast(module_base_address); + const auto* base_bytes = static_cast(module_base_address); + + if (!base_address) + return std::nullopt; + + const auto* dos_header = static_cast(module_base_address); + + if (invalid_dos_header_file(*dos_header)) [[unlikely]] + return std::nullopt; + + const auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address); + + if (!nt_header_variant) [[unlikely]] + return std::nullopt; + + return std::visit( + [base_bytes, base_address, lfanew = dos_header->e_lfanew, &target_section_name, + scan_pattern](const auto& nt_header) -> std::optional + { + constexpr std::size_t signature_size = sizeof(nt_header.signature); + const auto section_table_off = static_cast(lfanew) + signature_size + + sizeof(FileHeader) + nt_header.file_header.size_optional_header; + + const auto* section_table = reinterpret_cast(base_bytes + section_table_off); + + for (std::size_t i = 0; i < nt_header.file_header.num_sections; ++i) + { + const auto* section = section_table + i; + + if (std::string_view{section->name, sizeof(section->name)} != target_section_name + || section->size_raw_data == 0) + continue; + + const auto section_size = section->virtual_size != 0 + ? static_cast(section->virtual_size) + : static_cast(section->size_raw_data); + + const auto* section_begin = + reinterpret_cast(base_address + section->virtual_address); + const auto scan_range = std::span{section_begin, section_size}; + + const auto target_offset = scan_pattern(scan_range); + + if (target_offset.has_value()) + return reinterpret_cast(section_begin + target_offset.value()); + } + + return std::nullopt; + }, + nt_header_variant.value()); + } + std::optional PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, @@ -457,6 +519,27 @@ namespace omath .target_offset = offset}; } + std::optional + PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + const auto pe_section = extract_section_from_pe_file(path_to_file, target_section_name); + + if (!pe_section.has_value()) [[unlikely]] + return std::nullopt; + + const auto target_offset = + scan_pattern(std::span{pe_section->data.data(), pe_section->data.size()}); + + if (!target_offset.has_value()) + return std::nullopt; + + return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr, + .raw_base_addr = pe_section->raw_base_addr, + .target_offset = target_offset.value()}; + } + std::optional PePatternScanner::scan_for_pattern_in_memory_file(const std::span file_data, const std::string_view& pattern, @@ -479,4 +562,25 @@ namespace omath .raw_base_addr = pe_section->raw_base_addr, .target_offset = offset}; } -} // namespace omath \ No newline at end of file + + std::optional + PePatternScanner::scan_for_pattern_in_memory_file(const std::span file_data, + const std::string_view& target_section_name, + const SectionScanFunction scan_pattern) + { + const auto pe_section = extract_section_from_pe_memory(file_data, target_section_name); + + if (!pe_section.has_value()) [[unlikely]] + return std::nullopt; + + const auto target_offset = + scan_pattern(std::span{pe_section->data.data(), pe_section->data.size()}); + + if (!target_offset.has_value()) + return std::nullopt; + + return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr, + .raw_base_addr = pe_section->raw_base_addr, + .target_offset = target_offset.value()}; + } +} // namespace omath diff --git a/tests/general/unit_test_elf_scanner.cpp b/tests/general/unit_test_elf_scanner.cpp index bfe3f71..1a97285 100644 --- a/tests/general/unit_test_elf_scanner.cpp +++ b/tests/general/unit_test_elf_scanner.cpp @@ -35,13 +35,22 @@ static std::vector make_elf64_with_text_section(const std::vector buf(total_size, std::byte{0}); - auto w8 = [&](std::size_t off, std::uint8_t v) { buf[off] = std::byte{v}; }; + auto w8 = [&](std::size_t off, std::uint8_t v) + { + buf[off] = std::byte{v}; + }; auto w16 = [&](std::size_t off, std::uint16_t v) - { std::memcpy(buf.data() + off, &v, 2); }; + { + std::memcpy(buf.data() + off, &v, 2); + }; auto w32 = [&](std::size_t off, std::uint32_t v) - { std::memcpy(buf.data() + off, &v, 4); }; + { + std::memcpy(buf.data() + off, &v, 4); + }; auto w64 = [&](std::size_t off, std::uint64_t v) - { std::memcpy(buf.data() + off, &v, 8); }; + { + std::memcpy(buf.data() + off, &v, 8); + }; // --- ELF64 file header --- // e_ident @@ -53,19 +62,19 @@ static std::vector make_elf64_with_text_section(const std::vector(shdr_table_off)); // e_shoff - w32(48, 0); // e_flags + w32(48, 0); // e_flags w16(52, 64); // e_ehsize w16(54, 56); // e_phentsize - w16(56, 0); // e_phnum + w16(56, 0); // e_phnum w16(58, static_cast(shdr_size)); // e_shentsize w16(60, static_cast(num_sections)); // e_shnum - w16(62, 2); // e_shstrndx = 2 (.shstrtab is section index 2) + w16(62, 2); // e_shstrndx = 2 (.shstrtab is section index 2) // --- section data (.text) --- const std::size_t copy_len = std::min(code_bytes.size(), text_size); @@ -104,9 +113,9 @@ static std::vector make_elf64_with_text_section(const std::vector(text_off)); // sh_addr (same as offset in test) w64(base + 24, static_cast(text_off)); // sh_offset w64(base + 32, static_cast(text_size)); // sh_size @@ -116,8 +125,8 @@ static std::vector make_elf64_with_text_section(const std::vector(shstrtab_off)); // sh_offset w64(base + 32, static_cast(shstrtab_size)); // sh_size } @@ -151,6 +160,18 @@ TEST(unit_test_elf_pattern_scan_memory, finds_pattern_with_wildcard) EXPECT_EQ(result->target_offset, 0); } +TEST(unit_test_elf_pattern_scan_memory, consteval_finds_pattern_with_wildcard) +{ + const std::vector code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF}; + const auto buf = make_elf64_with_text_section(code); + + const auto result = + ElfPatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span{buf}, ".text"); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->target_offset, 1); +} + TEST(unit_test_elf_pattern_scan_memory, pattern_not_found_returns_nullopt) { const std::vector code = {0x01, 0x02, 0x03, 0x04}; @@ -182,8 +203,8 @@ TEST(unit_test_elf_pattern_scan_memory, missing_section_returns_nullopt) const std::vector code = {0x90, 0x90}; const auto buf = make_elf64_with_text_section(code); - const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span{buf}, - "90 90", ".nonexistent"); + const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span{buf}, "90 90", + ".nonexistent"); EXPECT_FALSE(result.has_value()); } @@ -201,8 +222,8 @@ TEST(unit_test_elf_pattern_scan_memory, matches_file_scan) } const auto file_result = ElfPatternScanner::scan_for_pattern_in_file(tmp_path, "48 89 E5 DE AD", ".text"); - const auto mem_result = - ElfPatternScanner::scan_for_pattern_in_memory_file(std::span{buf}, "48 89 E5 DE AD", ".text"); + const auto mem_result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span{buf}, + "48 89 E5 DE AD", ".text"); std::filesystem::remove(tmp_path); @@ -212,3 +233,27 @@ TEST(unit_test_elf_pattern_scan_memory, matches_file_scan) EXPECT_EQ(file_result->raw_base_addr, mem_result->raw_base_addr); EXPECT_EQ(file_result->target_offset, mem_result->target_offset); } + +TEST(unit_test_elf_pattern_scan_memory, consteval_file_scan_finds_pattern) +{ + const std::vector code = {0x48, 0x89, 0xE5, 0xDE, 0xAD}; + const auto buf = make_elf64_with_text_section(code); + const auto tmp_path = std::filesystem::temp_directory_path() / "omath_elf_consteval_test.elf"; + { + std::ofstream out(tmp_path, std::ios::binary); + out.write(reinterpret_cast(buf.data()), static_cast(buf.size())); + } + + const auto result = ElfPatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path, ".text"); + + std::filesystem::remove(tmp_path); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->target_offset, 0); +} + +TEST(unit_test_elf_pattern_scan_memory, consteval_loaded_module_null_returns_nullopt) +{ + const auto result = ElfPatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr); + EXPECT_FALSE(result.has_value()); +} diff --git a/tests/general/unit_test_macho_memory_file_scan.cpp b/tests/general/unit_test_macho_memory_file_scan.cpp index a00bdfe..245a9f0 100644 --- a/tests/general/unit_test_macho_memory_file_scan.cpp +++ b/tests/general/unit_test_macho_memory_file_scan.cpp @@ -1,5 +1,7 @@ // Tests for MachOPatternScanner::scan_for_pattern_in_memory_file #include +#include +#include #include #include #include @@ -37,38 +39,43 @@ static std::vector make_macho64_with_text_section(const std::vector(seg_size + sect_hdr_size); // segment + 1 section + constexpr std::uint32_t cmd_size = static_cast(seg_size + sect_hdr_size); // segment + 1 section std::vector buf(total_size, std::byte{0}); - auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, 4); }; - auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, 8); }; + auto w32 = [&](std::size_t off, std::uint32_t v) + { + std::memcpy(buf.data() + off, &v, 4); + }; + auto w64 = [&](std::size_t off, std::uint64_t v) + { + std::memcpy(buf.data() + off, &v, 8); + }; // MachHeader64 w32(0, mh_magic_64); - w32(4, 0x0100000C); // cputype = CPU_TYPE_ARM64 (doesn't matter for scan) - w32(12, 2); // filetype = MH_EXECUTE - w32(16, 1); // ncmds = 1 - w32(20, cmd_size); // sizeofcmds + w32(4, 0x0100000C); // cputype = CPU_TYPE_ARM64 (doesn't matter for scan) + w32(12, 2); // filetype = MH_EXECUTE + w32(16, 1); // ncmds = 1 + w32(20, cmd_size); // sizeofcmds // SegmentCommand64 at 0x20 constexpr std::size_t seg_off = hdr_size; w32(seg_off + 0, lc_segment_64); w32(seg_off + 4, cmd_size); std::memcpy(buf.data() + seg_off + 8, "__TEXT", 6); // segname - w64(seg_off + 24, text_vmaddr); // vmaddr - w64(seg_off + 32, text_raw_size); // vmsize - w64(seg_off + 40, text_raw_off); // fileoff - w64(seg_off + 48, text_raw_size); // filesize - w32(seg_off + 64, 1); // nsects + w64(seg_off + 24, text_vmaddr); // vmaddr + w64(seg_off + 32, text_raw_size); // vmsize + w64(seg_off + 40, text_raw_off); // fileoff + w64(seg_off + 48, text_raw_size); // filesize + w32(seg_off + 64, 1); // nsects // Section64 at 0x68 constexpr std::size_t sect_off = seg_off + seg_size; - std::memcpy(buf.data() + sect_off + 0, "__text", 6); // sectname + std::memcpy(buf.data() + sect_off + 0, "__text", 6); // sectname std::memcpy(buf.data() + sect_off + 16, "__TEXT", 6); // segname - w64(sect_off + 32, text_vmaddr); // addr - w64(sect_off + 40, text_raw_size); // size + w64(sect_off + 32, text_vmaddr); // addr + w64(sect_off + 40, text_raw_size); // size w32(sect_off + 48, static_cast(text_raw_off)); // offset (file offset) // Section data @@ -105,6 +112,18 @@ TEST(unit_test_macho_memory_file_scan, finds_pattern_with_wildcard) EXPECT_EQ(result->target_offset, 0); } +TEST(unit_test_macho_memory_file_scan, consteval_finds_pattern_with_wildcard) +{ + const std::vector code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF}; + const auto buf = make_macho64_with_text_section(code); + + const auto result = + MachOPatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span{buf}); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->target_offset, 1); +} + TEST(unit_test_macho_memory_file_scan, pattern_not_found_returns_nullopt) { const std::vector code = {0x01, 0x02, 0x03}; @@ -143,3 +162,27 @@ TEST(unit_test_macho_memory_file_scan, raw_addr_and_virtual_addr_correct) EXPECT_EQ(result->raw_base_addr, expected_raw_off); EXPECT_EQ(result->virtual_base_addr, 0x1000u); } + +TEST(unit_test_macho_memory_file_scan, consteval_file_scan_finds_pattern) +{ + const std::vector code = {0x48, 0x89, 0xE5, 0xDE, 0xAD}; + const auto buf = make_macho64_with_text_section(code); + const auto tmp_path = std::filesystem::temp_directory_path() / "omath_macho_consteval_test.bin"; + { + std::ofstream out(tmp_path, std::ios::binary); + out.write(reinterpret_cast(buf.data()), static_cast(buf.size())); + } + + const auto result = MachOPatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path); + + std::filesystem::remove(tmp_path); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->target_offset, 0); +} + +TEST(unit_test_macho_memory_file_scan, consteval_loaded_module_null_returns_nullopt) +{ + const auto result = MachOPatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr); + EXPECT_FALSE(result.has_value()); +} diff --git a/tests/general/unit_test_pattern_scan.cpp b/tests/general/unit_test_pattern_scan.cpp index b974c40..c74091d 100644 --- a/tests/general/unit_test_pattern_scan.cpp +++ b/tests/general/unit_test_pattern_scan.cpp @@ -4,8 +4,8 @@ #include "omath/utility/pe_pattern_scan.hpp" #include "gtest/gtest.h" #include -#include #include +#include TEST(unit_test_pattern_scan, read_test) { const auto result = omath::PatternScanner::parse_pattern("FF ? ?? E9"); @@ -50,4 +50,38 @@ TEST(unit_test_pattern_scan, corner_case_4) { const auto result = omath::PatternScanner::parse_pattern("X ? ?? E9 "); EXPECT_FALSE(result.has_value()); -} \ No newline at end of file +} + +TEST(unit_test_pattern_scan, consteval_read_test) +{ + constexpr auto result = omath::PatternScanner::parse_pattern<"FF ? ?? E9">(); + + static_assert(result.size() == 4); + static_assert(result[0] == static_cast(0xFF)); + static_assert(result[1] == std::nullopt); + static_assert(result[2] == std::nullopt); + static_assert(result[3] == static_cast(0xE9)); + + EXPECT_EQ(result[0], static_cast(0xFF)); + EXPECT_EQ(result[1], std::nullopt); + EXPECT_EQ(result[2], std::nullopt); + EXPECT_EQ(result[3], static_cast(0xE9)); +} + +TEST(unit_test_pattern_scan, consteval_spacing_and_case) +{ + constexpr auto result = omath::PatternScanner::parse_pattern<" \tde\nAD\r?? ? ef ">(); + + static_assert(result.size() == 5); + static_assert(result[0] == static_cast(0xDE)); + static_assert(result[1] == static_cast(0xAD)); + static_assert(result[2] == std::nullopt); + static_assert(result[3] == std::nullopt); + static_assert(result[4] == static_cast(0xEF)); + + EXPECT_EQ(result[0], static_cast(0xDE)); + EXPECT_EQ(result[1], static_cast(0xAD)); + EXPECT_EQ(result[2], std::nullopt); + EXPECT_EQ(result[3], std::nullopt); + EXPECT_EQ(result[4], static_cast(0xEF)); +} diff --git a/tests/general/unit_test_pattern_scan_extra.cpp b/tests/general/unit_test_pattern_scan_extra.cpp index cb37173..d39a591 100644 --- a/tests/general/unit_test_pattern_scan_extra.cpp +++ b/tests/general/unit_test_pattern_scan_extra.cpp @@ -14,6 +14,16 @@ TEST(unit_test_pattern_scan_extra, IteratorScanFound) EXPECT_EQ(std::distance(buf.begin(), it), 0); } +TEST(unit_test_pattern_scan_extra, ConstevalIteratorScan) +{ + std::vector buf = {static_cast(0x00), static_cast(0xDE), + static_cast(0xAD), static_cast(0xBE), + static_cast(0xEF), static_cast(0x11)}; + const auto it = PatternScanner::scan_for_pattern<"DE ?? BE EF">(buf.begin(), buf.end()); + EXPECT_NE(it, buf.end()); + EXPECT_EQ(std::distance(buf.begin(), it), 1); +} + TEST(unit_test_pattern_scan_extra, IteratorScanNotFound) { std::vector buf = {static_cast(0x00), static_cast(0x11), diff --git a/tests/general/unit_test_pe_memory_file_scan.cpp b/tests/general/unit_test_pe_memory_file_scan.cpp index 9143395..af94ef5 100644 --- a/tests/general/unit_test_pe_memory_file_scan.cpp +++ b/tests/general/unit_test_pe_memory_file_scan.cpp @@ -1,5 +1,7 @@ // Tests for PePatternScanner::scan_for_pattern_in_memory_file #include +#include +#include #include #include #include @@ -10,8 +12,7 @@ using namespace omath; // Reuse the fake-module builder from unit_test_pe_pattern_scan_loaded.cpp but // lay out the buffer as a raw PE *file* (ptr_raw_data != virtual_address). static std::vector make_fake_pe_file(std::uint32_t virtual_address, std::uint32_t ptr_raw_data, - std::uint32_t section_size, - const std::vector& code_bytes) + std::uint32_t section_size, const std::vector& code_bytes) { constexpr std::uint32_t e_lfanew = 0x80; constexpr std::uint32_t nt_sig = 0x4550; @@ -24,9 +25,18 @@ static std::vector make_fake_pe_file(std::uint32_t virtual_address, s const std::uint32_t total_size = ptr_raw_data + section_size + 0x100; std::vector buf(total_size, std::byte{0}); - auto w16 = [&](std::size_t off, std::uint16_t v) { std::memcpy(buf.data() + off, &v, 2); }; - auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, 4); }; - auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, 8); }; + auto w16 = [&](std::size_t off, std::uint16_t v) + { + std::memcpy(buf.data() + off, &v, 2); + }; + auto w32 = [&](std::size_t off, std::uint32_t v) + { + std::memcpy(buf.data() + off, &v, 4); + }; + auto w64 = [&](std::size_t off, std::uint64_t v) + { + std::memcpy(buf.data() + off, &v, 8); + }; // DOS header w16(0x00, 0x5A4D); @@ -48,10 +58,10 @@ static std::vector make_fake_pe_file(std::uint32_t virtual_address, s // Section header (.text) const std::size_t sh_off = section_table_off; std::memcpy(buf.data() + sh_off, ".text", 5); - w32(sh_off + 8, section_size); // VirtualSize - w32(sh_off + 12, virtual_address); // VirtualAddress - w32(sh_off + 16, section_size); // SizeOfRawData - w32(sh_off + 20, ptr_raw_data); // PointerToRawData + w32(sh_off + 8, section_size); // VirtualSize + w32(sh_off + 12, virtual_address); // VirtualAddress + w32(sh_off + 16, section_size); // SizeOfRawData + w32(sh_off + 20, ptr_raw_data); // PointerToRawData // Place code at raw file offset const std::size_t copy_len = std::min(code_bytes.size(), static_cast(section_size)); @@ -87,13 +97,24 @@ TEST(unit_test_pe_memory_file_scan, finds_pattern_with_wildcard) EXPECT_EQ(result->target_offset, 0); } +TEST(unit_test_pe_memory_file_scan, consteval_finds_pattern_with_wildcard) +{ + const std::vector code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF}; + const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast(code.size()), code); + + const auto result = + PePatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span{buf}); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->target_offset, 1); +} + TEST(unit_test_pe_memory_file_scan, pattern_not_found_returns_nullopt) { const std::vector code = {0x01, 0x02, 0x03}; const auto buf = make_fake_pe_file(0x1000, 0x400, static_cast(code.size()), code); - const auto result = - PePatternScanner::scan_for_pattern_in_memory_file(std::span{buf}, "AA BB CC"); + const auto result = PePatternScanner::scan_for_pattern_in_memory_file(std::span{buf}, "AA BB CC"); EXPECT_FALSE(result.has_value()); } @@ -126,3 +147,27 @@ TEST(unit_test_pe_memory_file_scan, raw_addr_differs_from_virtual_address) // virtual_base_addr = virtual_address + image_base (image_base = 0) EXPECT_EQ(result->virtual_base_addr, 0x3000u); } + +TEST(unit_test_pe_memory_file_scan, consteval_file_scan_finds_pattern) +{ + const std::vector code = {0x48, 0x89, 0xE5, 0xDE, 0xAD}; + const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast(code.size()), code); + const auto tmp_path = std::filesystem::temp_directory_path() / "omath_pe_consteval_test.exe"; + { + std::ofstream out(tmp_path, std::ios::binary); + out.write(reinterpret_cast(buf.data()), static_cast(buf.size())); + } + + const auto result = PePatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path); + + std::filesystem::remove(tmp_path); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->target_offset, 0); +} + +TEST(unit_test_pe_memory_file_scan, consteval_loaded_module_null_returns_nullopt) +{ + const auto result = PePatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr); + EXPECT_FALSE(result.has_value()); +}