include/DetourModKit/scanner.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_SCANNER_HPP | ||
| 2 | #define DETOURMODKIT_SCANNER_HPP | ||
| 3 | |||
| 4 | #include <vector> | ||
| 5 | #include <string> | ||
| 6 | #include <cstddef> | ||
| 7 | #include <optional> | ||
| 8 | #include <cstdint> | ||
| 9 | #include <span> | ||
| 10 | |||
| 11 | namespace DetourModKit | ||
| 12 | { | ||
| 13 | namespace Scanner | ||
| 14 | { | ||
| 15 | /** | ||
| 16 | * @struct CompiledPattern | ||
| 17 | * @brief A pre-compiled AOB pattern with separate bytes and mask. | ||
| 18 | * @details Stores the pattern bytes and a bitmask indicating which bytes | ||
| 19 | * are wildcards (mask=false) vs. literal values to match (mask=true). | ||
| 20 | * This design avoids sentinel byte conflicts (e.g., 0xCC is a valid byte). | ||
| 21 | */ | ||
| 22 | struct CompiledPattern | ||
| 23 | { | ||
| 24 | std::vector<std::byte> bytes; ///< Pattern bytes (wildcard positions contain arbitrary values) | ||
| 25 | std::vector<std::byte> mask; ///< 0xFF = match this byte, 0x00 = wildcard (skip) | ||
| 26 | size_t offset = 0; ///< Byte offset from pattern start to the point of interest. | ||
| 27 | ///< Set by `|` marker in the AOB string, or 0 if absent. | ||
| 28 | ///< May equal bytes.size() when `|` appears at the end. | ||
| 29 | |||
| 30 | /** | ||
| 31 | * @brief Returns the size of the pattern. | ||
| 32 | * @return size_t The number of bytes in the pattern. | ||
| 33 | */ | ||
| 34 | 503 | size_t size() const noexcept { return bytes.size(); } | |
| 35 | |||
| 36 | /** | ||
| 37 | * @brief Checks if the pattern is empty. | ||
| 38 | * @return true if the pattern has no bytes. | ||
| 39 | */ | ||
| 40 | 77 | bool empty() const noexcept { return bytes.empty(); } | |
| 41 | }; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * @brief Parses a space-separated AOB string into a compiled pattern. | ||
| 45 | * @details Converts hexadecimal strings to byte values and wildcard tokens | ||
| 46 | * ('??' or '?') into mask=false entries. An optional `|` token marks | ||
| 47 | * the offset within the pattern (stored in CompiledPattern::offset). | ||
| 48 | * This lets wider patterns precisely target a specific instruction: | ||
| 49 | * e.g., "48 8B 88 B8 00 00 00 | 48 89 4C 24 68" sets offset=7. | ||
| 50 | * @param aob_str The AOB pattern string. | ||
| 51 | * @return std::optional<CompiledPattern> The compiled pattern, or std::nullopt on parse failure. | ||
| 52 | */ | ||
| 53 | std::optional<CompiledPattern> parse_aob(std::string_view aob_str); | ||
| 54 | |||
| 55 | /** | ||
| 56 | * @brief Scans a specified memory region for a given byte pattern. | ||
| 57 | * @details Uses an optimized search algorithm that finds the first non-wildcard | ||
| 58 | * byte and uses memchr for fast skipping, then verifies the full pattern. | ||
| 59 | * @param start_address Pointer to the beginning of the memory region to scan. | ||
| 60 | * @param region_size The size (in bytes) of the memory region to scan. | ||
| 61 | * @param pattern The compiled pattern to search for. | ||
| 62 | * @return const std::byte* Pointer to the first occurrence of the pattern within | ||
| 63 | * the specified region. Returns nullptr if pattern not found. | ||
| 64 | */ | ||
| 65 | const std::byte *find_pattern(const std::byte *start_address, size_t region_size, | ||
| 66 | const CompiledPattern &pattern); | ||
| 67 | |||
| 68 | /** | ||
| 69 | * @brief Scans a memory region for the Nth occurrence of a byte pattern. | ||
| 70 | * @param start_address Pointer to the beginning of the memory region to scan. | ||
| 71 | * @param region_size The size (in bytes) of the memory region to scan. | ||
| 72 | * @param pattern The compiled pattern to search for. | ||
| 73 | * @param occurrence Which occurrence to return (1-based). 1 = first match. | ||
| 74 | * Passing 0 returns nullptr. | ||
| 75 | * @return const std::byte* Pointer to the Nth occurrence, or nullptr if fewer | ||
| 76 | * than N matches exist. | ||
| 77 | */ | ||
| 78 | const std::byte *find_pattern(const std::byte *start_address, size_t region_size, | ||
| 79 | const CompiledPattern &pattern, size_t occurrence); | ||
| 80 | // Common x86-64 RIP-relative opcode prefixes (bytes preceding the disp32 field) | ||
| 81 | inline constexpr std::byte PREFIX_MOV_RAX_RIP[] = {std::byte{0x48}, std::byte{0x8B}, std::byte{0x05}}; | ||
| 82 | inline constexpr std::byte PREFIX_MOV_RCX_RIP[] = {std::byte{0x48}, std::byte{0x8B}, std::byte{0x0D}}; | ||
| 83 | inline constexpr std::byte PREFIX_MOV_RDX_RIP[] = {std::byte{0x48}, std::byte{0x8B}, std::byte{0x15}}; | ||
| 84 | inline constexpr std::byte PREFIX_MOV_RBX_RIP[] = {std::byte{0x48}, std::byte{0x8B}, std::byte{0x1D}}; | ||
| 85 | inline constexpr std::byte PREFIX_LEA_RAX_RIP[] = {std::byte{0x48}, std::byte{0x8D}, std::byte{0x05}}; | ||
| 86 | inline constexpr std::byte PREFIX_LEA_RCX_RIP[] = {std::byte{0x48}, std::byte{0x8D}, std::byte{0x0D}}; | ||
| 87 | inline constexpr std::byte PREFIX_LEA_RDX_RIP[] = {std::byte{0x48}, std::byte{0x8D}, std::byte{0x15}}; | ||
| 88 | inline constexpr std::byte PREFIX_CALL_REL32[] = {std::byte{0xE8}}; | ||
| 89 | inline constexpr std::byte PREFIX_JMP_REL32[] = {std::byte{0xE9}}; | ||
| 90 | |||
| 91 | /** | ||
| 92 | * @brief Resolves an absolute address from an x86-64 RIP-relative instruction. | ||
| 93 | * @details Extracts the int32 displacement at the given offset within the instruction | ||
| 94 | * and computes the absolute target: instruction_address + instruction_length + displacement. | ||
| 95 | * @param instruction_address Pointer to the first byte of the instruction. | ||
| 96 | * @param displacement_offset Byte offset from instruction_address to the disp32 field. | ||
| 97 | * @param instruction_length Total length of the instruction in bytes. | ||
| 98 | * @return The resolved absolute address, or std::nullopt on null input or unreadable displacement. | ||
| 99 | */ | ||
| 100 | std::optional<uintptr_t> resolve_rip_relative(const std::byte *instruction_address, | ||
| 101 | size_t displacement_offset, | ||
| 102 | size_t instruction_length); | ||
| 103 | |||
| 104 | /** | ||
| 105 | * @brief Scans forward from a starting address for an opcode prefix, then resolves the RIP-relative target. | ||
| 106 | * @details Searches up to search_length bytes for the given opcode prefix. Once found, | ||
| 107 | * the displacement is assumed to immediately follow the prefix. The absolute address | ||
| 108 | * is computed as: found_address + instruction_length + displacement. | ||
| 109 | * @param search_start Pointer to the beginning of the search region. | ||
| 110 | * @param search_length Maximum number of bytes to search forward. | ||
| 111 | * @param opcode_prefix The opcode byte sequence to search for (disp32 must follow immediately). | ||
| 112 | * @param instruction_length Total length of the instruction in bytes. | ||
| 113 | * @return The resolved absolute address, or std::nullopt if prefix not found or displacement unreadable. | ||
| 114 | */ | ||
| 115 | std::optional<uintptr_t> find_and_resolve_rip_relative(const std::byte *search_start, | ||
| 116 | size_t search_length, | ||
| 117 | std::span<const std::byte> opcode_prefix, | ||
| 118 | size_t instruction_length); | ||
| 119 | |||
| 120 | /** | ||
| 121 | * @brief Scans all committed executable memory regions for a byte pattern. | ||
| 122 | * @details Walks the process address space via VirtualQuery, scanning each | ||
| 123 | * committed region with execute permission. Useful for games with | ||
| 124 | * packed or protected binaries that unpack code into anonymous pages | ||
| 125 | * outside any loaded module's address range. | ||
| 126 | * @param pattern The compiled pattern to search for. | ||
| 127 | * @param occurrence Which occurrence to return (1-based). 1 = first match. | ||
| 128 | * @return Pointer to the match (adjusted by pattern offset), or nullptr if not found. | ||
| 129 | */ | ||
| 130 | const std::byte *scan_executable_regions(const CompiledPattern &pattern, size_t occurrence = 1); | ||
| 131 | |||
| 132 | } // namespace Scanner | ||
| 133 | } // namespace DetourModKit | ||
| 134 | |||
| 135 | #endif // DETOURMODKIT_SCANNER_HPP | ||
| 136 |