mirror of
https://github.com/orange-cpp/omath.git
synced 2026-06-10 09:14:34 +00:00
Merge pull request #197 from orange-cpp/feature/compile_time_patten_parsing
Feature/compile time patten parsing
This commit is contained in:
@@ -2,12 +2,13 @@
|
||||
// Created by Vladislav on 30.12.2025.
|
||||
//
|
||||
#pragma once
|
||||
#include "pattern_scan.hpp"
|
||||
#include "section_scan_result.hpp"
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#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<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<std::uintptr_t>
|
||||
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<Pattern>);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
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<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
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<Pattern>);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
|
||||
const std::string_view& target_section_name = ".text");
|
||||
|
||||
template<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> 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<Pattern>);
|
||||
}
|
||||
|
||||
private:
|
||||
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
|
||||
|
||||
template<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
|
||||
{
|
||||
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
|
||||
|
||||
if (result == section_data.end())
|
||||
return std::nullopt;
|
||||
|
||||
return result - section_data.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<std::uintptr_t>
|
||||
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<SectionScanResult> 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<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
|
||||
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
|
||||
};
|
||||
} // namespace omath
|
||||
@@ -2,12 +2,13 @@
|
||||
// Created by Copilot on 04.02.2026.
|
||||
//
|
||||
#pragma once
|
||||
#include "pattern_scan.hpp"
|
||||
#include "section_scan_result.hpp"
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#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<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<std::uintptr_t>
|
||||
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<Pattern>);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
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<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
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<Pattern>);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
|
||||
const std::string_view& target_section_name = "__text");
|
||||
|
||||
template<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> 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<Pattern>);
|
||||
}
|
||||
|
||||
private:
|
||||
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
|
||||
|
||||
template<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
|
||||
{
|
||||
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
|
||||
|
||||
if (result == section_data.end())
|
||||
return std::nullopt;
|
||||
|
||||
return result - section_data.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<std::uintptr_t>
|
||||
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<SectionScanResult> 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<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
|
||||
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
|
||||
};
|
||||
} // namespace omath
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
@@ -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<std::size_t N>
|
||||
struct fixed_string final
|
||||
{
|
||||
char value[N]{};
|
||||
|
||||
constexpr fixed_string(const char (&text)[N])
|
||||
{
|
||||
std::ranges::copy(text, value);
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& 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<fixed_string Pattern, class IteratorType>
|
||||
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
|
||||
static IteratorType scan_for_pattern(const IteratorType& begin, const IteratorType& end)
|
||||
{
|
||||
constexpr auto parsed_pattern = parse_pattern<Pattern>();
|
||||
|
||||
return scan_for_parsed_pattern(begin, end, parsed_pattern);
|
||||
}
|
||||
|
||||
private:
|
||||
template<class IteratorType, class ParsedPattern>
|
||||
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
|
||||
static IteratorType scan_for_parsed_pattern(const IteratorType& begin, const IteratorType& end,
|
||||
const ParsedPattern& parsed_pattern)
|
||||
{
|
||||
const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end));
|
||||
|
||||
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern->size());
|
||||
const auto pattern_size = static_cast<std::ptrdiff_t>(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<std::ptrdiff_t>(parsed_pattern->size()); j++)
|
||||
for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(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<std::vector<std::optional<std::byte>>, 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<fixed_string Pattern>
|
||||
[[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<fixed_string Pattern>
|
||||
static consteval std::array<std::optional<std::byte>, signature_size<Pattern>()> parse_pattern()
|
||||
{
|
||||
std::array<std::optional<std::byte>, signature_size<Pattern>()> 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<std::byte>((high << 4) | low);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
} // namespace omath
|
||||
@@ -3,12 +3,13 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "pattern_scan.hpp"
|
||||
#include "section_scan_result.hpp"
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#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<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<std::uintptr_t>
|
||||
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<Pattern>);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
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<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
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<Pattern>);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
|
||||
const std::string_view& target_section_name = ".text");
|
||||
|
||||
template<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> 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<Pattern>);
|
||||
}
|
||||
|
||||
private:
|
||||
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
|
||||
|
||||
template<PatternScanner::fixed_string Pattern>
|
||||
[[nodiscard]]
|
||||
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
|
||||
{
|
||||
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
|
||||
|
||||
if (result == section_data.end())
|
||||
return std::nullopt;
|
||||
|
||||
return result - section_data.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static std::optional<std::uintptr_t>
|
||||
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<SectionScanResult> 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<SectionScanResult>
|
||||
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
|
||||
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
|
||||
};
|
||||
} // namespace omath
|
||||
@@ -304,8 +304,8 @@ namespace
|
||||
elf_headers);
|
||||
}
|
||||
|
||||
template<class FileHeader, class SectionHeader>
|
||||
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
|
||||
template<class FileHeader, class SectionHeader, class ScanPattern>
|
||||
std::optional<std::uintptr_t> 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<const FileHeader*>(base);
|
||||
@@ -336,13 +336,14 @@ namespace
|
||||
continue;
|
||||
|
||||
const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr);
|
||||
const auto* section_end = section_begin + static_cast<std::size_t>(section->sh_size);
|
||||
const auto scan_range =
|
||||
std::span<const std::byte>{section_begin, static_cast<std::size_t>(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<std::uintptr_t>(scan_result);
|
||||
return reinterpret_cast<std::uintptr_t>(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<const std::byte> section_data) -> std::optional<std::ptrdiff_t>
|
||||
{
|
||||
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<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address),
|
||||
pattern, target_section_name);
|
||||
scan_pattern, target_section_name);
|
||||
if (arch == FileArch::x32)
|
||||
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address),
|
||||
pattern, target_section_name);
|
||||
scan_pattern, target_section_name);
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
std::optional<std::uintptr_t>
|
||||
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<const std::byte*>(module_base_address);
|
||||
|
||||
constexpr std::string_view valid_elf_signature = "\x7F"
|
||||
"ELF";
|
||||
if (std::string_view{reinterpret_cast<const char*>(base), valid_elf_signature.size()} != valid_elf_signature)
|
||||
return std::nullopt;
|
||||
|
||||
const auto ei_class_value = static_cast<uint8_t>(base[ei_class]);
|
||||
const auto arch = ei_class_value == elfclass64 ? FileArch::x64
|
||||
: ei_class_value == elfclass32 ? FileArch::x32
|
||||
: std::optional<FileArch>{};
|
||||
if (!arch.has_value()) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
if (arch == FileArch::x64)
|
||||
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(base, scan_pattern, target_section_name);
|
||||
if (arch == FileArch::x32)
|
||||
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(base, scan_pattern, target_section_name);
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
std::optional<SectionScanResult>
|
||||
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<SectionScanResult>
|
||||
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<const std::byte>{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<SectionScanResult>
|
||||
ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
|
||||
const std::string_view& pattern,
|
||||
@@ -427,4 +491,24 @@ namespace omath
|
||||
.raw_base_addr = section->raw_base_addr,
|
||||
.target_offset = offset};
|
||||
}
|
||||
|
||||
std::optional<SectionScanResult>
|
||||
ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> 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<const std::byte>{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
|
||||
@@ -316,8 +316,8 @@ namespace
|
||||
data, section_name);
|
||||
|
||||
if (magic == mh_magic_32 || magic == mh_cigam_32)
|
||||
return extract_section_from_memory_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(data,
|
||||
section_name);
|
||||
return extract_section_from_memory_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(
|
||||
data, section_name);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -344,8 +344,9 @@ namespace
|
||||
return extract_section_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(file, section_name);
|
||||
}
|
||||
|
||||
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
|
||||
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
|
||||
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd,
|
||||
class ScanPattern>
|
||||
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern,
|
||||
const std::string_view target_section_name)
|
||||
{
|
||||
const auto* header = reinterpret_cast<const HeaderType*>(base);
|
||||
@@ -374,12 +375,13 @@ namespace
|
||||
continue;
|
||||
}
|
||||
const auto* section_begin = base + static_cast<std::size_t>(section->addr);
|
||||
const auto* section_end = section_begin + static_cast<std::size_t>(section->size);
|
||||
const auto scan_range =
|
||||
std::span<const std::byte>{section_begin, static_cast<std::size_t>(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<std::uintptr_t>(scan_result);
|
||||
if (target_offset.has_value())
|
||||
return reinterpret_cast<std::uintptr_t>(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<const std::byte> section_data) -> std::optional<std::ptrdiff_t>
|
||||
{
|
||||
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<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, pattern,
|
||||
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, scan_pattern,
|
||||
target_section_name);
|
||||
|
||||
if (magic == mh_magic_32 || magic == mh_cigam_32)
|
||||
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, pattern,
|
||||
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, scan_pattern,
|
||||
target_section_name);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::uintptr_t>
|
||||
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<const std::byte*>(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<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, scan_pattern,
|
||||
target_section_name);
|
||||
|
||||
if (magic == mh_magic_32 || magic == mh_cigam_32)
|
||||
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, scan_pattern,
|
||||
target_section_name);
|
||||
|
||||
return std::nullopt;
|
||||
@@ -438,6 +476,27 @@ namespace omath
|
||||
.target_offset = offset};
|
||||
}
|
||||
|
||||
std::optional<SectionScanResult>
|
||||
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<const std::byte>{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<SectionScanResult>
|
||||
MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> 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<SectionScanResult>
|
||||
MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> 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<const std::byte>{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
|
||||
|
||||
@@ -234,7 +234,11 @@ 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; },
|
||||
return std::visit(
|
||||
[&nt_hdr_magic](const auto& header) -> bool
|
||||
{
|
||||
return header.signature != nt_hdr_magic;
|
||||
},
|
||||
variant);
|
||||
}
|
||||
|
||||
@@ -262,8 +266,7 @@ namespace
|
||||
if (nt_off + sizeof(ImageNtHeaders<NtArchitecture::x32_bit>) > data.size())
|
||||
return std::nullopt;
|
||||
|
||||
const auto* x86_hdrs =
|
||||
reinterpret_cast<const ImageNtHeaders<NtArchitecture::x32_bit>*>(data.data() + nt_off);
|
||||
const auto* x86_hdrs = reinterpret_cast<const ImageNtHeaders<NtArchitecture::x32_bit>*>(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<ExtractedSection>
|
||||
{
|
||||
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<std::uintptr_t>
|
||||
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<std::uintptr_t>(module_base_address);
|
||||
const auto* base_bytes = static_cast<const std::byte*>(module_base_address);
|
||||
|
||||
if (!base_address)
|
||||
return std::nullopt;
|
||||
|
||||
const auto* dos_header = static_cast<const DosHeader*>(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<std::uintptr_t>
|
||||
{
|
||||
constexpr std::size_t signature_size = sizeof(nt_header.signature);
|
||||
const auto section_table_off = static_cast<std::size_t>(lfanew) + signature_size
|
||||
+ sizeof(FileHeader) + nt_header.file_header.size_optional_header;
|
||||
|
||||
const auto* section_table = reinterpret_cast<const SectionHeader*>(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<std::size_t>(section->virtual_size)
|
||||
: static_cast<std::size_t>(section->size_raw_data);
|
||||
|
||||
const auto* section_begin =
|
||||
reinterpret_cast<const std::byte*>(base_address + section->virtual_address);
|
||||
const auto scan_range = std::span<const std::byte>{section_begin, section_size};
|
||||
|
||||
const auto target_offset = scan_pattern(scan_range);
|
||||
|
||||
if (target_offset.has_value())
|
||||
return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value());
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
},
|
||||
nt_header_variant.value());
|
||||
}
|
||||
|
||||
std::optional<SectionScanResult>
|
||||
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<SectionScanResult>
|
||||
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<const std::byte>{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<SectionScanResult>
|
||||
PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
|
||||
const std::string_view& pattern,
|
||||
@@ -479,4 +562,25 @@ namespace omath
|
||||
.raw_base_addr = pe_section->raw_base_addr,
|
||||
.target_offset = offset};
|
||||
}
|
||||
|
||||
std::optional<SectionScanResult>
|
||||
PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> 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<const std::byte>{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
|
||||
@@ -35,13 +35,22 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
|
||||
|
||||
std::vector<std::byte> 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
|
||||
@@ -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<std::uint8_t> 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<const std::byte>{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<std::uint8_t> code = {0x01, 0x02, 0x03, 0x04};
|
||||
@@ -182,8 +203,8 @@ TEST(unit_test_elf_pattern_scan_memory, missing_section_returns_nullopt)
|
||||
const std::vector<std::uint8_t> code = {0x90, 0x90};
|
||||
const auto buf = make_elf64_with_text_section(code);
|
||||
|
||||
const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf},
|
||||
"90 90", ".nonexistent");
|
||||
const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{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<const std::byte>{buf}, "48 89 E5 DE AD", ".text");
|
||||
const auto mem_result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{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<std::uint8_t> 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<const char*>(buf.data()), static_cast<std::streamsize>(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());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Tests for MachOPatternScanner::scan_for_pattern_in_memory_file
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/macho_pattern_scan.hpp>
|
||||
#include <span>
|
||||
@@ -37,13 +39,18 @@ static std::vector<std::byte> make_macho64_with_text_section(const std::vector<s
|
||||
constexpr std::size_t total_size = text_raw_off + text_raw_size;
|
||||
constexpr std::uint64_t text_vmaddr = 0x1000ULL;
|
||||
|
||||
constexpr std::uint32_t cmd_size =
|
||||
static_cast<std::uint32_t>(seg_size + sect_hdr_size); // segment + 1 section
|
||||
constexpr std::uint32_t cmd_size = static_cast<std::uint32_t>(seg_size + sect_hdr_size); // segment + 1 section
|
||||
|
||||
std::vector<std::byte> 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);
|
||||
@@ -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<std::uint8_t> 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<const std::byte>{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<std::uint8_t> 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<std::uint8_t> 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<const char*>(buf.data()), static_cast<std::streamsize>(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());
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include "omath/utility/pe_pattern_scan.hpp"
|
||||
#include "gtest/gtest.h"
|
||||
#include <omath/utility/pattern_scan.hpp>
|
||||
#include <source_location>
|
||||
#include <print>
|
||||
#include <source_location>
|
||||
TEST(unit_test_pattern_scan, read_test)
|
||||
{
|
||||
const auto result = omath::PatternScanner::parse_pattern("FF ? ?? E9");
|
||||
@@ -51,3 +51,37 @@ TEST(unit_test_pattern_scan, corner_case_4)
|
||||
const auto result = omath::PatternScanner::parse_pattern("X ? ?? E9 ");
|
||||
EXPECT_FALSE(result.has_value());
|
||||
}
|
||||
|
||||
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<std::byte>(0xFF));
|
||||
static_assert(result[1] == std::nullopt);
|
||||
static_assert(result[2] == std::nullopt);
|
||||
static_assert(result[3] == static_cast<std::byte>(0xE9));
|
||||
|
||||
EXPECT_EQ(result[0], static_cast<std::byte>(0xFF));
|
||||
EXPECT_EQ(result[1], std::nullopt);
|
||||
EXPECT_EQ(result[2], std::nullopt);
|
||||
EXPECT_EQ(result[3], static_cast<std::byte>(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<std::byte>(0xDE));
|
||||
static_assert(result[1] == static_cast<std::byte>(0xAD));
|
||||
static_assert(result[2] == std::nullopt);
|
||||
static_assert(result[3] == std::nullopt);
|
||||
static_assert(result[4] == static_cast<std::byte>(0xEF));
|
||||
|
||||
EXPECT_EQ(result[0], static_cast<std::byte>(0xDE));
|
||||
EXPECT_EQ(result[1], static_cast<std::byte>(0xAD));
|
||||
EXPECT_EQ(result[2], std::nullopt);
|
||||
EXPECT_EQ(result[3], std::nullopt);
|
||||
EXPECT_EQ(result[4], static_cast<std::byte>(0xEF));
|
||||
}
|
||||
|
||||
@@ -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<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0xDE),
|
||||
static_cast<std::byte>(0xAD), static_cast<std::byte>(0xBE),
|
||||
static_cast<std::byte>(0xEF), static_cast<std::byte>(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<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Tests for PePatternScanner::scan_for_pattern_in_memory_file
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/utility/pe_pattern_scan.hpp>
|
||||
#include <span>
|
||||
@@ -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<std::byte> make_fake_pe_file(std::uint32_t virtual_address, std::uint32_t ptr_raw_data,
|
||||
std::uint32_t section_size,
|
||||
const std::vector<std::uint8_t>& code_bytes)
|
||||
std::uint32_t section_size, const std::vector<std::uint8_t>& code_bytes)
|
||||
{
|
||||
constexpr std::uint32_t e_lfanew = 0x80;
|
||||
constexpr std::uint32_t nt_sig = 0x4550;
|
||||
@@ -24,9 +25,18 @@ static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, s
|
||||
const std::uint32_t total_size = ptr_raw_data + section_size + 0x100;
|
||||
std::vector<std::byte> 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);
|
||||
@@ -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<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
|
||||
const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast<std::uint32_t>(code.size()), code);
|
||||
|
||||
const auto result =
|
||||
PePatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{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<std::uint8_t> code = {0x01, 0x02, 0x03};
|
||||
const auto buf = make_fake_pe_file(0x1000, 0x400, static_cast<std::uint32_t>(code.size()), code);
|
||||
|
||||
const auto result =
|
||||
PePatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "AA BB CC");
|
||||
const auto result = PePatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{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<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
|
||||
const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast<std::uint32_t>(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<const char*>(buf.data()), static_cast<std::streamsize>(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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user